From 5d5f8224d2ce59419fc929a44661681604173d0f Mon Sep 17 00:00:00 2001 From: Stephen Weatherford Date: Tue, 22 Aug 2017 10:07:32 -0700 Subject: [PATCH] provisioner/local-exec: allow user to specify interpreter --- .../local-exec/resource_provisioner.go | 34 +++++++++++++------ .../local-exec/resource_provisioner_test.go | 20 +++++++++++ .../provisioners/local-exec.html.markdown | 28 +++++++++++++++ 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/builtin/provisioners/local-exec/resource_provisioner.go b/builtin/provisioners/local-exec/resource_provisioner.go index 731c85baf..f537bb34a 100644 --- a/builtin/provisioners/local-exec/resource_provisioner.go +++ b/builtin/provisioners/local-exec/resource_provisioner.go @@ -28,6 +28,12 @@ func Provisioner() terraform.ResourceProvisioner { Type: schema.TypeString, Required: true, }, + + "interpreter": &schema.Schema{ + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, }, ApplyFunc: applyFn, @@ -39,19 +45,29 @@ func applyFn(ctx context.Context) error { o := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput) command := data.Get("command").(string) + if command == "" { return fmt.Errorf("local-exec provisioner command must be a non-empty string") } // Execute the command using a shell - var shell, flag string - if runtime.GOOS == "windows" { - shell = "cmd" - flag = "/C" + interpreter := data.Get("interpreter").([]interface{}) + + var cmdargs []string + if len(interpreter) > 0 { + for _, i := range interpreter { + if arg, ok := i.(string); ok { + cmdargs = append(cmdargs, arg) + } + } } else { - shell = "/bin/sh" - flag = "-c" + if runtime.GOOS == "windows" { + cmdargs = []string{"cmd", "/C"} + } else { + cmdargs = []string{"/bin/sh", "-c"} + } } + cmdargs = append(cmdargs, command) // Setup the reader that will read the output from the command. // We use an os.Pipe so that the *os.File can be passed directly to the @@ -63,7 +79,7 @@ func applyFn(ctx context.Context) error { } // Setup the command - cmd := exec.CommandContext(ctx, shell, flag, command) + cmd := exec.CommandContext(ctx, cmdargs[0], cmdargs[1:]...) cmd.Stderr = pw cmd.Stdout = pw @@ -77,9 +93,7 @@ func applyFn(ctx context.Context) error { go copyOutput(o, tee, copyDoneCh) // Output what we're about to run - o.Output(fmt.Sprintf( - "Executing: %s %s \"%s\"", - shell, flag, command)) + o.Output(fmt.Sprintf("Executing: %q", cmdargs)) // Start the command err = cmd.Start() diff --git a/builtin/provisioners/local-exec/resource_provisioner_test.go b/builtin/provisioners/local-exec/resource_provisioner_test.go index cb7dc1c1b..a32b15c59 100644 --- a/builtin/provisioners/local-exec/resource_provisioner_test.go +++ b/builtin/provisioners/local-exec/resource_provisioner_test.go @@ -125,3 +125,23 @@ func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfi return terraform.NewResourceConfig(r) } + +func TestResourceProvider_ApplyCustomInterpreter(t *testing.T) { + c := testConfig(t, map[string]interface{}{ + "interpreter": []interface{}{"echo", "is"}, + "command": "not really an interpreter", + }) + + output := new(terraform.MockUIOutput) + p := Provisioner() + + if err := p.Apply(output, nil, c); err != nil { + t.Fatalf("err: %v", err) + } + + got := strings.TrimSpace(output.OutputMessage) + want := "is not really an interpreter" + if got != want { + t.Errorf("wrong output\ngot: %s\nwant: %s", got, want) + } +} diff --git a/website/docs/provisioners/local-exec.html.markdown b/website/docs/provisioners/local-exec.html.markdown index 7e5673a26..bf08bf346 100644 --- a/website/docs/provisioners/local-exec.html.markdown +++ b/website/docs/provisioners/local-exec.html.markdown @@ -38,3 +38,31 @@ The following arguments are supported: as a relative path to the current working directory or as an absolute path. It is evaluated in a shell, and can use environment variables or Terraform variables. + +* `interpreter` - (Optional) If provided, this is a list of interpreter + arguments used to execute the command. The first argument is the + interpreter itself. It can be provided as a relative path to the current + working directory or as an absolute path. The remaining arguments are + appended prior to the command. This allows building command lines of the + form "/bin/bash", "-c", "echo foo". If `interpreter` is unspecified, + sensible defaults will be chosen based on the system OS. + +### Interpreter Examples + +```hcl +resource "null_resource" "example1" { + provisioner "local-exec" { + command = "open WFH, '>completed.txt' and print WFH scalar localtime" + interpreter = ["perl", "-e"] + } +} +``` + +```hcl +resource "null_resource" "example2" { + provisioner "local-exec" { + command = "Get-Date > completed.txt" + interpreter = ["PowerShell", "-Command"] + } +} +```