From 7515d27e7519287f17383944c88155580be0ad87 Mon Sep 17 00:00:00 2001 From: Yann Lambret Date: Thu, 30 May 2024 22:32:11 +0200 Subject: [PATCH] Switch to hc-install library to download Terraform binaries --- README.md | 11 +++------ go.mod | 9 +++++++ go.sum | 12 ++++++++++ pkg/tfs/cache.go | 5 ++-- pkg/tfs/config.go | 5 +--- pkg/tfs/release.go | 52 ++++++++++++++++++++++------------------- pkg/tfs/release_test.go | 10 -------- 7 files changed, 55 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 34b2e33..a4ca131 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ version constrainst: $ tfs ``` -When downloading a Terraform binary, an SHA256 checksum of the file is -automatically performed based on the information given by HashiCorp -(releases are by default fetched from https://releases.hashicorp.com). +`tfs` now uses HashiCorp `hc-install` library to automatically download and +install Terraform. This means it is no longer possible to download files from +an alternative website. If there is no specific constrainst, `tfs` will activate the most recent Terraform version that has been downloaded so far. @@ -75,11 +75,6 @@ By default, the configuration file PATH will be equivalent to Here is a configuration template with the supported values: ```yaml -# -- Download URL - -# Change this if you need to download release files from a specific location. -#terraform_download_url: "https://releases.hashicorp.com" # default value - # -- Cache management # Cache directory for Terraform release files. diff --git a/go.mod b/go.mod index 662c46b..bc75dd1 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,14 @@ require ( github.com/spf13/viper v1.18.2 ) +require ( + github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect + github.com/cloudflare/circl v1.3.7 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + golang.org/x/mod v0.17.0 // indirect +) + require ( cloud.google.com/go v0.110.10 // indirect cloud.google.com/go/compute v1.23.3 // indirect @@ -34,6 +42,7 @@ require ( github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/hc-install v0.7.0 github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl/v2 v2.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index f9fee49..f0ca7a1 100644 --- a/go.sum +++ b/go.sum @@ -188,6 +188,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= @@ -207,6 +209,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -347,16 +351,22 @@ github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56 github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-getter v1.7.4 h1:3yQjWuxICvSpYwqSayAdKRFcvBl1y/vogCxczWSmix0= github.com/hashicorp/go-getter v1.7.4/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk= +github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.0.0 h1:efQznTz+ydmQXq3BOnRa3AXzvCeTq1P4dKj/z5GLlY8= @@ -528,6 +538,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/pkg/tfs/cache.go b/pkg/tfs/cache.go index 8a281ca..076b43f 100644 --- a/pkg/tfs/cache.go +++ b/pkg/tfs/cache.go @@ -55,7 +55,7 @@ func (c *localCache) Load() error { c.Releases[tfVersion.String()] = NewRelease(tfVersion).Init() // Set cache most recent release. if c.LastRelease != nil { - constraint, _ := version.NewConstraint("> " + c.LastRelease.Version.String()) + constraint, _ := version.NewConstraint(">" + c.LastRelease.Version.String()) if !constraint.Check(tfVersion) { continue } @@ -163,7 +163,7 @@ func (c *localCache) PruneUntil(v *version.Version) error { // Ignoring potential errors here because we have already // checked that the argument is a valid semantic version. - constraint, _ := version.NewConstraint("< " + v.String()) + constraint, _ := version.NewConstraint("<" + v.String()) for _, release := range c.Releases { if constraint.Check(release.Version) { @@ -230,7 +230,6 @@ func (c *localCache) AutoClean() { for _, release := range c.Releases { if constraint.Check(release.Version) && !release.SameAs(c.CurrentRelease) { // Try to remove the file silently. - slog.Info("Removed " + fmt.Sprint(release.Version.String())) release.Remove() } } diff --git a/pkg/tfs/config.go b/pkg/tfs/config.go index 9901726..7662bf6 100644 --- a/pkg/tfs/config.go +++ b/pkg/tfs/config.go @@ -9,7 +9,7 @@ import ( ) // Current software version. -const tfsVersion = "v1.0.0" +const tfsVersion = "v1.1.0" func InitConfig() { userHomeDir, err := os.UserHomeDir() @@ -58,9 +58,6 @@ func InitConfig() { // Application configuration directory. viper.SetDefault("config_directory", filepath.Join(userConfigDir, "tfs")) - // Terraform download URL. - viper.SetDefault("terraform_download_url", "https://releases.hashicorp.com") - // File names in the cache will be of the form + . viper.SetDefault("terraform_file_name_prefix", "terraform_") diff --git a/pkg/tfs/release.go b/pkg/tfs/release.go index 1c4f9f9..345bdc6 100644 --- a/pkg/tfs/release.go +++ b/pkg/tfs/release.go @@ -1,15 +1,17 @@ package tfs import ( - "fmt" + "context" "log/slog" "os" "path/filepath" "reflect" - "runtime" - "github.com/hashicorp/go-getter" "github.com/hashicorp/go-version" + install "github.com/hashicorp/hc-install" + "github.com/hashicorp/hc-install/product" + "github.com/hashicorp/hc-install/releases" + "github.com/hashicorp/hc-install/src" "github.com/spf13/viper" ) @@ -17,10 +19,6 @@ type release struct { Version *version.Version CacheDirectory string FileName string - URLPrefix string - BinaryURL string - ChecksumURL string - URL string } func NewRelease(v *version.Version) *release { @@ -40,18 +38,6 @@ func (r *release) Init() *release { // The local name of the Terraform binary. r.FileName = viper.GetString("terraform_file_name_prefix") + r.Version.String() - // Terraform download URL prefix. - r.URLPrefix = fmt.Sprintf("%s/terraform/%s/terraform_%s", viper.GetString("terraform_download_url"), r.Version.String(), r.Version.String()) - - // Terraform binary download URL. - r.BinaryURL = fmt.Sprintf("%s_%s_%s.zip", r.URLPrefix, runtime.GOOS, runtime.GOARCH) - - // Terraform checksum file download URL. - r.ChecksumURL = fmt.Sprintf("%s_SHA256SUMS", r.URLPrefix) - - // Full download URL. - r.URL = fmt.Sprintf("%s?checksum=file:%s", r.BinaryURL, r.ChecksumURL) - // Already installed and active? if target == filepath.Join(r.CacheDirectory, r.FileName) { Cache.ActiveRelease = r @@ -67,19 +53,38 @@ func (r *release) Install() error { "version", r.Version.String(), "cacheDirectory", r.CacheDirectory, "fileName", r.FileName, - "binaryURL", r.BinaryURL, ) // Check if the desired Terraform binary is already // installed, download it otherwise. - target := filepath.Join(r.CacheDirectory, r.FileName) + targetPath := filepath.Join(r.CacheDirectory, r.FileName) + + if _, err := os.Stat(targetPath); os.IsNotExist(err) { + ctx := context.Background() + i := install.NewInstaller() - if _, err := os.Stat(target); os.IsNotExist(err) { slog.Info("Downloading Terraform") - if err := getter.GetFile(target, r.URL); err != nil { + + srcPath, err := i.Install(ctx, []src.Installable{ + &releases.ExactVersion{ + Product: product.Terraform, + Version: r.Version, + }, + }) + if err != nil { slog.Error("Download failed", "error", err) return err } + // Move downloaded file. + b, err := os.ReadFile(srcPath) + if err != nil { + slog.Error("Unabled to read downloaded file", "error", err, "srcPath", srcPath) + return err + } + err = os.WriteFile(targetPath, b, os.ModePerm) + if err != nil { + slog.Error("Unabled to move download file to cache", "error", err, "targetPath", targetPath) + } } // Keep track of the current release for we don't @@ -156,7 +161,6 @@ func (r *release) Remove() error { // Check if we should also remove the symbolic link. if path, _ := filepath.EvalSymlinks(symlink); path == target { - slog.Info(path) os.Remove(symlink) } diff --git a/pkg/tfs/release_test.go b/pkg/tfs/release_test.go index 4644b22..4f124ef 100644 --- a/pkg/tfs/release_test.go +++ b/pkg/tfs/release_test.go @@ -1,9 +1,7 @@ package tfs import ( - "fmt" "reflect" - "runtime" "testing" "github.com/hashicorp/go-version" @@ -40,19 +38,11 @@ func TestRelease(t *testing.T) { // Initialize the expected output. filename := viper.GetString("terraform_file_name_prefix") + v.String() - urlPrefix := fmt.Sprintf("%s/terraform/%s/terraform_%s", viper.GetString("terraform_download_url"), v.String(), v.String()) - binaryURL := fmt.Sprintf("%s_%s_%s.zip", urlPrefix, runtime.GOOS, runtime.GOARCH) - checksumURL := fmt.Sprintf("%s_SHA256SUMS", urlPrefix) - url := fmt.Sprintf("%s?checksum=file:%s", binaryURL, checksumURL) expectedOutput := &release{ Version: v, CacheDirectory: Cache.Directory, FileName: filename, - URLPrefix: urlPrefix, - BinaryURL: binaryURL, - ChecksumURL: checksumURL, - URL: url, } // Compare the output to the expected output.