From f8e859016d77a201db3e546247a90f27e449f1d0 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Mon, 4 Apr 2022 13:27:15 +0100 Subject: [PATCH] feat: Add config to be used as pre-commit hook --- .gitignore | 2 +- .pre-commit-config.yaml | 23 ++++++++++ .pre-commit-hooks.yaml | 7 +++ Makefile | 2 +- README.md | 17 +++++++- cmd/check.go | 97 +++++++++++++++++++++++++++++++---------- cmd/root.go | 3 ++ go.mod | 4 ++ go.sum | 5 +++ 9 files changed, 132 insertions(+), 28 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 .pre-commit-hooks.yaml diff --git a/.gitignore b/.gitignore index e1e9f0c..852a7df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Compiled stuff /bin terraform-versions* +dist/ # Golang .idea/ - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..df1c0e1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +repos: +- repo: https://github.com/dnephin/pre-commit-golang + rev: v0.5.0 + hooks: + - id: go-fmt + - id: go-vet + - id: go-lint + - id: go-imports + - id: go-cyclo + args: [-over=15] + - id: validate-toml + - id: no-go-testing + - id: golangci-lint + - id: go-critic + - id: go-unit-tests + - id: go-build + - id: go-mod-tidy +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: no-commit-to-branch diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000..3206b5c --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,7 @@ +- id: terraform-provider-version-check + name: terraform-provider-version-check + description: Check versions of terraform providers + language: system + pass_filenames: false + entry: tfpvc + types: [terraform] diff --git a/Makefile b/Makefile index e1c003b..1860704 100644 --- a/Makefile +++ b/Makefile @@ -36,4 +36,4 @@ release-mac-applesilicon: tar -C bin -czvf bin/$(BINARY_NAME).osx-arm64.tar.gz $(BINARY_NAME) git checkout -- ./cmd/version.go -all: build-all \ No newline at end of file +all: build-all diff --git a/README.md b/README.md index e91eb75..53550a4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A utility to check whether Terraform providers configured in a project are up-to ## Usage ```shell -tfpvc-osx-amd64 --help +tfpvc help A utility to check whether Terraform providers configured in a project are up-to-date. * Reads .terraform.lock.hcl file to get details of providers used in a project @@ -29,11 +29,24 @@ Available Commands: Flags: --errorOnUpdate Exit with error code if updates are available + --findLockFiles Search for lockfiles in tfDir (default true) -h, --help help for tfpvc --tfDir string Directory with TF Files (default ".") ``` +## pre-commit hook +With pre-commit, you can ensure you are notified of updates to your Terraform provider config each time you make a commit. + +First install `pre-commit` and then create or update a `.pre-commit-config.yaml` in the root of your Git repo with at least the following content: + +```yaml +repos: + - repo: https://github.com/cloudreach/tf-provider-version-check + rev: "1.0.0" + hooks: + - id: terraform-provder-version-check +``` ## Requirements * Terraform must be installed and available on the system PATH @@ -56,4 +69,4 @@ make install-mac-intel # OSX/arm64 make install-mac-applesilicon -``` \ No newline at end of file +``` diff --git a/cmd/check.go b/cmd/check.go index c74c09a..d7f17ee 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -5,45 +5,97 @@ import ( "encoding/json" "errors" "fmt" - "github.com/hashicorp/go-version" - "github.com/hashicorp/terraform-exec/tfexec" "io/ioutil" + "log" "net/http" "os" "os/exec" + "os/user" + "path/filepath" "strings" "time" + + mapset "github.com/deckarep/golang-set" + "github.com/fatih/color" + "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform-exec/tfexec" ) -func check() { - if !lockFileExists(tfDir) { - fmt.Println("No .terraform.lock.hcl found. Exiting") - os.Exit(1) +func findDirsWithFiles() []string { + var toReturn []string + + var searchPath string + if strings.HasPrefix(tfDir, "~/") { + usr, _ := user.Current() + homeDir := usr.HomeDir + + searchPath = filepath.Join(homeDir, tfDir[2:]) + } else { + searchPath = tfDir } - execPath, err := exec.LookPath("terraform") + err := filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Fatal(err) + return nil + } + + if !info.IsDir() && info.Name() == ".terraform.lock.hcl" { + toReturn = append(toReturn, filepath.Dir(path)) + } + + return nil + }) if err != nil { - fmt.Println("Terraform executable not found on path, install terraform") - os.Exit(1) + log.Fatal(err) } - tf, err := tfexec.NewTerraform(tfDir, execPath) - if err != nil { - fmt.Printf("Error accessing Terraform: %s\n", err) - os.Exit(1) + return toReturn +} + +func check() { + dirSet := mapset.NewSet() + + if findLockFiles { + for _, dir := range findDirsWithFiles() { + dirSet.Add(dir) + } + } else { + if !lockFileExists(tfDir) { + fmt.Println("No .terraform.lock.hcl found. Exiting") + os.Exit(1) + } + dirSet.Add(tfDir) } - _, providerVersions, err := tf.Version(context.Background(), true) + execPath, err := exec.LookPath("terraform") if err != nil { - fmt.Printf("Error running terraform version: %s\n", err) + fmt.Println("Terraform executable not found on path, install terraform") os.Exit(1) } updatesAvailable := false - for provider, version := range providerVersions { - updates := checkVersion(provider, version) - if updates { - updatesAvailable = true + + dirIterator := dirSet.Iterator() + for dir := range dirIterator.C { + fmt.Println("Found lockfile in: " + dir.(string)) + tf, err := tfexec.NewTerraform(dir.(string), execPath) + if err != nil { + fmt.Printf("Error accessing Terraform: %s\n", err) + os.Exit(1) + } + + _, providerVersions, err := tf.Version(context.Background(), true) + if err != nil { + fmt.Printf("Error running terraform version: %s\n", err) + os.Exit(1) + } + + for provider, version := range providerVersions { + updates := checkVersion(provider, version) + if updates { + updatesAvailable = true + } } } @@ -56,6 +108,7 @@ func check() { os.Exit(0) } +// RegistryResponse represents the JSON return by the HC Registry as a struct type RegistryResponse struct { ID string `json:"id"` Owner string `json:"owner"` @@ -93,30 +146,26 @@ func checkVersion(provider string, localVersion *version.Version) bool { resp, err := http.Get("https://registry.terraform.io/v1/providers/" + providerName) if err != nil { fmt.Printf("Couldn't get provider details from HC: %s\n", err) - os.Exit(1) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Printf("Couldn't get read response body: %s\n", err) - os.Exit(1) } var response RegistryResponse if err := json.Unmarshal(body, &response); err != nil { fmt.Printf("Could not unmarshal response JSON: %s\n", err) - os.Exit(1) } remoteVersion, err := version.NewVersion(response.Version) if err != nil { fmt.Printf("Could not parse remote version: %s\n", err) - os.Exit(1) } if localVersion.LessThan(remoteVersion) { - fmt.Println("Update of", providerName, "available", localVersion, "<", remoteVersion) + fmt.Println("Update of", color.HiYellowString(providerName), "available", color.RedString(localVersion.String()), "<", color.BlueString(remoteVersion.String())) return true } diff --git a/cmd/root.go b/cmd/root.go index 7004861..2c03a6d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,6 +7,7 @@ import ( var tfDir string var errorOnUpdate bool +var findLockFiles bool var rootCmd = &cobra.Command{ Use: "tfpvc", @@ -22,6 +23,7 @@ var rootCmd = &cobra.Command{ }, } +// Execute the check command func Execute() { cobra.CheckErr(rootCmd.Execute()) } @@ -30,6 +32,7 @@ func init() { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVar(&tfDir, "tfDir", ".", "Directory with TF Files") + rootCmd.PersistentFlags().BoolVar(&findLockFiles, "findLockFiles", true, "Search for lockfiles in tfDir") rootCmd.PersistentFlags().BoolVar(&errorOnUpdate, "errorOnUpdate", false, "Exit with error code if updates are available") } diff --git a/go.mod b/go.mod index c4d57f9..c002333 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module tf-provider-version-check go 1.17 require ( + github.com/deckarep/golang-set v1.8.0 + github.com/fatih/color v1.13.0 github.com/hashicorp/go-version v1.4.0 github.com/hashicorp/terraform-exec v0.16.0 github.com/spf13/cobra v1.4.0 @@ -15,6 +17,8 @@ require ( github.com/hashicorp/terraform-json v0.13.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/magiconair/properties v1.8.5 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/spf13/afero v1.6.0 // indirect diff --git a/go.sum b/go.sum index 012671c..612f2f9 100644 --- a/go.sum +++ b/go.sum @@ -103,6 +103,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -118,6 +120,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= @@ -299,12 +302,14 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=