diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index a9bcdd344..c6d0d6f1c 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -162,8 +162,8 @@ type Config struct { // test your playbook. this option is not used if you set an `inventory_file`. KeepInventoryFile bool `mapstructure:"keep_inventory_file"` // 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) + // install roles or collections with the [ansible-galaxy + // cli](https://docs.ansible.com/ansible/latest/galaxy/user_guide.html#the-ansible-galaxy-command-line-tool) // on the local machine before executing `ansible-playbook`. By default, this is empty. GalaxyFile string `mapstructure:"galaxy_file"` // The command to invoke ansible-galaxy. By default, this is @@ -173,11 +173,16 @@ type Config struct { // Adds `--force` option to `ansible-galaxy` command. By default, this is // `false`. GalaxyForceInstall bool `mapstructure:"galaxy_force_install"` - // The path to the directory on your local system to - // install the roles in. Adds `--roles-path /path/to/your/roles` to + // The path to the directory on your local system in which to + // install the roles. 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. RolesPath string `mapstructure:"roles_path"` + // The path to the directory on your local system in which to + // install the collections. Adds `--collections-path /path/to/your/collections` to + // `ansible-galaxy` command. By default, this is empty, and thus `--collections-path` + // option is not added to the command. + CollectionsPath string `mapstructure:"collections_path"` // When `true`, set up a localhost proxy adapter // so that Ansible has an IP address to connect to, even if your guest does not // have an IP address. For example, the adapter is necessary for Docker builds @@ -624,16 +629,38 @@ func (p *Provisioner) executeGalaxy(ui packer.Ui, comm packer.Communicator) erro galaxyFile := filepath.ToSlash(p.config.GalaxyFile) // ansible-galaxy install -r requirements.yml - args := []string{"install", "-r", galaxyFile} + roleArgs := []string{"install", "-r", galaxyFile} + // Instead of modifying args depending on config values and removing or modifying values from + // the slice between role and collection installs, just use 2 slices and simplify everything + collectionArgs := []string{"collection", "install", "-r", galaxyFile} // Add force to arguments if p.config.GalaxyForceInstall { - args = append(args, "-f") + roleArgs = append(roleArgs, "-f") + collectionArgs = append(collectionArgs, "-f") } + // Add roles_path argument if specified if p.config.RolesPath != "" { - args = append(args, "-p", filepath.ToSlash(p.config.RolesPath)) + roleArgs = append(roleArgs, "-p", filepath.ToSlash(p.config.RolesPath)) + } + // Add collections_path argument if specified + if p.config.CollectionsPath != "" { + collectionArgs = append(collectionArgs, "-p", filepath.ToSlash(p.config.CollectionsPath)) } + roleInstallError := p.invokeGalaxyCommand(roleArgs, ui, comm) + // Return the error if the role installation failed before attempting the collection install + if roleInstallError != nil { + return roleInstallError + } + // If all is well, proceed with collection install + // This variable isn't strictly necessary but including for readability to match the role installation + collectionInstallError := p.invokeGalaxyCommand(collectionArgs, ui, comm) + return collectionInstallError +} + +// Intended to be invoked from p.executeGalaxy depending on the Ansible Galaxy parameters passed to Packer +func (p *Provisioner) invokeGalaxyCommand(args []string, ui packer.Ui, comm packer.Communicator) error { ui.Message(fmt.Sprintf("Executing Ansible Galaxy")) cmd := exec.Command(p.config.GalaxyCommand, args...) diff --git a/provisioner/ansible/provisioner.hcl2spec.go b/provisioner/ansible/provisioner.hcl2spec.go index 91b7fce71..61217c677 100644 --- a/provisioner/ansible/provisioner.hcl2spec.go +++ b/provisioner/ansible/provisioner.hcl2spec.go @@ -39,6 +39,7 @@ type FlatConfig struct { GalaxyCommand *string `mapstructure:"galaxy_command" cty:"galaxy_command" hcl:"galaxy_command"` GalaxyForceInstall *bool `mapstructure:"galaxy_force_install" cty:"galaxy_force_install" hcl:"galaxy_force_install"` RolesPath *string `mapstructure:"roles_path" cty:"roles_path" hcl:"roles_path"` + CollectionsPath *string `mapstructure:"collections_path" cty:"collections_path" hcl:"collections_path"` UseProxy *bool `mapstructure:"use_proxy" cty:"use_proxy" hcl:"use_proxy"` } @@ -84,6 +85,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "galaxy_command": &hcldec.AttrSpec{Name: "galaxy_command", Type: cty.String, Required: false}, "galaxy_force_install": &hcldec.AttrSpec{Name: "galaxy_force_install", Type: cty.Bool, Required: false}, "roles_path": &hcldec.AttrSpec{Name: "roles_path", Type: cty.String, Required: false}, + "collections_path": &hcldec.AttrSpec{Name: "collections_path", Type: cty.String, Required: false}, "use_proxy": &hcldec.AttrSpec{Name: "use_proxy", Type: cty.Bool, Required: false}, } return s diff --git a/test/fixtures/provisioner-ansible/galaxy-playbook.yml b/test/fixtures/provisioner-ansible/galaxy-playbook.yml new file mode 100644 index 000000000..b91a3dab4 --- /dev/null +++ b/test/fixtures/provisioner-ansible/galaxy-playbook.yml @@ -0,0 +1,33 @@ +--- +- hosts: default:packer-test + gather_facts: no + collections: + - artis3n.github + tasks: + - name: touch + raw: touch /tmp/ansible-raw-test + - name: raw test + raw: date + - name: command test + command: echo "the command module" + - name: prepare remote directory + command: mkdir /tmp/remote-dir + args: + creates: /tmp/remote-dir + - name: transfer file.txt + copy: src=dir/file.txt dest=/tmp/remote-dir/file.txt + - name: fetch file.text + fetch: src=/tmp/remote-dir/file.txt dest=fetched-dir validate=yes fail_on_missing=yes + - name: copy contents of directory + copy: src=dir/contents-only/ dest=/tmp/remote-dir + - name: fetch contents of directory + fetch: src=/tmp/remote-dir/file.txt dest="fetched-dir/{{ inventory_hostname }}/tmp/remote-dir/contents-only/" flat=yes validate=yes fail_on_missing=yes + - name: copy directory recursively + copy: src=dir/subdir dest=/tmp/remote-dir + - name: fetch recursively copied directory + fetch: src=/tmp/remote-dir/subdir/file.txt dest=fetched-dir validate=yes fail_on_missing=yes + - copy: src=largish-file.txt dest=/tmp/largish-file.txt + - name: test collection - fetch latest repo version + set_fact: + # Ansible will fail if collection is not installed + packer_version: "{{ lookup('artis3n.github.latest_release', 'hashicorp/packer' }}" diff --git a/test/fixtures/provisioner-ansible/galaxy.json b/test/fixtures/provisioner-ansible/galaxy.json new file mode 100644 index 000000000..879c925f6 --- /dev/null +++ b/test/fixtures/provisioner-ansible/galaxy.json @@ -0,0 +1,21 @@ +{ + "variables": {}, + "provisioners": [ + { + "type": "ansible", + "playbook_file": "./galaxy-playbook.yml", + "galaxy_file": "./requirements.yml" + } + ], + "builders": [ + { + "type": "googlecompute", + "account_file": "{{user `account_file`}}", + "project_id": "{{user `project_id`}}", + "image_name": "packerbats-galaxy-{{timestamp}}", + "source_image": "debian-8-jessie-v20161027", + "zone": "us-central1-a", + "ssh_username": "debian" + } + ] +} diff --git a/test/fixtures/provisioner-ansible/requirements.yml b/test/fixtures/provisioner-ansible/requirements.yml new file mode 100644 index 000000000..4658d9204 --- /dev/null +++ b/test/fixtures/provisioner-ansible/requirements.yml @@ -0,0 +1,2 @@ +collections: + - name: artis3n.github diff --git a/test/provisioner_ansible.bats b/test/provisioner_ansible.bats index c9dc9abd9..2cc45b522 100755 --- a/test/provisioner_ansible.bats +++ b/test/provisioner_ansible.bats @@ -65,6 +65,14 @@ teardown() { diff -r dir fetched-dir/packer-test/tmp/remote-dir > /dev/null } +@test "ansible provisioner: build galaxy.json" { + cd $FIXTURE_ROOT + run packer build ${USER_VARS} $FIXTURE_ROOT/galaxy.json + [ "$status" -eq 0 ] + [ "$(gc_has_image "packerbats-galaxy")" -eq 1 ] + diff -r dir fetched-dir/default/tmp/remote-dir > /dev/null +} + @test "ansible provisioner: build scp.json" { cd $FIXTURE_ROOT run packer build ${USER_VARS} $FIXTURE_ROOT/scp.json diff --git a/website/pages/partials/provisioner/ansible/Config-not-required.mdx b/website/pages/partials/provisioner/ansible/Config-not-required.mdx index 751649e99..99d0df0f5 100644 --- a/website/pages/partials/provisioner/ansible/Config-not-required.mdx +++ b/website/pages/partials/provisioner/ansible/Config-not-required.mdx @@ -120,8 +120,8 @@ test your playbook. this option is not used if you set an `inventory_file`. - `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) + install roles or collections with the [ansible-galaxy + cli](https://docs.ansible.com/ansible/latest/galaxy/user_guide.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 @@ -131,11 +131,16 @@ Adds `--force` option to `ansible-galaxy` command. By default, this is `false`. -- `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 +- `roles_path` (string) - The path to the directory on your local system in which to + install the roles. 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. +- `collections_path` (string) - The path to the directory on your local system in which to + install the collections. Adds `--collections-path /path/to/your/collections` to + `ansible-galaxy` command. By default, this is empty, and thus `--collections-path` + option is not added to the command. + - `use_proxy` (boolean) - When `true`, set up a localhost proxy adapter so that Ansible has an IP address to connect to, even if your guest does not have an IP address. For example, the adapter is necessary for Docker builds