diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 6aa0b07e2..d61d4d44e 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -61,6 +61,10 @@ type Config struct { UseSFTP bool `mapstructure:"use_sftp"` InventoryDirectory string `mapstructure:"inventory_directory"` InventoryFile string `mapstructure:"inventory_file"` + GalaxyFile string `mapstructure:"galaxy_file"` + GalaxyCommand string `mapstructure:"galaxy_command"` + GalaxyForceInstall bool `mapstructure:"galaxy_force_install"` + RolesPath string `mapstructure:"roles_path"` } type Provisioner struct { @@ -100,6 +104,10 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.config.Command = "ansible-playbook" } + if p.config.GalaxyCommand == "" { + p.config.GalaxyCommand = "ansible-galaxy" + } + if p.config.HostAlias == "" { p.config.HostAlias = "default" } @@ -110,6 +118,14 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { errs = packer.MultiErrorAppend(errs, err) } + // Check that the galaxy file exists, if configured + if len(p.config.GalaxyFile) > 0 { + err = validateFileConfig(p.config.GalaxyFile, "galaxy_file", true) + if err != nil { + errs = packer.MultiErrorAppend(errs, err) + } + } + // Check that the authorized key file exists if len(p.config.SSHAuthorizedKeyFile) > 0 { err = validateFileConfig(p.config.SSHAuthorizedKeyFile, "ssh_authorized_key_file", true) @@ -343,12 +359,78 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C return nil } +func (p *Provisioner) executeGalaxy(ui packer.Ui, comm packer.Communicator) error { + galaxyFile := filepath.ToSlash(p.config.GalaxyFile) + + // ansible-galaxy install -r requirements.yml + args := []string{"install", "-r", galaxyFile} + // Add force to arguments + if p.config.GalaxyForceInstall { + args = append(args, "-f") + } + // Add roles_path argument if specified + if p.config.RolesPath != "" { + args = append(args, "-p", filepath.ToSlash(p.config.RolesPath)) + } + + ui.Message(fmt.Sprintf("Executing Ansible Galaxy")) + cmd := exec.Command(p.config.GalaxyCommand, args...) + + stdout, err := cmd.StdoutPipe() + if err != nil { + return err + } + stderr, err := cmd.StderrPipe() + if err != nil { + return err + } + wg := sync.WaitGroup{} + repeat := func(r io.ReadCloser) { + reader := bufio.NewReader(r) + for { + line, err := reader.ReadString('\n') + if line != "" { + line = strings.TrimRightFunc(line, unicode.IsSpace) + ui.Message(line) + } + if err != nil { + if err == io.EOF { + break + } else { + ui.Error(err.Error()) + break + } + } + } + wg.Done() + } + wg.Add(2) + go repeat(stdout) + go repeat(stderr) + + if err := cmd.Start(); err != nil { + return err + } + wg.Wait() + err = cmd.Wait() + if err != nil { + return fmt.Errorf("Non-zero exit status: %s", err) + } + return nil +} + func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator, privKeyFile string) error { playbook, _ := filepath.Abs(p.config.PlaybookFile) inventory := p.config.InventoryFile var envvars []string + // Fetch external dependencies + if len(p.config.GalaxyFile) > 0 { + if err := p.executeGalaxy(ui, comm); err != nil { + return fmt.Errorf("Error executing Ansible Galaxy: %s", err) + } + } args := []string{"--extra-vars", fmt.Sprintf("packer_build_name=%s packer_builder_type=%s -o IdentitiesOnly=yes", p.config.PackerBuildName, p.config.PackerBuilderType), "-i", inventory, playbook} diff --git a/website/source/docs/provisioners/ansible.html.md.erb b/website/source/docs/provisioners/ansible.html.md.erb index 0e98a7601..e212080f1 100644 --- a/website/source/docs/provisioners/ansible.html.md.erb +++ b/website/source/docs/provisioners/ansible.html.md.erb @@ -96,6 +96,17 @@ Optional Parameters: ] ``` +- `galaxy_file` (string) - A requirements file which provides a way to + install roles with the [ansible-galaxy + cli](http://docs.ansible.com/ansible/galaxy.html#the-ansible-galaxy-command-line-tool) + on the local machine before executing `ansible-playbook`. By default, this is empty. + +- `galaxy_command` (string) - The command to invoke ansible-galaxy. By + default, this is `ansible-galaxy`. + +- `galaxy_force_install` (string) - Force overwriting an existing role. + Adds `--force` option to `ansible-galaxy` command. By default, this is empty. + - `groups` (array of strings) - The groups into which the Ansible host should be placed. When unspecified, the host is not associated with any groups. @@ -121,6 +132,11 @@ Optional Parameters: `local_port`. A system-chosen port is used when `local_port` is missing or empty. +- `roles_path` (string) - The path to the directory on your local system to + install the roles in. Adds `--roles-path /path/to/your/roles` to + `ansible-galaxy` command. By default, this is empty, and thus `--roles-path` + option is not added to the command. + - `sftp_command` (string) - The command to run on the machine being provisioned by Packer to handle the SFTP protocol that Ansible will use to transfer files. The command should read and write on stdin and stdout,