diff --git a/provisioner/ansible-local/provisioner.go b/provisioner/ansible-local/provisioner.go index 96197331..eaa45ab6 100644 --- a/provisioner/ansible-local/provisioner.go +++ b/provisioner/ansible-local/provisioner.go @@ -16,6 +16,7 @@ import ( "strings" "github.com/hashicorp/hcl/v2/hcldec" + ansiblecommon "github.com/hashicorp/packer-plugin-ansible/provisioner/common" "github.com/hashicorp/packer-plugin-sdk/common" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer-plugin-sdk/template/config" @@ -27,8 +28,10 @@ import ( const DefaultStagingDir = "/tmp/packer-provisioner-ansible-local" type Config struct { - common.PackerConfig `mapstructure:",squash"` - ctx interpolate.Context + common.PackerConfig `mapstructure:",squash"` + ansiblecommon.GalaxyConfig `mapstructure:",squash"` + + ctx interpolate.Context // The command to invoke ansible. Defaults to // `ansible-playbook`. If you would like to provide a more complex command, // for example, something that sets up a virtual environment before calling @@ -103,7 +106,6 @@ type Config struct { // An array of local paths of collections to upload. CollectionPaths []string `mapstructure:"collection_paths"` - // The directory where files will be uploaded. Packer requires write // permissions in this directory. StagingDir string `mapstructure:"staging_directory"` @@ -164,7 +166,6 @@ 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 the remote system in which to // install the roles. Adds `--roles-path /path/to/your/roles` to // `ansible-galaxy` command. By default, this will install to a 'galaxy_roles' subfolder in the @@ -485,47 +486,6 @@ func (p *Provisioner) provisionPlaybookFile(ui packersdk.Ui, comm packersdk.Comm return nil } -func (p *Provisioner) executeGalaxy(ui packersdk.Ui, comm packersdk.Communicator) error { - galaxyFile := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.GalaxyFile))) - - // ansible-galaxy install -r requirements.yml - roleArgs := []string{"install", "-r", galaxyFile, "-p", filepath.ToSlash(p.config.GalaxyRolesPath)} - - // 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, "-p", filepath.ToSlash(p.config.GalaxyCollectionsPath)} - - // Add force to arguments - if p.config.GalaxyForceInstall { - roleArgs = append(roleArgs, "-f") - collectionArgs = append(collectionArgs, "-f") - } - - // Search galaxy_file for roles and collections keywords - f, err := ioutil.ReadFile(p.config.GalaxyFile) - if err != nil { - return err - } - hasRoles, _ := regexp.Match(`(?m)^roles:`, f) - hasCollections, _ := regexp.Match(`(?m)^collections:`, f) - - // If if roles keyword present (v2 format), or no collections keyword present (v1), install roles - if hasRoles || !hasCollections { - if roleInstallError := p.invokeGalaxyCommand(roleArgs, ui, comm); roleInstallError != nil { - return roleInstallError - } - } - - // If collections keyword present (v2 format), install collections - if hasCollections { - if collectionInstallError := p.invokeGalaxyCommand(collectionArgs, ui, comm); collectionInstallError != nil { - return collectionInstallError - } - } - - return nil -} - // Intended to be invoked from p.executeGalaxy depending on the Ansible Galaxy parameters passed to Packer func (p *Provisioner) invokeGalaxyCommand(args []string, ui packersdk.Ui, comm packersdk.Communicator) error { ctx := context.TODO() @@ -557,7 +517,19 @@ func (p *Provisioner) executeAnsible(ui packersdk.Ui, comm packersdk.Communicato // Fetch external dependencies if len(p.config.GalaxyFile) > 0 { - if err := p.executeGalaxy(ui, comm); err != nil { + executeConfig := ansiblecommon.GalaxyExectureArgsConfig{ + Filepath: filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.GalaxyFile))), + RolesPath: filepath.ToSlash(p.config.GalaxyRolesPath), + CollectionsPath: filepath.ToSlash(p.config.GalaxyCollectionsPath), + ForceInstall: p.config.GalaxyForceInstall, + } + + args, err := ansiblecommon.BuildGalaxyArgs(executeConfig) + if err != nil { + return fmt.Errorf("Error building Ansible Galaxy: %s", err) + } + + if err := p.invokeGalaxyCommand(args, ui, comm); err != nil { return fmt.Errorf("Error executing Ansible Galaxy: %s", err) } } diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 6f1d70de..a20cee26 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -35,6 +35,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/hashicorp/hcl/v2/hcldec" + ansiblecommon "github.com/hashicorp/packer-plugin-ansible/provisioner/common" "github.com/hashicorp/packer-plugin-sdk/adapter" "github.com/hashicorp/packer-plugin-sdk/common" "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" @@ -45,8 +46,10 @@ import ( ) type Config struct { - common.PackerConfig `mapstructure:",squash"` - ctx interpolate.Context + common.PackerConfig `mapstructure:",squash"` + ansiblecommon.GalaxyConfig `mapstructur:",squash"` + + ctx interpolate.Context // The command to invoke ansible. Defaults to // `ansible-playbook`. If you would like to provide a more complex command, // for example, something that sets up a virtual environment before calling @@ -211,10 +214,6 @@ type Config struct { // Adds `--force` option to `ansible-galaxy` command. By default, this is // `false`. GalaxyForceInstall bool `mapstructure:"galaxy_force_install"` - // Force overwriting an existing role and its dependencies. - // Adds `--force-with-deps` option to `ansible-galaxy` command. By default, - // this is `false`. - GalaxyForceWithDeps bool `mapstructure:"galaxy_force_with_deps"` // 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` @@ -726,11 +725,6 @@ func (p *Provisioner) executeGalaxy(ui packersdk.Ui, comm packersdk.Communicator roleArgs = append(roleArgs, "-f") collectionArgs = append(collectionArgs, "-f") } - // Add --force-with-deps to arguments - if p.config.GalaxyForceWithDeps { - roleArgs = append(roleArgs, "--force-with-deps") - collectionArgs = append(collectionArgs, "--force-with-deps") - } // Add roles_path argument if specified if p.config.RolesPath != "" { @@ -875,7 +869,19 @@ func (p *Provisioner) executeAnsible(ui packersdk.Ui, comm packersdk.Communicato // Fetch external dependencies if len(p.config.GalaxyFile) > 0 { - if err := p.executeGalaxy(ui, comm); err != nil { + executeConfig := ansiblecommon.GalaxyExectureArgsConfig{ + Filepath: filepath.ToSlash(p.config.GalaxyFile), + RolesPath: filepath.ToSlash(p.config.RolesPath), + CollectionsPath: filepath.ToSlash(p.config.CollectionsPath), + ForceInstall: p.config.GalaxyForceInstall, + } + + args, err := ansiblecommon.BuildGalaxyArgs(executeConfig) + if err != nil { + return fmt.Errorf("Error building Ansible Galaxy: %s", err) + } + + if err := p.invokeGalaxyCommand(args, ui, comm); err != nil { return fmt.Errorf("Error executing Ansible Galaxy: %s", err) } } diff --git a/provisioner/common/common.go b/provisioner/common/common.go new file mode 100644 index 00000000..e6930b7c --- /dev/null +++ b/provisioner/common/common.go @@ -0,0 +1,75 @@ +//go:generate packer-sdc struct-markdown +package common + +import ( + "io/ioutil" + "regexp" +) + +type GalaxyConfig struct { + // A requirements file which provides a way to + // 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 + // `ansible-galaxy`. + GalaxyCommand string `mapstructure:"galaxy_command"` + // Force overwriting an existing role. + // 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 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"` +} + +type GalaxyExectureArgsConfig struct { + Filepath string + RolesPath string + CollectionsPath string + ForceInstall bool +} + +func BuildGalaxyArgs(conf GalaxyExectureArgsConfig) ([]string, error) { + + // ansible-galaxy install -r requirements.yml + roleArgs := []string{"install", "-r", conf.Filepath, "-p", conf.RolesPath} + + // 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", conf.Filepath, "-p", conf.CollectionsPath} + + // Add force to arguments + if conf.ForceInstall { + roleArgs = append(roleArgs, "-f") + collectionArgs = append(collectionArgs, "-f") + } + + // Search galaxy_file for roles and collections keywords + f, err := ioutil.ReadFile(conf.Filepath) + if err != nil { + return nil, err + } + hasRoles, _ := regexp.Match(`(?m)^roles:`, f) + hasCollections, _ := regexp.Match(`(?m)^collections:`, f) + + // If if roles keyword present (v2 format), or no collections keyword present (v1), install roles + if hasRoles || !hasCollections { + return roleArgs, nil + } + + // If collections keyword present (v2 format), install collections + if hasCollections { + return collectionArgs, nil + } + + return nil, nil +}