From ff7e2e547eec7b489cfa977f15e099105e160f7e Mon Sep 17 00:00:00 2001 From: "marcin.suterski" Date: Tue, 8 Aug 2017 17:16:20 -0400 Subject: [PATCH 1/2] Add support for terraform destroy command --- main.go | 6 +++ plugin.go | 39 ++++++++++++++- plugin_test.go | 133 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 plugin_test.go diff --git a/main.go b/main.go index a50c4a3..ab9ab9a 100644 --- a/main.go +++ b/main.go @@ -85,6 +85,11 @@ func main() { Usage: "a list of var files to use. Each value is passed as -var-file=", EnvVar: "PLUGIN_VAR_FILES", }, + cli.BoolFlag{ + Name: "destroy", + Usage: "destory all resurces", + EnvVar: "PLUGIN_DESTROY", + }, } if err := app.Run(os.Args); err != nil { @@ -130,6 +135,7 @@ func run(c *cli.Context) error { Parallelism: c.Int("parallelism"), Targets: c.StringSlice("targets"), VarFiles: c.StringSlice("var_files"), + Destroy: c.Bool("destroy"), }, } diff --git a/plugin.go b/plugin.go index 18c1cfa..4cd9f55 100644 --- a/plugin.go +++ b/plugin.go @@ -16,6 +16,7 @@ import ( ) type ( + // Config holds input parameters for the plugin Config struct { Plan bool Vars map[string]string @@ -28,19 +29,23 @@ type ( Parallelism int Targets []string VarFiles []string + Destroy bool } + // InitOptions include options for the Terraform's init command InitOptions struct { BackendConfig []string `json:"backend-config"` Lock *bool `json:"lock"` LockTimeout string `json:"lock-timeout"` } + // Plugin represents the plugin instance to be executed Plugin struct { Config Config } ) +// Exec executes the plugin func (p Plugin) Exec() error { if p.Config.RoleARN != "" { assumeRole(p.Config.RoleARN) @@ -64,7 +69,7 @@ func (p Plugin) Exec() error { commands = append(commands, validateCommand()) commands = append(commands, planCommand(p.Config)) if !p.Config.Plan { - commands = append(commands, applyCommand(p.Config)) + commands = append(commands, terraformCommand(p.Config)) } commands = append(commands, deleteCache()) @@ -165,8 +170,13 @@ func validateCommand() *exec.Cmd { func planCommand(config Config) *exec.Cmd { args := []string{ "plan", - "-out=plan.tfout", } + if config.Destroy { + args = append(args, "-destroy") + } else { + args = append(args, "-out=plan.tfout") + } + for _, v := range config.Targets { args = append(args, "--target", fmt.Sprintf("%s", v)) } @@ -190,6 +200,14 @@ func planCommand(config Config) *exec.Cmd { ) } +func terraformCommand(config Config) *exec.Cmd { + if config.Destroy { + return destroyCommand(config) + } + + return applyCommand(config) +} + func applyCommand(config Config) *exec.Cmd { args := []string{ "apply", @@ -207,6 +225,23 @@ func applyCommand(config Config) *exec.Cmd { ) } +func destroyCommand(config Config) *exec.Cmd { + args := []string{ + "destroy", + } + for _, v := range config.Targets { + args = append(args, fmt.Sprintf("-target=%s", v)) + } + if config.Parallelism > 0 { + args = append(args, fmt.Sprintf("-parallelism=%d", config.Parallelism)) + } + args = append(args, "-force") + return exec.Command( + "terraform", + args..., + ) +} + func assumeRole(roleArn string) { client := sts.New(session.New()) duration := time.Hour * 1 diff --git a/plugin_test.go b/plugin_test.go new file mode 100644 index 0000000..d7cc9da --- /dev/null +++ b/plugin_test.go @@ -0,0 +1,133 @@ +package main + +import ( + "os/exec" + "reflect" + "testing" +) + +func Test_destroyCommand(t *testing.T) { + type args struct { + config Config + } + tests := []struct { + name string + args args + want *exec.Cmd + }{ + { + "default", + args{config: Config{}}, + exec.Command("terraform", "destroy", "-force"), + }, + { + "with targets", + args{config: Config{Targets: []string{"target1", "target2"}}}, + exec.Command("terraform", "destroy", "-target=target1", "-target=target2", "-force"), + }, + { + "with parallelism", + args{config: Config{Parallelism: 5}}, + exec.Command("terraform", "destroy", "-parallelism=5", "-force"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := destroyCommand(tt.args.config); !reflect.DeepEqual(got, tt.want) { + t.Errorf("destroyCommand() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_applyCommand(t *testing.T) { + type args struct { + config Config + } + tests := []struct { + name string + args args + want *exec.Cmd + }{ + { + "default", + args{config: Config{}}, + exec.Command("terraform", "apply", "plan.tfout"), + }, + { + "with targets", + args{config: Config{Targets: []string{"target1", "target2"}}}, + exec.Command("terraform", "apply", "--target", "target1", "--target", "target2", "plan.tfout"), + }, + { + "with parallelism", + args{config: Config{Parallelism: 5}}, + exec.Command("terraform", "apply", "-parallelism=5", "plan.tfout"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := applyCommand(tt.args.config); !reflect.DeepEqual(got, tt.want) { + t.Errorf("applyCommand() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_terraformCommand(t *testing.T) { + type args struct { + config Config + } + tests := []struct { + name string + args args + want *exec.Cmd + }{ + { + "default", + args{config: Config{}}, + exec.Command("terraform", "apply", "plan.tfout"), + }, + { + "destroy", + args{config: Config{Destroy: true}}, + exec.Command("terraform", "destroy", "-force"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := terraformCommand(tt.args.config); !reflect.DeepEqual(got, tt.want) { + t.Errorf("terraformCommand() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_planCommand(t *testing.T) { + type args struct { + config Config + } + tests := []struct { + name string + args args + want *exec.Cmd + }{ + { + "default", + args{config: Config{}}, + exec.Command("terraform", "plan", "-out=plan.tfout"), + }, + { + "destroy", + args{config: Config{Destroy: true}}, + exec.Command("terraform", "plan", "-destroy"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := planCommand(tt.args.config); !reflect.DeepEqual(got, tt.want) { + t.Errorf("planCommand() = %v, want %v", got, tt.want) + } + }) + } +} From 8e81b54bd3401689e0ac299d6b1a355ce77df875 Mon Sep 17 00:00:00 2001 From: "marcin.suterski" Date: Tue, 8 Aug 2017 17:31:32 -0400 Subject: [PATCH 2/2] Update docs with the destroy option --- DOCS.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/DOCS.md b/DOCS.md index d65bd6c..63d97f5 100644 --- a/DOCS.md +++ b/DOCS.md @@ -168,6 +168,16 @@ pipeline: + AWS_SECRET_ACCESS_KEY: PROD_AWS_SECRET_ACCESS_KEY ``` +Destroying the service can be done using the boolean `destory` option. Keep in mind that Fastly won't allow a service with active version be destoryed. Use `force_destroy` option in the service definition for terraform to handle it. + +```yaml +pipeline: + destroy: + image: jmccann/drone-terraform:1 + plan: false ++ destroy: true +``` + # Parameter Reference plan @@ -215,3 +225,6 @@ root_dir parallelism : The number of concurrent operations as Terraform walks its graph. + +destroy (boolean) +: Destroys the service (still requires [`force_destroy`](https://www.terraform.io/docs/providers/fastly/r/service_v1.html#force_destroy) option to be set in the service definition) \ No newline at end of file