// copyFiles is used to copy the files from a source to a destination func (p *ResourceProvisioner) copyFiles(comm communicator.Communicator, src, dst string) error { // Wait and retry until we establish the connection err := retryFunc(comm.Timeout(), func() error { err := comm.Connect(nil) return err }) if err != nil { return err } defer comm.Disconnect() info, err := os.Stat(src) if err != nil { return err } // If we're uploading a directory, short circuit and do that if info.IsDir() { if err := comm.UploadDir(dst, src); err != nil { return fmt.Errorf("Upload failed: %v", err) } return nil } // We're uploading a file... f, err := os.Open(src) if err != nil { return err } defer f.Close() err = comm.Upload(dst, f) if err != nil { return fmt.Errorf("Upload failed: %v", err) } return err }
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 }