Skip to content

Commit

Permalink
Detect terraform versions using >= and ~> specifiers (runatlantis…
Browse files Browse the repository at this point in the history
…#1776)

* Refactor TFVersion from Dir with tfswitch lib

* Added tests and cleaned up code

* linted fmtd

* fixed conflict go.mod

* bumped to tf1.0.6

* bumped go.mod

* Update project_command_context_builder.go

* Bump warrensbox/terraform-switcher

* Fix tests

* rm redundant mocks library import

Co-authored-by: xmurias <xmurias@gmail.com>
Co-authored-by: Xavier <77047592+xavipanda@users.noreply.github.com>
Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com>
  • Loading branch information
4 people authored and krrrr38 committed Dec 16, 2022
1 parent daef9e9 commit 333ca5f
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 26 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/runatlantis/atlantis
go 1.19

require (
github.com/Masterminds/semver v1.5.0
github.com/Masterminds/sprig/v3 v3.2.2
github.com/alicebob/miniredis/v2 v2.23.1
github.com/bradleyfalzon/ghinstallation/v2 v2.1.0
Expand Down Expand Up @@ -38,10 +39,10 @@ require (
github.com/stretchr/testify v1.8.1
github.com/urfave/cli v1.22.10
github.com/urfave/negroni v1.0.0
github.com/warrensbox/terraform-switcher v0.1.1-0.20221027055942-201c8e92e997
github.com/xanzy/go-gitlab v0.74.0
go.etcd.io/bbolt v1.3.6
go.uber.org/zap v1.23.0
golang.org/x/sync v0.1.0
golang.org/x/term v0.2.0
golang.org/x/text v0.4.0
gopkg.in/go-playground/validator.v9 v9.31.0
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
Expand Down Expand Up @@ -473,6 +475,8 @@ github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk=
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/warrensbox/terraform-switcher v0.1.1-0.20221027055942-201c8e92e997 h1:be5WC0FHdhimAhe2G3DPhduX117RM8qdTMYCMHDt4DM=
github.com/warrensbox/terraform-switcher v0.1.1-0.20221027055942-201c8e92e997/go.mod h1:saryXNaL624mlulV138FP+HhVw7IpvETUXLS3nTvH1g=
github.com/xanzy/go-gitlab v0.74.0 h1:Ha1cokbjn0PXy6B19t3W324dwM4AOT52fuHr7nERPrc=
github.com/xanzy/go-gitlab v0.74.0/go.mod h1:d/a0vswScO7Agg1CZNz15Ic6SSvBG9vfw8egL99t4kA=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down Expand Up @@ -616,8 +620,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down
3 changes: 1 addition & 2 deletions server/events/command_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import (
lockingmocks "github.com/runatlantis/atlantis/server/core/locking/mocks"
"github.com/runatlantis/atlantis/server/events"
"github.com/runatlantis/atlantis/server/events/mocks"
eventmocks "github.com/runatlantis/atlantis/server/events/mocks"
"github.com/runatlantis/atlantis/server/events/mocks/matchers"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/models/fixtures"
Expand Down Expand Up @@ -87,7 +86,7 @@ func setup(t *testing.T) *vcsmocks.MockClient {
Ok(t, err)

drainer = &events.Drainer{}
deleteLockCommand = eventmocks.NewMockDeleteLockCommand()
deleteLockCommand = mocks.NewMockDeleteLockCommand()
applyLockChecker = lockingmocks.NewMockApplyLockChecker()
lockingLocker = lockingmocks.NewMockLocker()

Expand Down
29 changes: 25 additions & 4 deletions server/events/project_command_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,7 @@ func TestDefaultProjectCommandBuilder_TerraformVersion(t *testing.T) {
// If terraform configuration is used, result should be `0.12.8`.
// If project configuration is used, result should be `0.12.6`.
// If default is to be used, result should be `nil`.

baseVersionConfig := `
terraform {
required_version = "%s0.12.8"
Expand All @@ -875,7 +876,29 @@ projects:
`

exactSymbols := []string{"", "="}
nonExactSymbols := []string{">", ">=", "<", "<=", "~="}
// Depending on when the tests are run, the > and >= matching versions will have to be increased.
// It's probably not worth testing the terraform-switcher version here so we only test <, <=, and ~>.
// One way to test this in the future is to mock tfswitcher.GetTFList() to return the highest
// version of 1.3.5.
// nonExactSymbols := []string{">", ">=", "<", "<=", "~>"}
nonExactSymbols := []string{"<", "<=", "~>"}
nonExactVersions := map[string]map[string][]int{
// ">": {
// "project1": {1, 3, 5},
// },
// ">=": {
// "project1": {1, 3, 5},
// },
"<": {
"project1": {0, 12, 7},
},
"<=": {
"project1": {0, 12, 8},
},
"~>": {
"project1": {0, 12, 31},
},
}

type testCase struct {
DirStructure map[string]interface{}
Expand Down Expand Up @@ -908,9 +931,7 @@ projects:
},
},
ModifiedFiles: []string{"project1/main.tf"},
Exp: map[string][]int{
"project1": nil,
},
Exp: nonExactVersions[nonExactSymbol],
}
}

Expand Down
47 changes: 33 additions & 14 deletions server/events/project_command_context_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@ package events

import (
"path/filepath"
"regexp"
"sort"

"github.com/Masterminds/semver"
"github.com/google/uuid"
"github.com/hashicorp/go-version"

"github.com/hashicorp/terraform-config-inspect/tfconfig"
"github.com/runatlantis/atlantis/server/core/config/valid"
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/uber-go/tally"
lib "github.com/warrensbox/terraform-switcher/lib"
)

var mirrorURL = "https://releases.hashicorp.com/terraform"

func NewProjectCommandContextBuilder(policyCheckEnabled bool, commentBuilder CommentBuilder, scope tally.Scope) ProjectCommandContextBuilder {
projectCommandContextBuilder := &DefaultProjectCommandContextBuilder{
CommentBuilder: commentBuilder,
Expand Down Expand Up @@ -287,29 +292,43 @@ func getTfVersion(ctx *command.Context, absProjDir string) *version.Version {
module, diags := tfconfig.LoadModule(absProjDir)
if diags.HasErrors() {
ctx.Log.Err("trying to detect required version: %s", diags.Error())
return nil
}

if len(module.RequiredCore) != 1 {
ctx.Log.Info("cannot determine which version to use from terraform configuration, detected %d possibilities.", len(module.RequiredCore))
return nil
}
requiredVersionSetting := module.RequiredCore[0]
ctx.Log.Debug("found required_version setting of %q", requiredVersionSetting)

// We allow `= x.y.z`, `=x.y.z` or `x.y.z` where `x`, `y` and `z` are integers.
re := regexp.MustCompile(`^=?\s*([^\s]+)\s*$`)
matched := re.FindStringSubmatch(requiredVersionSetting)
if len(matched) == 0 {
ctx.Log.Debug("did not specify exact version in terraform configuration, found %q", requiredVersionSetting)
return nil
tflist, _ := lib.GetTFList(mirrorURL, true)
constrains, _ := semver.NewConstraint(requiredVersionSetting)
versions := make([]*semver.Version, len(tflist))

for i, tfvals := range tflist {
version, err := semver.NewVersion(tfvals) //NewVersion parses a given version and returns an instance of Version or an error if unable to parse the version.
if err == nil {
versions[i] = version
}
}
ctx.Log.Debug("found required_version setting of %q", requiredVersionSetting)
version, err := version.NewVersion(matched[1])
if err != nil {
ctx.Log.Debug(err.Error())

if len(versions) == 0 {
ctx.Log.Debug("did not specify exact valid version in terraform configuration, found %q", requiredVersionSetting)
return nil
}

ctx.Log.Info("detected module requires version: %q", version.String())
return version
sort.Sort(sort.Reverse(semver.Collection(versions)))

for _, element := range versions {
if constrains.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)
ctx.Log.Info("detected module requires version: %s", tfversionStr)
return tfversion
}
}
}
ctx.Log.Debug("could not match any valid terraform version with %q", requiredVersionSetting)
return nil
}
5 changes: 2 additions & 3 deletions server/events/project_command_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import (
"github.com/runatlantis/atlantis/server/events"
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/mocks"
eventmocks "github.com/runatlantis/atlantis/server/events/mocks"
"github.com/runatlantis/atlantis/server/events/mocks/matchers"
"github.com/runatlantis/atlantis/server/events/models"
jobmocks "github.com/runatlantis/atlantis/server/jobs/mocks"
Expand Down Expand Up @@ -189,8 +188,8 @@ func TestProjectOutputWrapper(t *testing.T) {
var prjResult command.ProjectResult
var expCommitStatus models.CommitStatus

mockJobURLSetter := eventmocks.NewMockJobURLSetter()
mockJobMessageSender := eventmocks.NewMockJobMessageSender()
mockJobURLSetter := mocks.NewMockJobURLSetter()
mockJobMessageSender := mocks.NewMockJobMessageSender()
mockProjectCommandRunner := mocks.NewMockProjectCommandRunner()

runner := &events.ProjectOutputWrapper{
Expand Down

0 comments on commit 333ca5f

Please sign in to comment.