Skip to content

Commit

Permalink
Merge pull request #302 from gruntwork-io/backend-check
Browse files Browse the repository at this point in the history
Check that a backend is defined if remote_state settings are defined
  • Loading branch information
brikis98 authored Sep 29, 2017
2 parents 0a61ebb + f21f32b commit 619438c
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 6 deletions.
37 changes: 34 additions & 3 deletions cli/cli_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ func runTerragrunt(terragruntOptions *options.TerragruntOptions) error {
}
}

if terragruntConfig.RemoteState != nil {
if err := checkTerraformCodeDefinesBackend(terragruntOptions, terragruntConfig.RemoteState.Backend); err != nil {
return err
}
}

return runTerragruntWithConfig(terragruntOptions, terragruntConfig, false)
}

Expand Down Expand Up @@ -243,7 +249,6 @@ func prepareInitCommand(terragruntOptions *options.TerragruntOptions, terragrunt
}

if terragruntConfig.RemoteState != nil {

// Initialize the remote state if necessary (e.g. create S3 bucket and DynamoDB table)
remoteStateNeedsInit, err := remoteStateNeedsInit(terragruntConfig.RemoteState, terragruntOptions)
if err != nil {
Expand All @@ -261,6 +266,25 @@ func prepareInitCommand(terragruntOptions *options.TerragruntOptions, terragrunt
return nil
}

// Check that the specified Terraform code defines a backend { ... } block and return an error if doesn't
func checkTerraformCodeDefinesBackend(terragruntOptions *options.TerragruntOptions, backendType string) error {
terraformBackendRegexp, err := regexp.Compile(fmt.Sprintf(`backend[[:blank:]]+"%s"`, backendType))
if err != nil {
return errors.WithStackTrace(err)
}

definesBackend, err := util.Grep(terraformBackendRegexp, fmt.Sprintf("%s/**/*.tf", terragruntOptions.WorkingDir))
if err != nil {
return err
}

if !definesBackend {
return errors.WithStackTrace(BackendNotDefined{Opts: terragruntOptions, BackendType: backendType})
}

return nil
}

// Prepare for running any command other than 'terraform init' by
// running 'terraform init' if necessary
func prepareNonInitCommand(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) error {
Expand Down Expand Up @@ -487,8 +511,6 @@ func outputAll(terragruntOptions *options.TerragruntOptions) error {

// Custom error types

var DontManuallyConfigureRemoteState = fmt.Errorf("Instead of manually using the 'remote config' command, define your remote state settings in %s and Terragrunt will automatically configure it for you (and all your team members) next time you run it.", config.DefaultTerragruntConfigPath)

type UnrecognizedCommand string

func (commandName UnrecognizedCommand) Error() string {
Expand All @@ -509,3 +531,12 @@ type InitNeededButDisabled string
func (err InitNeededButDisabled) Error() string {
return string(err)
}

type BackendNotDefined struct {
Opts *options.TerragruntOptions
BackendType string
}

func (err BackendNotDefined) Error() string {
return fmt.Sprintf("Found remote_state settings in %s but no backend block in the Terraform code in %s. You must define a backend block (it can be empty!) in your Terraform code or your remote state settings will have no effect! It should look something like this:\n\nterraform {\n backend \"%s\" {}\n}\n\n", err.Opts.TerragruntConfigPath, err.Opts.WorkingDir, err.BackendType)
}
20 changes: 20 additions & 0 deletions test/fixture-download/local-with-missing-backend/terraform.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name = "World"

terragrunt = {
terraform {
source = "../hello-world"
}

# We configure remote state here, but the module in the source parameter does not specify a backend, so we should
# get an error when trying to use this module
remote_state {
backend = "s3"
config {
encrypt = true
bucket = "__FILL_IN_BUCKET_NAME__"
key = "terraform.tfstate"
region = "us-west-2"
lock_table = "__FILL_IN_LOCK_TABLE_NAME__"
}
}
}
24 changes: 22 additions & 2 deletions test/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/gruntwork-io/terragrunt/cli"
"github.com/gruntwork-io/terragrunt/config"
terragruntDynamoDb "github.com/gruntwork-io/terragrunt/dynamodb"
"github.com/gruntwork-io/terragrunt/errors"
"github.com/gruntwork-io/terragrunt/remote"
"github.com/gruntwork-io/terragrunt/util"
"github.com/stretchr/testify/assert"
Expand All @@ -42,6 +43,7 @@ const (
TEST_FIXTURE_REMOTE_RELATIVE_DOWNLOAD_PATH = "fixture-download/remote-relative"
TEST_FIXTURE_LOCAL_WITH_BACKEND = "fixture-download/local-with-backend"
TEST_FIXTURE_REMOTE_WITH_BACKEND = "fixture-download/remote-with-backend"
TEST_FIXTURE_LOCAL_MISSING_BACKEND = "fixture-download/local-with-missing-backend"
TEST_FIXTURE_LOCAL_WITH_HIDDEN_FOLDER = "fixture-download/local-with-hidden-folder"
TEST_FIXTURE_OLD_CONFIG_INCLUDE_PATH = "fixture-old-terragrunt-config/include"
TEST_FIXTURE_OLD_CONFIG_INCLUDE_CHILD_UPDATED_PATH = "fixture-old-terragrunt-config/include-child-updated"
Expand All @@ -52,7 +54,6 @@ const (
TERRAFORM_FOLDER = ".terraform"
TERRAFORM_STATE = "terraform.tfstate"
TERRAFORM_STATE_BACKUP = "terraform.tfstate.backup"
DEFAULT_TEST_REGION = "us-east-1"
)

func init() {
Expand Down Expand Up @@ -376,6 +377,25 @@ func TestLocalWithBackend(t *testing.T) {
runTerragrunt(t, fmt.Sprintf("terragrunt apply --terragrunt-non-interactive --terragrunt-working-dir %s", rootPath))
}

func TestLocalWithMissingBackend(t *testing.T) {
t.Parallel()

s3BucketName := fmt.Sprintf("terragrunt-test-bucket-%s", strings.ToLower(uniqueId()))
lockTableName := fmt.Sprintf("terragrunt-lock-table-%s", strings.ToLower(uniqueId()))

tmpEnvPath := copyEnvironment(t, "fixture-download")
rootPath := util.JoinPath(tmpEnvPath, TEST_FIXTURE_LOCAL_MISSING_BACKEND)

rootTerragruntConfigPath := util.JoinPath(rootPath, config.DefaultTerragruntConfigPath)
copyTerragruntConfigAndFillPlaceholders(t, rootTerragruntConfigPath, rootTerragruntConfigPath, s3BucketName, lockTableName)

err := runTerragruntCommand(t, fmt.Sprintf("terragrunt apply --terragrunt-non-interactive --terragrunt-working-dir %s", rootPath), os.Stdout, os.Stderr)
if assert.Error(t, err) {
underlying := errors.Unwrap(err)
assert.IsType(t, cli.BackendNotDefined{}, underlying)
}
}

func TestRemoteWithBackend(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -476,7 +496,7 @@ func runTerragruntRedirectOutput(t *testing.T, command string, writer io.Writer,
}

func copyEnvironment(t *testing.T, environmentPath string) string {
tmpDir, err := ioutil.TempDir("", "terragrunt-stack-test")
tmpDir, err := ioutil.TempDir("", "terragrunt-test")
if err != nil {
t.Fatalf("Failed to create temp dir due to error: %v", err)
}
Expand Down
15 changes: 14 additions & 1 deletion util/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"

"github.com/gruntwork-io/terragrunt/errors"
"github.com/mattn/go-zglob"
)

// Return true if the given file exists
Expand Down Expand Up @@ -61,12 +62,18 @@ func DeleteFiles(files []string) error {

// Returns true if the given regex can be found in any of the files matched by the given glob
func Grep(regex *regexp.Regexp, glob string) (bool, error) {
matches, err := filepath.Glob(glob)
// Ideally, we'd use a builin Go library like filepath.Glob here, but per https://github.com/golang/go/issues/11862,
// the current go implementation doesn't support treating ** as zero or more directories, just zero or one.
// So we use a third-party library.
matches, err := zglob.Glob(glob)
if err != nil {
return false, errors.WithStackTrace(err)
}

for _, match := range matches {
if IsDir(match) {
continue
}
bytes, err := ioutil.ReadFile(match)
if err != nil {
return false, errors.WithStackTrace(err)
Expand All @@ -80,6 +87,12 @@ func Grep(regex *regexp.Regexp, glob string) (bool, error) {
return false, nil
}

// Return true if the path points to a directory
func IsDir(path string) bool {
fileInfo, err := os.Stat(path)
return err == nil && fileInfo.IsDir()
}

// Return the relative path you would have to take to get from basePath to path
func GetPathRelativeTo(path string, basePath string) (string, error) {
if path == "" {
Expand Down

0 comments on commit 619438c

Please sign in to comment.