2014-07-09 22:34:08 +02:00
|
|
|
package localexec
|
|
|
|
|
|
|
|
import (
|
2016-12-22 23:11:41 +01:00
|
|
|
"context"
|
2014-07-09 22:34:08 +02:00
|
|
|
"fmt"
|
2014-10-06 08:05:49 +02:00
|
|
|
"io"
|
2014-07-09 22:34:08 +02:00
|
|
|
"os/exec"
|
|
|
|
"runtime"
|
|
|
|
|
|
|
|
"github.com/armon/circbuf"
|
2016-12-22 23:11:41 +01:00
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
2014-07-09 22:34:08 +02:00
|
|
|
"github.com/hashicorp/terraform/terraform"
|
2014-10-06 08:05:49 +02:00
|
|
|
"github.com/mitchellh/go-linereader"
|
2014-07-09 22:34:08 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// maxBufSize limits how much output we collect from a local
|
|
|
|
// invocation. This is to prevent TF memory usage from growing
|
|
|
|
// to an enormous amount due to a faulty process.
|
|
|
|
maxBufSize = 8 * 1024
|
|
|
|
)
|
|
|
|
|
2016-12-22 23:11:41 +01:00
|
|
|
func Provisioner() terraform.ResourceProvisioner {
|
|
|
|
return &schema.Provisioner{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"command": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
2014-07-09 22:34:08 +02:00
|
|
|
|
2016-12-22 23:11:41 +01:00
|
|
|
ApplyFunc: applyFn,
|
2014-07-09 22:34:08 +02:00
|
|
|
}
|
2016-12-22 23:11:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func applyFn(ctx context.Context) error {
|
|
|
|
data := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
|
|
|
|
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")
|
2014-07-09 22:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Execute the command using a shell
|
|
|
|
var shell, flag string
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
shell = "cmd"
|
|
|
|
flag = "/C"
|
|
|
|
} else {
|
|
|
|
shell = "/bin/sh"
|
|
|
|
flag = "-c"
|
|
|
|
}
|
|
|
|
|
2014-10-06 08:05:49 +02:00
|
|
|
// Setup the reader that will read the lines from the command
|
|
|
|
pr, pw := io.Pipe()
|
|
|
|
copyDoneCh := make(chan struct{})
|
2016-12-22 23:11:41 +01:00
|
|
|
go copyOutput(o, pr, copyDoneCh)
|
2014-10-06 08:05:49 +02:00
|
|
|
|
2014-07-09 22:34:08 +02:00
|
|
|
// Setup the command
|
|
|
|
cmd := exec.Command(shell, flag, command)
|
|
|
|
output, _ := circbuf.NewBuffer(maxBufSize)
|
2014-10-06 08:05:49 +02:00
|
|
|
cmd.Stderr = io.MultiWriter(output, pw)
|
|
|
|
cmd.Stdout = io.MultiWriter(output, pw)
|
|
|
|
|
|
|
|
// Output what we're about to run
|
|
|
|
o.Output(fmt.Sprintf(
|
|
|
|
"Executing: %s %s \"%s\"",
|
|
|
|
shell, flag, command))
|
2014-07-09 22:34:08 +02:00
|
|
|
|
|
|
|
// Run the command to completion
|
2014-10-06 08:05:49 +02:00
|
|
|
err := cmd.Run()
|
|
|
|
|
|
|
|
// Close the write-end of the pipe so that the goroutine mirroring output
|
|
|
|
// ends properly.
|
|
|
|
pw.Close()
|
|
|
|
<-copyDoneCh
|
|
|
|
|
|
|
|
if err != nil {
|
2014-07-22 19:38:39 +02:00
|
|
|
return fmt.Errorf("Error running command '%s': %v. Output: %s",
|
2014-07-09 22:34:08 +02:00
|
|
|
command, err, output.Bytes())
|
|
|
|
}
|
2014-10-06 08:05:49 +02:00
|
|
|
|
2014-07-22 19:38:39 +02:00
|
|
|
return nil
|
2014-07-09 22:34:08 +02:00
|
|
|
}
|
|
|
|
|
2016-12-22 23:11:41 +01:00
|
|
|
func copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) {
|
2014-10-06 08:05:49 +02:00
|
|
|
defer close(doneCh)
|
|
|
|
lr := linereader.New(r)
|
|
|
|
for line := range lr.Ch {
|
|
|
|
o.Output(line)
|
|
|
|
}
|
|
|
|
}
|