// Apply executes the file provisioner func (r *ResourceProvisioner) Apply( o terraform.UIOutput, s *terraform.InstanceState, c *terraform.ResourceConfig) error { // Decode the raw config for this provisioner p, err := r.decodeConfig(c) if err != nil { return err } // Set some values based on the targeted OS switch s.Ephemeral.ConnInfo["type"] { case "ssh", "": // The default connection type is ssh, so if the type is empty use ssh p.installChefClient = p.sshInstallChefClient p.createConfigFiles = p.sshCreateConfigFiles p.runChefClient = p.runChefClientFunc(linuxConfDir) p.useSudo = !p.PreventSudo && s.Ephemeral.ConnInfo["user"] != "root" case "winrm": p.installChefClient = p.winrmInstallChefClient p.createConfigFiles = p.winrmCreateConfigFiles p.runChefClient = p.runChefClientFunc(windowsConfDir) p.useSudo = false default: return fmt.Errorf("Unsupported connection type: %s", s.Ephemeral.ConnInfo["type"]) } // Get a new communicator comm, err := communicator.New(s) if err != nil { return err } // Wait and retry until we establish the connection err = retryFunc(comm.Timeout(), func() error { err := comm.Connect(o) return err }) if err != nil { return err } defer comm.Disconnect() if !p.SkipInstall { if err := p.installChefClient(o, comm); err != nil { return err } } o.Output("Creating configuration files...") if err := p.createConfigFiles(o, comm); err != nil { return err } o.Output("Starting initial Chef-Client run...") if err := p.runChefClient(o, comm); err != nil { return err } return nil }
func (p *Provisioner) 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) } }
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 }
// Connect implementation of communicator.Communicator interface func (c *Communicator) Connect(o terraform.UIOutput) error { if c.client != nil { return nil } params := winrm.DefaultParameters() params.Timeout = formatDuration(c.Timeout()) client, err := winrm.NewClientWithParameters( c.endpoint, c.connInfo.User, c.connInfo.Password, params) if err != nil { return err } if o != nil { o.Output(fmt.Sprintf( "Connecting to remote host via WinRM...\n"+ " Host: %s\n"+ " Port: %d\n"+ " User: %s\n"+ " Password: %t\n"+ " HTTPS: %t\n"+ " Insecure: %t\n"+ " CACert: %t", c.connInfo.Host, c.connInfo.Port, c.connInfo.User, c.connInfo.Password != "", c.connInfo.HTTPS, c.connInfo.Insecure, c.connInfo.CACert != nil, )) } log.Printf("connecting to remote shell using WinRM") shell, err := client.CreateShell() if err != nil { log.Printf("connection error: %s", err) return err } err = shell.Close() if err != nil { log.Printf("error closing connection: %s", err) return err } if o != nil { o.Output("Connected!") } c.client = client return nil }
func (r *ResourceProvisioner) Apply( o terraform.UIOutput, s *terraform.InstanceState, c *terraform.ResourceConfig) error { provisioner, err := r.decodeConfig(c) if err != nil { o.Output("erred out here") return err } err = provisioner.Validate() if err != nil { o.Output("Invalid provisioner configuration settings") return err } provisioner.useSudo = true provisioner.ansibleLocalScript = fmt.Sprintf("https://raw.githubusercontent.com/jonmorehouse/terraform-provisioner-ansible/%s/ansible-local.py", VERSION) // ensure that this is a linux machine if s.Ephemeral.ConnInfo["type"] != "ssh" { return fmt.Errorf("Unsupported connection type: %s. This provisioner currently only supports linux", s.Ephemeral.ConnInfo["type"]) } // build a communicator for the provisioner to use comm, err := communicator.New(s) if err != nil { o.Output("erred out here 3") return err } err = retryFunc(comm.Timeout(), func() error { err := comm.Connect(o) return err }) if err != nil { return err } defer comm.Disconnect() err = provisioner.Run(o, comm) if err != nil { o.Output("erred out here 4") return err } return nil }
// Connect implementation of communicator.Communicator interface func (c *Communicator) Connect(o terraform.UIOutput) (err error) { if c.conn != nil { c.conn.Close() } // Set the conn and client to nil since we'll recreate it c.conn = nil c.client = nil if o != nil { o.Output(fmt.Sprintf( "Connecting to remote host via SSH...\n"+ " Host: %s\n"+ " User: %s\n"+ " Password: %t\n"+ " Private key: %t\n"+ " SSH Agent: %t", c.connInfo.Host, c.connInfo.User, c.connInfo.Password != "", c.connInfo.KeyFile != "", c.connInfo.Agent, )) if c.connInfo.BastionHost != "" { o.Output(fmt.Sprintf( "Using configured bastion host...\n"+ " Host: %s\n"+ " User: %s\n"+ " Password: %t\n"+ " Private key: %t\n"+ " SSH Agent: %t", c.connInfo.BastionHost, c.connInfo.BastionUser, c.connInfo.BastionPassword != "", c.connInfo.BastionKeyFile != "", c.connInfo.Agent, )) } } log.Printf("connecting to TCP connection for SSH") c.conn, err = c.config.connection() if err != nil { // Explicitly set this to the REAL nil. Connection() can return // a nil implementation of net.Conn which will make the // "if c.conn == nil" check fail above. Read here for more information // on this psychotic language feature: // // http://golang.org/doc/faq#nil_error c.conn = nil log.Printf("connection error: %s", err) return err } log.Printf("handshaking with SSH") host := fmt.Sprintf("%s:%d", c.connInfo.Host, c.connInfo.Port) sshConn, sshChan, req, err := ssh.NewClientConn(c.conn, host, c.config.config) if err != nil { log.Printf("handshake error: %s", err) return err } c.client = ssh.NewClient(sshConn, sshChan, req) if c.config.sshAgent != nil { log.Printf("[DEBUG] Telling SSH config to foward to agent") if err := c.config.sshAgent.ForwardToAgent(c.client); err != nil { return err } log.Printf("[DEBUG] Setting up a session to request agent forwarding") session, err := c.newSession() if err != nil { return err } defer session.Close() err = agent.RequestAgentForwarding(session) if err == nil { log.Printf("[INFO] agent forwarding enabled") } else { log.Printf("[WARN] error forwarding agent: %s", err) } } if o != nil { o.Output("Connected!") } return err }
// Apply executes the file provisioner func (r *ResourceProvisioner) Apply( o terraform.UIOutput, s *terraform.InstanceState, c *terraform.ResourceConfig) error { // Decode the raw config for this provisioner p, err := r.decodeConfig(c) if err != nil { return err } if p.OSType == "" { switch s.Ephemeral.ConnInfo["type"] { case "ssh", "": // The default connection type is ssh, so if the type is empty assume ssh p.OSType = "linux" case "winrm": p.OSType = "windows" default: return fmt.Errorf("Unsupported connection type: %s", s.Ephemeral.ConnInfo["type"]) } } // Set some values based on the targeted OS switch p.OSType { case "linux": p.cleanupUserKeyCmd = fmt.Sprintf("rm -f %s", path.Join(linuxConfDir, p.UserName+".pem")) p.createConfigFiles = p.linuxCreateConfigFiles p.installChefClient = p.linuxInstallChefClient p.fetchChefCertificates = p.fetchChefCertificatesFunc(linuxKnifeCmd, linuxConfDir) p.generateClientKey = p.generateClientKeyFunc(linuxKnifeCmd, linuxConfDir, linuxNoOutput) p.configureVaults = p.configureVaultsFunc(linuxGemCmd, linuxKnifeCmd, linuxConfDir) p.runChefClient = p.runChefClientFunc(linuxChefCmd, linuxConfDir) p.useSudo = !p.PreventSudo && s.Ephemeral.ConnInfo["user"] != "root" case "windows": p.cleanupUserKeyCmd = fmt.Sprintf("cd %s && del /F /Q %s", windowsConfDir, p.UserName+".pem") p.createConfigFiles = p.windowsCreateConfigFiles p.installChefClient = p.windowsInstallChefClient p.fetchChefCertificates = p.fetchChefCertificatesFunc(windowsKnifeCmd, windowsConfDir) p.generateClientKey = p.generateClientKeyFunc(windowsKnifeCmd, windowsConfDir, windowsNoOutput) p.configureVaults = p.configureVaultsFunc(windowsGemCmd, windowsKnifeCmd, windowsConfDir) p.runChefClient = p.runChefClientFunc(windowsChefCmd, windowsConfDir) p.useSudo = false default: return fmt.Errorf("Unsupported os type: %s", p.OSType) } // Get a new communicator comm, err := communicator.New(s) if err != nil { return err } // Wait and retry until we establish the connection err = retryFunc(comm.Timeout(), func() error { err := comm.Connect(o) return err }) if err != nil { return err } defer comm.Disconnect() // Make sure we always delete the user key from the new node! defer func() { o.Output("Cleanup user key...") if err := p.runCommand(o, comm, p.cleanupUserKeyCmd); err != nil { o.Output("WARNING: Failed to cleanup user key on new node: " + err.Error()) } }() if !p.SkipInstall { if err := p.installChefClient(o, comm); err != nil { return err } } o.Output("Creating configuration files...") if err := p.createConfigFiles(o, comm); err != nil { return err } if p.FetchChefCertificates { o.Output("Fetch Chef certificates...") if err := p.fetchChefCertificates(o, comm); err != nil { return err } } o.Output("Generate the private key...") if err := p.generateClientKey(o, comm); err != nil { return err } if p.VaultJSON != "" { o.Output("Configure Chef vaults...") if err := p.configureVaults(o, comm); err != nil { return err } } o.Output("Starting initial Chef-Client run...") if err := p.runChefClient(o, comm); err != nil { return err } return nil }
// runScripts is used to copy and execute a set of scripts func (p *ResourceProvisioner) runScripts( o terraform.UIOutput, conf *helper.SSHConfig, scripts []io.ReadCloser) error { // Get the SSH client config config, err := helper.PrepareConfig(conf) if err != nil { return err } o.Output(fmt.Sprintf( "Connecting to remote host via SSH...\n"+ " Host: %s\n"+ " User: %s\n"+ " Password: %v\n"+ " Private key: %v", conf.Host, conf.User, conf.Password != "", conf.KeyFile != "")) // Wait and retry until we establish the SSH connection var comm *helper.SSHCommunicator err = retryFunc(conf.TimeoutVal, func() error { host := fmt.Sprintf("%s:%d", conf.Host, conf.Port) comm, err = helper.New(host, config) if err != nil { o.Output(fmt.Sprintf("Connection error, will retry: %s", err)) } return err }) if err != nil { return err } o.Output("Connected! Executing scripts...") for _, script := range scripts { var cmd *helper.RemoteCmd outR, outW := io.Pipe() errR, errW := io.Pipe() outDoneCh := make(chan struct{}) errDoneCh := make(chan struct{}) go p.copyOutput(o, outR, outDoneCh) go p.copyOutput(o, errR, errDoneCh) err := retryFunc(conf.TimeoutVal, func() error { if err := comm.Upload(conf.ScriptPath, script); err != nil { return fmt.Errorf("Failed to upload script: %v", err) } cmd = &helper.RemoteCmd{ Command: fmt.Sprintf("chmod 0777 %s", conf.ScriptPath), } if err := comm.Start(cmd); err != nil { return fmt.Errorf( "Error chmodding script file to 0777 in remote "+ "machine: %s", err) } cmd.Wait() cmd = &helper.RemoteCmd{ Command: conf.ScriptPath, Stdout: outW, Stderr: errW, } if err := comm.Start(cmd); err != nil { return fmt.Errorf("Error starting script: %v", err) } return nil }) if err == nil { cmd.Wait() if cmd.ExitStatus != 0 { err = fmt.Errorf("Script exited with non-zero exit status: %d", cmd.ExitStatus) } } // Wait for output to clean up outW.Close() errW.Close() <-outDoneCh <-errDoneCh // If we have an error, return it out now that we've cleaned up if err != nil { return err } } return nil }
func (p *Provisioner) Run(o terraform.UIOutput, comm communicator.Communicator) error { // parse the playbook path and ensure that it is valid before doing // anything else. This is done in validate but is repeated here, just // in case. playbookPath, err := p.resolvePath(p.Playbook) if err != nil { return err } // commands that are needed to setup a basic environment to run the `ansible-local.py` script // TODO pivot based upon different platforms and allow optional python provision steps // TODO this should be configurable for folks who want to customize this provisionAnsibleCommands := []string{ // https://github.com/hashicorp/terraform/issues/1025 // cloud-init runs on fresh sources and can interfere with apt-get update commands causing intermittent failures "/bin/bash -c 'until [[ -f /var/lib/cloud/instance/boot-finished ]]; do sleep 1; done'", "apt-get update", "apt-get install -y build-essential python-dev", "curl https://bootstrap.pypa.io/get-pip.py | sudo python", "pip install ansible", } for _, command := range provisionAnsibleCommands { o.Output(fmt.Sprintf("running command: %s", command)) err := p.runCommand(o, comm, command) if err != nil { return err } } // ansible projects are structured such that the playbook file is in // the top level of the module path. As such, we parse the playbook // path's directory and upload the entire thing playbookDir := filepath.Dir(playbookPath) // the host playbook path is the path on the host where the playbook // will be uploaded too remotePlaybookPath := filepath.Join("/tmp/ansible", filepath.Base(playbookPath)) // upload ansible source and playbook to the host if err := comm.UploadDir("/tmp/ansible", playbookDir); err != nil { return err } extraVars, err := json.Marshal(p.ExtraVars) if err != nil { return err } // build a command to run ansible on the host machine command := fmt.Sprintf("curl %s | python - --playbook=%s --hosts=%s --plays=%s --groups=%s --extra-vars=%s", p.ansibleLocalScript, remotePlaybookPath, strings.Join(p.Hosts, ","), strings.Join(p.Plays, ","), strings.Join(p.Groups, ","), string(extraVars)) o.Output(fmt.Sprintf("running command: %s", command)) if err := p.runCommand(o, comm, command); err != nil { return err } return nil }