package localexec import ( "fmt" "io" "os/exec" "runtime" "github.com/armon/circbuf" "github.com/hashicorp/terraform/helper/config" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/go-linereader" ) 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 ) type ResourceProvisioner struct{} func (p *ResourceProvisioner) Apply( o terraform.UIOutput, s *terraform.InstanceState, c *terraform.ResourceConfig) error { // Get the command commandRaw, ok := c.Config["command"] if !ok { return fmt.Errorf("local-exec provisioner missing 'command'") } command, ok := commandRaw.(string) if !ok { return fmt.Errorf("local-exec provisioner command must be a string") } // Execute the command using a shell var shell, flag string if runtime.GOOS == "windows" { shell = "cmd" flag = "/C" } else { shell = "/bin/sh" flag = "-c" } // Setup the reader that will read the lines from the command pr, pw := io.Pipe() copyDoneCh := make(chan struct{}) go p.copyOutput(o, pr, copyDoneCh) // Setup the command cmd := exec.Command(shell, flag, command) output, _ := circbuf.NewBuffer(maxBufSize) 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)) // Run the command to completion err := cmd.Run() // Close the write-end of the pipe so that the goroutine mirroring output // ends properly. pw.Close() <-copyDoneCh if err != nil { return fmt.Errorf("Error running command '%s': %v. Output: %s", command, err, output.Bytes()) } return nil } func (p *ResourceProvisioner) Validate(c *terraform.ResourceConfig) ([]string, []error) { validator := config.Validator{ Required: []string{"command"}, } return validator.Validate(c) } func (p *ResourceProvisioner) copyOutput( o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) { defer close(doneCh) lr := linereader.New(r) for line := range lr.Ch { o.Output(line) } }