diff --git a/DOCS.md b/DOCS.md index 6ebba8a..b4914a2 100644 --- a/DOCS.md +++ b/DOCS.md @@ -196,6 +196,19 @@ pipeline: + check: true ``` +You may want to run some executions in parallel without having racing condition problems with the .terraform dir and +plan's output file. + +```diff +pipeline: + backend-service: + image: jmccann/drone-terraform: ++ tf_data_dir: .backend-service.terraform + frontend-service: + image: jmccann/drone-terraform: ++ tf_data_dir: .frontend-service.terraform +``` + # Parameter Reference actions @@ -253,3 +266,6 @@ root_dir parallelism : The number of concurrent operations as Terraform walks its graph. + +tf_data_dir +: changes the location where Terraform keeps its per-working-directory data, such as the current remote backend configuration. diff --git a/main.go b/main.go index 20e9d3c..7f230b0 100644 --- a/main.go +++ b/main.go @@ -108,6 +108,11 @@ func main() { Usage: "a list of var files to use. Each value is passed as -var-file=", EnvVar: "PLUGIN_VAR_FILES", }, + cli.StringFlag{ + Name: "tf_data_dir", + Usage: "changes the location where Terraform keeps its per-working-directory data, such as the current remote backend configuration", + EnvVar: "PLUGIN_TF_DATA_DIR", + }, } if err := app.Run(os.Args); err != nil { @@ -144,18 +149,19 @@ func run(c *cli.Context) error { plugin := Plugin{ Config: Config{ - Actions: c.StringSlice("actions"), - Vars: vars, - Secrets: secrets, - InitOptions: initOptions, - FmtOptions: fmtOptions, - Cacert: c.String("ca_cert"), - Sensitive: c.Bool("sensitive"), - RoleARN: c.String("role_arn_to_assume"), - RootDir: c.String("root_dir"), - Parallelism: c.Int("parallelism"), - Targets: c.StringSlice("targets"), - VarFiles: c.StringSlice("var_files"), + Actions: c.StringSlice("actions"), + Vars: vars, + Secrets: secrets, + InitOptions: initOptions, + FmtOptions: fmtOptions, + Cacert: c.String("ca_cert"), + Sensitive: c.Bool("sensitive"), + RoleARN: c.String("role_arn_to_assume"), + RootDir: c.String("root_dir"), + Parallelism: c.Int("parallelism"), + Targets: c.StringSlice("targets"), + VarFiles: c.StringSlice("var_files"), + TerraformDataDir: c.String("tf_data_dir"), }, Netrc: Netrc{ Login: c.String("netrc.username"), diff --git a/plugin.go b/plugin.go index 6fefc24..07dfaa0 100644 --- a/plugin.go +++ b/plugin.go @@ -21,18 +21,19 @@ import ( type ( // Config holds input parameters for the plugin Config struct { - Actions []string - Vars map[string]string - Secrets map[string]string - InitOptions InitOptions - FmtOptions FmtOptions - Cacert string - Sensitive bool - RoleARN string - RootDir string - Parallelism int - Targets []string - VarFiles []string + Actions []string + Vars map[string]string + Secrets map[string]string + InitOptions InitOptions + FmtOptions FmtOptions + Cacert string + Sensitive bool + RoleARN string + RootDir string + Parallelism int + Targets []string + VarFiles []string + TerraformDataDir string } // Netrc is credentials for cloning @@ -86,6 +87,12 @@ func (p Plugin) Exec() error { return err } + var terraformDataDir string = ".terraform" + if p.Config.TerraformDataDir != "" { + terraformDataDir = p.Config.TerraformDataDir + os.Setenv("TF_DATA_DIR", p.Config.TerraformDataDir) + } + var commands []*exec.Cmd commands = append(commands, exec.Command("terraform", "version")) @@ -96,7 +103,7 @@ func (p Plugin) Exec() error { commands = append(commands, installCaCert(p.Config.Cacert)) } - commands = append(commands, deleteCache()) + commands = append(commands, deleteCache(terraformDataDir)) commands = append(commands, initCommand(p.Config.InitOptions)) commands = append(commands, getModules()) @@ -120,7 +127,7 @@ func (p Plugin) Exec() error { } } - commands = append(commands, deleteCache()) + commands = append(commands, deleteCache(terraformDataDir)) for _, c := range commands { if c.Dir == "" { @@ -183,11 +190,11 @@ func assumeRole(roleArn string) { os.Setenv("AWS_SESSION_TOKEN", value.SessionToken) } -func deleteCache() *exec.Cmd { +func deleteCache(terraformDataDir string) *exec.Cmd { return exec.Command( "rm", "-rf", - ".terraform", + terraformDataDir, ) } @@ -253,7 +260,8 @@ func tfApply(config Config) *exec.Cmd { if config.InitOptions.LockTimeout != "" { args = append(args, fmt.Sprintf("-lock-timeout=%s", config.InitOptions.LockTimeout)) } - args = append(args, "plan.tfout") + args = append(args, getTfoutPath()) + return exec.Command( "terraform", args..., @@ -293,7 +301,7 @@ func tfPlan(config Config, destroy bool) *exec.Cmd { if destroy { args = append(args, "-destroy") } else { - args = append(args, "-out=plan.tfout") + args = append(args, fmt.Sprintf("-out=%s", getTfoutPath())) } for _, v := range config.Targets { @@ -354,6 +362,15 @@ func tfFmt(config Config) *exec.Cmd { ) } +func getTfoutPath() string { + terraformDataDir := os.Getenv("TF_DATA_DIR") + if terraformDataDir == ".terraform" || terraformDataDir == "" { + return "plan.tfout" + } else { + return fmt.Sprintf("%s.plan.tfout", terraformDataDir) + } +} + func vars(vs map[string]string) []string { var args []string for k, v := range vs { diff --git a/plugin_test.go b/plugin_test.go index dc091d5..a131f11 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -204,4 +204,42 @@ func TestPlugin(t *testing.T) { } }) }) + + g.Describe("tfDataDir", func() { + g.It("Should override the terraform data dir environment variable when provided", func() { + type args struct { + config Config + } + + tests := []struct { + name string + args args + want *exec.Cmd + }{ + { + "with TerraformDataDir", + args{config: Config{TerraformDataDir: ".overriden_terraform_dir"}}, + exec.Command("terraform", "apply", ".overriden_terraform_dir.plan.tfout"), + }, + { + "with TerraformDataDir value as .terraform", + args{config: Config{TerraformDataDir: ".terraform"}}, + exec.Command("terraform", "apply", "plan.tfout"), + }, + { + "without TerraformDataDir", + args{config: Config{}}, + exec.Command("terraform", "apply", "plan.tfout"), + }, + } + + for _, tt := range tests { + os.Setenv("TF_DATA_DIR", tt.args.config.TerraformDataDir) + applied := tfApply(tt.args.config) + + g.Assert(applied).Equal(tt.want) + + } + }) + }) }