diff --git a/.conform.yaml b/.conform.yaml index 98a026b1..823b1e6b 100644 --- a/.conform.yaml +++ b/.conform.yaml @@ -1,10 +1,3 @@ -metadata: - repository: autonomy/conform - variables: - linuxBinaryPath: /conform-linux-amd64 - darwinBinaryPath: /conform-darwin-amd64 - gitRepository: github.com/autonomy/conform - maintainer: Andrew Rynhard policies: - type: conventionalCommit spec: @@ -16,96 +9,5 @@ policies: - style - test scopes: - - ci - - cli - - docker - - fmt - - git - - metadata - - pipeline - policy - - readme - - renderer - - service - '*' -script: - template: | - #!/bin/bash - - set -e - - {{ if and (.Git.IsClean) (or (.Git.IsTag) (eq .Git.Branch "master")) }} - docker login --username=$DOCKER_USERNAME --password=$DOCKER_PASSWORD - docker tag {{ .Docker.Image.Name }}:{{ .Docker.Image.Tag }} {{ .Docker.Image.Name }}:latest - docker push {{ .Docker.Image.Name }}:{{ .Docker.Image.Tag }} - docker push {{ .Docker.Image.Name }}:latest - {{ if .Git.IsTag }} - docker tag {{ .Docker.Image.Name }}:{{ .Docker.Image.Tag }} {{ .Docker.Image.Name }}:{{ .Version.Original }} - docker push {{ .Docker.Image.Name }}:{{ .Version.Original }} - {{ end }} - {{ end }} -pipeline: - stages: - - src - - test - - build - - image -stages: - build: - artifacts: - - source: /conform-linux-amd64 - destination: ./build/conform-linux-amd64 - - source: /conform-darwin-amd64 - destination: ./build/conform-darwin-amd64 - tasks: - - build - image: - tasks: - - image - src: - tasks: - - src - test: - artifacts: - - source: /src/github.com/autonomy/conform/coverage.txt - destination: coverage.txt - tasks: - - test -tasks: - build: - template: | - FROM autonomy/conform:src AS {{ .Docker.CurrentStage }} - {{ if and .Git.IsClean .Git.IsTag }} - RUN go build -o {{ index .Variables "linuxBinaryPath" }} -ldflags "-s -w -X \"{{ index .Variables "gitRepository" }}/cmd.Tag={{ trimAll "v" .Git.Tag }}\" -X \"{{ index .Variables "gitRepository" }}/cmd.SHA={{ .Git.SHA }}\" -X \"{{ index .Variables "gitRepository" }}/cmd.Built={{ .Built }}\"" - RUN GOOS=darwin go build -o {{ index .Variables "darwinBinaryPath" }} -ldflags "-s -w -X \"{{ index .Variables "gitRepository" }}/cmd.Tag={{ trimAll "v" .Git.Tag }}\" -X \"{{ index .Variables "gitRepository" }}/cmd.SHA={{ .Git.SHA }}\" -X \"{{ index .Variables "gitRepository" }}/cmd.Built={{ .Built }}\"" - {{ else if .Git.IsClean }} - RUN go build -o {{ index .Variables "linuxBinaryPath" }} -ldflags "-s -w -X \"{{ index .Variables "gitRepository" }}/cmd.SHA={{ .Git.SHA }}\" -X \"{{ index .Variables "gitRepository" }}/cmd.Built={{ .Built }}\"" - RUN GOOS=darwin go build -o {{ index .Variables "darwinBinaryPath" }} -ldflags "-s -w -X \"{{ index .Variables "gitRepository" }}/cmd.SHA={{ .Git.SHA }}\" -X \"{{ index .Variables "gitRepository" }}/cmd.Built={{ .Built }}\"" - {{ else }} - RUN go build -o {{ index .Variables "linuxBinaryPath" }} - RUN GOOS=darwin go build -o {{ index .Variables "darwinBinaryPath" }} - {{ end }} - image: - template: | - FROM alpine:3.8 AS {{ .Docker.CurrentStage }} - LABEL maintainer="{{ index .Variables "maintainer" }}" - RUN apk --no-cache add bash - COPY --from={{ .Repository}}:build {{ index .Variables "linuxBinaryPath" }} /bin/conform - ENTRYPOINT ["conform"] - src: - template: | - FROM golang:1.11.1 AS {{ .Docker.CurrentStage }} - ENV GO111MODULE on - ENV CGO_ENABLED 0 - WORKDIR /src/{{ index .Variables "gitRepository" }} - COPY ./ ./ - RUN go mod download - RUN go mod verify - test: - template: | - FROM autonomy/conform:src AS {{ .Docker.CurrentStage }} - RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.10.1 - RUN chmod +x ./hack/test.sh - RUN ./hack/test.sh --lint ./hack/golangci-lint.yaml - RUN ./hack/test.sh --unit - RUN ./hack/test.sh --coverage diff --git a/.gitignore b/.gitignore index b71d78fc..c39381cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -conform-* -coverage.txt +build +cache vendor diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..45e8239d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +ARG GOLANG_IMAGE +FROM ${GOLANG_IMAGE} + +ENV CGO_ENABLED 0 +ENV GO111MODULES on + +WORKDIR /conform +COPY ./ ./ +RUN go mod download +RUN go mod verify +RUN go mod tidy +RUN go mod vendor + +ARG TAG +ARG SHA +ARG BUILT +ENV GOOS linux +ENV GOARCH amd64 +RUN go build -o /build/conform-${GOOS}-${GOARCH} -ldflags "-s -w -X \"github.com/autonomy/conform/cmd.Tag=${TAG}\" -X \"github.com/autonomy/conform/cmd.SHA=${SHA}\" -X \"github.com/autonomy/conform/cmd.Built=${BUILT}\"" . + +ARG TAG +ARG SHA +ARG BUILT +ENV GOOS darwin +ENV GOARCH amd64 +RUN go build -o /build/conform-${GOOS}-${GOARCH} -ldflags "-s -w -X \"github.com/autonomy/conform/cmd.Tag=${TAG}\" -X \"github.com/autonomy/conform/cmd.SHA=${SHA}\" -X \"github.com/autonomy/conform/cmd.Built=${BUILT}\"" . + +ENV GOOS linux +ENV GOARCH amd64 +COPY ./hack ./hack +RUN chmod +x ./hack/test.sh +RUN ./hack/test.sh --all + +FROM scratch +COPY /build/conform-linux-amd64 /conform +ENTRYPOINT [ "/conform" ] diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..96b09129 --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +REPO ?= docker.io/autonomy +EXECUTOR ?= gcr.io/kaniko-project/executor +EXECUTOR_TAG ?= latest +WARMER ?= gcr.io/kaniko-project/warmer +WARMER_TAG ?= latest +GOLANG_IMAGE ?= golang:1.11.2 +AUTH_CONFIG ?= $(HOME)/.kaniko/config.json + +SHA := $(shell gitmeta git sha) +TAG := $(shell gitmeta image tag) +BUILT := $(shell gitmeta built) + +EXECUTOR_ARGS := --context=/workspace --cache=true --cache-dir=/cache --cleanup +EXECUTOR_VOLUMES := --volume $(AUTH_CONFIG):/kaniko/.docker/config.json:ro --volume $(PWD)/cache:/cache --volume $(PWD)/build:/build + +all: enforce clean conform + +enforce: + conform enforce + +conform: cache + docker run \ + --rm \ + $(EXECUTOR_VOLUMES) \ + --volume $(PWD):/workspace \ + $(EXECUTOR):$(EXECUTOR_TAG) \ + $(EXECUTOR_ARGS) \ + --dockerfile=Dockerfile \ + --cache-repo=$(REPO)/$@ \ + --destination=$(REPO)/$@:$(TAG) \ + --single-snapshot \ + --no-push \ + --build-arg GOLANG_IMAGE=$(GOLANG_IMAGE) \ + --build-arg SHA=$(SHA) \ + --build-arg TAG=$(TAG) \ + --build-arg BUILT="$(BUILT)" + +.PHONY: cache +cache: + docker run \ + --rm \ + $(EXECUTOR_VOLUMES) \ + $(WARMER):$(WARMER_TAG) \ + --cache-dir=/cache \ + --image=$(GOLANG_IMAGE) + +debug: + docker run \ + --rm \ + -it \ + $(EXECUTOR_VOLUMES) \ + --volume $(PWD):/workspace \ + --entrypoint=/busybox/sh \ + $(EXECUTOR):debug + +clean: + rm -rf ./build diff --git a/README.md b/README.md index 71002452..c7e2e326 100644 --- a/README.md +++ b/README.md @@ -14,52 +14,29 @@ --- -**Conform** is a tool for building projects in a flexible and reliabale manner. +**Conform** is a tool for building enforcing policies on your build pipelines. -The key features of Conform are: -- **DRY**: Templatized multi-stage Docker builds. -- **Hygienic**: Builds run in Docker. -- **Fast**: Leverages Docker caching, building only what has changed. +Some of the policies included are: + +- **Convetion Commits**: Enforce [conventional commits](https://www.conventionalcommits.org) for all commit messages. + +## Getting Started -Getting Started ---------------- Create a file named `.conform.yaml` with the following contents: -```yaml -metadata: - repository: hello/world -policies: +```yaml: - type: conventionalCommit spec: types: - "type" scopes: - "scope" - -script: - template: | - #!/bin/bash - - echo "Hello, world!" - -pipeline: - stages: - - example - -stages: - example: - tasks: - - task - -tasks: - task: - template: | - FROM scratch ``` In the same directory, run: -``` -$ conform build + +```bash +conform enforce ``` ### License diff --git a/build/.keep b/build/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/cmd/build.go b/cmd/build.go deleted file mode 100644 index 682265b3..00000000 --- a/cmd/build.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright © 2017 NAME HERE -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "fmt" - "os" - "strings" - - "github.com/autonomy/conform/pkg/enforcer" - "github.com/autonomy/conform/pkg/utilities" - "github.com/spf13/cobra" -) - -var ( - skipArray []string - varArray []string -) - -// buildCmd represents the build command -var buildCmd = &cobra.Command{ - Use: "build", - Short: "", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 0 { - err := fmt.Errorf("The build command does not take arguments") - - fmt.Println(err) - os.Exit(1) - } - if err := utilities.CheckDockerVersion(); err != nil { - fmt.Println(err) - os.Exit(1) - } - e, err := enforcer.New(cmd.Flags()) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - for _, variable := range varArray { - s := strings.Split(variable, "=") - if len(s) != 2 { - fmt.Printf("Variable key and value must be delimited by a '=': [%s]", variable) - os.Exit(1) - } - e.Metadata.Variables[s[0]] = s[1] - } - for _, skip := range skipArray { - for i, stage := range e.Pipeline.Stages { - if stage == skip { - e.Pipeline.Stages = append(e.Pipeline.Stages[:i], e.Pipeline.Stages[i+1:]...) - } - } - } - if !e.Metadata.Git.IsClean { - fmt.Printf("Git status:\n%s\n", e.Metadata.Git.Status) - } - if err := e.Pipeline.Build(e.Metadata, e.Stages, e.Tasks); err != nil { - fmt.Println(err) - os.Exit(1) - } - - if e.Script == nil { - return - } - if err := e.Script.Execute(e.Metadata); err != nil { - fmt.Println(err) - os.Exit(1) - } - }, -} - -func init() { - RootCmd.AddCommand(buildCmd) - buildCmd.Flags().StringArrayVar(&skipArray, "skip", []string{}, "skip a stage in the pipeline") - buildCmd.Flags().StringArrayVar(&varArray, "var", []string{}, "set a variable") -} diff --git a/cmd/enforce.go b/cmd/enforce.go index 8cca0ec5..4d495c76 100644 --- a/cmd/enforce.go +++ b/cmd/enforce.go @@ -17,7 +17,9 @@ import ( "fmt" "os" - "github.com/autonomy/conform/pkg/enforcer" + "github.com/autonomy/conform/internal/enforcer" + "github.com/autonomy/conform/internal/policy" + "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -28,17 +30,24 @@ var enforceCmd = &cobra.Command{ Long: ``, Run: func(cmd *cobra.Command, args []string) { if len(args) != 0 { - err := fmt.Errorf("The enforce command does not take arguments") + err := errors.Errorf("The enforce command does not take arguments") fmt.Println(err) os.Exit(1) } - e, err := enforcer.New(cmd.Flags()) + e, err := enforcer.New() if err != nil { fmt.Println(err) os.Exit(1) } - if err = e.Enforce(); err != nil { + + opts := []policy.Option{} + + if commitMsgFile := cmd.Flags().Lookup("commit-msg-file").Value.String(); commitMsgFile != "" { + opts = append(opts, policy.WithCommitMsgFile(&commitMsgFile)) + } + + if err = e.Enforce(opts...); err != nil { fmt.Println(err) os.Exit(1) } diff --git a/cmd/fmt.go b/cmd/fmt.go deleted file mode 100644 index 428b78b9..00000000 --- a/cmd/fmt.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright © 2018 NAME HERE -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "io/ioutil" - "log" - "sort" - - "github.com/spf13/cobra" - yaml "gopkg.in/yaml.v2" -) - -// fmtCmd represents the fmt command -var fmtCmd = &cobra.Command{ - Use: "fmt", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - configBytes, err := ioutil.ReadFile(".conform.yaml") - if err != nil { - log.Fatal(err.Error()) - } - m := yaml.MapSlice{} - err = yaml.Unmarshal(configBytes, &m) - if err != nil { - log.Fatalf(err.Error()) - } - for _, v := range m { - if v.Key.(string) == "tasks" || v.Key.(string) == "stages" { - mapSlice := v.Value.(yaml.MapSlice) - sort.Slice(mapSlice, func(i, j int) bool { - return mapSlice[i].Key.(string) < mapSlice[j].Key.(string) - }) - } - } - configBytes, err = yaml.Marshal(m) - if err != nil { - log.Fatal(err.Error()) - } - err = ioutil.WriteFile(".conform.yaml", configBytes, 0644) - if err != nil { - log.Fatal(err.Error()) - } - }, -} - -func init() { - RootCmd.AddCommand(fmtCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // fmtCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // fmtCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} diff --git a/cmd/version.go b/cmd/version.go index 20280691..432768db 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -19,7 +19,7 @@ import ( "html/template" "runtime" - "github.com/autonomy/conform/pkg/constants" + "github.com/autonomy/conform/internal/constants" "github.com/spf13/cobra" ) diff --git a/go.mod b/go.mod index 1cb6e9f4..1ba8ea81 100644 --- a/go.mod +++ b/go.mod @@ -1,25 +1,14 @@ module github.com/autonomy/conform require ( - github.com/Masterminds/semver v1.3.0 - github.com/Masterminds/sprig v0.0.0-20170516202909-9526be0327b2 - github.com/Microsoft/go-winio v0.4.2 // indirect - github.com/Sirupsen/logrus v1.0.0 // indirect github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 // indirect github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect - github.com/aokoli/goutils v1.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/distribution v2.6.1+incompatible // indirect - github.com/docker/docker v0.0.0-20170601211448-f5ec1e2936dc - github.com/docker/go-connections v0.2.1 // indirect - github.com/docker/go-units v0.3.1 // indirect github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect github.com/fsnotify/fsnotify v1.4.2 // indirect github.com/gliderlabs/ssh v0.1.1 // indirect github.com/google/go-cmp v0.2.0 // indirect github.com/hashicorp/hcl v0.0.0-20170509225359-392dba7d905e // indirect - github.com/huandu/xstrings v0.0.0-20151130125119-3959339b3335 // indirect - github.com/imdario/mergo v0.0.0-20160216103600-3e95a51e0639 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v0.0.0-20170525151105-fa48d7ff1cfb // indirect @@ -27,21 +16,18 @@ require ( github.com/magiconair/properties v1.7.2 // indirect github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992 - github.com/opencontainers/runc v0.1.1 // indirect github.com/pelletier/go-buffruneio v0.2.0 // indirect github.com/pelletier/go-toml v1.0.0 // indirect - github.com/pkg/errors v0.8.0 // indirect + github.com/pkg/errors v0.8.0 github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/satori/go.uuid v1.1.0 // indirect github.com/sergi/go-diff v0.0.0-20170409071739-feef008d51ad // indirect github.com/spf13/afero v0.0.0-20170217164146-9be650865eab // indirect github.com/spf13/cast v1.1.0 // indirect github.com/spf13/cobra v0.0.0-20170629105234-8c6fa02d2225 github.com/spf13/jwalterweatherman v0.0.0-20170523133247-0efa5202c046 // indirect - github.com/spf13/pflag v1.0.0 + github.com/spf13/pflag v1.0.0 // indirect github.com/spf13/viper v0.0.0-20170619124313-c1de95864d73 github.com/src-d/gcfg v1.3.0 // indirect - github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 // indirect github.com/stretchr/testify v1.2.2 // indirect github.com/xanzy/ssh-agent v0.1.0 // indirect golang.org/x/crypto v0.0.0-20170703161049-69be088f8606 // indirect @@ -53,6 +39,5 @@ require ( gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 // indirect gopkg.in/src-d/go-git.v4 v4.0.0 gopkg.in/warnings.v0 v0.1.1 // indirect - gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect gopkg.in/yaml.v2 v2.0.0-20170407172122-cd8b52f8269e ) diff --git a/go.sum b/go.sum index bcce84b7..edcc3183 100644 --- a/go.sum +++ b/go.sum @@ -1,27 +1,9 @@ -github.com/Masterminds/semver v1.3.0 h1:7H8mLwaeisxNSFxW39uQ9UHGv7HOevcDtjFjgbPDE/4= -github.com/Masterminds/semver v1.3.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/sprig v0.0.0-20170516202909-9526be0327b2 h1:N6GC4DMeuyK2NHsNyb1sLGGiFYfoklLFon86X/siC10= -github.com/Masterminds/sprig v0.0.0-20170516202909-9526be0327b2/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Microsoft/go-winio v0.4.2 h1:ZJbCAgklAxf91aYmsiUHS7dIGCqw+EjpOg/oq/Groaw= -github.com/Microsoft/go-winio v0.4.2/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Sirupsen/logrus v1.0.0 h1:Rb4797caW6l6qnpZqL25Z79EjY2OG26Bbqwf7ozOIgQ= -github.com/Sirupsen/logrus v1.0.0/go.mod h1:rmk17hk6i8ZSAJkSDa7nOxamrG+SP4P0mm+DAvExv4U= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg= -github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/distribution v2.6.1+incompatible h1:nWLG5KfUJK1PBt7i7iLBE8KpgWVF4uXwNfK1YOi1K/I= -github.com/docker/distribution v2.6.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v0.0.0-20170601211448-f5ec1e2936dc h1:AmviDYdbQqIJukzphvRzpsKbNxSWIxhMFZ3ovTn4bnw= -github.com/docker/docker v0.0.0-20170601211448-f5ec1e2936dc/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.2.1 h1:XB0Pr+bR+RGw8D0C/ADeRiiPVyMftTtKFblUw3sNFXQ= -github.com/docker/go-connections v0.2.1/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.3.1 h1:QAFdsA6jLCnglbqE6mUsHuPcJlntY94DkxHf4deHKIU= -github.com/docker/go-units v0.3.1/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.2 h1:v5tKwtf2hNhBV24eNYfQ5UmvFOGlOCmRqk7/P1olxtk= @@ -32,10 +14,6 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/hashicorp/hcl v0.0.0-20170509225359-392dba7d905e h1:KJWs1uTCkN3E/J5ofCH9Pf8KKsibTFc3fv0CA9+WsVo= github.com/hashicorp/hcl v0.0.0-20170509225359-392dba7d905e/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= -github.com/huandu/xstrings v0.0.0-20151130125119-3959339b3335 h1:KZOP9q7J/P4eMBibPuVwuloXgd2dTbLAHRPqxw7NXOw= -github.com/huandu/xstrings v0.0.0-20151130125119-3959339b3335/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= -github.com/imdario/mergo v0.0.0-20160216103600-3e95a51e0639 h1:VMd01CgpBpmLpuERyY4Oibn2PpcVS1fK9sjh5UZG8+o= -github.com/imdario/mergo v0.0.0-20160216103600-3e95a51e0639/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -53,8 +31,6 @@ github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 h1:eQox4Rh4ew github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992 h1:W7VHAEVflA5/eTyRvQ53Lz5j8bhRd1myHZlI/IZFvbU= github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.0.0 h1:QFDlmAXZrfPXEF6c9+15fMqhQIS3O0pxszhnk936vg4= @@ -63,8 +39,6 @@ github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/satori/go.uuid v1.1.0 h1:B9KXyj+GzIpJbV7gmr873NsY6zpbxNy24CBtGrk7jHo= -github.com/satori/go.uuid v1.1.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v0.0.0-20170409071739-feef008d51ad h1:tSFsPEWlyDYLf376k3+aunLH2qE7TMd/8arj5jZtqg8= github.com/sergi/go-diff v0.0.0-20170409071739-feef008d51ad/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/spf13/afero v0.0.0-20170217164146-9be650865eab h1:IVAbBHQR8rXL2Fc8Zba/lMF7KOnTi70lqdx91UTuAwQ= @@ -81,8 +55,6 @@ github.com/spf13/viper v0.0.0-20170619124313-c1de95864d73 h1:qJXvCTq/ZNWp7kk3MGb github.com/spf13/viper v0.0.0-20170619124313-c1de95864d73/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/src-d/gcfg v1.3.0 h1:2BEDr8r0I0b8h/fOqwtxCEiq2HJu8n2JGZJQFGXWLjg= github.com/src-d/gcfg v1.3.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= -github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 h1:4bT0pPowCpQImewr+BjzfUKcuFW+KVyB8d1OF3b6oTI= -github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/xanzy/ssh-agent v0.1.0 h1:lOhdXLxtmYjaHc76ZtNmJWPg948y/RnT+3N3cvKWFzY= @@ -105,7 +77,5 @@ gopkg.in/src-d/go-git.v4 v4.0.0 h1:9ZRNKHuhaTaJRGcGaH6Qg7uUORO2X0MNB5WL/CDdqto= gopkg.in/src-d/go-git.v4 v4.0.0/go.mod h1:CzbUWqMn4pvmvndg3gnh5iZFmSsbhyhUWdI0IQ60AQo= gopkg.in/warnings.v0 v0.1.1 h1:XM28wIgFzaBmeZ5dNHIpWLQpt/9DGKxk+rCg/22nnYE= gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU= -gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.0.0-20170407172122-cd8b52f8269e h1:o/mfNjxpTLivuKEfxzzwrJ8PmulH2wEp7t713uMwKAA= gopkg.in/yaml.v2 v2.0.0-20170407172122-cd8b52f8269e/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= diff --git a/hack/test.sh b/hack/test.sh old mode 100644 new mode 100755 index 1c9db7bd..407d9cbb --- a/hack/test.sh +++ b/hack/test.sh @@ -6,53 +6,74 @@ CGO_ENABLED=1 GOPACKAGES=$(go list ./...) lint_packages() { - echo "Linting packages" - golangci-lint run --config ${1} + if [ "${lint}" = true ]; then + echo "linting packages" + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin v1.10.1 + golangci-lint run --config "${BASH_SOURCE%/*}/golangci-lint.yaml" + fi } -perform_unit_tests() { - echo "Performing unit tests" - go test -v -short ./... -} +go_test() { + if [ "${short}" = true ]; then + echo "performing short tests" + go test -v -short ./... + fi -perform_integration_tests() { - echo "Performing integration tests" - go test -v ./... + if [ "${tests}" = true ]; then + echo "performing tests" + go test -v ./... + fi } -perform_coverage_tests() { - echo "Performing coverage tests" - local coverage_report="coverage.txt" - local profile="profile.out" - if [[ -f ${coverage_report} ]]; then - rm ${coverage_report} - fi - touch ${coverage_report} - for package in ${GOPACKAGES[@]}; do - go test -v -short -race -coverprofile=${profile} -covermode=atomic $package - if [ -f ${profile} ]; then - cat ${profile} >> ${coverage_report} - rm ${profile} +coverage_tests() { + if [ "${coverage}" = true ]; then + echo "performing coverage tests" + local coverage_report="../build/coverage.txt" + local profile="../build/profile.out" + if [[ -f ${coverage_report} ]]; then + rm ${coverage_report} fi - done + touch ${coverage_report} + for package in ${GOPACKAGES[@]}; do + go test -v -short -race -coverprofile=${profile} -covermode=atomic $package + if [ -f ${profile} ]; then + cat ${profile} >> ${coverage_report} + rm ${profile} + fi + done + fi } +lint=false +short=false +tests=false +coverage=false + case $1 in --lint) - lint_packages ${2} + lint=true ;; - --unit) - perform_unit_tests + --short) + short=true ;; --integration) - perform_integration_tests + tests=true ;; --coverage) - perform_coverage_tests + coverage=true + ;; + --all) + lint=true + short=true + tests=true + coverage=true ;; *) - exit 1 ;; esac +go_test +coverage_tests +lint_packages + exit 0 diff --git a/internal/constants/constants.go b/internal/constants/constants.go new file mode 100644 index 00000000..9d02cc50 --- /dev/null +++ b/internal/constants/constants.go @@ -0,0 +1,6 @@ +package constants + +const ( + // AppName is the application name. + AppName = "Conform" +) diff --git a/pkg/enforcer/enforcer.go b/internal/enforcer/enforcer.go similarity index 53% rename from pkg/enforcer/enforcer.go rename to internal/enforcer/enforcer.go index 5a0c85d6..aab35a2f 100644 --- a/pkg/enforcer/enforcer.go +++ b/internal/enforcer/enforcer.go @@ -5,27 +5,17 @@ import ( "io/ioutil" "os" - "github.com/autonomy/conform/pkg/metadata" - "github.com/autonomy/conform/pkg/pipeline" - "github.com/autonomy/conform/pkg/policy" - "github.com/autonomy/conform/pkg/policy/conventionalcommit" - "github.com/autonomy/conform/pkg/script" - "github.com/autonomy/conform/pkg/stage" - "github.com/autonomy/conform/pkg/task" + "github.com/autonomy/conform/internal/policy" + "github.com/autonomy/conform/internal/policy/conventionalcommit" "github.com/mitchellh/mapstructure" - flag "github.com/spf13/pflag" + "github.com/pkg/errors" yaml "gopkg.in/yaml.v2" ) // Conform is a struct that conform.yaml gets decoded into. type Conform struct { - Metadata *metadata.Metadata `yaml:"metadata"` - Policies []*PolicyDeclaration `yaml:"policies"` - Pipeline *pipeline.Pipeline `yaml:"pipeline"` - Stages map[string]*stage.Stage `yaml:"stages"` - Tasks map[string]*task.Task `yaml:"tasks"` - Script *script.Script `yaml:"script"` + Policies []*PolicyDeclaration `yaml:"policies"` } // PolicyDeclaration allows a user to declare an arbitrary type along with a @@ -42,7 +32,7 @@ var policyMap = map[string]policy.Policy{ } // New loads the conform.yaml file and unmarshals it into a Conform struct. -func New(flags *flag.FlagSet) (*Conform, error) { +func New() (*Conform, error) { configBytes, err := ioutil.ReadFile(".conform.yaml") if err != nil { return nil, err @@ -53,44 +43,40 @@ func New(flags *flag.FlagSet) (*Conform, error) { return nil, err } - c.Metadata.Flags = flags - return c, nil } // Enforce enforces all policies defined in the conform.yaml file. -func (c *Conform) Enforce() error { +func (c *Conform) Enforce(setters ...policy.Option) error { + opts := policy.NewDefaultOptions(setters...) + for _, p := range c.Policies { fmt.Printf("Enforcing policy %q: ", p.Type) - err := c.enforce(p) + err := c.enforce(p, opts) if err != nil { fmt.Printf("failed\n") return err } - fmt.Printf("passed\n") + fmt.Printf("pass\n") } return nil } -func (c *Conform) enforce(p *PolicyDeclaration) error { - if _, ok := policyMap[p.Type]; !ok { - return fmt.Errorf("Policy %q is not defined", p.Type) +func (c *Conform) enforce(declaration *PolicyDeclaration, opts *policy.Options) error { + if _, ok := policyMap[declaration.Type]; !ok { + return errors.Errorf("Policy %q is not defined", declaration.Type) } - policy := policyMap[p.Type] - err := mapstructure.Decode(p.Spec, policy) + p := policyMap[declaration.Type] + err := mapstructure.Decode(declaration.Spec, p) if err != nil { return err } - report := policy.Compliance( - c.Metadata, - policy.Pipeline(c.Pipeline), - policy.Tasks(c.Tasks), - ) + report := p.Compliance(opts) if !report.Valid() { - fmt.Printf("Violation of policy %q:\n", p.Type) + fmt.Printf("Violation of policy %q:\n", declaration.Type) for i, err := range report.Errors { fmt.Printf("\tViolation %d: %v\n", i+1, err) } diff --git a/pkg/policy/conventionalcommit/conventionalcommit.go b/internal/policy/conventionalcommit/conventionalcommit.go similarity index 63% rename from pkg/policy/conventionalcommit/conventionalcommit.go rename to internal/policy/conventionalcommit/conventionalcommit.go index 88cb271e..5fe65795 100644 --- a/pkg/policy/conventionalcommit/conventionalcommit.go +++ b/internal/policy/conventionalcommit/conventionalcommit.go @@ -1,15 +1,13 @@ package conventionalcommit import ( - "fmt" "io/ioutil" "regexp" "strings" - "github.com/autonomy/conform/pkg/metadata" - "github.com/autonomy/conform/pkg/pipeline" - "github.com/autonomy/conform/pkg/policy" - "github.com/autonomy/conform/pkg/task" + "github.com/autonomy/conform/internal/policy" + "github.com/autonomy/conform/internal/policy/conventionalcommit/internal/git" + "github.com/pkg/errors" ) // Conventional implements the policy.Policy interface and enforces commit @@ -36,26 +34,34 @@ const TypeFeat = "feat" const TypeFix = "fix" // Compliance implements the policy.Policy.Compliance function. -func (c *Conventional) Compliance(metadata *metadata.Metadata, options ...policy.Option) (report policy.Report) { +func (c *Conventional) Compliance(options *policy.Options) (report policy.Report) { report = policy.Report{} - var commitMsgFile string - if metadata.Flags != nil { - commitMsgFile = metadata.Flags.Lookup("commit-msg-file").Value.String() - } - msg := metadata.Git.Message // start with last commit message in log - if commitMsgFile != "" { - contents, err := ioutil.ReadFile(commitMsgFile) + + var msg string + if options.CommitMsgFile != nil { + contents, err := ioutil.ReadFile(*options.CommitMsgFile) if err != nil { - report.Errors = append(report.Errors, fmt.Errorf("failed to read commit message file: %v", err)) + report.Errors = append(report.Errors, errors.Errorf("failed to read commit message file: %v", err)) return } msg = string(contents) + } else { + g, err := git.NewGit() + if err != nil { + report.Errors = append(report.Errors, errors.Errorf("failed to open git repo: %v", err)) + return + } + if msg, err = g.Message(); err != nil { + report.Errors = append(report.Errors, errors.Errorf("failed to get commit message: %v", err)) + return + } } groups := parseHeader(msg) if len(groups) != 6 { - report.Errors = append(report.Errors, fmt.Errorf("Invalid commit format: %s", msg)) + report.Errors = append(report.Errors, errors.Errorf("Invalid commit format: %s", msg)) return } + ValidateHeaderLength(&report, groups) ValidateType(&report, groups, c.Types) ValidateScope(&report, groups, c.Scopes) @@ -64,20 +70,10 @@ func (c *Conventional) Compliance(metadata *metadata.Metadata, options ...policy return report } -// Pipeline implements the policy.Policy.Pipeline function. -func (c *Conventional) Pipeline(*pipeline.Pipeline) policy.Option { - return func(args *policy.Options) {} -} - -// Tasks implements the policy.Policy.Tasks function. -func (c *Conventional) Tasks(map[string]*task.Task) policy.Option { - return func(args *policy.Options) {} -} - // ValidateHeaderLength checks the header length. func ValidateHeaderLength(report *policy.Report, groups []string) { if len(groups[0]) > MaxNumberOfCommitCharacters { - report.Errors = append(report.Errors, fmt.Errorf("Commit header is %d characters", len(groups[0]))) + report.Errors = append(report.Errors, errors.Errorf("Commit header is %d characters", len(groups[0]))) } } @@ -89,7 +85,7 @@ func ValidateType(report *policy.Report, groups []string, types []string) { return } } - report.Errors = append(report.Errors, fmt.Errorf("Invalid type: %s, allowed types are: %v", groups[1], types)) + report.Errors = append(report.Errors, errors.Errorf("Invalid type: %s, allowed types are: %v", groups[1], types)) } // ValidateScope returns the commit scope. @@ -103,7 +99,7 @@ func ValidateScope(report *policy.Report, groups []string, scopes []string) { return } } - report.Errors = append(report.Errors, fmt.Errorf("Invalid scope: %s, allowed scopes are: %v", groups[3], scopes)) + report.Errors = append(report.Errors, errors.Errorf("Invalid scope: %s, allowed scopes are: %v", groups[3], scopes)) } // ValidateDescription returns the commit description. @@ -111,7 +107,7 @@ func ValidateDescription(report *policy.Report, groups []string) { if len(groups[4]) <= 72 && len(groups[4]) != 0 { return } - report.Errors = append(report.Errors, fmt.Errorf("Invalid description: %s", groups[4])) + report.Errors = append(report.Errors, errors.Errorf("Invalid description: %s", groups[4])) } func parseHeader(message string) []string { diff --git a/pkg/policy/conventionalcommit/conventionalcommit_test.go b/internal/policy/conventionalcommit/conventionalcommit_test.go similarity index 80% rename from pkg/policy/conventionalcommit/conventionalcommit_test.go rename to internal/policy/conventionalcommit/conventionalcommit_test.go index e5160159..c3242636 100644 --- a/pkg/policy/conventionalcommit/conventionalcommit_test.go +++ b/internal/policy/conventionalcommit/conventionalcommit_test.go @@ -1,16 +1,13 @@ package conventionalcommit import ( - "fmt" "io/ioutil" "log" "os" "os/exec" "testing" - "github.com/autonomy/conform/pkg/git" - "github.com/autonomy/conform/pkg/metadata" - "github.com/autonomy/conform/pkg/policy" + "github.com/autonomy/conform/internal/policy" ) func RemoveAll(dir string) { @@ -75,27 +72,15 @@ func TestInvalidConventionalCommitPolicy(t *testing.T) { } func runCompliance() (*policy.Report, error) { - g, err := git.NewGit() - if err != nil { - return nil, fmt.Errorf("failed to open git: %v", err) - } - message, err := g.Message() - if err != nil { - return nil, fmt.Errorf("failed to get commit message: %v", err) - } c := &Conventional{} c.Types = []string{"type"} c.Scopes = []string{"scope"} - m := &metadata.Metadata{ - Git: &metadata.Git{ - Message: message, - IsClean: true, - }, - } - report := c.Compliance(m) + + report := c.Compliance(&policy.Options{}) return &report, nil } + func initRepo() error { _, err := exec.Command("git", "init").Output() if err != nil { diff --git a/internal/policy/conventionalcommit/internal/git/git.go b/internal/policy/conventionalcommit/internal/git/git.go new file mode 100644 index 00000000..53496f26 --- /dev/null +++ b/internal/policy/conventionalcommit/internal/git/git.go @@ -0,0 +1,68 @@ +package git + +import ( + "os" + "path" + "path/filepath" + + git "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing/object" +) + +// Git is a helper for git. +type Git struct { + repo *git.Repository +} + +func findDotGit(name string) (string, error) { + if _, err := os.Stat(name); os.IsNotExist(err) { + return findDotGit(path.Join("..", name)) + } + + return filepath.Abs(name) +} + +// NewGit instantiates and returns a Git struct. +func NewGit() (g *Git, err error) { + p, err := findDotGit(".git") + if err != nil { + return + } + repo, err := git.PlainOpen(path.Dir(p)) + if err != nil { + return + } + g = &Git{repo: repo} + + return g, err +} + +// Message returns the commit message. In the case that a commit has multiple +// parents, the message of the last parent is returned. +func (g *Git) Message() (message string, err error) { + ref, err := g.repo.Head() + if err != nil { + return + } + commit, err := g.repo.CommitObject(ref.Hash()) + if err != nil { + return + } + if commit.NumParents() > 1 { + parents := commit.Parents() + for i := 1; i <= commit.NumParents(); i++ { + var next *object.Commit + next, err = parents.Next() + if err != nil { + return + } + if i == commit.NumParents() { + message = next.Message + } + } + } else { + message = commit.Message + } + + return message, err +} diff --git a/internal/policy/policy.go b/internal/policy/policy.go new file mode 100644 index 00000000..5ab06209 --- /dev/null +++ b/internal/policy/policy.go @@ -0,0 +1,16 @@ +package policy + +// Report summarizes the compliance of a policy. +type Report struct { + Errors []error +} + +// Policy is an interface that policies must implement. +type Policy interface { + Compliance(*Options) Report +} + +// Valid checks if a report is valid. +func (r Report) Valid() bool { + return len(r.Errors) == 0 +} diff --git a/internal/policy/policy_options.go b/internal/policy/policy_options.go new file mode 100644 index 00000000..ba8b4002 --- /dev/null +++ b/internal/policy/policy_options.go @@ -0,0 +1,29 @@ +package policy + +// Option is a functional option used to pass in arguments to a Policy. +type Option func(*Options) + +// Options defines the set of options available to a Policy. +type Options struct { + CommitMsgFile *string +} + +// WithCommitMsgFile sets the path to the commit message file. +func WithCommitMsgFile(o *string) Option { + return func(args *Options) { + args.CommitMsgFile = o + } +} + +// NewDefaultOptions initializes a Options struct with default values. +func NewDefaultOptions(setters ...Option) *Options { + opts := &Options{ + CommitMsgFile: nil, + } + + for _, setter := range setters { + setter(opts) + } + + return opts +} diff --git a/pkg/policy/version/version.go b/internal/policy/version/version.go similarity index 100% rename from pkg/policy/version/version.go rename to internal/policy/version/version.go diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go deleted file mode 100644 index bccde1d4..00000000 --- a/pkg/constants/constants.go +++ /dev/null @@ -1,8 +0,0 @@ -package constants - -const ( - // AppName is the application name. - AppName = "Conform" - // MinDockerVersion is the minimum required Docker version. - MinDockerVersion = "17.05.0-ce" -) diff --git a/pkg/git/git.go b/pkg/git/git.go deleted file mode 100644 index d11e5c71..00000000 --- a/pkg/git/git.go +++ /dev/null @@ -1,151 +0,0 @@ -package git - -import ( - "os" - "path" - "path/filepath" - - git "gopkg.in/src-d/go-git.v4" - "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/object" -) - -// Git is a helper for git. -type Git struct { - repo *git.Repository -} - -func findDotGit(name string) (string, error) { - if _, err := os.Stat(name); os.IsNotExist(err) { - return findDotGit(path.Join("..", name)) - } - - return filepath.Abs(name) -} - -// NewGit instantiates and returns a Git struct. -func NewGit() (g *Git, err error) { - p, err := findDotGit(".git") - if err != nil { - return - } - repo, err := git.PlainOpen(path.Dir(p)) - if err != nil { - return - } - g = &Git{repo: repo} - - return g, err -} - -// Branch returns the current git branch name. -func (g *Git) Branch() (branch string, isBranch bool, err error) { - ref, err := g.repo.Head() - if err != nil { - return - } - if ref.Name().IsBranch() { - isBranch = true - branch = ref.Name().Short() - } - - return branch, isBranch, err -} - -// Ref returns the current git ref name. -func (g *Git) Ref() (ref string, err error) { - r, err := g.repo.Head() - if err != nil { - return - } - - ref = r.Name().String() - - return ref, err -} - -// SHA returns the sha of the current commit. -func (g *Git) SHA() (sha string, err error) { - ref, err := g.repo.Head() - if err != nil { - return - } - sha = ref.Hash().String()[0:7] - - return sha, err -} - -// Tag returns the tag name if HEAD is a tag. -func (g *Git) Tag() (tag string, isTag bool, err error) { - ref, err := g.repo.Head() - if err != nil { - return - } - tags, err := g.repo.Tags() - if err != nil { - return - } - err = tags.ForEach(func(_ref *plumbing.Reference) error { - if _ref.Hash().String() == ref.Hash().String() { - isTag = true - tag = _ref.Name().Short() - return nil - } - return nil - }) - if err != nil { - return - } - - return tag, isTag, err -} - -// Status returns the status of the working tree. -func (g *Git) Status() (status string, isClean bool, err error) { - worktree, err := g.repo.Worktree() - if err != nil { - return - } - worktreeStatus, err := worktree.Status() - if err != nil { - return - } - if worktreeStatus.IsClean() { - isClean = true - status = " nothing to commit, working tree clean" - } else { - status = worktreeStatus.String() - } - - return status, isClean, err -} - -// Message returns the commit message. In the case that a commit has multiple -// parents, the message of the last parent is returned. -func (g *Git) Message() (message string, err error) { - ref, err := g.repo.Head() - if err != nil { - return - } - commit, err := g.repo.CommitObject(ref.Hash()) - if err != nil { - return - } - if commit.NumParents() > 1 { - parents := commit.Parents() - for i := 1; i <= commit.NumParents(); i++ { - var next *object.Commit - next, err = parents.Next() - if err != nil { - return - } - if i == commit.NumParents() { - message = next.Message - } - } - } else { - message = commit.Message - } - - return message, err -} diff --git a/pkg/metadata/metadata.go b/pkg/metadata/metadata.go deleted file mode 100644 index a33f307d..00000000 --- a/pkg/metadata/metadata.go +++ /dev/null @@ -1,219 +0,0 @@ -package metadata - -import ( - "time" - - "github.com/Masterminds/semver" - "github.com/autonomy/conform/pkg/git" - flag "github.com/spf13/pflag" -) - -// Metadata contains metadata. -type Metadata struct { - Repository string `yaml:"repository"` - Docker *Docker - Git *Git - Version *Version - Variables VariablesMap `yaml:"variables"` - Flags *flag.FlagSet - Built string -} - -// Docker contains docker specific metadata. -type Docker struct { - Image *Image - PreviousStage string - CurrentStage string - NextStage string -} - -// Image contains information used to identity an image. -type Image struct { - Name string - Tag string -} - -// Git contains git specific metadata. -type Git struct { - Branch string - Ref string - Message string - SHA string - Tag string - Status string - IsBranch bool - IsClean bool - IsTag bool -} - -// Version contains version specific metadata. -type Version struct { - Original string - Major int64 - Minor int64 - Patch int64 - Prerelease string - IsPrerelease bool -} - -// VariablesMap is a map for user defined metadata. -type VariablesMap = map[string]interface{} - -// UnmarshalYAML implements the yaml.UnmarshalYAML interface. -func (m *Metadata) UnmarshalYAML(unmarshal func(interface{}) error) error { - var aux struct { - Repository string `yaml:"repository"` - Variables VariablesMap `yaml:"variables"` - } - if err := unmarshal(&aux); err != nil { - return err - } - - m.Repository = aux.Repository - m.Variables = aux.Variables - m.Built = time.Now().UTC().Format(time.RFC1123) - - if err := addMetadataForGit(m); err != nil { - return err - } - if err := addMetadataForDocker(m); err != nil { - return err - } - if err := addMetadataForVersion(m); err != nil { - return err - } - - return nil -} - -func addMetadataForVersion(m *Metadata) error { - m.Version = &Version{} - if m.Git.IsTag { - var ver *semver.Version - ver, err := semver.NewVersion(m.Git.Tag) - if err != nil { - return err - } - m.Version.Original = ver.Original() - m.Version.Major = ver.Major() - m.Version.Minor = ver.Minor() - m.Version.Patch = ver.Patch() - m.Version.Prerelease = ver.Prerelease() - if ver.Prerelease() != "" { - m.Version.IsPrerelease = true - } - } - - return nil -} - -func addMetadataForDocker(m *Metadata) error { - tag := m.Git.SHA - name := m.Repository - - dockerMetadata := &Docker{ - Image: &Image{ - Name: name, - Tag: tag, - }, - } - m.Docker = dockerMetadata - - return nil -} - -func addMetadataForGit(m *Metadata) error { - g, err := git.NewGit() - if err != nil { - return err - } - m.Git = &Git{} - if err = addBranchMetadataForGit(g, m); err != nil { - return err - } - if err = addRefMetadataForGit(g, m); err != nil { - return err - } - if err = addMessageMetadataForGit(g, m); err != nil { - return err - } - if err = addStatusMetadataForGit(g, m); err != nil { - return err - } - if err = addSHAMetadataForGit(g, m); err != nil { - return err - } - if err = addTagMetadataForGit(g, m); err != nil { - return err - } - - return nil -} - -func addBranchMetadataForGit(g *git.Git, m *Metadata) error { - branch, isBranch, err := g.Branch() - if err != nil { - return err - } - m.Git.Branch = branch - m.Git.IsBranch = isBranch - - return nil -} - -func addRefMetadataForGit(g *git.Git, m *Metadata) error { - ref, err := g.Ref() - if err != nil { - return err - } - m.Git.Ref = ref - - return nil -} - -func addMessageMetadataForGit(g *git.Git, m *Metadata) error { - message, err := g.Message() - if err != nil { - return err - } - m.Git.Message = message - - return nil -} - -func addSHAMetadataForGit(g *git.Git, m *Metadata) error { - sha, err := g.SHA() - if err != nil { - return err - } - - if !m.Git.IsClean { - sha = sha + "-dirty" - } - - m.Git.SHA = sha - - return nil -} - -func addStatusMetadataForGit(g *git.Git, m *Metadata) error { - status, isClean, err := g.Status() - if err != nil { - return err - } - m.Git.Status = status - m.Git.IsClean = isClean - - return nil -} - -func addTagMetadataForGit(g *git.Git, m *Metadata) error { - tag, isTag, err := g.Tag() - if err != nil { - return err - } - m.Git.Tag = tag - m.Git.IsTag = isTag - - return nil -} diff --git a/pkg/pipeline/pipeline.go b/pkg/pipeline/pipeline.go deleted file mode 100644 index ce5dc027..00000000 --- a/pkg/pipeline/pipeline.go +++ /dev/null @@ -1,160 +0,0 @@ -package pipeline - -import ( - "fmt" - "os" - "os/exec" - "os/signal" - "strings" - - "github.com/autonomy/conform/pkg/metadata" - "github.com/autonomy/conform/pkg/renderer" - "github.com/autonomy/conform/pkg/service" - "github.com/autonomy/conform/pkg/stage" - "github.com/autonomy/conform/pkg/task" -) - -// Pipeline defines the stages and artifacts. -type Pipeline struct { - Stages []string `yaml:"stages"` -} - -// Build executes a docker build. -// nolint: gocyclo -func (p *Pipeline) Build(metadata *metadata.Metadata, stages map[string]*stage.Stage, tasks map[string]*task.Task) (err error) { - if p == nil { - return - } - for i, stageName := range p.Stages { - if _, ok := stages[stageName]; !ok { - return fmt.Errorf("Stage %q is not defined in conform.yaml", stageName) - } - stage := stages[stageName] - // Anonymous func so the deferred service stop is executed at the end - // of each stage. - err = func() error { - done := make(chan bool) - for _, svc := range stage.Services { - err = svc.Start() - if err != nil { - return err - } - defer svc.Stop() - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - go func(s *service.Service) { - for { - select { - case sig := <-c: - fmt.Printf("Received %v signal, forcefully removing: %s\n", sig, s.Name) - s.Rm() - case <-done: - return - } - } - }(svc) - } - s, _err := p.render(metadata, stage.Tasks, tasks) - if _err != nil { - return _err - } - - var image string - if i+1 == len(p.Stages) { - image = metadata.Docker.Image.Name + ":" + metadata.Docker.Image.Tag - } else { - image = metadata.Docker.Image.Name + ":" + stageName - } - - _err = build(image, s) - if _err != nil { - return _err - } - for _, artifact := range stage.Artifacts { - _err = p.extract(metadata.Git.SHA, image, artifact) - if _err != nil { - return _err - } - } - - return nil - }() - - if err != nil { - return err - } - } - - return nil -} - -// extract extracts an artifact from a docker image. -func (p *Pipeline) extract(sha, image string, artifact *stage.Artifact) error { - if err := os.RemoveAll(artifact.Destination); err != nil { - return fmt.Errorf("failed to clean artifact destination: %v", err) - } - argsSlice := [][]string{ - {"create", "--name=" + sha, image}, - {"cp", sha + ":" + artifact.Source, artifact.Destination}, - {"rm", sha}, - } - for _, args := range argsSlice { - command := exec.Command("docker", args...) - command.Stderr = os.Stderr - err := command.Start() - if err != nil { - return err - } - err = command.Wait() - if err != nil { - return err - } - } - - return nil -} - -// render renders the stage tasks. -func (p *Pipeline) render(m *metadata.Metadata, requestedTasks []string, tasks map[string]*task.Task) (string, error) { - var s string - - defer func(m *metadata.Metadata) { - m.Docker.PreviousStage = "" - m.Docker.NextStage = "" - m.Docker.CurrentStage = "" - }(m) - - for i, task := range requestedTasks { - if _, ok := tasks[task]; !ok { - return "", fmt.Errorf("Task %q is not defined in conform.yaml", task) - } - if i != 0 { - m.Docker.PreviousStage = requestedTasks[i-1] - } - if i != len(requestedTasks)-1 { - m.Docker.NextStage = requestedTasks[i+1] - } - m.Docker.CurrentStage = requestedTasks[i] - rendered, err := renderer.RenderTemplate(tasks[task].Template, m) - if err != nil { - return "", err - } - s += rendered - } - - return s, nil -} - -func build(image, s string) error { - args := append([]string{"build", "--network", "host", "--tag", image, "-f", "-", "."}) - command := exec.Command("docker", args...) - command.Stdin = strings.NewReader(s) - command.Stdout = os.Stdout - command.Stderr = os.Stderr - err := command.Start() - if err != nil { - return err - } - - return command.Wait() -} diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go deleted file mode 100644 index df6d5007..00000000 --- a/pkg/policy/policy.go +++ /dev/null @@ -1,33 +0,0 @@ -package policy - -import ( - "github.com/autonomy/conform/pkg/metadata" - "github.com/autonomy/conform/pkg/pipeline" - "github.com/autonomy/conform/pkg/task" -) - -// Option is a functional option used to pass in arguments to a Policy. -type Option func(*Options) - -// Options defines the set of options available to a Policy. -type Options struct { - Pipeline *pipeline.Pipeline - Tasks map[string]*task.Task -} - -// Report summarizes the compliance of a policy. -type Report struct { - Errors []error -} - -// Policy is an interface that policies must implement. -type Policy interface { - Compliance(*metadata.Metadata, ...Option) Report - Pipeline(*pipeline.Pipeline) Option - Tasks(map[string]*task.Task) Option -} - -// Valid checks if a report is valid. -func (r Report) Valid() bool { - return len(r.Errors) == 0 -} diff --git a/pkg/renderer/renderer.go b/pkg/renderer/renderer.go deleted file mode 100644 index 5f1a4967..00000000 --- a/pkg/renderer/renderer.go +++ /dev/null @@ -1,76 +0,0 @@ -package renderer - -import ( - "bytes" - "fmt" - "io/ioutil" - "net/http" - "text/template" - - "github.com/Masterminds/sprig" - "github.com/autonomy/conform/pkg/metadata" -) - -// Renderer renders all pipeline templates. -type Renderer struct{} - -// Render renders a template. -func (r *Renderer) Render(m *metadata.Metadata, s string) (string, error) { - var wr bytes.Buffer - funcMap := makeFuncMap(m) - tmpl, err := template.New("").Funcs(funcMap).Parse(s) - if err != nil { - return "", err - } - err = tmpl.Execute(&wr, m) - if err != nil { - return "", err - } - - return wr.String(), nil -} - -// RenderTemplate renders a template with the provided metadata. -func RenderTemplate(contents string, metadata *metadata.Metadata) (string, error) { - renderer := Renderer{} - rendered, err := renderer.Render(metadata, contents) - if err != nil { - return "", err - } - - return rendered, nil -} - -func makeFuncMap(m *metadata.Metadata) template.FuncMap { - funcMap := sprig.TxtFuncMap() - - funcMap["fromURL"] = func(url string) (string, error) { - resp, err := http.Get(url) - if err != nil { - return "", err - } - defer func() { - _err := resp.Body.Close() - if _err != nil { - fmt.Printf("Failed to close the response body: %#v", _err) - } - }() - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("Failed to download from URL: %d", resp.StatusCode) - } - - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", err - } - - rendered, err := RenderTemplate(string(b), m) - if err != nil { - return "", err - } - - return rendered, nil - } - - return funcMap -} diff --git a/pkg/script/script.go b/pkg/script/script.go deleted file mode 100644 index ce743b34..00000000 --- a/pkg/script/script.go +++ /dev/null @@ -1,38 +0,0 @@ -package script - -import ( - "fmt" - "os" - "os/exec" - - "github.com/autonomy/conform/pkg/metadata" - "github.com/autonomy/conform/pkg/renderer" -) - -// Script defines a template that can be executed. -type Script struct { - Template string `yaml:"template"` - Rendered string -} - -// Execute executes the pipeline script. -func (s *Script) Execute(metadata *metadata.Metadata) error { - r, err := renderer.RenderTemplate(s.Template, metadata) - if err != nil { - return err - } - s.Rendered = r - command := exec.Command("bash", "-c", s.Rendered) - command.Stdout = os.Stdout - command.Stderr = os.Stderr - err = command.Start() - if err != nil { - return err - } - err = command.Wait() - if err != nil { - return fmt.Errorf("Failed executing script: %v", err) - } - - return nil -} diff --git a/pkg/service/service.go b/pkg/service/service.go deleted file mode 100644 index e29d0629..00000000 --- a/pkg/service/service.go +++ /dev/null @@ -1,60 +0,0 @@ -package service - -import ( - "log" - "os" - "os/exec" -) - -// Service represents a container that can be run during a stage. -type Service struct { - Name string `yaml:"name"` - Image string `yaml:"image"` - Ports []string `yaml:"ports,omitempty"` - Cmd string `yaml:"cmd,omitempty"` -} - -// Start starts the service. -func (s *Service) Start() error { - args := []string{"run", "--rm", "-d", "--network=host", "--name=" + s.Name, s.Image} - if s.Cmd != "" { - args = append(args, s.Cmd) - } - command := exec.Command("docker", args...) - command.Stderr = os.Stderr - err := command.Start() - if err != nil { - return err - } - err = command.Wait() - - return err -} - -// Stop stops the service. -func (s *Service) Stop() { - command := exec.Command("docker", []string{"stop", s.Name}...) - command.Stderr = os.Stderr - err := command.Start() - if err != nil { - log.Printf("Failed to stop service %q: %v", s.Name, err) - } - err = command.Wait() - if err != nil { - log.Printf("Failed to stop service %q: %v", s.Name, err) - } -} - -// Rm forcefully removes the service. -func (s *Service) Rm() { - command := exec.Command("docker", []string{"rm", "-f", s.Name}...) - command.Stderr = os.Stderr - err := command.Start() - if err != nil { - log.Printf("Failed to remove service %q: %v", s.Name, err) - } - err = command.Wait() - if err != nil { - log.Printf("Failed to remove service %q: %v", s.Name, err) - } -} diff --git a/pkg/stage/stage.go b/pkg/stage/stage.go deleted file mode 100644 index 8510a880..00000000 --- a/pkg/stage/stage.go +++ /dev/null @@ -1,16 +0,0 @@ -package stage - -import "github.com/autonomy/conform/pkg/service" - -// Stage defines a stage within a pipeline. -type Stage struct { - Artifacts []*Artifact `yaml:"artifacts"` - Services []*service.Service `yaml:"services"` - Tasks []string `yaml:"tasks"` -} - -// Artifact is a struct that represents an artifact. -type Artifact struct { - Source string `yaml:"source"` - Destination string `yaml:"destination"` -} diff --git a/pkg/task/task.go b/pkg/task/task.go deleted file mode 100644 index 82458960..00000000 --- a/pkg/task/task.go +++ /dev/null @@ -1,7 +0,0 @@ -package task - -// Task defines a stage that can be used within a pipeline. -type Task struct { - Template string `yaml:"template"` - Rendered string -} diff --git a/pkg/utilities/utilities.go b/pkg/utilities/utilities.go deleted file mode 100644 index 8c5c914b..00000000 --- a/pkg/utilities/utilities.go +++ /dev/null @@ -1,36 +0,0 @@ -package utilities - -import ( - "context" - "fmt" - - "github.com/Masterminds/semver" - "github.com/autonomy/conform/pkg/constants" - "github.com/docker/docker/client" -) - -// CheckDockerVersion checks the Docker server version and returns an error if -// it is an incompatible version. -func CheckDockerVersion() error { - cli, err := client.NewEnvClient() - if err != nil { - return err - } - serverVersion, err := cli.ServerVersion(context.Background()) - if err != nil { - return err - } - minVersion, err := semver.NewVersion(constants.MinDockerVersion) - if err != nil { - return err - } - serverSemVer := semver.MustParse(serverVersion.Version) - i := serverSemVer.Compare(minVersion) - if i < 0 { - err = fmt.Errorf("At least Docker version %s is required", constants.MinDockerVersion) - - return err - } - - return nil -}