Skip to content

Commit

Permalink
Switch to hc-install library to download Terraform binaries
Browse files Browse the repository at this point in the history
  • Loading branch information
yannlambret committed May 30, 2024
1 parent d28cc7c commit 72380b2
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 48 deletions.
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
4 changes: 2 additions & 2 deletions pkg/tfs/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 1 addition & 4 deletions pkg/tfs/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

// Current software version.
const tfsVersion = "v1.0.0"
const tfsVersion = "v1.1.0"

func InitConfig() {
userHomeDir, err := os.UserHomeDir()
Expand Down Expand Up @@ -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 <prefix> + <semver>.
viper.SetDefault("terraform_file_name_prefix", "terraform_")

Expand Down
52 changes: 28 additions & 24 deletions pkg/tfs/release.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
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"
)

type release struct {
Version *version.Version
CacheDirectory string
FileName string
URLPrefix string
BinaryURL string
ChecksumURL string
URL string
}

func NewRelease(v *version.Version) *release {
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down
10 changes: 0 additions & 10 deletions pkg/tfs/release_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package tfs

import (
"fmt"
"reflect"
"runtime"
"testing"

"github.com/hashicorp/go-version"
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 72380b2

Please sign in to comment.