diff --git a/go.mod b/go.mod index a2de791c61..6cc67eb4b9 100644 --- a/go.mod +++ b/go.mod @@ -20,8 +20,9 @@ require ( github.com/gorilla/websocket v1.5.2 github.com/hashicorp/go-getter/v2 v2.2.2 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-version v1.6.0 + github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/golang-lru/v2 v2.0.7 + github.com/hashicorp/hc-install v0.7.1-0.20240529171616-e08210622997 github.com/hashicorp/terraform-config-inspect v0.0.0-20240607080351-271db412dbcb github.com/jpillora/backoff v1.0.0 github.com/kr/pretty v0.3.1 @@ -43,7 +44,6 @@ require ( github.com/stretchr/testify v1.9.0 github.com/uber-go/tally/v4 v4.1.10 github.com/urfave/negroni/v3 v3.1.0 - github.com/warrensbox/terraform-switcher v0.1.1-0.20240413181427-4d66b260d90c github.com/xanzy/go-gitlab v0.102.0 go.etcd.io/bbolt v1.3.10 go.uber.org/zap v1.27.0 @@ -65,16 +65,18 @@ require github.com/twmb/murmur3 v1.1.8 // indirect require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // 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/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 - github.com/fatih/color v1.15.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-fed/httpsig v1.1.0 // indirect @@ -85,14 +87,10 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-github/v60 v60.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/gookit/color v1.5.4 // indirect - github.com/gookit/goutil v0.6.15 // indirect - github.com/gookit/gsr v0.1.0 // indirect - github.com/gookit/slog v0.5.5 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.4 // indirect + github.com/hashicorp/go-retryablehttp v0.7.6 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect @@ -126,8 +124,6 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yuin/gopher-lua v1.1.1 // indirect github.com/zclconf/go-cty v1.14.4 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/go.sum b/go.sum index 289a73fa5b..2f34ec878a 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= code.gitea.io/sdk/gitea v0.18.0 h1:+zZrwVmujIrgobt6wVBWCqITz6bn1aBjnCUHmpZrerI= code.gitea.io/sdk/gitea v0.18.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 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= @@ -42,6 +44,10 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0 github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +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 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.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= @@ -85,9 +91,13 @@ 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/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -96,12 +106,14 @@ github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454Wv github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -110,6 +122,12 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -148,6 +166,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -215,14 +235,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= -github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= -github.com/gookit/goutil v0.6.15 h1:mMQ0ElojNZoyPD0eVROk5QXJPh2uKR4g06slgPDF5Jo= -github.com/gookit/goutil v0.6.15/go.mod h1:qdKdYEHQdEtyH+4fNdQNZfJHhI0jUZzHxQVAV3DaMDY= -github.com/gookit/gsr v0.1.0 h1:0gadWaYGU4phMs0bma38t+Do5OZowRMEVlHv31p0Zig= -github.com/gookit/gsr v0.1.0/go.mod h1:7wv4Y4WCnil8+DlDYHBjidzrEzfHhXEoFjEA0pPPWpI= -github.com/gookit/slog v0.5.5 h1:XoyK3NilKzuC/umvnqTQDHTOnpC8R6pvlr/ht9PyfgU= -github.com/gookit/slog v0.5.5/go.mod h1:RfIwzoaQ8wZbKdcqG7+3EzbkMqcp2TUn3mcaSZAw2EQ= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -236,21 +248,22 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-getter/v2 v2.2.2 h1:Al5bzCNW5DrlZMK6TumGrSue7Xz8beyLcen+4N4erwo= github.com/hashicorp/go-getter/v2 v2.2.2/go.mod h1:hp5Yy0GMQvwWVUmwLs3ygivz1JSLI323hdIE9J9m7TY= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 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-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= -github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.6 h1:TwRYfx2z2C4cLbXmT8I5PgP/xmuqASDyiVuGYfs9GZM= +github.com/hashicorp/go-retryablehttp v0.7.6/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 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/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.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/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hc-install v0.7.1-0.20240529171616-e08210622997 h1:VkzPKrY9fojm5nRx3/b5AYghzypdxHvIbHBAjjhKtWk= +github.com/hashicorp/hc-install v0.7.1-0.20240529171616-e08210622997/go.mod h1:G0oTDPGfCrnS1UbNoVPvrzzJIEwpK+lBuTgI0f7B+10= 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.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= @@ -266,6 +279,8 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -277,6 +292,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= @@ -341,6 +358,8 @@ github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6 github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/petergtz/pegomock/v4 v4.0.0 h1:BIGMUof4NXc+xBbuFk0VBfK5Ls7DplcP+LWz4hfYWsY= github.com/petergtz/pegomock/v4 v4.0.0/go.mod h1:Xscaw/kXYcuh9sGsns+If19FnSMMQy4Wz60YJTn3XOU= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -387,6 +406,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -397,6 +418,8 @@ github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29/go.mod h1:AuYgA5K github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/slack-go/slack v0.12.5 h1:ddZ6uz6XVaB+3MTDhoW04gG+Vc/M/X1ctC+wssy2cqs= github.com/slack-go/slack v0.12.5/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -436,14 +459,10 @@ github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/negroni/v3 v3.1.0 h1:lzmuxGSpnJCT/ujgIAjkU3+LW3NX8alCglO/L6KjIGQ= github.com/urfave/negroni/v3 v3.1.0/go.mod h1:jWvnX03kcSjDBl/ShB0iHvx5uOs7mAzZXW+JvJ5XYAs= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/warrensbox/terraform-switcher v0.1.1-0.20240413181427-4d66b260d90c h1:gQw6llCIsW/RGSiKT7BfV22CNtyqPKbMX6GE0eaU2e4= -github.com/warrensbox/terraform-switcher v0.1.1-0.20240413181427-4d66b260d90c/go.mod h1:g/BtIOjGxYaOe1HMyvl740MMkOoGi3Ib0dv0P6ihiVI= github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4= github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -777,6 +796,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/validator.v2 v2.0.0-20200605151824-2b28d334fa05/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/server/controllers/events/events_controller_e2e_test.go b/server/controllers/events/events_controller_e2e_test.go index 2d38f23685..7f63f191f5 100644 --- a/server/controllers/events/events_controller_e2e_test.go +++ b/server/controllers/events/events_controller_e2e_test.go @@ -53,12 +53,12 @@ var mockPreWorkflowHookRunner *runtimemocks.MockPreWorkflowHookRunner var mockPostWorkflowHookRunner *runtimemocks.MockPostWorkflowHookRunner -func (m *NoopTFDownloader) GetFile(_, _ string) error { +func (m *NoopTFDownloader) GetAny(_, _ string) error { return nil } -func (m *NoopTFDownloader) GetAny(_, _ string) error { - return nil +func (m *NoopTFDownloader) Install(_ string, _ string, _ *version.Version) (string, error) { + return "", nil } type LocalConftestCache struct { diff --git a/server/core/runtime/policy/conftest_client_test.go b/server/core/runtime/policy/conftest_client_test.go index d8c5f5b9dc..c50875e996 100644 --- a/server/core/runtime/policy/conftest_client_test.go +++ b/server/core/runtime/policy/conftest_client_test.go @@ -33,7 +33,6 @@ func TestConfTestVersionDownloader(t *testing.T) { t.Run("success", func(t *testing.T) { - When(mockDownloader.GetFile(Eq(destPath), Eq(fullURL))).ThenReturn(nil) binPath, err := subject.downloadConfTestVersion(version, destPath) mockDownloader.VerifyWasCalledOnce().GetAny(Eq(destPath), Eq(fullURL)) diff --git a/server/core/terraform/mocks/mock_downloader.go b/server/core/terraform/mocks/mock_downloader.go index ed16db0eff..06b82f6706 100644 --- a/server/core/terraform/mocks/mock_downloader.go +++ b/server/core/terraform/mocks/mock_downloader.go @@ -4,6 +4,7 @@ package mocks import ( + go_version "github.com/hashicorp/go-version" pegomock "github.com/petergtz/pegomock/v4" "reflect" "time" @@ -39,19 +40,23 @@ func (mock *MockDownloader) GetAny(dst string, src string) error { return ret0 } -func (mock *MockDownloader) GetFile(dst string, src string) error { +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{dst, src} - result := pegomock.GetGenericMockFrom(mock).Invoke("GetFile", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 error + 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].(error) + ret0 = result[0].(string) + } + if result[1] != nil { + ret1 = result[1].(error) } } - return ret0 + return ret0, ret1 } func (mock *MockDownloader) VerifyWasCalledOnce() *VerifierMockDownloader { @@ -122,23 +127,23 @@ func (c *MockDownloader_GetAny_OngoingVerification) GetAllCapturedArguments() (_ return } -func (verifier *VerifierMockDownloader) GetFile(dst string, src string) *MockDownloader_GetFile_OngoingVerification { - params := []pegomock.Param{dst, src} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetFile", params, verifier.timeout) - return &MockDownloader_GetFile_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +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) + return &MockDownloader_Install_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } -type MockDownloader_GetFile_OngoingVerification struct { +type MockDownloader_Install_OngoingVerification struct { mock *MockDownloader methodInvocations []pegomock.MethodInvocation } -func (c *MockDownloader_GetFile_OngoingVerification) GetCapturedArguments() (string, string) { - dst, src := c.GetAllCapturedArguments() - return dst[len(dst)-1], src[len(src)-1] +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_GetFile_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string) { +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)) @@ -149,6 +154,10 @@ func (c *MockDownloader_GetFile_OngoingVerification) GetAllCapturedArguments() ( 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) + } } return } diff --git a/server/core/terraform/mocks/mock_terraform_client.go b/server/core/terraform/mocks/mock_terraform_client.go index f5fe812a36..dae620f2e1 100644 --- a/server/core/terraform/mocks/mock_terraform_client.go +++ b/server/core/terraform/mocks/mock_terraform_client.go @@ -57,25 +57,6 @@ func (mock *MockClient) EnsureVersion(log logging.SimpleLogging, v *go_version.V return ret0 } -func (mock *MockClient) ListAvailableVersions(log logging.SimpleLogging) ([]string, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClient().") - } - params := []pegomock.Param{log} - result := pegomock.GetGenericMockFrom(mock).Invoke("ListAvailableVersions", 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 -} - 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().") @@ -194,33 +175,6 @@ func (c *MockClient_EnsureVersion_OngoingVerification) GetAllCapturedArguments() return } -func (verifier *VerifierMockClient) ListAvailableVersions(log logging.SimpleLogging) *MockClient_ListAvailableVersions_OngoingVerification { - params := []pegomock.Param{log} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ListAvailableVersions", params, verifier.timeout) - return &MockClient_ListAvailableVersions_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClient_ListAvailableVersions_OngoingVerification struct { - mock *MockClient - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClient_ListAvailableVersions_OngoingVerification) GetCapturedArguments() logging.SimpleLogging { - log := c.GetAllCapturedArguments() - return log[len(log)-1] -} - -func (c *MockClient_ListAvailableVersions_OngoingVerification) GetAllCapturedArguments() (_param0 []logging.SimpleLogging) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]logging.SimpleLogging, len(c.methodInvocations)) - for u, param := range params[0] { - _param0[u] = param.(logging.SimpleLogging) - } - } - 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) diff --git a/server/core/terraform/terraform_client.go b/server/core/terraform/terraform_client.go index 89c02c8634..792b028612 100644 --- a/server/core/terraform/terraform_client.go +++ b/server/core/terraform/terraform_client.go @@ -19,23 +19,23 @@ package terraform import ( "context" "fmt" - "net/http" "os" "os/exec" "path/filepath" "regexp" - "runtime" - "sort" "strings" "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" - "github.com/warrensbox/terraform-switcher/lib" "github.com/runatlantis/atlantis/server/core/runtime/models" "github.com/runatlantis/atlantis/server/events/command" @@ -57,9 +57,6 @@ type Client interface { // EnsureVersion makes sure that terraform version `v` is available to use EnsureVersion(log logging.SimpleLogging, v *version.Version) error - // ListAvailableVersions returns all available version of Terraform, if available; otherwise this will return an empty list. - ListAvailableVersions(log logging.SimpleLogging) ([]string, error) - // DetectVersion Extracts required_version from Terraform configuration in the specified project directory. Returns nil if unable to determine the version. DetectVersion(log logging.SimpleLogging, projectDirectory string) *version.Version } @@ -97,7 +94,7 @@ type DefaultClient struct { // Downloader is for downloading terraform versions. type Downloader interface { - GetFile(dst, src string) error + Install(dir string, downloadURL string, v *version.Version) (string, error) GetAny(dst, src string) error } @@ -278,99 +275,83 @@ func (c *DefaultClient) TerraformBinDir() string { return c.binDir } -// ListAvailableVersions returns all available version of Terraform. If downloads are not allowed, this will return an empty list. -func (c *DefaultClient) ListAvailableVersions(log logging.SimpleLogging) ([]string, error) { - url := fmt.Sprintf("%s/terraform", c.downloadBaseURL) - - if !c.downloadAllowed { - log.Debug("Terraform downloads disabled. Won't list Terraform versions available at %s", url) - return []string{}, nil - } - - log.Debug("Listing Terraform versions available at: %s", url) - - // terraform-switcher calls os.Exit(1) if it fails to successfully GET the configured URL. - // So, before calling it, test if we can connect. Then we can return an error instead if the request fails. - resp, err := http.Get(url) // #nosec G107 -- terraform-switch makes this same call below. Also, we don't process the response payload. - if err != nil { - return nil, fmt.Errorf("Unable to list Terraform versions: %s", err) - } - defer resp.Body.Close() // nolint: errcheck - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Unable to list Terraform versions: response code %d from %s", resp.StatusCode, url) +// ExtractExactRegex attempts to extract an exact version number from the provided string as a fallback. +// The function expects the version string to be in one of the following formats: "= x.y.z", "=x.y.z", or "x.y.z" where x, y, and z are integers. +// If the version string matches one of these formats, the function returns a slice containing the exact version number. +// If the version string does not match any of these formats, the function logs a debug message and returns nil. +func (c *DefaultClient) ExtractExactRegex(log logging.SimpleLogging, version string) []string { + re := regexp.MustCompile(`^=?\s*([0-9.]+)\s*$`) + matched := re.FindStringSubmatch(version) + if len(matched) == 0 { + log.Debug("exact version regex not found in the version %q", version) + return nil } - - versions, err := lib.GetTFList(url, true) - return versions, err + // The first element of the slice is the entire string, so we want the second element (the first capture group) + tfVersions := []string{matched[1]} + log.Debug("extracted exact version %q from version %q", tfVersions[0], version) + return tfVersions } -// DetectVersion Extracts required_version from Terraform configuration in the specified project directory. Returns nil if unable to determine the version. -// This will also try to intelligently evaluate non-exact matches by listing the available versions of Terraform and picking the best match. +// DetectVersion extracts required_version from Terraform configuration in the specified project directory. Returns nil if unable to determine the version. +// It will also try to evaluate non-exact matches by passing the Constraints to the hc-install Releases API, which will return a list of available versions. +// It will then select the highest version that satisfies the constraint. func (c *DefaultClient) DetectVersion(log logging.SimpleLogging, projectDirectory string) *version.Version { module, diags := tfconfig.LoadModule(projectDirectory) if diags.HasErrors() { - log.Err("Trying to detect required version: %s", diags.Error()) + log.Err("trying to detect required version: %s", diags.Error()) } if len(module.RequiredCore) != 1 { - log.Info("Cannot determine which version to use from terraform configuration, detected %d possibilities.", len(module.RequiredCore)) + log.Info("cannot determine which version to use from terraform configuration, detected %d possibilities.", len(module.RequiredCore)) return nil } requiredVersionSetting := module.RequiredCore[0] log.Debug("Found required_version setting of %q", requiredVersionSetting) - tfVersions, err := c.ListAvailableVersions(log) - if err != nil { - log.Err("Unable to list Terraform versions, may fall back to default: %s", err) - } - - if len(tfVersions) == 0 { - // Fall back to an exact required version string - // We allow `= x.y.z`, `=x.y.z` or `x.y.z` where `x`, `y` and `z` are integers. - re := regexp.MustCompile(`^=?\s*([0-9.]+)\s*$`) - matched := re.FindStringSubmatch(requiredVersionSetting) + if !c.downloadAllowed { + log.Debug("terraform downloads disabled.") + matched := c.ExtractExactRegex(log, requiredVersionSetting) if len(matched) == 0 { - log.Debug("Did not specify exact version in terraform configuration, found %q", requiredVersionSetting) + log.Debug("did not specify exact version in terraform configuration, found %q", requiredVersionSetting) return nil } - tfVersions = []string{matched[1]} - } - - constraint, _ := version.NewConstraint(requiredVersionSetting) - // Since terraform version 1.8.2, terraform is not a single file download anymore and - // Atlantis fails to download version 1.8.2 and higher. So, as a short-term fix, - // we need to block any version higher than 1.8.1 until proper solution is implemented. - // More details on the issue here - https://github.com/runatlantis/atlantis/issues/4471 - highestSupportedConstraint, _ := version.NewConstraint("<= 1.8.1") - versions := make([]*version.Version, len(tfVersions)) - for i, tfvals := range tfVersions { - newVersion, err := version.NewVersion(tfvals) - if err == nil { - versions[i] = newVersion + version, err := version.NewVersion(matched[0]) + if err != nil { + log.Err("error parsing version string: %s", err) + return nil } + return version } - if len(versions) == 0 { - log.Debug("Did not specify exact valid version in terraform configuration, found %q", requiredVersionSetting) + constraintStr := requiredVersionSetting + vc, err := version.NewConstraint(constraintStr) + if err != nil { + log.Err("Error parsing constraint string: %s", err) return nil } - sort.Sort(sort.Reverse(version.Collection(versions))) - - for _, element := range versions { - if constraint.Check(element) && highestSupportedConstraint.Check(element) { // Validate a version against a constraint - tfversionStr := element.String() - if lib.ValidVersionFormat(tfversionStr) { //check if version format is correct - tfversion, _ := version.NewVersion(tfversionStr) - log.Info("Detected module requires version: %s", tfversionStr) - return tfversion - } - } + constrainedVersions := &releases.Versions{ + Product: product.Terraform, + Constraints: vc, } - log.Debug("Could not match any valid terraform version with %q", requiredVersionSetting) - return nil + 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 } // See Client.EnsureVersion. @@ -532,7 +513,15 @@ func MustConstraint(v string) version.Constraints { // ensureVersion returns the path to a terraform binary of version v. // It will download this version if we don't have it. -func ensureVersion(log logging.SimpleLogging, dl Downloader, versions map[string]string, v *version.Version, binDir string, downloadURL string, downloadsAllowed bool) (string, error) { +func ensureVersion( + log logging.SimpleLogging, + dl Downloader, + versions map[string]string, + v *version.Version, + binDir string, + downloadURL string, + downloadsAllowed bool, +) (string, error) { if binPath, ok := versions[v.String()]; ok { return binPath, nil } @@ -554,21 +543,25 @@ func ensureVersion(log logging.SimpleLogging, dl Downloader, versions map[string return dest, nil } if !downloadsAllowed { - return "", fmt.Errorf("Could not find terraform version %s in PATH or %s, and downloads are disabled", v.String(), binDir) + return "", fmt.Errorf( + "could not find terraform version %s in PATH or %s, and downloads are disabled", + v.String(), + binDir, + ) } - log.Info("Could not find terraform version %s in PATH or %s, downloading from %s", v.String(), binDir, downloadURL) - urlPrefix := fmt.Sprintf("%s/terraform/%s/terraform_%s", downloadURL, v.String(), v.String()) - binURL := fmt.Sprintf("%s_%s_%s.zip", urlPrefix, runtime.GOOS, runtime.GOARCH) - checksumURL := fmt.Sprintf("%s_SHA256SUMS", urlPrefix) - fullSrcURL := fmt.Sprintf("%s?checksum=file:%s", binURL, checksumURL) - if err := dl.GetFile(dest, fullSrcURL); err != nil { - return "", errors.Wrapf(err, "downloading terraform version %s at %q", v.String(), fullSrcURL) + log.Info("could not find terraform version %s in PATH or %s", 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) + + if err != nil { + return "", errors.Wrapf(err, "error downloading terraform version %s", v.String()) } - log.Info("Downloaded terraform %s to %s", v.String(), dest) - versions[v.String()] = dest - return dest, nil + log.Info("Downloaded terraform %s to %s", v.String(), execPath) + versions[v.String()] = execPath + return execPath, nil } // generateRCFile generates a .terraformrc file containing config for tfeToken @@ -632,13 +625,31 @@ var rcFileContents = `credentials "%s" { type DefaultDownloader struct{} -// See go-getter.GetFile. -func (d *DefaultDownloader) GetFile(dst, src string) error { - _, err := getter.GetFile(context.Background(), dst, src) - return err +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.GetFile. +// See go-getter.GetAny. func (d *DefaultDownloader) GetAny(dst, src string) error { _, err := getter.GetAny(context.Background(), dst, src) return err diff --git a/server/core/terraform/terraform_client_test.go b/server/core/terraform/terraform_client_test.go index cfa0f60fb3..6903b75791 100644 --- a/server/core/terraform/terraform_client_test.go +++ b/server/core/terraform/terraform_client_test.go @@ -17,7 +17,7 @@ import ( "fmt" "os" "path/filepath" - "runtime" + "reflect" "strings" "testing" "time" @@ -211,22 +211,18 @@ func TestNewClient_DefaultTFFlagDownload(t *testing.T) { defer tempSetEnv(t, "PATH", "")() mockDownloader := mocks.NewMockDownloader() - When(mockDownloader.GetFile(Any[string](), Any[string]())).Then(func(params []pegomock.Param) pegomock.ReturnValues { - err := os.WriteFile(params[0].(string), []byte("#!/bin/sh\necho '\nTerraform v0.11.10\n'"), 0700) // #nosec G306 - return []pegomock.ReturnValue{err} + 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") + err := os.WriteFile(binPath, []byte("#!/bin/sh\necho '\nTerraform v0.11.10\n'"), 0700) // #nosec G306 + return []pegomock.ReturnValue{binPath, err} }) - c, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, "https://my-mirror.releases.mycompany.com", mockDownloader, true, true, projectCmdOutputHandler) + c, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, mockDownloader, true, true, projectCmdOutputHandler) Ok(t, err) Ok(t, err) Equals(t, "0.11.10", c.DefaultVersion().String()) - baseURL := "https://my-mirror.releases.mycompany.com/terraform/0.11.10" - expURL := fmt.Sprintf("%s/terraform_0.11.10_%s_%s.zip?checksum=file:%s/terraform_0.11.10_SHA256SUMS", - baseURL, - runtime.GOOS, - runtime.GOARCH, - baseURL) - mockDownloader.VerifyWasCalledEventually(Once(), 2*time.Second).GetFile(filepath.Join(tmp, "bin", "terraform0.11.10"), expURL) + + mockDownloader.VerifyWasCalledEventually(Once(), 2*time.Second).Install(binDir, cmd.DefaultTFDownloadURL, version.Must(version.NewVersion("0.11.10"))) // Reset PATH so that it has sh. Ok(t, os.Setenv("PATH", orig)) @@ -257,26 +253,21 @@ func TestRunCommandWithVersion_DLsTF(t *testing.T) { RepoRelDir: ".", } + v, err := version.NewVersion("99.99.99") + Ok(t, err) + mockDownloader := mocks.NewMockDownloader() // Set up our mock downloader to write a fake tf binary when it's called. - baseURL := fmt.Sprintf("%s/terraform/99.99.99", cmd.DefaultTFDownloadURL) - expURL := fmt.Sprintf("%s/terraform_99.99.99_%s_%s.zip?checksum=file:%s/terraform_99.99.99_SHA256SUMS", - baseURL, - runtime.GOOS, - runtime.GOARCH, - baseURL) - When(mockDownloader.GetFile(filepath.Join(tmp, "bin", "terraform99.99.99"), expURL)).Then(func(params []pegomock.Param) pegomock.ReturnValues { - err := os.WriteFile(params[0].(string), []byte("#!/bin/sh\necho '\nTerraform v99.99.99\n'"), 0700) // #nosec G306 - return []pegomock.ReturnValue{err} + When(mockDownloader.Install(binDir, cmd.DefaultTFDownloadURL, v)).Then(func(params []pegomock.Param) pegomock.ReturnValues { + binPath := filepath.Join(params[0].(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} }) c, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, mockDownloader, true, true, projectCmdOutputHandler) Ok(t, err) Equals(t, "0.11.10", c.DefaultVersion().String()) - v, err := version.NewVersion("99.99.99") - Ok(t, err) - output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{}, v, "") Assert(t, err == nil, "err: %s: %s", err, output) @@ -287,7 +278,7 @@ func TestRunCommandWithVersion_DLsTF(t *testing.T) { func TestEnsureVersion_downloaded(t *testing.T) { logger := logging.NewNoopLogger(t) RegisterMockTestingT(t) - tmp, binDir, cacheDir := mkSubDirs(t) + _, binDir, cacheDir := mkSubDirs(t) projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler() mockDownloader := mocks.NewMockDownloader() @@ -300,17 +291,49 @@ 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") + err := os.WriteFile(binPath, []byte("#!/bin/sh\necho '\nTerraform v99.99.99\n'"), 0700) // #nosec G306 + return []pegomock.ReturnValue{binPath, err} + }) + err = c.EnsureVersion(logger, v) Ok(t, err) - baseURL := fmt.Sprintf("%s/terraform/99.99.99", cmd.DefaultTFDownloadURL) - expURL := fmt.Sprintf("%s/terraform_99.99.99_%s_%s.zip?checksum=file:%s/terraform_99.99.99_SHA256SUMS", - baseURL, - runtime.GOOS, - runtime.GOARCH, - baseURL) - mockDownloader.VerifyWasCalledEventually(Once(), 2*time.Second).GetFile(filepath.Join(tmp, "bin", "terraform99.99.99"), expURL) + mockDownloader.VerifyWasCalledEventually(Once(), 2*time.Second).Install(binDir, cmd.DefaultTFDownloadURL, v) +} + +// Test that EnsureVersion downloads terraform from a custom URL. +func TestEnsureVersion_downloaded_customURL(t *testing.T) { + logger := logging.NewNoopLogger(t) + RegisterMockTestingT(t) + _, binDir, cacheDir := mkSubDirs(t) + projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler() + + mockDownloader := mocks.NewMockDownloader() + downloadsAllowed := true + customURL := "http://releases.example.com" + + c, err := terraform.NewTestClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, customURL, mockDownloader, downloadsAllowed, true, projectCmdOutputHandler) + Ok(t, err) + + Equals(t, "0.11.10", c.DefaultVersion().String()) + + 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") + err := os.WriteFile(binPath, []byte("#!/bin/sh\necho '\nTerraform v99.99.99\n'"), 0700) // #nosec G306 + return []pegomock.ReturnValue{binPath, err} + }) + + err = c.EnsureVersion(logger, v) + + Ok(t, err) + + mockDownloader.VerifyWasCalledEventually(Once(), 2*time.Second).Install(binDir, customURL, v) } // Test that EnsureVersion throws an error when downloads are disabled @@ -332,7 +355,7 @@ func TestEnsureVersion_downloaded_downloadingDisabled(t *testing.T) { Ok(t, err) err = c.EnsureVersion(logger, v) - ErrContains(t, "Could not find terraform version", err) + ErrContains(t, "could not find terraform version", err) ErrContains(t, "downloads are disabled", err) mockDownloader.VerifyWasCalled(Never()) } @@ -393,12 +416,6 @@ terraform { // cannot use ~> 1.3 or ~> 1.0 since that is a moving target since it will always // resolve to the latest terraform 1.x "~> 1.3.0": "1.3.10", - // Since terraform version 1.8.2, terraform is not a single file download anymore and - // Atlantis fails to download version 1.8.2 and higher. So, as a short-term fix, - // we need to block any version higher than 1.8.1 until proper solution is implemented. - // More details on the issue here - https://github.com/runatlantis/atlantis/issues/4471 - ">= 1.3.0": "1.8.1", - ">= 1.8.2": "", } type testCase struct { @@ -497,3 +514,55 @@ terraform { runDetectVersionTestCase(t, name+": Downloads Disabled", testCase, false) } } + +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() + + c, err := terraform.NewTestClient(logger, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, mockDownloader, true, true, projectCmdOutputHandler) + Ok(t, err) + + tests := []struct { + version string + want []string + }{ + {"= 1.2.3", []string{"1.2.3"}}, + {"=1.2.3", []string{"1.2.3"}}, + {"1.2.3", []string{"1.2.3"}}, + {"v1.2.3", nil}, + {">= 1.2.3", nil}, + {">=1.2.3", nil}, + {"<= 1.2.3", nil}, + {"<=1.2.3", nil}, + {"~> 1.2.3", nil}, + } + + for _, tt := range tests { + t.Run(tt.version, func(t *testing.T) { + if got := c.ExtractExactRegex(logger, tt.version); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ExtractExactRegex() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/server/events/project_command_builder_test.go b/server/events/project_command_builder_test.go index e1e35eb675..7560b5d6de 100644 --- a/server/events/project_command_builder_test.go +++ b/server/events/project_command_builder_test.go @@ -234,7 +234,6 @@ terraform { userConfig := defaultUserConfig terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) for _, c := range cases { t.Run(c.Description, func(t *testing.T) { @@ -618,7 +617,6 @@ projects: } terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) builder := events.NewProjectCommandBuilder( false, @@ -807,7 +805,6 @@ projects: } terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) builder := events.NewProjectCommandBuilder( false, @@ -1137,7 +1134,6 @@ projects: } terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) builder := events.NewProjectCommandBuilder( false, @@ -1236,7 +1232,6 @@ func TestDefaultProjectCommandBuilder_BuildMultiApply(t *testing.T) { scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) builder := events.NewProjectCommandBuilder( false, @@ -1323,7 +1318,6 @@ projects: userConfig := defaultUserConfig terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) builder := events.NewProjectCommandBuilder( false, @@ -1412,7 +1406,6 @@ func TestDefaultProjectCommandBuilder_EscapeArgs(t *testing.T) { } terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) builder := events.NewProjectCommandBuilder( false, @@ -1685,7 +1678,6 @@ projects: } scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) builder := events.NewProjectCommandBuilder( false, @@ -1755,7 +1747,6 @@ func TestDefaultProjectCommandBuilder_WithPolicyCheckEnabled_BuildAutoplanComman globalCfg := valid.NewGlobalCfgFromArgs(globalCfgArgs) terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) builder := events.NewProjectCommandBuilder( true, @@ -1844,7 +1835,6 @@ func TestDefaultProjectCommandBuilder_BuildVersionCommand(t *testing.T) { AllowAllRepoSettings: false, } terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) builder := events.NewProjectCommandBuilder( false, @@ -1975,7 +1965,6 @@ func TestDefaultProjectCommandBuilder_BuildPlanCommands_Single_With_RestrictFile } terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) builder := events.NewProjectCommandBuilder( false, // policyChecksSupported @@ -2087,7 +2076,6 @@ func TestDefaultProjectCommandBuilder_BuildPlanCommands_with_IncludeGitUntracked } terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) builder := events.NewProjectCommandBuilder( false, // policyChecksSupported diff --git a/server/events/project_command_context_builder_test.go b/server/events/project_command_context_builder_test.go index 8bee1d9fb0..c3d75e950c 100644 --- a/server/events/project_command_context_builder_test.go +++ b/server/events/project_command_context_builder_test.go @@ -48,7 +48,6 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) { expectedPlanCmt := "Plan Comment" terraformClient := terraform_mocks.NewMockClient() - When(terraformClient.ListAvailableVersions(commandCtx.Log)) t.Run("with project name defined", func(t *testing.T) { When(mockCommentBuilder.BuildPlanComment(projRepoRelDir, projWorkspace, projName, []string{})).ThenReturn(expectedPlanCmt)