diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 6aa0b07e2..9640e1b05 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"` + RolesDir string `mapstructure:"roles_dir"` } type Provisioner struct { @@ -100,6 +104,14 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.config.Command = "ansible-playbook" } + if p.config.GalaxyCommand == "" { + p.config.GalaxyCommand = "ansible-galaxy" + } + + if p.config.RolesDir == "" { + p.config.RolesDir = "~/.ansible/roles" + } + if p.config.HostAlias == "" { p.config.HostAlias = "default" } @@ -110,6 +122,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 +363,75 @@ 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 { + rolesDir := filepath.ToSlash(p.config.RolesDir) + galaxyFile := filepath.ToSlash(p.config.GalaxyFile) + + // ansible-galaxy install -r requirements.yml -p roles/ + args := []string{"install", "-r", galaxyFile, "-p", rolesDir} + // Add force to arguments + if p.config.GalaxyForceInstall { + args = append(args, "-f") + } + + 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}