From 609e70face95d677bd01987a8b1ec0ea0c17104d Mon Sep 17 00:00:00 2001 From: Dan Fuchs Date: Tue, 4 Apr 2017 16:43:46 -0500 Subject: [PATCH 1/6] inventory_directory option for ansible provisioner Add an `inventory_directory` setting to the Ansible provisioner that allows a user to specify a directory in which the Packer Ansible provisioner would write the generated inventory file. If a value is specified for this setting, then have the Packer Ansible provisioner pass this directory as the -i arg when it calls ansible. This would allow an Ansible playbook used by the Packer Ansible provisioner to use variables specified in `host_vars` and `group_vars` in this inventory directory. --- provisioner/ansible/provisioner.go | 12 +++- provisioner/ansible/provisioner_test.go | 55 +++++++++++++++++++ .../source/docs/provisioners/ansible.html.md | 9 +++ 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 0577c58f3..64ad08f9c 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -53,6 +53,7 @@ type Config struct { SSHAuthorizedKeyFile string `mapstructure:"ssh_authorized_key_file"` SFTPCmd string `mapstructure:"sftp_command"` UseSFTP bool `mapstructure:"use_sftp"` + InventoryDirectory string `mapstructure:"inventory_directory"` inventoryFile string } @@ -249,7 +250,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { go p.adapter.Serve() if len(p.config.inventoryFile) == 0 { - tf, err := ioutil.TempFile("", "packer-provisioner-ansible") + tf, err := ioutil.TempFile(p.config.InventoryDirectory, "packer-provisioner-ansible") if err != nil { return fmt.Errorf("Error preparing inventory file: %s", err) } @@ -300,9 +301,16 @@ func (p *Provisioner) Cancel() { os.Exit(0) } +func (p *Provisioner) getInventoryArg() string { + if len(p.config.InventoryDirectory) != 0 { + return p.config.InventoryDirectory + } + return p.config.inventoryFile +} + func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator, privKeyFile string) error { playbook, _ := filepath.Abs(p.config.PlaybookFile) - inventory := p.config.inventoryFile + inventory := p.getInventoryArg() var envvars []string args := []string{playbook, "-i", inventory} diff --git a/provisioner/ansible/provisioner_test.go b/provisioner/ansible/provisioner_test.go index 13042c5e1..60098ac32 100644 --- a/provisioner/ansible/provisioner_test.go +++ b/provisioner/ansible/provisioner_test.go @@ -76,6 +76,12 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { if err != nil { t.Fatalf("err: %s", err) } + + config["inventory_directory"] = "some_directory" + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } } func TestProvisionerPrepare_PlaybookFile(t *testing.T) { @@ -246,6 +252,55 @@ func TestProvisionerPrepare_LocalPort(t *testing.T) { } } +func TestProvisioner_getInventoryArg(t *testing.T) { + var p Provisioner + config := testConfig(t) + defer os.Remove(config["command"].(string)) + + hostkey_file, err := ioutil.TempFile("", "hostkey") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(hostkey_file.Name()) + + publickey_file, err := ioutil.TempFile("", "publickey") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(publickey_file.Name()) + + playbook_file, err := ioutil.TempFile("", "playbook") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(playbook_file.Name()) + + config["ssh_host_key_file"] = hostkey_file.Name() + config["ssh_authorized_key_file"] = publickey_file.Name() + config["playbook_file"] = playbook_file.Name() + + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + p.config.inventoryFile = "somefile" + if p.getInventoryArg() != "somefile" { + t.Fatal("getInventoryArg should return inventoryFile if InventoryDirectory is not set.") + } + + // Uses InventoryDirectory if set + config["inventory_directory"] = "somedirectory" + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if p.getInventoryArg() != "somedirectory" { + t.Fatal("getInventoryArg should return InventoryDirectory if InventoryDirectory is set.") + } +} + func TestAnsibleGetVersion(t *testing.T) { if os.Getenv("PACKER_ACC") == "" { t.Skip("This test is only run with PACKER_ACC=1 and it requires Ansible to be installed") diff --git a/website/source/docs/provisioners/ansible.html.md b/website/source/docs/provisioners/ansible.html.md index 316263538..61f263fc5 100644 --- a/website/source/docs/provisioners/ansible.html.md +++ b/website/source/docs/provisioners/ansible.html.md @@ -113,6 +113,15 @@ Optional Parameters: - `user` (string) - The `ansible_user` to use. Defaults to the user running packer. +- `inventory_directory` (string) - The directory in which to place the + generated ansible inventory file. If this is specified, then this directory + is what will get passed to the `-i` argument of the `ansible` command when + this provisioner runs Ansible. By default, the generated ansible inventory + will be placed in a temporary file in the system-specific temporary file + location, and the fully-qualified name of this temporary file will be passed + to the `-i` argument of the `ansible` command when this provisioner runs + ansible. + ## Limitations ### Redhat / CentOS From 492bff474bed1f19fb84e31c2fcee2c715ae8bd1 Mon Sep 17 00:00:00 2001 From: Dan Fuchs Date: Tue, 4 Apr 2017 22:04:26 -0500 Subject: [PATCH 2/6] Ansible provisioner: Specify full path to generated inventory file --- provisioner/ansible/provisioner.go | 9 +---- provisioner/ansible/provisioner_test.go | 49 ------------------------- 2 files changed, 1 insertion(+), 57 deletions(-) diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 64ad08f9c..156870747 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -301,16 +301,9 @@ func (p *Provisioner) Cancel() { os.Exit(0) } -func (p *Provisioner) getInventoryArg() string { - if len(p.config.InventoryDirectory) != 0 { - return p.config.InventoryDirectory - } - return p.config.inventoryFile -} - func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator, privKeyFile string) error { playbook, _ := filepath.Abs(p.config.PlaybookFile) - inventory := p.getInventoryArg() + inventory := p.config.inventoryFile var envvars []string args := []string{playbook, "-i", inventory} diff --git a/provisioner/ansible/provisioner_test.go b/provisioner/ansible/provisioner_test.go index 60098ac32..cd9a7e95f 100644 --- a/provisioner/ansible/provisioner_test.go +++ b/provisioner/ansible/provisioner_test.go @@ -252,55 +252,6 @@ func TestProvisionerPrepare_LocalPort(t *testing.T) { } } -func TestProvisioner_getInventoryArg(t *testing.T) { - var p Provisioner - config := testConfig(t) - defer os.Remove(config["command"].(string)) - - hostkey_file, err := ioutil.TempFile("", "hostkey") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(hostkey_file.Name()) - - publickey_file, err := ioutil.TempFile("", "publickey") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(publickey_file.Name()) - - playbook_file, err := ioutil.TempFile("", "playbook") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(playbook_file.Name()) - - config["ssh_host_key_file"] = hostkey_file.Name() - config["ssh_authorized_key_file"] = publickey_file.Name() - config["playbook_file"] = playbook_file.Name() - - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - p.config.inventoryFile = "somefile" - if p.getInventoryArg() != "somefile" { - t.Fatal("getInventoryArg should return inventoryFile if InventoryDirectory is not set.") - } - - // Uses InventoryDirectory if set - config["inventory_directory"] = "somedirectory" - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - if p.getInventoryArg() != "somedirectory" { - t.Fatal("getInventoryArg should return InventoryDirectory if InventoryDirectory is set.") - } -} - func TestAnsibleGetVersion(t *testing.T) { if os.Getenv("PACKER_ACC") == "" { t.Skip("This test is only run with PACKER_ACC=1 and it requires Ansible to be installed") From 3fc809c05b4bba1c23dffb6ea11bd6090d108da3 Mon Sep 17 00:00:00 2001 From: Dan Fuchs Date: Tue, 4 Apr 2017 22:49:24 -0500 Subject: [PATCH 3/6] Ansible provisioner: check if inventory directory exists during prepare --- provisioner/ansible/provisioner.go | 23 +++++++++++ provisioner/ansible/provisioner_test.go | 52 ++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 156870747..5cf921740 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -124,6 +124,14 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.config.LocalPort = "0" } + if len(p.config.InventoryDirectory) > 0 { + err = validateDirectoryConfig(p.config.InventoryDirectory, "inventory_directory", true) + if err != nil { + log.Println(p.config.InventoryDirectory, "does not exist") + errs = packer.MultiErrorAppend(errs, err) + } + } + err = p.getVersion() if err != nil { errs = packer.MultiErrorAppend(errs, err) @@ -383,6 +391,21 @@ func validateFileConfig(name string, config string, req bool) error { return nil } +func validateDirectoryConfig(name string, config string, req bool) error { + if req { + if name == "" { + return fmt.Errorf("%s must be specified.", config) + } + } + info, err := os.Stat(name) + if err != nil { + return fmt.Errorf("%s: %s is invalid: %s", config, name, err) + } else if !info.IsDir() { + return fmt.Errorf("%s: %s must point to a directory", config, name) + } + return nil +} + type userKey struct { ssh.PublicKey privKeyFile string diff --git a/provisioner/ansible/provisioner_test.go b/provisioner/ansible/provisioner_test.go index cd9a7e95f..5882af346 100644 --- a/provisioner/ansible/provisioner_test.go +++ b/provisioner/ansible/provisioner_test.go @@ -76,12 +76,6 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { if err != nil { t.Fatalf("err: %s", err) } - - config["inventory_directory"] = "some_directory" - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } } func TestProvisionerPrepare_PlaybookFile(t *testing.T) { @@ -252,6 +246,52 @@ func TestProvisionerPrepare_LocalPort(t *testing.T) { } } +func TestProvisionerPrepare_InventoryDirectory(t *testing.T) { + var p Provisioner + config := testConfig(t) + defer os.Remove(config["command"].(string)) + + hostkey_file, err := ioutil.TempFile("", "hostkey") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(hostkey_file.Name()) + + publickey_file, err := ioutil.TempFile("", "publickey") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(publickey_file.Name()) + + playbook_file, err := ioutil.TempFile("", "playbook") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(playbook_file.Name()) + + config["ssh_host_key_file"] = hostkey_file.Name() + config["ssh_authorized_key_file"] = publickey_file.Name() + config["playbook_file"] = playbook_file.Name() + + config["inventory_directory"] = "doesnotexist" + err = p.Prepare(config) + if err == nil { + t.Errorf("should error if inventory_directory does not exist") + } + + inventoryDirectory, err := ioutil.TempDir("", "some_inventory_dir") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(inventoryDirectory) + + config["inventory_directory"] = inventoryDirectory + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } +} + func TestAnsibleGetVersion(t *testing.T) { if os.Getenv("PACKER_ACC") == "" { t.Skip("This test is only run with PACKER_ACC=1 and it requires Ansible to be installed") From 50637a68bfa406d02e5cd665d710189777c9da9a Mon Sep 17 00:00:00 2001 From: Dan Fuchs Date: Tue, 4 Apr 2017 22:58:46 -0500 Subject: [PATCH 4/6] Clarify `inventory_directory` in ansible provisioner docs --- website/source/docs/provisioners/ansible.html.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/website/source/docs/provisioners/ansible.html.md b/website/source/docs/provisioners/ansible.html.md index 61f263fc5..7a3909b9b 100644 --- a/website/source/docs/provisioners/ansible.html.md +++ b/website/source/docs/provisioners/ansible.html.md @@ -114,13 +114,12 @@ Optional Parameters: packer. - `inventory_directory` (string) - The directory in which to place the - generated ansible inventory file. If this is specified, then this directory - is what will get passed to the `-i` argument of the `ansible` command when - this provisioner runs Ansible. By default, the generated ansible inventory - will be placed in a temporary file in the system-specific temporary file - location, and the fully-qualified name of this temporary file will be passed - to the `-i` argument of the `ansible` command when this provisioner runs - ansible. + temporary generated Ansible inventory file. By default, this is the + system-specific temporary file location. The fully-qualified name of this + temporary file will be passed to the `-i` argument of the `ansible` command + when this provisioner runs ansible. Specify this if you have an existing + inventory directory with `host_vars` `group_vars` that you would like to use + in the playbook that this provisioner will run. ## Limitations From 39a9eac44f47e212065851b650feb8aa55eb277e Mon Sep 17 00:00:00 2001 From: Dan Fuchs Date: Wed, 5 Apr 2017 09:14:30 -0500 Subject: [PATCH 5/6] Remove `req` param from validateDirectoryConfig --- provisioner/ansible/provisioner.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 5cf921740..337a83fa8 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -125,7 +125,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if len(p.config.InventoryDirectory) > 0 { - err = validateDirectoryConfig(p.config.InventoryDirectory, "inventory_directory", true) + err = validateDirectoryConfig(p.config.InventoryDirectory, "inventory_directory") if err != nil { log.Println(p.config.InventoryDirectory, "does not exist") errs = packer.MultiErrorAppend(errs, err) @@ -391,12 +391,7 @@ func validateFileConfig(name string, config string, req bool) error { return nil } -func validateDirectoryConfig(name string, config string, req bool) error { - if req { - if name == "" { - return fmt.Errorf("%s must be specified.", config) - } - } +func validateDirectoryConfig(name string, config string) error { info, err := os.Stat(name) if err != nil { return fmt.Errorf("%s: %s is invalid: %s", config, name, err) From f45f4568dbfb7ffdad7eed608eb19369353ecf1c Mon Sep 17 00:00:00 2001 From: Dan Fuchs Date: Thu, 6 Apr 2017 14:50:02 -0500 Subject: [PATCH 6/6] make validateInventoryDirectoryConfig function --- provisioner/ansible/provisioner.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 337a83fa8..5623920cc 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -125,7 +125,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if len(p.config.InventoryDirectory) > 0 { - err = validateDirectoryConfig(p.config.InventoryDirectory, "inventory_directory") + err = validateInventoryDirectoryConfig(p.config.InventoryDirectory) if err != nil { log.Println(p.config.InventoryDirectory, "does not exist") errs = packer.MultiErrorAppend(errs, err) @@ -391,12 +391,12 @@ func validateFileConfig(name string, config string, req bool) error { return nil } -func validateDirectoryConfig(name string, config string) error { +func validateInventoryDirectoryConfig(name string) error { info, err := os.Stat(name) if err != nil { - return fmt.Errorf("%s: %s is invalid: %s", config, name, err) + return fmt.Errorf("inventory_directory: %s is invalid: %s", name, err) } else if !info.IsDir() { - return fmt.Errorf("%s: %s must point to a directory", config, name) + return fmt.Errorf("inventory_directory: %s must point to a directory", name) } return nil }