mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-28 22:28:25 +00:00
feat: OpenTofu support (#4499)
Co-authored-by: PePe Amengual <2208324+jamengual@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
82b95fce9d
commit
9c7e5fcac8
@@ -37,6 +37,12 @@ const (
|
||||
CheckoutStrategyMerge = "merge"
|
||||
)
|
||||
|
||||
// TF distributions
|
||||
const (
|
||||
TFDistributionTerraform = "terraform"
|
||||
TFDistributionOpenTofu = "opentofu"
|
||||
)
|
||||
|
||||
// To add a new flag you must:
|
||||
// 1. Add a const with the flag name (in alphabetic order).
|
||||
// 2. Add a new field to server.UserConfig and set the mapstructure tag equal to the flag name.
|
||||
@@ -134,6 +140,7 @@ const (
|
||||
SSLCertFileFlag = "ssl-cert-file"
|
||||
SSLKeyFileFlag = "ssl-key-file"
|
||||
RestrictFileList = "restrict-file-list"
|
||||
TFDistributionFlag = "tf-distribution"
|
||||
TFDownloadFlag = "tf-download"
|
||||
TFDownloadURLFlag = "tf-download-url"
|
||||
UseTFPluginCache = "use-tf-plugin-cache"
|
||||
@@ -176,6 +183,7 @@ const (
|
||||
DefaultRedisPort = 6379
|
||||
DefaultRedisTLSEnabled = false
|
||||
DefaultRedisInsecureSkipVerify = false
|
||||
DefaultTFDistribution = TFDistributionTerraform
|
||||
DefaultTFDownloadURL = "https://releases.hashicorp.com"
|
||||
DefaultTFDownload = true
|
||||
DefaultTFEHostname = "app.terraform.io"
|
||||
@@ -406,6 +414,10 @@ var stringFlags = map[string]stringFlag{
|
||||
SSLKeyFileFlag: {
|
||||
description: fmt.Sprintf("File containing x509 private key matching --%s.", SSLCertFileFlag),
|
||||
},
|
||||
TFDistributionFlag: {
|
||||
description: fmt.Sprintf("Which TF distribution to use. Can be set to %s or %s.", TFDistributionTerraform, TFDistributionOpenTofu),
|
||||
defaultValue: DefaultTFDistribution,
|
||||
},
|
||||
TFDownloadURLFlag: {
|
||||
description: "Base URL to download Terraform versions from.",
|
||||
defaultValue: DefaultTFDownloadURL,
|
||||
@@ -897,6 +909,9 @@ func (s *ServerCmd) setDefaults(c *server.UserConfig, v *viper.Viper) {
|
||||
if c.RedisPort == 0 {
|
||||
c.RedisPort = DefaultRedisPort
|
||||
}
|
||||
if c.TFDistribution == "" {
|
||||
c.TFDistribution = DefaultTFDistribution
|
||||
}
|
||||
if c.TFDownloadURL == "" {
|
||||
c.TFDownloadURL = DefaultTFDownloadURL
|
||||
}
|
||||
@@ -923,6 +938,11 @@ func (s *ServerCmd) validate(userConfig server.UserConfig) error {
|
||||
return fmt.Errorf("invalid log level: must be one of %v", ValidLogLevels)
|
||||
}
|
||||
|
||||
if userConfig.TFDistribution != TFDistributionTerraform && userConfig.TFDistribution != TFDistributionOpenTofu {
|
||||
return fmt.Errorf("invalid tf distribution: expected one of %s or %s",
|
||||
TFDistributionTerraform, TFDistributionOpenTofu)
|
||||
}
|
||||
|
||||
checkoutStrategy := userConfig.CheckoutStrategy
|
||||
if checkoutStrategy != CheckoutStrategyBranch && checkoutStrategy != CheckoutStrategyMerge {
|
||||
return fmt.Errorf("invalid checkout strategy: not one of %s or %s",
|
||||
|
||||
@@ -136,6 +136,7 @@ var testFlags = map[string]interface{}{
|
||||
SSLCertFileFlag: "cert-file",
|
||||
SSLKeyFileFlag: "key-file",
|
||||
RestrictFileList: false,
|
||||
TFDistributionFlag: "terraform",
|
||||
TFDownloadFlag: true,
|
||||
TFDownloadURLFlag: "https://my-hostname.com",
|
||||
TFEHostnameFlag: "my-hostname",
|
||||
|
||||
5
go.mod
5
go.mod
@@ -32,6 +32,7 @@ require (
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/moby/patternmatcher v0.6.0
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/opentofu/tofudl v0.0.0-20240923062014-8c1e00f33ce6
|
||||
github.com/petergtz/pegomock/v4 v4.1.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/redis/go-redis/v9 v9.6.1
|
||||
@@ -67,13 +68,15 @@ require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.5 // indirect
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/cloudflare/circl v1.3.9 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
|
||||
31
go.sum
31
go.sum
@@ -45,8 +45,13 @@ github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
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/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.5 h1:STOY3vgES59gNgoOt2w0nyHBjKViB/qSg7NjbQWPJkA=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.5/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -78,6 +83,7 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cactus/go-statsd-client/v5 v5.0.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY=
|
||||
github.com/cactus/go-statsd-client/v5 v5.1.0 h1:sbbdfIl9PgisjEoXzvXI1lwUKWElngsjJKaZeC021P4=
|
||||
github.com/cactus/go-statsd-client/v5 v5.1.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY=
|
||||
@@ -90,8 +96,9 @@ 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/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
|
||||
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
@@ -347,6 +354,8 @@ github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/opentofu/tofudl v0.0.0-20240923062014-8c1e00f33ce6 h1:+1yJm0gEoDaxYmMhmmU3gRAOMx3A43z84bokm1dQroU=
|
||||
github.com/opentofu/tofudl v0.0.0-20240923062014-8c1e00f33ce6/go.mod h1:CD1BhvxxNPp4ZBwNBjWycf5isG9UaPrzfE7J/E/s6RY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/petergtz/pegomock/v4 v4.1.0 h1:Reoy2rlwshuxNaD2ZWp5TrSCrmoFH5SSLHb5U1z2pog=
|
||||
@@ -434,6 +443,7 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
@@ -490,6 +500,8 @@ golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -525,6 +537,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -560,6 +573,9 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -582,6 +598,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -625,11 +642,17 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -640,6 +663,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -688,6 +714,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -1241,6 +1241,14 @@ This is useful when you have many projects and want to keep the pull request cle
|
||||
|
||||
Namespace for emitting stats/metrics. See [stats](stats.md) section.
|
||||
|
||||
### `--tf-distribution`
|
||||
```bash
|
||||
atlantis server --tf-distribution="terraform"
|
||||
# or
|
||||
ATLANTIS_TF_DISTRIBUTION="terraform"
|
||||
```
|
||||
Which TF distribution to use. Can be set to `terraform` or `opentofu`.
|
||||
|
||||
### `--tf-download`
|
||||
|
||||
```bash
|
||||
@@ -1266,6 +1274,8 @@ Setting this to `false` can be useful in an air-gapped environment where a downl
|
||||
|
||||
This has no impact if `--tf-download` is set to `false`.
|
||||
|
||||
This setting is not yet supported when `--tf-distribution` is set to `opentofu`.
|
||||
|
||||
### `--tfe-hostname`
|
||||
|
||||
```bash
|
||||
|
||||
@@ -26,7 +26,9 @@ import (
|
||||
"github.com/runatlantis/atlantis/server/core/runtime"
|
||||
runtimemocks "github.com/runatlantis/atlantis/server/core/runtime/mocks"
|
||||
"github.com/runatlantis/atlantis/server/core/runtime/policy"
|
||||
mock_policy "github.com/runatlantis/atlantis/server/core/runtime/policy/mocks"
|
||||
"github.com/runatlantis/atlantis/server/core/terraform"
|
||||
terraform_mocks "github.com/runatlantis/atlantis/server/core/terraform/mocks"
|
||||
"github.com/runatlantis/atlantis/server/events"
|
||||
"github.com/runatlantis/atlantis/server/events/command"
|
||||
"github.com/runatlantis/atlantis/server/events/mocks"
|
||||
@@ -53,10 +55,6 @@ var mockPreWorkflowHookRunner *runtimemocks.MockPreWorkflowHookRunner
|
||||
|
||||
var mockPostWorkflowHookRunner *runtimemocks.MockPostWorkflowHookRunner
|
||||
|
||||
func (m *NoopTFDownloader) GetAny(_, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NoopTFDownloader) Install(_ string, _ string, _ *version.Version) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
@@ -1317,7 +1315,11 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
|
||||
ExecutableName: "atlantis",
|
||||
AllowCommands: allowCommands,
|
||||
}
|
||||
terraformClient, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "", "default-tf-version", "https://releases.hashicorp.com", &NoopTFDownloader{}, true, false, projectCmdOutputHandler)
|
||||
|
||||
mockDownloader := terraform_mocks.NewMockDownloader()
|
||||
distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
|
||||
terraformClient, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "", "default-tf-version", "https://releases.hashicorp.com", true, false, projectCmdOutputHandler)
|
||||
Ok(t, err)
|
||||
boltdb, err := db.New(dataDir)
|
||||
Ok(t, err)
|
||||
@@ -1431,7 +1433,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
|
||||
|
||||
Ok(t, err)
|
||||
|
||||
conftextExec := policy.NewConfTestExecutorWorkflow(logger, binDir, &NoopTFDownloader{})
|
||||
conftextExec := policy.NewConfTestExecutorWorkflow(logger, binDir, mock_policy.NewMockDownloader())
|
||||
|
||||
// swapping out version cache to something that always returns local conftest
|
||||
// binary
|
||||
@@ -1881,4 +1883,7 @@ func ensureRunning014(t *testing.T) {
|
||||
//
|
||||
// Terraform v0.11.10
|
||||
// => 0.11.10
|
||||
var versionRegex = regexp.MustCompile("Terraform v(.*?)(\\s.*)?\n")
|
||||
//
|
||||
// OpenTofu v1.0.0
|
||||
// => 1.0.0
|
||||
var versionRegex = regexp.MustCompile("(?:Terraform|OpenTofu) v(.*?)(\\s.*)?\n")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -10,13 +11,13 @@ import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
|
||||
"github.com/hashicorp/go-getter/v2"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/runatlantis/atlantis/server/core/config/valid"
|
||||
"github.com/runatlantis/atlantis/server/core/runtime/cache"
|
||||
runtime_models "github.com/runatlantis/atlantis/server/core/runtime/models"
|
||||
"github.com/runatlantis/atlantis/server/core/terraform"
|
||||
"github.com/runatlantis/atlantis/server/events/command"
|
||||
"github.com/runatlantis/atlantis/server/events/models"
|
||||
"github.com/runatlantis/atlantis/server/logging"
|
||||
@@ -103,8 +104,21 @@ func (p *SourceResolverProxy) Resolve(policySet valid.PolicySet) (string, error)
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate pegomock generate --package mocks -o mocks/mock_downloader.go Downloader
|
||||
|
||||
type Downloader interface {
|
||||
GetAny(dst, src string) error
|
||||
}
|
||||
|
||||
type ConfTestGoGetterVersionDownloader struct{}
|
||||
|
||||
func (c ConfTestGoGetterVersionDownloader) GetAny(dst, src string) error {
|
||||
_, err := getter.GetAny(context.Background(), dst, src)
|
||||
return err
|
||||
}
|
||||
|
||||
type ConfTestVersionDownloader struct {
|
||||
downloader terraform.Downloader
|
||||
downloader Downloader
|
||||
}
|
||||
|
||||
func (c ConfTestVersionDownloader) downloadConfTestVersion(v *version.Version, destPath string) (runtime_models.FilePath, error) {
|
||||
@@ -142,7 +156,7 @@ type ConfTestExecutorWorkflow struct {
|
||||
Exec runtime_models.Exec
|
||||
}
|
||||
|
||||
func NewConfTestExecutorWorkflow(log logging.SimpleLogging, versionRootDir string, conftestDownloder terraform.Downloader) *ConfTestExecutorWorkflow {
|
||||
func NewConfTestExecutorWorkflow(log logging.SimpleLogging, versionRootDir string, conftestDownloder Downloader) *ConfTestExecutorWorkflow {
|
||||
downloader := ConfTestVersionDownloader{
|
||||
downloader: conftestDownloder,
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/runatlantis/atlantis/server/core/runtime/cache/mocks"
|
||||
models_mocks "github.com/runatlantis/atlantis/server/core/runtime/models/mocks"
|
||||
conftest_mocks "github.com/runatlantis/atlantis/server/core/runtime/policy/mocks"
|
||||
terraform_mocks "github.com/runatlantis/atlantis/server/core/terraform/mocks"
|
||||
"github.com/runatlantis/atlantis/server/events/command"
|
||||
"github.com/runatlantis/atlantis/server/logging"
|
||||
. "github.com/runatlantis/atlantis/testing"
|
||||
@@ -27,9 +26,11 @@ func TestConfTestVersionDownloader(t *testing.T) {
|
||||
|
||||
RegisterMockTestingT(t)
|
||||
|
||||
mockDownloader := terraform_mocks.NewMockDownloader()
|
||||
mockDownloader := conftest_mocks.NewMockDownloader()
|
||||
|
||||
subject := ConfTestVersionDownloader{downloader: mockDownloader}
|
||||
subject := ConfTestVersionDownloader{
|
||||
downloader: mockDownloader,
|
||||
}
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
|
||||
|
||||
112
server/core/runtime/policy/mocks/mock_downloader.go
Normal file
112
server/core/runtime/policy/mocks/mock_downloader.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// Code generated by pegomock. DO NOT EDIT.
|
||||
// Source: github.com/runatlantis/atlantis/server/core/runtime/policy (interfaces: Downloader)
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
pegomock "github.com/petergtz/pegomock/v4"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MockDownloader struct {
|
||||
fail func(message string, callerSkip ...int)
|
||||
}
|
||||
|
||||
func NewMockDownloader(options ...pegomock.Option) *MockDownloader {
|
||||
mock := &MockDownloader{}
|
||||
for _, option := range options {
|
||||
option.Apply(mock)
|
||||
}
|
||||
return mock
|
||||
}
|
||||
|
||||
func (mock *MockDownloader) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh }
|
||||
func (mock *MockDownloader) FailHandler() pegomock.FailHandler { return mock.fail }
|
||||
|
||||
func (mock *MockDownloader) GetAny(dst string, src string) error {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockDownloader().")
|
||||
}
|
||||
_params := []pegomock.Param{dst, src}
|
||||
_result := pegomock.GetGenericMockFrom(mock).Invoke("GetAny", _params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var _ret0 error
|
||||
if len(_result) != 0 {
|
||||
if _result[0] != nil {
|
||||
_ret0 = _result[0].(error)
|
||||
}
|
||||
}
|
||||
return _ret0
|
||||
}
|
||||
|
||||
func (mock *MockDownloader) VerifyWasCalledOnce() *VerifierMockDownloader {
|
||||
return &VerifierMockDownloader{
|
||||
mock: mock,
|
||||
invocationCountMatcher: pegomock.Times(1),
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockDownloader) VerifyWasCalled(invocationCountMatcher pegomock.InvocationCountMatcher) *VerifierMockDownloader {
|
||||
return &VerifierMockDownloader{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockDownloader) VerifyWasCalledInOrder(invocationCountMatcher pegomock.InvocationCountMatcher, inOrderContext *pegomock.InOrderContext) *VerifierMockDownloader {
|
||||
return &VerifierMockDownloader{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
inOrderContext: inOrderContext,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockDownloader) VerifyWasCalledEventually(invocationCountMatcher pegomock.InvocationCountMatcher, timeout time.Duration) *VerifierMockDownloader {
|
||||
return &VerifierMockDownloader{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type VerifierMockDownloader struct {
|
||||
mock *MockDownloader
|
||||
invocationCountMatcher pegomock.InvocationCountMatcher
|
||||
inOrderContext *pegomock.InOrderContext
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockDownloader) GetAny(dst string, src string) *MockDownloader_GetAny_OngoingVerification {
|
||||
_params := []pegomock.Param{dst, src}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetAny", _params, verifier.timeout)
|
||||
return &MockDownloader_GetAny_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockDownloader_GetAny_OngoingVerification struct {
|
||||
mock *MockDownloader
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockDownloader_GetAny_OngoingVerification) GetCapturedArguments() (string, string) {
|
||||
dst, src := c.GetAllCapturedArguments()
|
||||
return dst[len(dst)-1], src[len(src)-1]
|
||||
}
|
||||
|
||||
func (c *MockDownloader_GetAny_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string) {
|
||||
_params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(_params) > 0 {
|
||||
if len(_params) > 0 {
|
||||
_param0 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range _params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
}
|
||||
if len(_params) > 1 {
|
||||
_param1 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range _params[1] {
|
||||
_param1[u] = param.(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
133
server/core/terraform/distribution.go
Normal file
133
server/core/terraform/distribution.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/hc-install/product"
|
||||
"github.com/hashicorp/hc-install/releases"
|
||||
"github.com/opentofu/tofudl"
|
||||
)
|
||||
|
||||
type Distribution interface {
|
||||
BinName() string
|
||||
Downloader() Downloader
|
||||
// ResolveConstraint gets the latest version for the given constraint
|
||||
ResolveConstraint(context.Context, string) (*version.Version, error)
|
||||
}
|
||||
|
||||
type DistributionOpenTofu struct {
|
||||
downloader Downloader
|
||||
}
|
||||
|
||||
func NewDistributionOpenTofu() Distribution {
|
||||
return &DistributionOpenTofu{
|
||||
downloader: &TofuDownloader{},
|
||||
}
|
||||
}
|
||||
|
||||
func NewDistributionOpenTofuWithDownloader(downloader Downloader) Distribution {
|
||||
return &DistributionOpenTofu{
|
||||
downloader: downloader,
|
||||
}
|
||||
}
|
||||
|
||||
func (*DistributionOpenTofu) BinName() string {
|
||||
return "tofu"
|
||||
}
|
||||
|
||||
func (d *DistributionOpenTofu) Downloader() Downloader {
|
||||
return d.downloader
|
||||
}
|
||||
|
||||
func (*DistributionOpenTofu) ResolveConstraint(ctx context.Context, constraintStr string) (*version.Version, error) {
|
||||
dl, err := tofudl.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vc, err := version.NewConstraint(constraintStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing constraint string: %s", err)
|
||||
}
|
||||
|
||||
allVersions, err := dl.ListVersions(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing OpenTofu versions: %s", err)
|
||||
}
|
||||
|
||||
var versions []*version.Version
|
||||
for _, ver := range allVersions {
|
||||
v, err := version.NewVersion(string(ver.ID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if vc.Check(v) {
|
||||
versions = append(versions, v)
|
||||
}
|
||||
}
|
||||
sort.Sort(version.Collection(versions))
|
||||
|
||||
if len(versions) == 0 {
|
||||
return nil, fmt.Errorf("no OpenTofu versions found for constraints %s", constraintStr)
|
||||
}
|
||||
|
||||
// We want to select the highest version that satisfies the constraint.
|
||||
version := versions[len(versions)-1]
|
||||
|
||||
// Get the Version object from the versionDownloader.
|
||||
return version, nil
|
||||
}
|
||||
|
||||
type DistributionTerraform struct {
|
||||
downloader Downloader
|
||||
}
|
||||
|
||||
func NewDistributionTerraform() Distribution {
|
||||
return &DistributionTerraform{
|
||||
downloader: &TerraformDownloader{},
|
||||
}
|
||||
}
|
||||
|
||||
func NewDistributionTerraformWithDownloader(downloader Downloader) Distribution {
|
||||
return &DistributionTerraform{
|
||||
downloader: downloader,
|
||||
}
|
||||
}
|
||||
|
||||
func (*DistributionTerraform) BinName() string {
|
||||
return "terraform"
|
||||
}
|
||||
|
||||
func (d *DistributionTerraform) Downloader() Downloader {
|
||||
return d.downloader
|
||||
}
|
||||
|
||||
func (*DistributionTerraform) ResolveConstraint(ctx context.Context, constraintStr string) (*version.Version, error) {
|
||||
vc, err := version.NewConstraint(constraintStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing constraint string: %s", err)
|
||||
}
|
||||
|
||||
constrainedVersions := &releases.Versions{
|
||||
Product: product.Terraform,
|
||||
Constraints: vc,
|
||||
}
|
||||
|
||||
installCandidates, err := constrainedVersions.List(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing available versions: %s", err)
|
||||
}
|
||||
if len(installCandidates) == 0 {
|
||||
return nil, fmt.Errorf("no Terraform versions found for constraints %s", constraintStr)
|
||||
}
|
||||
|
||||
// We want to select the highest version that satisfies the constraint.
|
||||
versionDownloader := installCandidates[len(installCandidates)-1]
|
||||
|
||||
// Get the Version object from the versionDownloader.
|
||||
return versionDownloader.(*releases.ExactVersion).Version, nil
|
||||
}
|
||||
33
server/core/terraform/distribution_test.go
Normal file
33
server/core/terraform/distribution_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package terraform_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/runatlantis/atlantis/server/core/terraform"
|
||||
. "github.com/runatlantis/atlantis/testing"
|
||||
)
|
||||
|
||||
func TestOpenTofuBinName(t *testing.T) {
|
||||
d := terraform.NewDistributionOpenTofu()
|
||||
Equals(t, d.BinName(), "tofu")
|
||||
}
|
||||
|
||||
func TestResolveOpenTofuVersions(t *testing.T) {
|
||||
d := terraform.NewDistributionOpenTofu()
|
||||
version, err := d.ResolveConstraint(context.Background(), "= 1.8.0")
|
||||
Ok(t, err)
|
||||
Equals(t, version.String(), "1.8.0")
|
||||
}
|
||||
|
||||
func TestTerraformBinName(t *testing.T) {
|
||||
d := terraform.NewDistributionTerraform()
|
||||
Equals(t, d.BinName(), "terraform")
|
||||
}
|
||||
|
||||
func TestResolveTerraformVersions(t *testing.T) {
|
||||
d := terraform.NewDistributionTerraform()
|
||||
version, err := d.ResolveConstraint(context.Background(), "= 1.9.3")
|
||||
Ok(t, err)
|
||||
Equals(t, version.String(), "1.9.3")
|
||||
}
|
||||
70
server/core/terraform/downloader.go
Normal file
70
server/core/terraform/downloader.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"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/opentofu/tofudl"
|
||||
)
|
||||
|
||||
//go:generate pegomock generate --package mocks -o mocks/mock_downloader.go Downloader
|
||||
|
||||
// Downloader is for downloading terraform versions.
|
||||
type Downloader interface {
|
||||
Install(ctx context.Context, dir string, downloadURL string, v *version.Version) (string, error)
|
||||
}
|
||||
|
||||
type TofuDownloader struct{}
|
||||
|
||||
func (d *TofuDownloader) Install(ctx context.Context, dir string, _downloadURL string, v *version.Version) (string, error) {
|
||||
// Initialize the downloader:
|
||||
dl, err := tofudl.New()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
binary, err := dl.Download(ctx, tofudl.DownloadOptVersion(tofudl.Version(v.String())))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Write out the tofu binary to the disk:
|
||||
file := filepath.Join(dir, "tofu"+v.String())
|
||||
if err := os.WriteFile(file, binary, 0755); /* #nosec G306 */ err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
type TerraformDownloader struct{}
|
||||
|
||||
func (d *TerraformDownloader) Install(ctx context.Context, dir string, downloadURL string, v *version.Version) (string, error) {
|
||||
installer := install.NewInstaller()
|
||||
execPath, err := installer.Install(ctx, []src.Installable{
|
||||
&releases.ExactVersion{
|
||||
Product: product.Terraform,
|
||||
Version: v,
|
||||
InstallDir: dir,
|
||||
ApiBaseURL: downloadURL,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// hc-install installs terraform binary as just "terraform".
|
||||
// We need to rename it to terraform{version} to be consistent with current naming convention.
|
||||
newPath := filepath.Join(dir, "terraform"+v.String())
|
||||
if err := os.Rename(execPath, newPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return newPath, nil
|
||||
}
|
||||
46
server/core/terraform/downloader_test.go
Normal file
46
server/core/terraform/downloader_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package terraform_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
. "github.com/petergtz/pegomock/v4"
|
||||
"github.com/runatlantis/atlantis/cmd"
|
||||
"github.com/runatlantis/atlantis/server/core/terraform"
|
||||
)
|
||||
|
||||
func TestTerraformInstall(t *testing.T) {
|
||||
d := &terraform.TerraformDownloader{}
|
||||
RegisterMockTestingT(t)
|
||||
binDir := t.TempDir()
|
||||
|
||||
v, _ := version.NewVersion("1.8.1")
|
||||
|
||||
newPath, err := d.Install(context.Background(), binDir, cmd.DefaultTFDownloadURL, v)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(newPath); os.IsNotExist(err) {
|
||||
t.Errorf("Binary not found at %s", newPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenTofuInstall(t *testing.T) {
|
||||
d := &terraform.TofuDownloader{}
|
||||
RegisterMockTestingT(t)
|
||||
binDir := t.TempDir()
|
||||
|
||||
v, _ := version.NewVersion("1.8.0")
|
||||
|
||||
newPath, err := d.Install(context.Background(), binDir, cmd.DefaultTFDownloadURL, v)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(newPath); os.IsNotExist(err) {
|
||||
t.Errorf("Binary not found at %s", newPath)
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
go_version "github.com/hashicorp/go-version"
|
||||
pegomock "github.com/petergtz/pegomock/v4"
|
||||
"reflect"
|
||||
@@ -25,38 +26,23 @@ func NewMockDownloader(options ...pegomock.Option) *MockDownloader {
|
||||
func (mock *MockDownloader) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh }
|
||||
func (mock *MockDownloader) FailHandler() pegomock.FailHandler { return mock.fail }
|
||||
|
||||
func (mock *MockDownloader) GetAny(dst string, src string) error {
|
||||
func (mock *MockDownloader) Install(ctx context.Context, dir string, downloadURL string, v *go_version.Version) (string, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockDownloader().")
|
||||
}
|
||||
params := []pegomock.Param{dst, src}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("GetAny", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(error)
|
||||
_params := []pegomock.Param{ctx, dir, downloadURL, v}
|
||||
_result := pegomock.GetGenericMockFrom(mock).Invoke("Install", _params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var _ret0 string
|
||||
var _ret1 error
|
||||
if len(_result) != 0 {
|
||||
if _result[0] != nil {
|
||||
_ret0 = _result[0].(string)
|
||||
}
|
||||
if _result[1] != nil {
|
||||
_ret1 = _result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockDownloader) Install(dir string, downloadURL string, v *go_version.Version) (string, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockDownloader().")
|
||||
}
|
||||
params := []pegomock.Param{dir, downloadURL, v}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Install", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 string
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
return _ret0, _ret1
|
||||
}
|
||||
|
||||
func (mock *MockDownloader) VerifyWasCalledOnce() *VerifierMockDownloader {
|
||||
@@ -96,40 +82,9 @@ type VerifierMockDownloader struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockDownloader) GetAny(dst string, src string) *MockDownloader_GetAny_OngoingVerification {
|
||||
params := []pegomock.Param{dst, src}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetAny", params, verifier.timeout)
|
||||
return &MockDownloader_GetAny_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockDownloader_GetAny_OngoingVerification struct {
|
||||
mock *MockDownloader
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockDownloader_GetAny_OngoingVerification) GetCapturedArguments() (string, string) {
|
||||
dst, src := c.GetAllCapturedArguments()
|
||||
return dst[len(dst)-1], src[len(src)-1]
|
||||
}
|
||||
|
||||
func (c *MockDownloader_GetAny_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
_param1 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[1] {
|
||||
_param1[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockDownloader) Install(dir string, downloadURL string, v *go_version.Version) *MockDownloader_Install_OngoingVerification {
|
||||
params := []pegomock.Param{dir, downloadURL, v}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Install", params, verifier.timeout)
|
||||
func (verifier *VerifierMockDownloader) Install(ctx context.Context, dir string, downloadURL string, v *go_version.Version) *MockDownloader_Install_OngoingVerification {
|
||||
_params := []pegomock.Param{ctx, dir, downloadURL, v}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Install", _params, verifier.timeout)
|
||||
return &MockDownloader_Install_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
@@ -138,25 +93,37 @@ type MockDownloader_Install_OngoingVerification struct {
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockDownloader_Install_OngoingVerification) GetCapturedArguments() (string, string, *go_version.Version) {
|
||||
dir, downloadURL, v := c.GetAllCapturedArguments()
|
||||
return dir[len(dir)-1], downloadURL[len(downloadURL)-1], v[len(v)-1]
|
||||
func (c *MockDownloader_Install_OngoingVerification) GetCapturedArguments() (context.Context, string, string, *go_version.Version) {
|
||||
ctx, dir, downloadURL, v := c.GetAllCapturedArguments()
|
||||
return ctx[len(ctx)-1], dir[len(dir)-1], downloadURL[len(downloadURL)-1], v[len(v)-1]
|
||||
}
|
||||
|
||||
func (c *MockDownloader_Install_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string, _param2 []*go_version.Version) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
func (c *MockDownloader_Install_OngoingVerification) GetAllCapturedArguments() (_param0 []context.Context, _param1 []string, _param2 []string, _param3 []*go_version.Version) {
|
||||
_params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(_params) > 0 {
|
||||
if len(_params) > 0 {
|
||||
_param0 = make([]context.Context, len(c.methodInvocations))
|
||||
for u, param := range _params[0] {
|
||||
_param0[u] = param.(context.Context)
|
||||
}
|
||||
}
|
||||
if len(_params) > 1 {
|
||||
_param1 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[1] {
|
||||
for u, param := range _params[1] {
|
||||
_param1[u] = param.(string)
|
||||
}
|
||||
_param2 = make([]*go_version.Version, len(c.methodInvocations))
|
||||
for u, param := range params[2] {
|
||||
_param2[u] = param.(*go_version.Version)
|
||||
}
|
||||
if len(_params) > 2 {
|
||||
_param2 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range _params[2] {
|
||||
_param2[u] = param.(string)
|
||||
}
|
||||
}
|
||||
if len(_params) > 3 {
|
||||
_param3 = make([]*go_version.Version, len(c.methodInvocations))
|
||||
for u, param := range _params[3] {
|
||||
_param3[u] = param.(*go_version.Version)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
@@ -31,49 +31,49 @@ func (mock *MockClient) DetectVersion(log logging.SimpleLogging, projectDirector
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockClient().")
|
||||
}
|
||||
params := []pegomock.Param{log, projectDirectory}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("DetectVersion", params, []reflect.Type{reflect.TypeOf((**go_version.Version)(nil)).Elem()})
|
||||
var ret0 *go_version.Version
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(*go_version.Version)
|
||||
_params := []pegomock.Param{log, projectDirectory}
|
||||
_result := pegomock.GetGenericMockFrom(mock).Invoke("DetectVersion", _params, []reflect.Type{reflect.TypeOf((**go_version.Version)(nil)).Elem()})
|
||||
var _ret0 *go_version.Version
|
||||
if len(_result) != 0 {
|
||||
if _result[0] != nil {
|
||||
_ret0 = _result[0].(*go_version.Version)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
return _ret0
|
||||
}
|
||||
|
||||
func (mock *MockClient) EnsureVersion(log logging.SimpleLogging, v *go_version.Version) error {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockClient().")
|
||||
}
|
||||
params := []pegomock.Param{log, v}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("EnsureVersion", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(error)
|
||||
_params := []pegomock.Param{log, v}
|
||||
_result := pegomock.GetGenericMockFrom(mock).Invoke("EnsureVersion", _params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var _ret0 error
|
||||
if len(_result) != 0 {
|
||||
if _result[0] != nil {
|
||||
_ret0 = _result[0].(error)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
return _ret0
|
||||
}
|
||||
|
||||
func (mock *MockClient) RunCommandWithVersion(ctx command.ProjectContext, path string, args []string, envs map[string]string, v *go_version.Version, workspace string) (string, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockClient().")
|
||||
}
|
||||
params := []pegomock.Param{ctx, path, args, envs, v, workspace}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("RunCommandWithVersion", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 string
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
_params := []pegomock.Param{ctx, path, args, envs, v, workspace}
|
||||
_result := pegomock.GetGenericMockFrom(mock).Invoke("RunCommandWithVersion", _params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var _ret0 string
|
||||
var _ret1 error
|
||||
if len(_result) != 0 {
|
||||
if _result[0] != nil {
|
||||
_ret0 = _result[0].(string)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
if _result[1] != nil {
|
||||
_ret1 = _result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
return _ret0, _ret1
|
||||
}
|
||||
|
||||
func (mock *MockClient) VerifyWasCalledOnce() *VerifierMockClient {
|
||||
@@ -114,8 +114,8 @@ type VerifierMockClient struct {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockClient) DetectVersion(log logging.SimpleLogging, projectDirectory string) *MockClient_DetectVersion_OngoingVerification {
|
||||
params := []pegomock.Param{log, projectDirectory}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DetectVersion", params, verifier.timeout)
|
||||
_params := []pegomock.Param{log, projectDirectory}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DetectVersion", _params, verifier.timeout)
|
||||
return &MockClient_DetectVersion_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
@@ -130,23 +130,27 @@ func (c *MockClient_DetectVersion_OngoingVerification) GetCapturedArguments() (l
|
||||
}
|
||||
|
||||
func (c *MockClient_DetectVersion_OngoingVerification) GetAllCapturedArguments() (_param0 []logging.SimpleLogging, _param1 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(_params) > 0 {
|
||||
if len(_params) > 0 {
|
||||
_param0 = make([]logging.SimpleLogging, len(c.methodInvocations))
|
||||
for u, param := range params[0] {
|
||||
for u, param := range _params[0] {
|
||||
_param0[u] = param.(logging.SimpleLogging)
|
||||
}
|
||||
}
|
||||
if len(_params) > 1 {
|
||||
_param1 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[1] {
|
||||
for u, param := range _params[1] {
|
||||
_param1[u] = param.(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockClient) EnsureVersion(log logging.SimpleLogging, v *go_version.Version) *MockClient_EnsureVersion_OngoingVerification {
|
||||
params := []pegomock.Param{log, v}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "EnsureVersion", params, verifier.timeout)
|
||||
_params := []pegomock.Param{log, v}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "EnsureVersion", _params, verifier.timeout)
|
||||
return &MockClient_EnsureVersion_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
@@ -161,23 +165,27 @@ func (c *MockClient_EnsureVersion_OngoingVerification) GetCapturedArguments() (l
|
||||
}
|
||||
|
||||
func (c *MockClient_EnsureVersion_OngoingVerification) GetAllCapturedArguments() (_param0 []logging.SimpleLogging, _param1 []*go_version.Version) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(_params) > 0 {
|
||||
if len(_params) > 0 {
|
||||
_param0 = make([]logging.SimpleLogging, len(c.methodInvocations))
|
||||
for u, param := range params[0] {
|
||||
for u, param := range _params[0] {
|
||||
_param0[u] = param.(logging.SimpleLogging)
|
||||
}
|
||||
}
|
||||
if len(_params) > 1 {
|
||||
_param1 = make([]*go_version.Version, len(c.methodInvocations))
|
||||
for u, param := range params[1] {
|
||||
for u, param := range _params[1] {
|
||||
_param1[u] = param.(*go_version.Version)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockClient) RunCommandWithVersion(ctx command.ProjectContext, path string, args []string, envs map[string]string, v *go_version.Version, workspace string) *MockClient_RunCommandWithVersion_OngoingVerification {
|
||||
params := []pegomock.Param{ctx, path, args, envs, v, workspace}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RunCommandWithVersion", params, verifier.timeout)
|
||||
_params := []pegomock.Param{ctx, path, args, envs, v, workspace}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RunCommandWithVersion", _params, verifier.timeout)
|
||||
return &MockClient_RunCommandWithVersion_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
@@ -192,32 +200,44 @@ func (c *MockClient_RunCommandWithVersion_OngoingVerification) GetCapturedArgume
|
||||
}
|
||||
|
||||
func (c *MockClient_RunCommandWithVersion_OngoingVerification) GetAllCapturedArguments() (_param0 []command.ProjectContext, _param1 []string, _param2 [][]string, _param3 []map[string]string, _param4 []*go_version.Version, _param5 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(_params) > 0 {
|
||||
if len(_params) > 0 {
|
||||
_param0 = make([]command.ProjectContext, len(c.methodInvocations))
|
||||
for u, param := range params[0] {
|
||||
for u, param := range _params[0] {
|
||||
_param0[u] = param.(command.ProjectContext)
|
||||
}
|
||||
}
|
||||
if len(_params) > 1 {
|
||||
_param1 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[1] {
|
||||
for u, param := range _params[1] {
|
||||
_param1[u] = param.(string)
|
||||
}
|
||||
}
|
||||
if len(_params) > 2 {
|
||||
_param2 = make([][]string, len(c.methodInvocations))
|
||||
for u, param := range params[2] {
|
||||
for u, param := range _params[2] {
|
||||
_param2[u] = param.([]string)
|
||||
}
|
||||
}
|
||||
if len(_params) > 3 {
|
||||
_param3 = make([]map[string]string, len(c.methodInvocations))
|
||||
for u, param := range params[3] {
|
||||
for u, param := range _params[3] {
|
||||
_param3[u] = param.(map[string]string)
|
||||
}
|
||||
}
|
||||
if len(_params) > 4 {
|
||||
_param4 = make([]*go_version.Version, len(c.methodInvocations))
|
||||
for u, param := range params[4] {
|
||||
for u, param := range _params[4] {
|
||||
_param4[u] = param.(*go_version.Version)
|
||||
}
|
||||
}
|
||||
if len(_params) > 5 {
|
||||
_param5 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[5] {
|
||||
for u, param := range _params[5] {
|
||||
_param5[u] = param.(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -27,12 +27,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-getter/v2"
|
||||
"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/hashicorp/terraform-config-inspect/tfconfig"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
@@ -62,6 +57,9 @@ type Client interface {
|
||||
}
|
||||
|
||||
type DefaultClient struct {
|
||||
// Distribution handles logic specific to the TF distribution being used by Atlantis
|
||||
distribution Distribution
|
||||
|
||||
// defaultVersion is the default version of terraform to use if another
|
||||
// version isn't specified.
|
||||
defaultVersion *version.Version
|
||||
@@ -72,8 +70,7 @@ type DefaultClient struct {
|
||||
// overrideTF can be used to override the terraform binary during testing
|
||||
// with another binary, ex. echo.
|
||||
overrideTF string
|
||||
// downloader downloads terraform versions.
|
||||
downloader Downloader
|
||||
// settings for the downloader.
|
||||
downloadBaseURL string
|
||||
downloadAllowed bool
|
||||
// versions maps from the string representation of a tf version (ex. 0.11.10)
|
||||
@@ -90,14 +87,6 @@ type DefaultClient struct {
|
||||
projectCmdOutputHandler jobs.ProjectCommandOutputHandler
|
||||
}
|
||||
|
||||
//go:generate pegomock generate --package mocks -o mocks/mock_downloader.go Downloader
|
||||
|
||||
// Downloader is for downloading terraform versions.
|
||||
type Downloader interface {
|
||||
Install(dir string, downloadURL string, v *version.Version) (string, error)
|
||||
GetAny(dst, src string) error
|
||||
}
|
||||
|
||||
// versionRegex extracts the version from `terraform version` output.
|
||||
//
|
||||
// Terraform v0.12.0-alpha4 (2c36829d3265661d8edbd5014de8090ea7e2a076)
|
||||
@@ -105,11 +94,15 @@ type Downloader interface {
|
||||
//
|
||||
// Terraform v0.11.10
|
||||
// => 0.11.10
|
||||
var versionRegex = regexp.MustCompile("Terraform v(.*?)(\\s.*)?\n")
|
||||
//
|
||||
// OpenTofu v1.0.0
|
||||
// => 1.0.0
|
||||
var versionRegex = regexp.MustCompile("(?:Terraform|OpenTofu) v(.*?)(\\s.*)?\n")
|
||||
|
||||
// NewClientWithDefaultVersion creates a new terraform client and pre-fetches the default version
|
||||
func NewClientWithDefaultVersion(
|
||||
log logging.SimpleLogging,
|
||||
distribution Distribution,
|
||||
binDir string,
|
||||
cacheDir string,
|
||||
tfeToken string,
|
||||
@@ -117,7 +110,6 @@ func NewClientWithDefaultVersion(
|
||||
defaultVersionStr string,
|
||||
defaultVersionFlagName string,
|
||||
tfDownloadURL string,
|
||||
tfDownloader Downloader,
|
||||
tfDownloadAllowed bool,
|
||||
usePluginCache bool,
|
||||
fetchAsync bool,
|
||||
@@ -128,9 +120,9 @@ func NewClientWithDefaultVersion(
|
||||
versions := make(map[string]string)
|
||||
var versionsLock sync.Mutex
|
||||
|
||||
localPath, err := exec.LookPath("terraform")
|
||||
localPath, err := exec.LookPath(distribution.BinName())
|
||||
if err != nil && defaultVersionStr == "" {
|
||||
return nil, fmt.Errorf("terraform not found in $PATH. Set --%s or download terraform from https://developer.hashicorp.com/terraform/downloads", defaultVersionFlagName)
|
||||
return nil, fmt.Errorf("%s not found in $PATH. Set --%s or download terraform from https://developer.hashicorp.com/terraform/downloads", distribution.BinName(), defaultVersionFlagName)
|
||||
}
|
||||
if err == nil {
|
||||
localVersion, err = getVersion(localPath)
|
||||
@@ -139,6 +131,7 @@ func NewClientWithDefaultVersion(
|
||||
}
|
||||
versions[localVersion.String()] = localPath
|
||||
if defaultVersionStr == "" {
|
||||
|
||||
// If they haven't set a default version, then whatever they had
|
||||
// locally is now the default.
|
||||
finalDefaultVersion = localVersion
|
||||
@@ -155,10 +148,10 @@ func NewClientWithDefaultVersion(
|
||||
// Since ensureVersion might end up downloading terraform,
|
||||
// we call it asynchronously so as to not delay server startup.
|
||||
versionsLock.Lock()
|
||||
_, err := ensureVersion(log, tfDownloader, versions, defaultVersion, binDir, tfDownloadURL, tfDownloadAllowed)
|
||||
_, err := ensureVersion(log, distribution, versions, defaultVersion, binDir, tfDownloadURL, tfDownloadAllowed)
|
||||
versionsLock.Unlock()
|
||||
if err != nil {
|
||||
log.Err("could not download terraform %s: %s", defaultVersion.String(), err)
|
||||
log.Err("could not download %s %s: %s", distribution.BinName(), defaultVersion.String(), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,10 +173,10 @@ func NewClientWithDefaultVersion(
|
||||
}
|
||||
}
|
||||
return &DefaultClient{
|
||||
distribution: distribution,
|
||||
defaultVersion: finalDefaultVersion,
|
||||
terraformPluginCacheDir: cacheDir,
|
||||
binDir: binDir,
|
||||
downloader: tfDownloader,
|
||||
downloadBaseURL: tfDownloadURL,
|
||||
downloadAllowed: tfDownloadAllowed,
|
||||
versionsLock: &versionsLock,
|
||||
@@ -196,6 +189,7 @@ func NewClientWithDefaultVersion(
|
||||
|
||||
func NewTestClient(
|
||||
log logging.SimpleLogging,
|
||||
distribution Distribution,
|
||||
binDir string,
|
||||
cacheDir string,
|
||||
tfeToken string,
|
||||
@@ -203,13 +197,13 @@ func NewTestClient(
|
||||
defaultVersionStr string,
|
||||
defaultVersionFlagName string,
|
||||
tfDownloadURL string,
|
||||
tfDownloader Downloader,
|
||||
tfDownloadAllowed bool,
|
||||
usePluginCache bool,
|
||||
projectCmdOutputHandler jobs.ProjectCommandOutputHandler,
|
||||
) (*DefaultClient, error) {
|
||||
return NewClientWithDefaultVersion(
|
||||
log,
|
||||
distribution,
|
||||
binDir,
|
||||
cacheDir,
|
||||
tfeToken,
|
||||
@@ -217,7 +211,6 @@ func NewTestClient(
|
||||
defaultVersionStr,
|
||||
defaultVersionFlagName,
|
||||
tfDownloadURL,
|
||||
tfDownloader,
|
||||
tfDownloadAllowed,
|
||||
usePluginCache,
|
||||
false,
|
||||
@@ -231,10 +224,10 @@ func NewTestClient(
|
||||
// a specific version is set.
|
||||
// defaultVersionFlagName is the name of the flag that sets the default terraform
|
||||
// version.
|
||||
// tfDownloader is used to download terraform versions.
|
||||
// Will asynchronously download the required version if it doesn't exist already.
|
||||
func NewClient(
|
||||
log logging.SimpleLogging,
|
||||
distribution Distribution,
|
||||
binDir string,
|
||||
cacheDir string,
|
||||
tfeToken string,
|
||||
@@ -242,13 +235,13 @@ func NewClient(
|
||||
defaultVersionStr string,
|
||||
defaultVersionFlagName string,
|
||||
tfDownloadURL string,
|
||||
tfDownloader Downloader,
|
||||
tfDownloadAllowed bool,
|
||||
usePluginCache bool,
|
||||
projectCmdOutputHandler jobs.ProjectCommandOutputHandler,
|
||||
) (*DefaultClient, error) {
|
||||
return NewClientWithDefaultVersion(
|
||||
log,
|
||||
distribution,
|
||||
binDir,
|
||||
cacheDir,
|
||||
tfeToken,
|
||||
@@ -256,7 +249,6 @@ func NewClient(
|
||||
defaultVersionStr,
|
||||
defaultVersionFlagName,
|
||||
tfDownloadURL,
|
||||
tfDownloader,
|
||||
tfDownloadAllowed,
|
||||
usePluginCache,
|
||||
true,
|
||||
@@ -324,33 +316,12 @@ func (c *DefaultClient) DetectVersion(log logging.SimpleLogging, projectDirector
|
||||
return version
|
||||
}
|
||||
|
||||
constraintStr := requiredVersionSetting
|
||||
vc, err := version.NewConstraint(constraintStr)
|
||||
downloadVersion, err := c.distribution.ResolveConstraint(context.Background(), requiredVersionSetting)
|
||||
if err != nil {
|
||||
log.Err("Error parsing constraint string: %s", err)
|
||||
log.Err("%s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
constrainedVersions := &releases.Versions{
|
||||
Product: product.Terraform,
|
||||
Constraints: vc,
|
||||
}
|
||||
installCandidates, err := constrainedVersions.List(context.Background())
|
||||
if err != nil {
|
||||
log.Err("error listing available versions: %s", err)
|
||||
return nil
|
||||
}
|
||||
if len(installCandidates) == 0 {
|
||||
log.Err("no Terraform versions found for constraints %s", constraintStr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// We want to select the highest version that satisfies the constraint.
|
||||
versionDownloader := installCandidates[len(installCandidates)-1]
|
||||
|
||||
// Get the Version object from the versionDownloader.
|
||||
downloadVersion := versionDownloader.(*releases.ExactVersion).Version
|
||||
|
||||
return downloadVersion
|
||||
}
|
||||
|
||||
@@ -362,7 +333,7 @@ func (c *DefaultClient) EnsureVersion(log logging.SimpleLogging, v *version.Vers
|
||||
|
||||
var err error
|
||||
c.versionsLock.Lock()
|
||||
_, err = ensureVersion(log, c.downloader, c.versions, v, c.binDir, c.downloadBaseURL, c.downloadAllowed)
|
||||
_, err = ensureVersion(log, c.distribution, c.versions, v, c.binDir, c.downloadBaseURL, c.downloadAllowed)
|
||||
c.versionsLock.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -442,7 +413,7 @@ func (c *DefaultClient) prepCmd(log logging.SimpleLogging, v *version.Version, w
|
||||
} else {
|
||||
var err error
|
||||
c.versionsLock.Lock()
|
||||
binPath, err = ensureVersion(log, c.downloader, c.versions, v, c.binDir, c.downloadBaseURL, c.downloadAllowed)
|
||||
binPath, err = ensureVersion(log, c.distribution, c.versions, v, c.binDir, c.downloadBaseURL, c.downloadAllowed)
|
||||
c.versionsLock.Unlock()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
@@ -515,7 +486,7 @@ func MustConstraint(v string) version.Constraints {
|
||||
// It will download this version if we don't have it.
|
||||
func ensureVersion(
|
||||
log logging.SimpleLogging,
|
||||
dl Downloader,
|
||||
dist Distribution,
|
||||
versions map[string]string,
|
||||
v *version.Version,
|
||||
binDir string,
|
||||
@@ -529,7 +500,7 @@ func ensureVersion(
|
||||
// This tf version might not yet be in the versions map even though it
|
||||
// exists on disk. This would happen if users have manually added
|
||||
// terraform{version} binaries. In this case we don't want to re-download.
|
||||
binFile := "terraform" + v.String()
|
||||
binFile := dist.BinName() + v.String()
|
||||
if binPath, err := exec.LookPath(binFile); err == nil {
|
||||
versions[v.String()] = binPath
|
||||
return binPath, nil
|
||||
@@ -544,22 +515,24 @@ func ensureVersion(
|
||||
}
|
||||
if !downloadsAllowed {
|
||||
return "", fmt.Errorf(
|
||||
"could not find terraform version %s in PATH or %s, and downloads are disabled",
|
||||
"could not find %s version %s in PATH or %s, and downloads are disabled",
|
||||
dist.BinName(),
|
||||
v.String(),
|
||||
binDir,
|
||||
)
|
||||
}
|
||||
|
||||
log.Info("could not find terraform version %s in PATH or %s", v.String(), binDir)
|
||||
log.Info("could not find %s version %s in PATH or %s", dist.BinName(), v.String(), binDir)
|
||||
|
||||
log.Info("using Hashicorp's 'hc-install' to download Terraform version %s from download URL %s", v.String(), downloadURL)
|
||||
execPath, err := dl.Install(binDir, downloadURL, v)
|
||||
log.Info("downloading %s version %s from download URL %s", dist.BinName(), v.String(), downloadURL)
|
||||
|
||||
execPath, err := dist.Downloader().Install(context.Background(), binDir, downloadURL, v)
|
||||
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error downloading terraform version %s", v.String())
|
||||
}
|
||||
|
||||
log.Info("Downloaded terraform %s to %s", v.String(), execPath)
|
||||
log.Info("Downloaded %s %s to %s", dist.BinName(), v.String(), execPath)
|
||||
versions[v.String()] = execPath
|
||||
return execPath, nil
|
||||
}
|
||||
@@ -622,35 +595,3 @@ func getVersion(tfBinary string) (*version.Version, error) {
|
||||
var rcFileContents = `credentials "%s" {
|
||||
token = %q
|
||||
}`
|
||||
|
||||
type DefaultDownloader struct{}
|
||||
|
||||
func (d *DefaultDownloader) Install(dir string, downloadURL string, v *version.Version) (string, error) {
|
||||
installer := install.NewInstaller()
|
||||
execPath, err := installer.Install(context.Background(), []src.Installable{
|
||||
&releases.ExactVersion{
|
||||
Product: product.Terraform,
|
||||
Version: v,
|
||||
InstallDir: dir,
|
||||
ApiBaseURL: downloadURL,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// hc-install installs terraform binary as just "terraform".
|
||||
// We need to rename it to terraform{version} to be consistent with current naming convention.
|
||||
newPath := filepath.Join(dir, "terraform"+v.String())
|
||||
if err := os.Rename(execPath, newPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return newPath, nil
|
||||
}
|
||||
|
||||
// See go-getter.GetAny.
|
||||
func (d *DefaultDownloader) GetAny(dst, src string) error {
|
||||
_, err := getter.GetAny(context.Background(), dst, src)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package terraform_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -24,7 +25,6 @@ import (
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
. "github.com/petergtz/pegomock/v4"
|
||||
pegomock "github.com/petergtz/pegomock/v4"
|
||||
"github.com/runatlantis/atlantis/cmd"
|
||||
"github.com/runatlantis/atlantis/server/core/terraform"
|
||||
"github.com/runatlantis/atlantis/server/core/terraform/mocks"
|
||||
@@ -77,7 +77,10 @@ is 0.11.13. You can update by downloading from developer.hashicorp.com/terraform
|
||||
Ok(t, err)
|
||||
defer tempSetEnv(t, "PATH", fmt.Sprintf("%s:%s", tmp, os.Getenv("PATH")))()
|
||||
|
||||
c, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, nil, true, true, projectCmdOutputHandler)
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
distibution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
|
||||
c, err := terraform.NewClient(logger, distibution, binDir, cacheDir, "", "", "", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler)
|
||||
Ok(t, err)
|
||||
|
||||
Ok(t, err)
|
||||
@@ -111,7 +114,10 @@ is 0.11.13. You can update by downloading from developer.hashicorp.com/terraform
|
||||
Ok(t, err)
|
||||
defer tempSetEnv(t, "PATH", fmt.Sprintf("%s:%s", tmp, os.Getenv("PATH")))()
|
||||
|
||||
c, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, nil, true, true, projectCmdOutputHandler)
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
|
||||
c, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler)
|
||||
Ok(t, err)
|
||||
|
||||
Ok(t, err)
|
||||
@@ -132,7 +138,10 @@ func TestNewClient_NoTF(t *testing.T) {
|
||||
// Set PATH to only include our empty directory.
|
||||
defer tempSetEnv(t, "PATH", tmp)()
|
||||
|
||||
_, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, nil, true, true, projectCmdOutputHandler)
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
|
||||
_, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler)
|
||||
ErrEquals(t, "terraform not found in $PATH. Set --default-tf-version or download terraform from https://developer.hashicorp.com/terraform/downloads", err)
|
||||
}
|
||||
|
||||
@@ -155,7 +164,10 @@ func TestNewClient_DefaultTFFlagInPath(t *testing.T) {
|
||||
Ok(t, err)
|
||||
defer tempSetEnv(t, "PATH", fmt.Sprintf("%s:%s", tmp, os.Getenv("PATH")))()
|
||||
|
||||
c, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, nil, false, true, projectCmdOutputHandler)
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
|
||||
c, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, false, true, projectCmdOutputHandler)
|
||||
Ok(t, err)
|
||||
|
||||
Ok(t, err)
|
||||
@@ -183,7 +195,10 @@ func TestNewClient_DefaultTFFlagInBinDir(t *testing.T) {
|
||||
Ok(t, err)
|
||||
defer tempSetEnv(t, "PATH", fmt.Sprintf("%s:%s", tmp, os.Getenv("PATH")))()
|
||||
|
||||
c, err := terraform.NewClient(logging.NewNoopLogger(t), binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, nil, true, true, projectCmdOutputHandler)
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
|
||||
c, err := terraform.NewClient(logging.NewNoopLogger(t), distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler)
|
||||
Ok(t, err)
|
||||
|
||||
Ok(t, err)
|
||||
@@ -211,18 +226,19 @@ func TestNewClient_DefaultTFFlagDownload(t *testing.T) {
|
||||
defer tempSetEnv(t, "PATH", "")()
|
||||
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
When(mockDownloader.Install(Any[string](), Any[string](), Any[*version.Version]())).Then(func(params []pegomock.Param) pegomock.ReturnValues {
|
||||
binPath := filepath.Join(params[0].(string), "terraform0.11.10")
|
||||
When(mockDownloader.Install(Any[context.Context](), Any[string](), Any[string](), Any[*version.Version]())).Then(func(params []Param) ReturnValues {
|
||||
binPath := filepath.Join(params[1].(string), "terraform0.11.10")
|
||||
err := os.WriteFile(binPath, []byte("#!/bin/sh\necho '\nTerraform v0.11.10\n'"), 0700) // #nosec G306
|
||||
return []pegomock.ReturnValue{binPath, err}
|
||||
return []ReturnValue{binPath, err}
|
||||
})
|
||||
c, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, mockDownloader, true, true, projectCmdOutputHandler)
|
||||
distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
c, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler)
|
||||
Ok(t, err)
|
||||
|
||||
Ok(t, err)
|
||||
Equals(t, "0.11.10", c.DefaultVersion().String())
|
||||
|
||||
mockDownloader.VerifyWasCalledEventually(Once(), 2*time.Second).Install(binDir, cmd.DefaultTFDownloadURL, version.Must(version.NewVersion("0.11.10")))
|
||||
mockDownloader.VerifyWasCalledEventually(Once(), 2*time.Second).Install(context.Background(), binDir, cmd.DefaultTFDownloadURL, version.Must(version.NewVersion("0.11.10")))
|
||||
|
||||
// Reset PATH so that it has sh.
|
||||
Ok(t, os.Setenv("PATH", orig))
|
||||
@@ -237,7 +253,9 @@ func TestNewClient_BadVersion(t *testing.T) {
|
||||
logger := logging.NewNoopLogger(t)
|
||||
_, binDir, cacheDir := mkSubDirs(t)
|
||||
projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler()
|
||||
_, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "malformed", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, nil, true, true, projectCmdOutputHandler)
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
_, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "malformed", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler)
|
||||
ErrEquals(t, "Malformed version: malformed", err)
|
||||
}
|
||||
|
||||
@@ -257,14 +275,15 @@ func TestRunCommandWithVersion_DLsTF(t *testing.T) {
|
||||
Ok(t, err)
|
||||
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
// Set up our mock downloader to write a fake tf binary when it's called.
|
||||
When(mockDownloader.Install(binDir, cmd.DefaultTFDownloadURL, v)).Then(func(params []pegomock.Param) pegomock.ReturnValues {
|
||||
binPath := filepath.Join(params[0].(string), "terraform99.99.99")
|
||||
When(mockDownloader.Install(context.Background(), binDir, cmd.DefaultTFDownloadURL, v)).Then(func(params []Param) ReturnValues {
|
||||
binPath := filepath.Join(params[1].(string), "terraform99.99.99")
|
||||
err := os.WriteFile(binPath, []byte("#!/bin/sh\necho '\nTerraform v99.99.99\n'"), 0700) // #nosec G306
|
||||
return []pegomock.ReturnValue{binPath, err}
|
||||
return []ReturnValue{binPath, err}
|
||||
})
|
||||
|
||||
c, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, mockDownloader, true, true, projectCmdOutputHandler)
|
||||
c, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler)
|
||||
Ok(t, err)
|
||||
Equals(t, "0.11.10", c.DefaultVersion().String())
|
||||
|
||||
@@ -282,8 +301,10 @@ func TestEnsureVersion_downloaded(t *testing.T) {
|
||||
projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler()
|
||||
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
distibution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
|
||||
downloadsAllowed := true
|
||||
c, err := terraform.NewTestClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, mockDownloader, downloadsAllowed, true, projectCmdOutputHandler)
|
||||
c, err := terraform.NewTestClient(logger, distibution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, downloadsAllowed, true, projectCmdOutputHandler)
|
||||
Ok(t, err)
|
||||
|
||||
Equals(t, "0.11.10", c.DefaultVersion().String())
|
||||
@@ -291,17 +312,17 @@ func TestEnsureVersion_downloaded(t *testing.T) {
|
||||
v, err := version.NewVersion("99.99.99")
|
||||
Ok(t, err)
|
||||
|
||||
When(mockDownloader.Install(binDir, cmd.DefaultTFDownloadURL, v)).Then(func(params []pegomock.Param) pegomock.ReturnValues {
|
||||
binPath := filepath.Join(params[0].(string), "terraform99.99.99")
|
||||
When(mockDownloader.Install(context.Background(), binDir, cmd.DefaultTFDownloadURL, v)).Then(func(params []Param) ReturnValues {
|
||||
binPath := filepath.Join(params[1].(string), "terraform99.99.99")
|
||||
err := os.WriteFile(binPath, []byte("#!/bin/sh\necho '\nTerraform v99.99.99\n'"), 0700) // #nosec G306
|
||||
return []pegomock.ReturnValue{binPath, err}
|
||||
return []ReturnValue{binPath, err}
|
||||
})
|
||||
|
||||
err = c.EnsureVersion(logger, v)
|
||||
|
||||
Ok(t, err)
|
||||
|
||||
mockDownloader.VerifyWasCalledEventually(Once(), 2*time.Second).Install(binDir, cmd.DefaultTFDownloadURL, v)
|
||||
mockDownloader.VerifyWasCalledEventually(Once(), 2*time.Second).Install(context.Background(), binDir, cmd.DefaultTFDownloadURL, v)
|
||||
}
|
||||
|
||||
// Test that EnsureVersion downloads terraform from a custom URL.
|
||||
@@ -312,10 +333,11 @@ func TestEnsureVersion_downloaded_customURL(t *testing.T) {
|
||||
projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler()
|
||||
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
downloadsAllowed := true
|
||||
customURL := "http://releases.example.com"
|
||||
|
||||
c, err := terraform.NewTestClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, customURL, mockDownloader, downloadsAllowed, true, projectCmdOutputHandler)
|
||||
c, err := terraform.NewTestClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, customURL, downloadsAllowed, true, projectCmdOutputHandler)
|
||||
Ok(t, err)
|
||||
|
||||
Equals(t, "0.11.10", c.DefaultVersion().String())
|
||||
@@ -323,17 +345,17 @@ func TestEnsureVersion_downloaded_customURL(t *testing.T) {
|
||||
v, err := version.NewVersion("99.99.99")
|
||||
Ok(t, err)
|
||||
|
||||
When(mockDownloader.Install(binDir, customURL, v)).Then(func(params []pegomock.Param) pegomock.ReturnValues {
|
||||
binPath := filepath.Join(params[0].(string), "terraform99.99.99")
|
||||
When(mockDownloader.Install(context.Background(), binDir, customURL, v)).Then(func(params []Param) ReturnValues {
|
||||
binPath := filepath.Join(params[1].(string), "terraform99.99.99")
|
||||
err := os.WriteFile(binPath, []byte("#!/bin/sh\necho '\nTerraform v99.99.99\n'"), 0700) // #nosec G306
|
||||
return []pegomock.ReturnValue{binPath, err}
|
||||
return []ReturnValue{binPath, err}
|
||||
})
|
||||
|
||||
err = c.EnsureVersion(logger, v)
|
||||
|
||||
Ok(t, err)
|
||||
|
||||
mockDownloader.VerifyWasCalledEventually(Once(), 2*time.Second).Install(binDir, customURL, v)
|
||||
mockDownloader.VerifyWasCalledEventually(Once(), 2*time.Second).Install(context.Background(), binDir, customURL, v)
|
||||
}
|
||||
|
||||
// Test that EnsureVersion throws an error when downloads are disabled
|
||||
@@ -344,9 +366,10 @@ func TestEnsureVersion_downloaded_downloadingDisabled(t *testing.T) {
|
||||
projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler()
|
||||
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
|
||||
downloadsAllowed := false
|
||||
c, err := terraform.NewTestClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, mockDownloader, downloadsAllowed, true, projectCmdOutputHandler)
|
||||
c, err := terraform.NewTestClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, downloadsAllowed, true, projectCmdOutputHandler)
|
||||
Ok(t, err)
|
||||
|
||||
Equals(t, "0.11.10", c.DefaultVersion().String())
|
||||
@@ -475,9 +498,12 @@ terraform {
|
||||
RegisterMockTestingT(t)
|
||||
_, binDir, cacheDir := mkSubDirs(t)
|
||||
projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler()
|
||||
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
c, err := terraform.NewTestClient(logger,
|
||||
distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
|
||||
c, err := terraform.NewTestClient(
|
||||
logger,
|
||||
distribution,
|
||||
binDir,
|
||||
cacheDir,
|
||||
"",
|
||||
@@ -485,7 +511,6 @@ terraform {
|
||||
"",
|
||||
cmd.DefaultTFVersionFlag,
|
||||
cmd.DefaultTFDownloadURL,
|
||||
mockDownloader,
|
||||
downloadsAllowed,
|
||||
true,
|
||||
projectCmdOutputHandler)
|
||||
@@ -515,32 +540,15 @@ terraform {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstall(t *testing.T) {
|
||||
d := &terraform.DefaultDownloader{}
|
||||
RegisterMockTestingT(t)
|
||||
_, binDir, _ := mkSubDirs(t)
|
||||
|
||||
v, _ := version.NewVersion("1.8.1")
|
||||
|
||||
newPath, err := d.Install(binDir, cmd.DefaultTFDownloadURL, v)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(newPath); os.IsNotExist(err) {
|
||||
t.Errorf("Binary not found at %s", newPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractExactRegex(t *testing.T) {
|
||||
logger := logging.NewNoopLogger(t)
|
||||
RegisterMockTestingT(t)
|
||||
_, binDir, cacheDir := mkSubDirs(t)
|
||||
projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler()
|
||||
|
||||
mockDownloader := mocks.NewMockDownloader()
|
||||
distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader)
|
||||
|
||||
c, err := terraform.NewTestClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, mockDownloader, true, true, projectCmdOutputHandler)
|
||||
c, err := terraform.NewTestClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler)
|
||||
Ok(t, err)
|
||||
|
||||
tests := []struct {
|
||||
|
||||
@@ -421,8 +421,14 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
)
|
||||
}
|
||||
|
||||
distribution := terraform.NewDistributionTerraform()
|
||||
if userConfig.TFDistribution == "opentofu" {
|
||||
distribution = terraform.NewDistributionOpenTofu()
|
||||
}
|
||||
|
||||
terraformClient, err := terraform.NewClient(
|
||||
logger,
|
||||
distribution,
|
||||
binDir,
|
||||
cacheDir,
|
||||
userConfig.TFEToken,
|
||||
@@ -430,7 +436,6 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
userConfig.DefaultTFVersion,
|
||||
config.DefaultTFVersionFlag,
|
||||
userConfig.TFDownloadURL,
|
||||
&terraform.DefaultDownloader{},
|
||||
userConfig.TFDownload,
|
||||
userConfig.UseTFPluginCache,
|
||||
projectCmdOutputHandler)
|
||||
@@ -438,7 +443,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
// are, then we don't error out because we don't have/want terraform
|
||||
// installed on our CI system where the unit tests run.
|
||||
if err != nil && flag.Lookup("test.v") == nil {
|
||||
return nil, errors.Wrap(err, "initializing terraform")
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("initializing %s", userConfig.TFDistribution))
|
||||
}
|
||||
markdownRenderer := events.NewMarkdownRenderer(
|
||||
gitlabClient.SupportsCommonMark(),
|
||||
@@ -635,7 +640,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
|
||||
policyCheckStepRunner, err := runtime.NewPolicyCheckStepRunner(
|
||||
defaultTfVersion,
|
||||
policy.NewConfTestExecutorWorkflow(logger, binDir, &terraform.DefaultDownloader{}),
|
||||
policy.NewConfTestExecutorWorkflow(logger, binDir, &policy.ConfTestGoGetterVersionDownloader{}),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -107,6 +107,7 @@ type UserConfig struct {
|
||||
SSLCertFile string `mapstructure:"ssl-cert-file"`
|
||||
SSLKeyFile string `mapstructure:"ssl-key-file"`
|
||||
RestrictFileList bool `mapstructure:"restrict-file-list"`
|
||||
TFDistribution string `mapstructure:"tf-distribution"`
|
||||
TFDownload bool `mapstructure:"tf-download"`
|
||||
TFDownloadURL string `mapstructure:"tf-download-url"`
|
||||
TFEHostname string `mapstructure:"tfe-hostname"`
|
||||
|
||||
Reference in New Issue
Block a user