Skip to content

Commit

Permalink
Version detection using terraform-config-inspect
Browse files Browse the repository at this point in the history
  • Loading branch information
YesYouKenSpace committed Sep 25, 2019
1 parent 30f9ba1 commit f49d253
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 23 deletions.
40 changes: 36 additions & 4 deletions server/events/project_command_builder.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package events

import (
"regexp"
"path/filepath"
"fmt"
"strings"

"github.com/runatlantis/atlantis/server/events/yaml/valid"

"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-config-inspect/tfconfig"
"github.com/pkg/errors"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs"
Expand Down Expand Up @@ -137,7 +141,7 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext,
for _, mp := range matchingProjects {
ctx.Log.Debug("determining config for project at dir: %q workspace: %q", mp.Dir, mp.Workspace)
mergedCfg := p.GlobalCfg.MergeProjectCfg(ctx.Log, ctx.BaseRepo.ID(), mp, repoCfg)
projCtxs = append(projCtxs, p.buildCtx(ctx, models.PlanCommand, mergedCfg, commentFlags, repoCfg.Automerge, verbose))
projCtxs = append(projCtxs, p.buildCtx(ctx, models.PlanCommand, mergedCfg, commentFlags, repoCfg.Automerge, verbose, repoDir))
}
} else {
// If there is no config file, then we'll plan each project that
Expand All @@ -148,7 +152,7 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext,
for _, mp := range modifiedProjects {
ctx.Log.Debug("determining config for project at dir: %q", mp.Path)
pCfg := p.GlobalCfg.DefaultProjCfg(ctx.Log, ctx.BaseRepo.ID(), mp.Path, DefaultWorkspace)
projCtxs = append(projCtxs, p.buildCtx(ctx, models.PlanCommand, pCfg, commentFlags, DefaultAutomergeEnabled, verbose))
projCtxs = append(projCtxs, p.buildCtx(ctx, models.PlanCommand, pCfg, commentFlags, DefaultAutomergeEnabled, verbose, repoDir))
}
}

Expand Down Expand Up @@ -282,7 +286,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(
if repoCfgPtr != nil {
automerge = repoCfgPtr.Automerge
}
return p.buildCtx(ctx, cmd, projCfg, commentFlags, automerge, verbose), nil
return p.buildCtx(ctx, cmd, projCfg, commentFlags, automerge, verbose, repoDir), nil
}

// getCfg returns the atlantis.yaml config (if it exists) for this project. If
Expand Down Expand Up @@ -371,7 +375,8 @@ func (p *DefaultProjectCommandBuilder) buildCtx(ctx *CommandContext,
projCfg valid.MergedProjectCfg,
commentArgs []string,
automergeEnabled bool,
verbose bool) models.ProjectCommandContext {
verbose bool,
absRepoDir string) models.ProjectCommandContext {

var steps []valid.Step
switch cmd {
Expand All @@ -381,6 +386,17 @@ func (p *DefaultProjectCommandBuilder) buildCtx(ctx *CommandContext,
steps = projCfg.Workflow.Apply.Steps
}

// if TerraformVersion not defined in config file fallback to terraform configuration
if projCfg.TerraformVersion == nil {
version, err := p.getTfVersion(filepath.Join(absRepoDir, projCfg.RepoRelDir))
if err != nil {
fmt.Println(err.Error())
ctx.Log.Debug(err.Error())
} else {
projCfg.TerraformVersion = version
}
}

return models.ProjectCommandContext{
ApplyCmd: p.CommentBuilder.BuildApplyComment(projCfg.RepoRelDir, projCfg.Workspace, projCfg.Name),
BaseRepo: ctx.BaseRepo,
Expand Down Expand Up @@ -415,3 +431,19 @@ func (p *DefaultProjectCommandBuilder) escapeArgs(args []string) []string {
}
return escaped
}

// Extracts required_version from Terraform configuration
func (p *DefaultProjectCommandBuilder) getTfVersion(absProjDir string) (*version.Version, error) {
module, diags := tfconfig.LoadModule(absProjDir)
if diags.HasErrors() {
return nil, diags.Err()
}
if len(module.RequiredCore) == 1 {
re := regexp.MustCompile(`^=?\s*([^\s]+)\s*$`)
if matched := re.FindStringSubmatch(module.RequiredCore[0]); len(matched) > 0 {
return version.NewVersion(matched[1])
}
return nil, errors.Errorf("did not specify exact version in terraform configuration.")
}
return nil, errors.Errorf("cannot determine required core from terraform configuration.")
}
39 changes: 20 additions & 19 deletions server/events/project_command_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"path/filepath"
"strings"
"testing"
"fmt"

. "github.com/petergtz/pegomock"
"github.com/runatlantis/atlantis/server/events"
Expand Down Expand Up @@ -726,8 +725,11 @@ projects:
- dir: project1 # project1 uses the defaults
terraform_version: v0.12.6
`
versionConfig := fmt.Sprintf("terraform { required_version = \"= %s\"}", "0.12.8")

versionConfig := `
terraform {
required_version = "= 0.12.8"
}
`
testCases := map[string]struct {
DirStructure map[string]interface{}
AtlantisYAML string
Expand All @@ -737,7 +739,7 @@ projects:
"no atlantis.yaml": {
DirStructure: map[string]interface{}{
"project1": map[string]interface{}{
"main.tf": nil,
"main.tf": nil,
"version.tf": versionConfig,
},
},
Expand All @@ -746,11 +748,12 @@ projects:
"TerraformVersion": []int{0, 12, 8},
},
},
// atlantis.yaml should take precedence over terraform config
"defined in atlantis.yaml": {
DirStructure: map[string]interface{}{
"project1": map[string]interface{}{
"main.tf": nil,
"version.tf": versionConfig,
"main.tf": nil,
"version.tf": versionConfig,
},
yaml.AtlantisYAMLFilename: atlantisYamlContent,
},
Expand All @@ -762,24 +765,24 @@ projects:
}

for name, testCase := range testCases {
t.Run(name, func (t *testing.T) {
t.Run(name, func(t *testing.T) {
RegisterMockTestingT(t)

tmpDir, cleanup := DirStructure(t, testCase.DirStructure)

defer cleanup()

workingDir := mocks.NewMockWorkingDir()
When(workingDir.Clone(
matchers.AnyPtrToLoggingSimpleLogger(),
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
matchers.AnyPtrToLoggingSimpleLogger(),
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyString())).ThenReturn(tmpDir, nil)

When(workingDir.GetWorkingDir(
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyString())).ThenReturn(tmpDir, nil)

builder := &events.DefaultProjectCommandBuilder{
Expand All @@ -791,14 +794,12 @@ projects:
GlobalCfg: valid.NewGlobalCfg(true, false, false),
}

var actCtxs []models.ProjectCommandContext
var err error
actCtxs, err = builder.BuildPlanCommands(&events.CommandContext{}, &events.CommentCommand{
actCtxs, err := builder.BuildPlanCommands(&events.CommandContext{}, &events.CommentCommand{
RepoRelDir: "project1",
Name: models.PlanCommand,
Verbose: false,
})

Ok(t, err)
Equals(t, 1, len(actCtxs))
actCtx := actCtxs[0]
Expand Down

0 comments on commit f49d253

Please sign in to comment.