From ceb96d061a00bae13eb4e34bfd37dc2c7f4cbde7 Mon Sep 17 00:00:00 2001 From: Wilken Rivera Date: Fri, 16 Apr 2021 10:31:09 -0400 Subject: [PATCH] Extract ansible plugins (#10912) * Remove ansible components and docs * Vendored packer-plugin-ansible * Add remote ansible docs --- command/plugin.go | 4 - command/vendored_plugins.go | 7 +- go.mod | 1 + go.sum | 3 + provisioner/ansible-local/provisioner_test.go | 497 ----------- .../ansible-local/test-fixtures/hello.yml | 5 - .../ansible-local/test-fixtures/world.yml | 5 - provisioner/ansible-local/version/version.go | 13 - .../connection-plugin/2.4.x/packer.py | 186 ---- .../connection-plugin/2.5.x/packer.py | 204 ----- .../connection-plugin/2.6.x/packer.py | 218 ----- provisioner/ansible/provisioner_test.go | 781 ----------------- provisioner/ansible/test-fixtures/exit1 | 3 - .../test-fixtures/long-debug-message.yml | 8 - provisioner/ansible/version/version.go | 13 - .../provisioner-ansible/all_options.json | 34 - .../connection_plugins/packer.py | 16 - .../dir/contents-only/file.txt | 1 - .../fixtures/provisioner-ansible/dir/file.txt | 1 - .../provisioner-ansible/dir/subdir/file.txt | 1 - test/fixtures/provisioner-ansible/docker.json | 26 - .../provisioner-ansible/galaxy-playbook.yml | 33 - test/fixtures/provisioner-ansible/galaxy.json | 21 - .../provisioner-ansible/largish-file.txt | Bin 66556 -> 0 bytes .../fixtures/provisioner-ansible/minimal.json | 20 - .../fixtures/provisioner-ansible/playbook.yml | 27 - .../provisioner-ansible/requirements.yml | 2 - .../provisioner-ansible/scp-to-sftp.json | 25 - test/fixtures/provisioner-ansible/scp.json | 24 - test/fixtures/provisioner-ansible/sftp.json | 22 - .../provisioner-ansible/win-playbook.yml | 17 - test/fixtures/provisioner-ansible/winrm.json | 31 - test/provisioner_ansible.bats | 107 --- .../hashicorp/packer-plugin-ansible/LICENSE | 373 ++++++++ .../ansible-local/communicator_mock.go | 1 + .../provisioner}/ansible-local/provisioner.go | 0 .../ansible-local/provisioner.hcl2spec.go | 0 .../docker_playbookdir_template.pkr.hcl | 31 + .../docker_playbookfiles_template.pkr.hcl | 30 + .../provisioner}/ansible/mock_ansible.go | 0 .../provisioner}/ansible/provisioner.go | 6 +- .../ansible/provisioner.hcl2spec.go | 0 .../docker_playbookfile_template.pkr.hcl | 23 + vendor/modules.txt | 4 + .../docs/provisioners/ansible-local.mdx | 136 --- website/content/docs/provisioners/ansible.mdx | 805 ------------------ .../ansible-local/Config-not-required.mdx | 124 --- .../ansible/Config-not-required.mdx | 167 ---- .../provisioner/ansible/Config-required.mdx | 5 - website/data/docs-nav-data.json | 8 - website/data/docs-remote-plugins.json | 6 + 51 files changed, 482 insertions(+), 3593 deletions(-) delete mode 100644 provisioner/ansible-local/provisioner_test.go delete mode 100644 provisioner/ansible-local/test-fixtures/hello.yml delete mode 100644 provisioner/ansible-local/test-fixtures/world.yml delete mode 100644 provisioner/ansible-local/version/version.go delete mode 100644 provisioner/ansible/examples/connection-plugin/2.4.x/packer.py delete mode 100644 provisioner/ansible/examples/connection-plugin/2.5.x/packer.py delete mode 100644 provisioner/ansible/examples/connection-plugin/2.6.x/packer.py delete mode 100644 provisioner/ansible/provisioner_test.go delete mode 100755 provisioner/ansible/test-fixtures/exit1 delete mode 100644 provisioner/ansible/test-fixtures/long-debug-message.yml delete mode 100644 provisioner/ansible/version/version.go delete mode 100644 test/fixtures/provisioner-ansible/all_options.json delete mode 100644 test/fixtures/provisioner-ansible/connection_plugins/packer.py delete mode 100644 test/fixtures/provisioner-ansible/dir/contents-only/file.txt delete mode 100644 test/fixtures/provisioner-ansible/dir/file.txt delete mode 100644 test/fixtures/provisioner-ansible/dir/subdir/file.txt delete mode 100644 test/fixtures/provisioner-ansible/docker.json delete mode 100644 test/fixtures/provisioner-ansible/galaxy-playbook.yml delete mode 100644 test/fixtures/provisioner-ansible/galaxy.json delete mode 100644 test/fixtures/provisioner-ansible/largish-file.txt delete mode 100644 test/fixtures/provisioner-ansible/minimal.json delete mode 100644 test/fixtures/provisioner-ansible/playbook.yml delete mode 100644 test/fixtures/provisioner-ansible/requirements.yml delete mode 100644 test/fixtures/provisioner-ansible/scp-to-sftp.json delete mode 100644 test/fixtures/provisioner-ansible/scp.json delete mode 100644 test/fixtures/provisioner-ansible/sftp.json delete mode 100644 test/fixtures/provisioner-ansible/win-playbook.yml delete mode 100644 test/fixtures/provisioner-ansible/winrm.json delete mode 100755 test/provisioner_ansible.bats create mode 100644 vendor/github.com/hashicorp/packer-plugin-ansible/LICENSE rename {provisioner => vendor/github.com/hashicorp/packer-plugin-ansible/provisioner}/ansible-local/communicator_mock.go (98%) rename {provisioner => vendor/github.com/hashicorp/packer-plugin-ansible/provisioner}/ansible-local/provisioner.go (100%) rename {provisioner => vendor/github.com/hashicorp/packer-plugin-ansible/provisioner}/ansible-local/provisioner.hcl2spec.go (100%) create mode 100644 vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/test-fixtures/docker_playbookdir_template.pkr.hcl create mode 100644 vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/test-fixtures/docker_playbookfiles_template.pkr.hcl rename {provisioner => vendor/github.com/hashicorp/packer-plugin-ansible/provisioner}/ansible/mock_ansible.go (100%) rename {provisioner => vendor/github.com/hashicorp/packer-plugin-ansible/provisioner}/ansible/provisioner.go (99%) rename {provisioner => vendor/github.com/hashicorp/packer-plugin-ansible/provisioner}/ansible/provisioner.hcl2spec.go (100%) create mode 100644 vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible/test-fixtures/docker_playbookfile_template.pkr.hcl delete mode 100644 website/content/docs/provisioners/ansible-local.mdx delete mode 100644 website/content/docs/provisioners/ansible.mdx delete mode 100644 website/content/partials/provisioner/ansible-local/Config-not-required.mdx delete mode 100644 website/content/partials/provisioner/ansible/Config-not-required.mdx delete mode 100644 website/content/partials/provisioner/ansible/Config-required.mdx diff --git a/command/plugin.go b/command/plugin.go index f5bb2ab61..1614f9375 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -71,8 +71,6 @@ import ( vagrantcloudpostprocessor "github.com/hashicorp/packer/post-processor/vagrant-cloud" yandexexportpostprocessor "github.com/hashicorp/packer/post-processor/yandex-export" yandeximportpostprocessor "github.com/hashicorp/packer/post-processor/yandex-import" - ansibleprovisioner "github.com/hashicorp/packer/provisioner/ansible" - ansiblelocalprovisioner "github.com/hashicorp/packer/provisioner/ansible-local" azuredtlartifactprovisioner "github.com/hashicorp/packer/provisioner/azure-dtlartifact" breakpointprovisioner "github.com/hashicorp/packer/provisioner/breakpoint" chefclientprovisioner "github.com/hashicorp/packer/provisioner/chef-client" @@ -143,8 +141,6 @@ var Builders = map[string]packersdk.Builder{ } var Provisioners = map[string]packersdk.Provisioner{ - "ansible": new(ansibleprovisioner.Provisioner), - "ansible-local": new(ansiblelocalprovisioner.Provisioner), "azure-dtlartifact": new(azuredtlartifactprovisioner.Provisioner), "breakpoint": new(breakpointprovisioner.Provisioner), "chef-client": new(chefclientprovisioner.Provisioner), diff --git a/command/vendored_plugins.go b/command/vendored_plugins.go index 092bd4b90..5cd42931a 100644 --- a/command/vendored_plugins.go +++ b/command/vendored_plugins.go @@ -15,6 +15,8 @@ import ( amazonamidatasource "github.com/hashicorp/packer-plugin-amazon/datasource/ami" amazonsecretsmanagerdatasource "github.com/hashicorp/packer-plugin-amazon/datasource/secretsmanager" anazibimportpostprocessor "github.com/hashicorp/packer-plugin-amazon/post-processor/import" + ansibleprovisioner "github.com/hashicorp/packer-plugin-ansible/provisioner/ansible" + ansiblelocalprovisioner "github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local" dockerbuilder "github.com/hashicorp/packer-plugin-docker/builder/docker" dockerimportpostprocessor "github.com/hashicorp/packer-plugin-docker/post-processor/docker-import" dockerpushpostprocessor "github.com/hashicorp/packer-plugin-docker/post-processor/docker-push" @@ -48,7 +50,10 @@ var VendoredBuilders = map[string]packersdk.Builder{ // VendoredProvisioners are provisioner components that were once bundled with the // Packer core, but are now being imported from their counterpart plugin repos -var VendoredProvisioners = map[string]packersdk.Provisioner{} +var VendoredProvisioners = map[string]packersdk.Provisioner{ + "ansible": new(ansibleprovisioner.Provisioner), + "ansible-local": new(ansiblelocalprovisioner.Provisioner), +} // VendoredPostProcessors are post-processor components that were once bundled with the // Packer core, but are now being imported from their counterpart plugin repos diff --git a/go.mod b/go.mod index d6dccd95e..068c38d6f 100644 --- a/go.mod +++ b/go.mod @@ -49,6 +49,7 @@ require ( github.com/hashicorp/go-version v1.2.0 github.com/hashicorp/hcl/v2 v2.9.1 github.com/hashicorp/packer-plugin-amazon v0.0.1 + github.com/hashicorp/packer-plugin-ansible v0.0.2 github.com/hashicorp/packer-plugin-docker v0.0.7 github.com/hashicorp/packer-plugin-sdk v0.1.3 github.com/hashicorp/packer-plugin-vsphere v0.0.0-20210415100050-d0269b5646e6 diff --git a/go.sum b/go.sum index c4aa579c3..c8460acf8 100644 --- a/go.sum +++ b/go.sum @@ -433,6 +433,8 @@ github.com/hashicorp/packer v1.7.0/go.mod h1:3KRJcwOctl2JaAGpQMI1bWQRArfWNWqcYjO github.com/hashicorp/packer v1.7.1/go.mod h1:ApnmMINvuhhnfPyTVqZu6jznDWPVYDJUw7e188DFCmo= github.com/hashicorp/packer-plugin-amazon v0.0.1 h1:EuyjNK9bL7WhQeIJzhBJxOx8nyc61ai5UbOsb1PIVwI= github.com/hashicorp/packer-plugin-amazon v0.0.1/go.mod h1:12c9msibyHdId+Mk/pCbdRb1KaLIhaNyxeJ6n8bZt30= +github.com/hashicorp/packer-plugin-ansible v0.0.2 h1:nvBtCedXhUI5T6Up5+bmhlY7rmk8FjWuFv9A2joK7TU= +github.com/hashicorp/packer-plugin-ansible v0.0.2/go.mod h1:ocXB4KTU+I+DBRGfMP4XE7dPlURaUnb7NJvyddZ6bh0= github.com/hashicorp/packer-plugin-docker v0.0.7 h1:hMTrH7vrkFIjphtbbtpuzffTzSjMNgxayo2DPLz9y+c= github.com/hashicorp/packer-plugin-docker v0.0.7/go.mod h1:IpeKlwOSy2kdgQcysqd3gCsoqjME9jtmpFoKxn7RRNI= github.com/hashicorp/packer-plugin-sdk v0.0.6/go.mod h1:Nvh28f+Jmpp2rcaN79bULTouNkGNDRfHckhHKTAXtyU= @@ -446,6 +448,7 @@ github.com/hashicorp/packer-plugin-sdk v0.0.14/go.mod h1:tNb3XzJPnjMl3QuUdKmF47B github.com/hashicorp/packer-plugin-sdk v0.1.0/go.mod h1:CFsC20uZjtER/EnTn/CSMKD0kEdkqOVev8mtOmfnZiI= github.com/hashicorp/packer-plugin-sdk v0.1.1/go.mod h1:1d3nqB9LUsXMQaNUiL67Q+WYEtjsVcLNTX8ikVlpBrc= github.com/hashicorp/packer-plugin-sdk v0.1.2/go.mod h1:KRjczE1/c9NV5Re+PXt3myJsVTI/FxEHpZjRjOH0Fug= +github.com/hashicorp/packer-plugin-sdk v0.1.3-0.20210407232143-c217d82aefb6/go.mod h1:xePpgQgQYv/bamiypx3hH9ukidxDdcN8q0R0wLi8IEQ= github.com/hashicorp/packer-plugin-sdk v0.1.3 h1:oHTVlgoX2piUzL54+LBo9uIMfW+L/kY7or83dDStdIY= github.com/hashicorp/packer-plugin-sdk v0.1.3/go.mod h1:xePpgQgQYv/bamiypx3hH9ukidxDdcN8q0R0wLi8IEQ= github.com/hashicorp/packer-plugin-vsphere v0.0.0-20210415100050-d0269b5646e6 h1:pOv7Apd4P3KEpNBHLV4E7tKlwHoInCU/bnPVadGSDxY= diff --git a/provisioner/ansible-local/provisioner_test.go b/provisioner/ansible-local/provisioner_test.go deleted file mode 100644 index 0cf4555a7..000000000 --- a/provisioner/ansible-local/provisioner_test.go +++ /dev/null @@ -1,497 +0,0 @@ -package ansiblelocal - -import ( - "context" - "io/ioutil" - "os" - "path/filepath" - "strings" - "testing" - - "fmt" - "os/exec" - - "github.com/hashicorp/packer-plugin-docker/builder/docker" - packersdk "github.com/hashicorp/packer-plugin-sdk/packer" - builderT "github.com/hashicorp/packer/acctest" - "github.com/hashicorp/packer/provisioner/file" -) - -func TestProvisioner_Impl(t *testing.T) { - var raw interface{} - raw = &Provisioner{} - if _, ok := raw.(packersdk.Provisioner); !ok { - t.Fatalf("must be a Provisioner") - } -} - -func TestProvisionerPrepare_Defaults(t *testing.T) { - var p Provisioner - config := testConfig() - - playbook_file, err := ioutil.TempFile("", "playbook") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(playbook_file.Name()) - - config["playbook_file"] = playbook_file.Name() - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - if !strings.HasPrefix(filepath.ToSlash(p.config.StagingDir), DefaultStagingDir) { - t.Fatalf("unexpected staging dir %s, expected %s", - p.config.StagingDir, DefaultStagingDir) - } -} - -func TestProvisionerPrepare_PlaybookFile(t *testing.T) { - var p Provisioner - config := testConfig() - - err := p.Prepare(config) - if err == nil { - t.Fatal("should have error") - } - - config["playbook_file"] = "" - err = p.Prepare(config) - if err == nil { - t.Fatal("should have error") - } - - playbook_file, err := ioutil.TempFile("", "playbook") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(playbook_file.Name()) - - config["playbook_file"] = playbook_file.Name() - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestProvisionerPrepare_PlaybookFiles(t *testing.T) { - var p Provisioner - config := testConfig() - - err := p.Prepare(config) - if err == nil { - t.Fatal("should have error") - } - - config["playbook_file"] = "" - config["playbook_files"] = []string{} - err = p.Prepare(config) - if err == nil { - t.Fatal("should have error") - } - - playbook_file, err := ioutil.TempFile("", "playbook") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(playbook_file.Name()) - - config["playbook_file"] = playbook_file.Name() - config["playbook_files"] = []string{"some_other_file"} - err = p.Prepare(config) - if err == nil { - t.Fatal("should have error") - } - - p = Provisioner{} - config["playbook_file"] = playbook_file.Name() - config["playbook_files"] = []string{} - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - config["playbook_file"] = "" - config["playbook_files"] = []string{playbook_file.Name()} - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestProvisionerProvision_PlaybookFiles(t *testing.T) { - var p Provisioner - config := testConfig() - - playbooks := createTempFiles("", 3) - defer removeFiles(playbooks...) - - config["playbook_files"] = playbooks - err := p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - comm := &communicatorMock{} - if err := p.Provision(context.Background(), packersdk.TestUi(t), comm, make(map[string]interface{})); err != nil { - t.Fatalf("err: %s", err) - } - - assertPlaybooksUploaded(comm, playbooks) - assertPlaybooksExecuted(comm, playbooks) -} - -func TestProvisionerProvision_PlaybookFilesWithPlaybookDir(t *testing.T) { - var p Provisioner - config := testConfig() - - playbook_dir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Failed to create playbook_dir: %s", err) - } - defer os.RemoveAll(playbook_dir) - playbooks := createTempFiles(playbook_dir, 3) - - playbookNames := make([]string, 0, len(playbooks)) - playbooksInPlaybookDir := make([]string, 0, len(playbooks)) - for _, playbook := range playbooks { - playbooksInPlaybookDir = append(playbooksInPlaybookDir, strings.TrimPrefix(playbook, playbook_dir)) - playbookNames = append(playbookNames, filepath.Base(playbook)) - } - - config["playbook_files"] = playbooks - config["playbook_dir"] = playbook_dir - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - comm := &communicatorMock{} - if err := p.Provision(context.Background(), packersdk.TestUi(t), comm, make(map[string]interface{})); err != nil { - t.Fatalf("err: %s", err) - } - - assertPlaybooksNotUploaded(comm, playbookNames) - assertPlaybooksExecuted(comm, playbooksInPlaybookDir) -} - -func TestProvisionerPrepare_InventoryFile(t *testing.T) { - var p Provisioner - config := testConfig() - - err := p.Prepare(config) - if err == nil { - t.Fatal("should have error") - } - - config["playbook_file"] = "" - err = p.Prepare(config) - if err == nil { - t.Fatal("should have error") - } - - playbook_file, err := ioutil.TempFile("", "playbook") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(playbook_file.Name()) - - config["playbook_file"] = playbook_file.Name() - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - inventory_file, err := ioutil.TempFile("", "inventory") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(inventory_file.Name()) - - config["inventory_file"] = inventory_file.Name() - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestProvisionerPrepare_Dirs(t *testing.T) { - var p Provisioner - config := testConfig() - - err := p.Prepare(config) - if err == nil { - t.Fatal("should have error") - } - - config["playbook_file"] = "" - err = p.Prepare(config) - if err == nil { - t.Fatal("should have error") - } - - playbook_file, err := ioutil.TempFile("", "playbook") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(playbook_file.Name()) - - config["playbook_file"] = playbook_file.Name() - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - config["playbook_paths"] = []string{playbook_file.Name()} - err = p.Prepare(config) - if err == nil { - t.Fatal("should error if playbook paths is not a dir") - } - - config["playbook_paths"] = []string{os.TempDir()} - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - config["role_paths"] = []string{playbook_file.Name()} - err = p.Prepare(config) - if err == nil { - t.Fatal("should error if role paths is not a dir") - } - - config["role_paths"] = []string{os.TempDir()} - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - config["group_vars"] = playbook_file.Name() - err = p.Prepare(config) - if err == nil { - t.Fatalf("should error if group_vars path is not a dir") - } - - config["group_vars"] = os.TempDir() - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - config["host_vars"] = playbook_file.Name() - err = p.Prepare(config) - if err == nil { - t.Fatalf("should error if host_vars path is not a dir") - } - - config["host_vars"] = os.TempDir() - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestProvisionerPrepare_CleanStagingDir(t *testing.T) { - var p Provisioner - config := testConfig() - - playbook_file, err := ioutil.TempFile("", "playbook") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(playbook_file.Name()) - - config["playbook_file"] = playbook_file.Name() - config["clean_staging_directory"] = true - - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - if !p.config.CleanStagingDir { - t.Fatalf("expected clean_staging_directory to be set") - } -} - -func TestProvisionerProvisionDocker_PlaybookFiles(t *testing.T) { - testProvisionerProvisionDockerWithPlaybookFiles(t, playbookFilesDockerTemplate) -} - -func TestProvisionerProvisionDocker_PlaybookFilesWithPlaybookDir(t *testing.T) { - testProvisionerProvisionDockerWithPlaybookFiles(t, playbookFilesWithPlaybookDirDockerTemplate) -} - -func testProvisionerProvisionDockerWithPlaybookFiles(t *testing.T, templateString string) { - if os.Getenv("PACKER_ACC") == "" { - t.Skip("This test is only run with PACKER_ACC=1") - } - - // this should be a precheck - cmd := exec.Command("docker", "-v") - err := cmd.Run() - if err != nil { - t.Error("docker command not found; please make sure docker is installed") - } - - builderT.Test(t, builderT.TestCase{ - Builder: &docker.Builder{}, - Template: templateString, - Check: func(a []packersdk.Artifact) error { - - actualContent, err := ioutil.ReadFile("hello_world") - if err != nil { - return fmt.Errorf("Expected file not found: %s", err) - } - - expectedContent := "Hello world!" - if string(actualContent) != expectedContent { - return fmt.Errorf(`Unexpected file content: expected="%s", actual="%s"`, expectedContent, actualContent) - } - return nil - }, - Teardown: func() error { - os.Remove("hello_world") - return nil - }, - ProvisionerStore: packersdk.MapOfProvisioner{ - "ansible-local": func() (packersdk.Provisioner, error) { return &Provisioner{}, nil }, - "file": func() (packersdk.Provisioner, error) { return &file.Provisioner{}, nil }, - }, - }) -} - -func assertPlaybooksExecuted(comm *communicatorMock, playbooks []string) { - cmdIndex := 0 - for _, playbook := range playbooks { - playbook = filepath.ToSlash(playbook) - for ; cmdIndex < len(comm.startCommand); cmdIndex++ { - cmd := comm.startCommand[cmdIndex] - if strings.Contains(cmd, "ansible-playbook") && strings.Contains(cmd, playbook) { - break - } - } - if cmdIndex == len(comm.startCommand) { - panic(fmt.Sprintf("Playbook %s was not executed", playbook)) - } - } -} - -func assertPlaybooksUploaded(comm *communicatorMock, playbooks []string) { - uploadIndex := 0 - for _, playbook := range playbooks { - playbook = filepath.ToSlash(playbook) - for ; uploadIndex < len(comm.uploadDestination); uploadIndex++ { - dest := comm.uploadDestination[uploadIndex] - if strings.HasSuffix(dest, playbook) { - break - } - } - if uploadIndex == len(comm.uploadDestination) { - panic(fmt.Sprintf("Playbook %s was not uploaded", playbook)) - } - } -} - -func assertPlaybooksNotUploaded(comm *communicatorMock, playbooks []string) { - for _, playbook := range playbooks { - playbook = filepath.ToSlash(playbook) - for _, destination := range comm.uploadDestination { - if strings.HasSuffix(destination, playbook) { - panic(fmt.Sprintf("Playbook %s was uploaded", playbook)) - } - } - } -} - -func testConfig() map[string]interface{} { - m := make(map[string]interface{}) - return m -} - -func createTempFile(dir string) string { - file, err := ioutil.TempFile(dir, "") - if err != nil { - panic(fmt.Sprintf("err: %s", err)) - } - return file.Name() -} - -func createTempFiles(dir string, numFiles int) []string { - files := make([]string, 0, numFiles) - defer func() { - // Cleanup the files if not all were created. - if len(files) < numFiles { - for _, file := range files { - os.Remove(file) - } - } - }() - - for i := 0; i < numFiles; i++ { - files = append(files, createTempFile(dir)) - } - return files -} - -func removeFiles(files ...string) { - for _, file := range files { - os.Remove(file) - } -} - -const playbookFilesDockerTemplate = ` -{ - "builders": [ - { - "type": "test", - "image": "williamyeh/ansible:centos7", - "discard": true - } - ], - "provisioners": [ - { - "type": "ansible-local", - "playbook_files": [ - "test-fixtures/hello.yml", - "test-fixtures/world.yml" - ] - }, - { - "type": "file", - "source": "/tmp/hello_world", - "destination": "hello_world", - "direction": "download" - } - ] -} -` - -const playbookFilesWithPlaybookDirDockerTemplate = ` -{ - "builders": [ - { - "type": "test", - "image": "williamyeh/ansible:centos7", - "discard": true - } - ], - "provisioners": [ - { - "type": "ansible-local", - "playbook_files": [ - "test-fixtures/hello.yml", - "test-fixtures/world.yml" - ], - "playbook_dir": "test-fixtures" - }, - { - "type": "file", - "source": "/tmp/hello_world", - "destination": "hello_world", - "direction": "download" - } - ] -} -` diff --git a/provisioner/ansible-local/test-fixtures/hello.yml b/provisioner/ansible-local/test-fixtures/hello.yml deleted file mode 100644 index 6bb8797d8..000000000 --- a/provisioner/ansible-local/test-fixtures/hello.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- hosts: all - tasks: - - name: write Hello - shell: echo -n "Hello" >> /tmp/hello_world \ No newline at end of file diff --git a/provisioner/ansible-local/test-fixtures/world.yml b/provisioner/ansible-local/test-fixtures/world.yml deleted file mode 100644 index 98a205c7b..000000000 --- a/provisioner/ansible-local/test-fixtures/world.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- hosts: all - tasks: - - name: write world! - shell: echo -n " world!" >> /tmp/hello_world \ No newline at end of file diff --git a/provisioner/ansible-local/version/version.go b/provisioner/ansible-local/version/version.go deleted file mode 100644 index a4d455aad..000000000 --- a/provisioner/ansible-local/version/version.go +++ /dev/null @@ -1,13 +0,0 @@ -package version - -import ( - "github.com/hashicorp/packer-plugin-sdk/version" - packerVersion "github.com/hashicorp/packer/version" -) - -var AnsibleLocalPluginVersion *version.PluginVersion - -func init() { - AnsibleLocalPluginVersion = version.InitializePluginVersion( - packerVersion.Version, packerVersion.VersionPrerelease) -} diff --git a/provisioner/ansible/examples/connection-plugin/2.4.x/packer.py b/provisioner/ansible/examples/connection-plugin/2.4.x/packer.py deleted file mode 100644 index ea4210d32..000000000 --- a/provisioner/ansible/examples/connection-plugin/2.4.x/packer.py +++ /dev/null @@ -1,186 +0,0 @@ -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.connection.ssh import Connection as SSHConnection - -DOCUMENTATION = ''' - connection: packer - short_description: ssh based connections for powershell via packer - description: - - This connection plugin allows ansible to communicate to the target packer machines via ssh based connections for powershell. - author: Packer Community - version_added: na - options: - host: - description: Hostname/ip to connect to. - default: inventory_hostname - vars: - - name: ansible_host - - name: ansible_ssh_host - host_key_checking: - #constant: HOST_KEY_CHECKING - description: Determines if ssh should check host keys - type: boolean - ini: - - section: defaults - key: 'host_key_checking' - env: - - name: ANSIBLE_HOST_KEY_CHECKING - password: - description: Authentication password for the C(remote_user). Can be supplied as CLI option. - vars: - - name: ansible_password - - name: ansible_ssh_pass - ssh_args: - description: Arguments to pass to all ssh cli tools - default: '-C -o ControlMaster=auto -o ControlPersist=60s' - ini: - - section: 'ssh_connection' - key: 'ssh_args' - env: - - name: ANSIBLE_SSH_ARGS - ssh_common_args: - description: Common extra args for all ssh CLI tools - vars: - - name: ansible_ssh_common_args - ssh_executable: - default: ssh - description: - - This defines the location of the ssh binary. It defaults to `ssh` which will use the first ssh binary available in $PATH. - - This option is usually not required, it might be useful when access to system ssh is restricted, - or when using ssh wrappers to connect to remote hosts. - env: [{name: ANSIBLE_SSH_EXECUTABLE}] - ini: - - {key: ssh_executable, section: ssh_connection} - yaml: {key: ssh_connection.ssh_executable} - #const: ANSIBLE_SSH_EXECUTABLE - version_added: "2.2" - scp_extra_args: - description: Extra exclusive to the 'scp' CLI - vars: - - name: ansible_scp_extra_args - sftp_extra_args: - description: Extra exclusive to the 'sftp' CLI - vars: - - name: ansible_sftp_extra_args - ssh_extra_args: - description: Extra exclusive to the 'ssh' CLI - vars: - - name: ansible_ssh_extra_args - retries: - # constant: ANSIBLE_SSH_RETRIES - description: Number of attempts to connect. - default: 3 - type: integer - env: - - name: ANSIBLE_SSH_RETRIES - ini: - - section: connection - key: retries - - section: ssh_connection - key: retries - port: - description: Remote port to connect to. - type: int - default: 22 - ini: - - section: defaults - key: remote_port - env: - - name: ANSIBLE_REMOTE_PORT - vars: - - name: ansible_port - - name: ansible_ssh_port - remote_user: - description: - - User name with which to login to the remote server, normally set by the remote_user keyword. - - If no user is supplied, Ansible will let the ssh client binary choose the user as it normally - ini: - - section: defaults - key: remote_user - env: - - name: ANSIBLE_REMOTE_USER - vars: - - name: ansible_user - - name: ansible_ssh_user - pipelining: - default: ANSIBLE_PIPELINING - description: - - Pipelining reduces the number of SSH operations required to execute a module on the remote server, - by executing many Ansible modules without actual file transfer. - - This can result in a very significant performance improvement when enabled. - - However this conflicts with privilege escalation (become). - For example, when using sudo operations you must first disable 'requiretty' in the sudoers file for the target hosts, - which is why this feature is disabled by default. - env: - - name: ANSIBLE_PIPELINING - #- name: ANSIBLE_SSH_PIPELINING - ini: - - section: defaults - key: pipelining - #- section: ssh_connection - # key: pipelining - type: boolean - vars: - - name: ansible_pipelining - - name: ansible_ssh_pipelining - private_key_file: - description: - - Path to private key file to use for authentication - ini: - - section: defaults - key: private_key_file - env: - - name: ANSIBLE_PRIVATE_KEY_FILE - vars: - - name: ansible_private_key_file - - name: ansible_ssh_private_key_file - control_path: - default: null - description: - - This is the location to save ssh's ControlPath sockets, it uses ssh's variable substitution. - - Since 2.3, if null, ansible will generate a unique hash. Use `%(directory)s` to indicate where to use the control dir path setting. - env: - - name: ANSIBLE_SSH_CONTROL_PATH - ini: - - key: control_path - section: ssh_connection - control_path_dir: - default: ~/.ansible/cp - description: - - This sets the directory to use for ssh control path if the control path setting is null. - - Also, provides the `%(directory)s` variable for the control path setting. - env: - - name: ANSIBLE_SSH_CONTROL_PATH_DIR - ini: - - section: ssh_connection - key: control_path_dir - sftp_batch_mode: - default: True - description: 'TODO: write it' - env: [{name: ANSIBLE_SFTP_BATCH_MODE}] - ini: - - {key: sftp_batch_mode, section: ssh_connection} - type: boolean - scp_if_ssh: - default: smart - description: - - "Prefered method to use when transfering files over ssh" - - When set to smart, Ansible will try them until one succeeds or they all fail - - If set to True, it will force 'scp', if False it will use 'sftp' - env: [{name: ANSIBLE_SCP_IF_SSH}] - ini: - - {key: scp_if_ssh, section: ssh_connection} -''' - -class Connection(SSHConnection): - ''' ssh based connections for powershell via packer''' - - transport = 'packer' - has_pipelining = True - become_methods = [] - allow_executable = False - module_implementation_preferences = ('.ps1', '') - - def __init__(self, *args, **kwargs): - super(Connection, self).__init__(*args, **kwargs) \ No newline at end of file diff --git a/provisioner/ansible/examples/connection-plugin/2.5.x/packer.py b/provisioner/ansible/examples/connection-plugin/2.5.x/packer.py deleted file mode 100644 index e133aeea4..000000000 --- a/provisioner/ansible/examples/connection-plugin/2.5.x/packer.py +++ /dev/null @@ -1,204 +0,0 @@ -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.connection.ssh import Connection as SSHConnection - -DOCUMENTATION = ''' - connection: packer - short_description: ssh based connections for powershell via packer - description: - - This connection plugin allows ansible to communicate to the target packer machines via ssh based connections for powershell. - author: Packer Community - version_added: na - options: - host: - description: Hostname/ip to connect to. - default: inventory_hostname - vars: - - name: ansible_host - - name: ansible_ssh_host - host_key_checking: - description: Determines if ssh should check host keys - type: boolean - ini: - - section: defaults - key: 'host_key_checking' - - section: ssh_connection - key: 'host_key_checking' - version_added: '2.5' - env: - - name: ANSIBLE_HOST_KEY_CHECKING - - name: ANSIBLE_SSH_HOST_KEY_CHECKING - version_added: '2.5' - vars: - - name: ansible_host_key_checking - version_added: '2.5' - - name: ansible_ssh_host_key_checking - version_added: '2.5' - password: - description: Authentication password for the C(remote_user). Can be supplied as CLI option. - vars: - - name: ansible_password - - name: ansible_ssh_pass - ssh_args: - description: Arguments to pass to all ssh cli tools - default: '-C -o ControlMaster=auto -o ControlPersist=60s' - ini: - - section: 'ssh_connection' - key: 'ssh_args' - env: - - name: ANSIBLE_SSH_ARGS - ssh_common_args: - description: Common extra args for all ssh CLI tools - vars: - - name: ansible_ssh_common_args - ssh_executable: - default: ssh - description: - - This defines the location of the ssh binary. It defaults to `ssh` which will use the first ssh binary available in $PATH. - - This option is usually not required, it might be useful when access to system ssh is restricted, - or when using ssh wrappers to connect to remote hosts. - env: [{name: ANSIBLE_SSH_EXECUTABLE}] - ini: - - {key: ssh_executable, section: ssh_connection} - yaml: {key: ssh_connection.ssh_executable} - #const: ANSIBLE_SSH_EXECUTABLE - version_added: "2.2" - scp_extra_args: - description: Extra exclusive to the 'scp' CLI - vars: - - name: ansible_scp_extra_args - sftp_extra_args: - description: Extra exclusive to the 'sftp' CLI - vars: - - name: ansible_sftp_extra_args - ssh_extra_args: - description: Extra exclusive to the 'ssh' CLI - vars: - - name: ansible_ssh_extra_args - retries: - # constant: ANSIBLE_SSH_RETRIES - description: Number of attempts to connect. - default: 3 - type: integer - env: - - name: ANSIBLE_SSH_RETRIES - ini: - - section: connection - key: retries - - section: ssh_connection - key: retries - port: - description: Remote port to connect to. - type: int - default: 22 - ini: - - section: defaults - key: remote_port - env: - - name: ANSIBLE_REMOTE_PORT - vars: - - name: ansible_port - - name: ansible_ssh_port - remote_user: - description: - - User name with which to login to the remote server, normally set by the remote_user keyword. - - If no user is supplied, Ansible will let the ssh client binary choose the user as it normally - ini: - - section: defaults - key: remote_user - env: - - name: ANSIBLE_REMOTE_USER - vars: - - name: ansible_user - - name: ansible_ssh_user - pipelining: - default: ANSIBLE_PIPELINING - description: - - Pipelining reduces the number of SSH operations required to execute a module on the remote server, - by executing many Ansible modules without actual file transfer. - - This can result in a very significant performance improvement when enabled. - - However this conflicts with privilege escalation (become). - For example, when using sudo operations you must first disable 'requiretty' in the sudoers file for the target hosts, - which is why this feature is disabled by default. - env: - - name: ANSIBLE_PIPELINING - #- name: ANSIBLE_SSH_PIPELINING - ini: - - section: defaults - key: pipelining - #- section: ssh_connection - # key: pipelining - type: boolean - vars: - - name: ansible_pipelining - - name: ansible_ssh_pipelining - private_key_file: - description: - - Path to private key file to use for authentication - ini: - - section: defaults - key: private_key_file - env: - - name: ANSIBLE_PRIVATE_KEY_FILE - vars: - - name: ansible_private_key_file - - name: ansible_ssh_private_key_file - control_path: - default: null - description: - - This is the location to save ssh's ControlPath sockets, it uses ssh's variable substitution. - - Since 2.3, if null, ansible will generate a unique hash. Use `%(directory)s` to indicate where to use the control dir path setting. - env: - - name: ANSIBLE_SSH_CONTROL_PATH - ini: - - key: control_path - section: ssh_connection - control_path_dir: - default: ~/.ansible/cp - description: - - This sets the directory to use for ssh control path if the control path setting is null. - - Also, provides the `%(directory)s` variable for the control path setting. - env: - - name: ANSIBLE_SSH_CONTROL_PATH_DIR - ini: - - section: ssh_connection - key: control_path_dir - sftp_batch_mode: - default: True - description: 'TODO: write it' - env: [{name: ANSIBLE_SFTP_BATCH_MODE}] - ini: - - {key: sftp_batch_mode, section: ssh_connection} - type: boolean - scp_if_ssh: - default: smart - description: - - "Prefered method to use when transfering files over ssh" - - When set to smart, Ansible will try them until one succeeds or they all fail - - If set to True, it will force 'scp', if False it will use 'sftp' - env: [{name: ANSIBLE_SCP_IF_SSH}] - ini: - - {key: scp_if_ssh, section: ssh_connection} - use_tty: - version_added: '2.5' - default: True - description: add -tt to ssh commands to force tty allocation - env: [{name: ANSIBLE_SSH_USETTY}] - ini: - - {key: usetty, section: ssh_connection} - type: boolean - yaml: {key: connection.usetty} -''' - -class Connection(SSHConnection): - ''' ssh based connections for powershell via packer''' - - transport = 'packer' - has_pipelining = True - become_methods = [] - allow_executable = False - module_implementation_preferences = ('.ps1', '') - - def __init__(self, *args, **kwargs): - super(Connection, self).__init__(*args, **kwargs) \ No newline at end of file diff --git a/provisioner/ansible/examples/connection-plugin/2.6.x/packer.py b/provisioner/ansible/examples/connection-plugin/2.6.x/packer.py deleted file mode 100644 index 8ef8c0548..000000000 --- a/provisioner/ansible/examples/connection-plugin/2.6.x/packer.py +++ /dev/null @@ -1,218 +0,0 @@ -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.connection.ssh import Connection as SSHConnection - -DOCUMENTATION = ''' - connection: packer - short_description: ssh based connections for powershell via packer - description: - - This connection plugin allows ansible to communicate to the target packer machines via ssh based connections for powershell. - author: Packer Community - version_added: na - options: - host: - description: Hostname/ip to connect to. - default: inventory_hostname - vars: - - name: ansible_host - - name: ansible_ssh_host - host_key_checking: - description: Determines if ssh should check host keys - type: boolean - ini: - - section: defaults - key: 'host_key_checking' - - section: ssh_connection - key: 'host_key_checking' - version_added: '2.5' - env: - - name: ANSIBLE_HOST_KEY_CHECKING - - name: ANSIBLE_SSH_HOST_KEY_CHECKING - version_added: '2.5' - vars: - - name: ansible_host_key_checking - version_added: '2.5' - - name: ansible_ssh_host_key_checking - version_added: '2.5' - password: - description: Authentication password for the C(remote_user). Can be supplied as CLI option. - vars: - - name: ansible_password - - name: ansible_ssh_pass - ssh_args: - description: Arguments to pass to all ssh cli tools - default: '-C -o ControlMaster=auto -o ControlPersist=60s' - ini: - - section: 'ssh_connection' - key: 'ssh_args' - env: - - name: ANSIBLE_SSH_ARGS - ssh_common_args: - description: Common extra args for all ssh CLI tools - vars: - - name: ansible_ssh_common_args - ssh_executable: - default: ssh - description: - - This defines the location of the ssh binary. It defaults to ``ssh`` which will use the first ssh binary available in $PATH. - - This option is usually not required, it might be useful when access to system ssh is restricted, - or when using ssh wrappers to connect to remote hosts. - env: [{name: ANSIBLE_SSH_EXECUTABLE}] - ini: - - {key: ssh_executable, section: ssh_connection} - #const: ANSIBLE_SSH_EXECUTABLE - version_added: "2.2" - sftp_executable: - default: sftp - description: - - This defines the location of the sftp binary. It defaults to ``sftp`` which will use the first binary available in $PATH. - env: [{name: ANSIBLE_SFTP_EXECUTABLE}] - ini: - - {key: sftp_executable, section: ssh_connection} - version_added: "2.6" - scp_executable: - default: scp - description: - - This defines the location of the scp binary. It defaults to `scp` which will use the first binary available in $PATH. - env: [{name: ANSIBLE_SCP_EXECUTABLE}] - ini: - - {key: scp_executable, section: ssh_connection} - version_added: "2.6" - scp_extra_args: - description: Extra exclusive to the ``scp`` CLI - vars: - - name: ansible_scp_extra_args - sftp_extra_args: - description: Extra exclusive to the ``sftp`` CLI - vars: - - name: ansible_sftp_extra_args - ssh_extra_args: - description: Extra exclusive to the 'ssh' CLI - vars: - - name: ansible_ssh_extra_args - retries: - # constant: ANSIBLE_SSH_RETRIES - description: Number of attempts to connect. - default: 3 - type: integer - env: - - name: ANSIBLE_SSH_RETRIES - ini: - - section: connection - key: retries - - section: ssh_connection - key: retries - port: - description: Remote port to connect to. - type: int - default: 22 - ini: - - section: defaults - key: remote_port - env: - - name: ANSIBLE_REMOTE_PORT - vars: - - name: ansible_port - - name: ansible_ssh_port - remote_user: - description: - - User name with which to login to the remote server, normally set by the remote_user keyword. - - If no user is supplied, Ansible will let the ssh client binary choose the user as it normally - ini: - - section: defaults - key: remote_user - env: - - name: ANSIBLE_REMOTE_USER - vars: - - name: ansible_user - - name: ansible_ssh_user - pipelining: - default: ANSIBLE_PIPELINING - description: - - Pipelining reduces the number of SSH operations required to execute a module on the remote server, - by executing many Ansible modules without actual file transfer. - - This can result in a very significant performance improvement when enabled. - - However this conflicts with privilege escalation (become). - For example, when using sudo operations you must first disable 'requiretty' in the sudoers file for the target hosts, - which is why this feature is disabled by default. - env: - - name: ANSIBLE_PIPELINING - #- name: ANSIBLE_SSH_PIPELINING - ini: - - section: defaults - key: pipelining - #- section: ssh_connection - # key: pipelining - type: boolean - vars: - - name: ansible_pipelining - - name: ansible_ssh_pipelining - private_key_file: - description: - - Path to private key file to use for authentication - ini: - - section: defaults - key: private_key_file - env: - - name: ANSIBLE_PRIVATE_KEY_FILE - vars: - - name: ansible_private_key_file - - name: ansible_ssh_private_key_file - control_path: - description: - - This is the location to save ssh's ControlPath sockets, it uses ssh's variable substitution. - - Since 2.3, if null, ansible will generate a unique hash. Use `%(directory)s` to indicate where to use the control dir path setting. - env: - - name: ANSIBLE_SSH_CONTROL_PATH - ini: - - key: control_path - section: ssh_connection - control_path_dir: - default: ~/.ansible/cp - description: - - This sets the directory to use for ssh control path if the control path setting is null. - - Also, provides the `%(directory)s` variable for the control path setting. - env: - - name: ANSIBLE_SSH_CONTROL_PATH_DIR - ini: - - section: ssh_connection - key: control_path_dir - sftp_batch_mode: - default: 'yes' - description: 'TODO: write it' - env: [{name: ANSIBLE_SFTP_BATCH_MODE}] - ini: - - {key: sftp_batch_mode, section: ssh_connection} - type: bool - scp_if_ssh: - default: smart - description: - - "Prefered method to use when transfering files over ssh" - - When set to smart, Ansible will try them until one succeeds or they all fail - - If set to True, it will force 'scp', if False it will use 'sftp' - env: [{name: ANSIBLE_SCP_IF_SSH}] - ini: - - {key: scp_if_ssh, section: ssh_connection} - use_tty: - version_added: '2.5' - default: 'yes' - description: add -tt to ssh commands to force tty allocation - env: [{name: ANSIBLE_SSH_USETTY}] - ini: - - {key: usetty, section: ssh_connection} - type: bool - yaml: {key: connection.usetty} -''' - -class Connection(SSHConnection): - ''' ssh based connections for powershell via packer''' - - transport = 'packer' - has_pipelining = True - become_methods = [] - allow_executable = False - module_implementation_preferences = ('.ps1', '') - - def __init__(self, *args, **kwargs): - super(Connection, self).__init__(*args, **kwargs) \ No newline at end of file diff --git a/provisioner/ansible/provisioner_test.go b/provisioner/ansible/provisioner_test.go deleted file mode 100644 index 12354f91f..000000000 --- a/provisioner/ansible/provisioner_test.go +++ /dev/null @@ -1,781 +0,0 @@ -// +build !windows - -package ansible - -import ( - "bytes" - "context" - "crypto/rand" - "fmt" - "io" - "io/ioutil" - "os" - "path" - "strings" - "testing" - - "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" - packersdk "github.com/hashicorp/packer-plugin-sdk/packer" - confighelper "github.com/hashicorp/packer-plugin-sdk/template/config" - "github.com/stretchr/testify/assert" -) - -// Be sure to remove the Ansible stub file in each test with: -// defer os.Remove(config["command"].(string)) -func testConfig(t *testing.T) map[string]interface{} { - m := make(map[string]interface{}) - wd, err := os.Getwd() - if err != nil { - t.Fatalf("err: %s", err) - } - ansible_stub := path.Join(wd, "packer-ansible-stub.sh") - - err = ioutil.WriteFile(ansible_stub, []byte("#!/usr/bin/env bash\necho ansible 1.6.0"), 0777) - if err != nil { - t.Fatalf("err: %s", err) - } - m["command"] = ansible_stub - - return m -} - -func TestProvisioner_Impl(t *testing.T) { - var raw interface{} - raw = &Provisioner{} - if _, ok := raw.(packersdk.Provisioner); !ok { - t.Fatalf("must be a Provisioner") - } -} - -func TestProvisionerPrepare_Defaults(t *testing.T) { - var p Provisioner - config := testConfig(t) - defer os.Remove(config["command"].(string)) - - err := p.Prepare(config) - if err == nil { - t.Fatalf("should have error") - } - - 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) - } - defer os.Remove(playbook_file.Name()) - - err = os.Unsetenv("USER") - if err != nil { - t.Fatalf("err: %s", err) - } - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestProvisionerPrepare_PlaybookFile(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()) - - config["ssh_host_key_file"] = hostkey_file.Name() - config["ssh_authorized_key_file"] = publickey_file.Name() - - err = p.Prepare(config) - if err == nil { - t.Fatal("should have error") - } - - playbook_file, err := ioutil.TempFile("", "playbook") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(playbook_file.Name()) - - config["playbook_file"] = playbook_file.Name() - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestProvisionerPrepare_HostKeyFile(t *testing.T) { - var p Provisioner - config := testConfig(t) - defer os.Remove(config["command"].(string)) - - 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()) - - filename := make([]byte, 10) - n, err := io.ReadFull(rand.Reader, filename) - if n != len(filename) || err != nil { - t.Fatal("could not create random file name") - } - - config["ssh_host_key_file"] = fmt.Sprintf("%x", filename) - config["ssh_authorized_key_file"] = publickey_file.Name() - config["playbook_file"] = playbook_file.Name() - - err = p.Prepare(config) - if err == nil { - t.Fatal("should error if ssh_host_key_file does not exist") - } - - hostkey_file, err := ioutil.TempFile("", "hostkey") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(hostkey_file.Name()) - - config["ssh_host_key_file"] = hostkey_file.Name() - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestProvisionerPrepare_AuthorizedKeyFile(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()) - - playbook_file, err := ioutil.TempFile("", "playbook") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(playbook_file.Name()) - - filename := make([]byte, 10) - n, err := io.ReadFull(rand.Reader, filename) - if n != len(filename) || err != nil { - t.Fatal("could not create random file name") - } - - config["ssh_host_key_file"] = hostkey_file.Name() - config["playbook_file"] = playbook_file.Name() - config["ssh_authorized_key_file"] = fmt.Sprintf("%x", filename) - - err = p.Prepare(config) - if err == nil { - t.Errorf("should error if ssh_authorized_key_file does not exist") - } - - publickey_file, err := ioutil.TempFile("", "publickey") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(publickey_file.Name()) - - config["ssh_authorized_key_file"] = publickey_file.Name() - err = p.Prepare(config) - if err != nil { - t.Errorf("err: %s", err) - } -} - -func TestProvisionerPrepare_LocalPort(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["local_port"] = 65537 - err = p.Prepare(config) - if err == nil { - t.Fatal("should have error") - } - - config["local_port"] = 22222 - err = p.Prepare(config) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -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") - } - - var p Provisioner - p.config.Command = "ansible-playbook" - err := p.getVersion() - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestAnsibleGetVersionError(t *testing.T) { - var p Provisioner - p.config.Command = "./test-fixtures/exit1" - err := p.getVersion() - if err == nil { - t.Fatal("Should return error") - } - if !strings.Contains(err.Error(), "./test-fixtures/exit1 --version") { - t.Fatal("Error message should include command name") - } -} - -func TestAnsibleLongMessages(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") - } - - var p Provisioner - p.config.Command = "ansible-playbook" - p.config.PlaybookFile = "./test-fixtures/long-debug-message.yml" - err := p.Prepare() - if err != nil { - t.Fatalf("err: %s", err) - } - - comm := &packersdk.MockCommunicator{} - ui := &packersdk.BasicUi{ - Reader: new(bytes.Buffer), - Writer: new(bytes.Buffer), - } - - err = p.Provision(context.Background(), ui, comm, make(map[string]interface{})) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestCreateInventoryFile(t *testing.T) { - type inventoryFileTestCases struct { - AnsibleVersion uint - User string - Groups []string - EmptyGroups []string - UseProxy confighelper.Trilean - GeneratedData map[string]interface{} - Expected string - } - - TestCases := []inventoryFileTestCases{ - { - AnsibleVersion: 1, - User: "testuser", - UseProxy: confighelper.TriFalse, - GeneratedData: basicGenData(nil), - Expected: "default ansible_ssh_host=123.45.67.89 ansible_ssh_user=testuser ansible_ssh_port=1234\n", - }, - { - AnsibleVersion: 2, - User: "testuser", - UseProxy: confighelper.TriFalse, - GeneratedData: basicGenData(nil), - Expected: "default ansible_host=123.45.67.89 ansible_user=testuser ansible_port=1234\n", - }, - { - AnsibleVersion: 1, - User: "testuser", - Groups: []string{"Group1", "Group2"}, - UseProxy: confighelper.TriFalse, - GeneratedData: basicGenData(nil), - Expected: `default ansible_ssh_host=123.45.67.89 ansible_ssh_user=testuser ansible_ssh_port=1234 -[Group1] -default ansible_ssh_host=123.45.67.89 ansible_ssh_user=testuser ansible_ssh_port=1234 -[Group2] -default ansible_ssh_host=123.45.67.89 ansible_ssh_user=testuser ansible_ssh_port=1234 -`, - }, - { - AnsibleVersion: 1, - User: "testuser", - EmptyGroups: []string{"Group1", "Group2"}, - UseProxy: confighelper.TriFalse, - GeneratedData: basicGenData(nil), - Expected: `default ansible_ssh_host=123.45.67.89 ansible_ssh_user=testuser ansible_ssh_port=1234 -[Group1] -[Group2] -`, - }, - { - AnsibleVersion: 1, - User: "testuser", - Groups: []string{"Group1", "Group2"}, - EmptyGroups: []string{"Group3"}, - UseProxy: confighelper.TriFalse, - GeneratedData: basicGenData(nil), - Expected: `default ansible_ssh_host=123.45.67.89 ansible_ssh_user=testuser ansible_ssh_port=1234 -[Group1] -default ansible_ssh_host=123.45.67.89 ansible_ssh_user=testuser ansible_ssh_port=1234 -[Group2] -default ansible_ssh_host=123.45.67.89 ansible_ssh_user=testuser ansible_ssh_port=1234 -[Group3] -`, - }, - { - AnsibleVersion: 2, - User: "testuser", - UseProxy: confighelper.TriFalse, - GeneratedData: basicGenData(map[string]interface{}{ - "ConnType": "winrm", - "Password": "12345", - }), - Expected: "default ansible_host=123.45.67.89 ansible_connection=winrm ansible_winrm_transport=basic ansible_shell_type=powershell ansible_user=testuser ansible_port=1234\n", - }, - } - - for _, tc := range TestCases { - var p Provisioner - p.Prepare(testConfig(t)) - defer os.Remove(p.config.Command) - p.ansibleMajVersion = tc.AnsibleVersion - p.config.User = tc.User - p.config.Groups = tc.Groups - p.config.EmptyGroups = tc.EmptyGroups - p.config.UseProxy = tc.UseProxy - p.generatedData = tc.GeneratedData - - err := p.createInventoryFile() - if err != nil { - t.Fatalf("error creating config using localhost and local port proxy") - } - if p.config.InventoryFile == "" { - t.Fatalf("No inventory file was created") - } - defer os.Remove(p.config.InventoryFile) - f, err := ioutil.ReadFile(p.config.InventoryFile) - if err != nil { - t.Fatalf("couldn't read created inventoryfile: %s", err) - } - - expected := tc.Expected - if fmt.Sprintf("%s", f) != expected { - t.Fatalf("File didn't match expected:\n\n expected: \n%s\n; recieved: \n%s\n", expected, f) - } - } -} - -func basicGenData(input map[string]interface{}) map[string]interface{} { - gd := map[string]interface{}{ - "Host": "123.45.67.89", - "Port": int64(1234), - "ConnType": "ssh", - "SSHPrivateKeyFile": "", - "SSHPrivateKey": "asdf", - "SSHAgentAuth": false, - "User": "PartyPacker", - "PackerHTTPAddr": commonsteps.HttpAddrNotImplemented, - "PackerHTTPIP": commonsteps.HttpIPNotImplemented, - "PackerHTTPPort": commonsteps.HttpPortNotImplemented, - } - if input == nil { - return gd - } - for k, v := range input { - gd[k] = v - } - return gd -} - -func TestCreateCmdArgs(t *testing.T) { - type testcase struct { - TestName string - PackerBuildName string - PackerBuilderType string - UseProxy confighelper.Trilean - generatedData map[string]interface{} - AnsibleSSHExtraArgs []string - ExtraArguments []string - AnsibleEnvVars []string - callArgs []string // httpAddr inventory playbook privKeyFile - ExpectedArgs []string - ExpectedEnvVars []string - } - TestCases := []testcase{ - { - // SSH with private key and an extra argument. - TestName: "SSH with private key and an extra argument", - PackerBuildName: "packerparty", - generatedData: basicGenData(nil), - ExtraArguments: []string{"-e", "hello-world"}, - AnsibleEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - callArgs: []string{commonsteps.HttpAddrNotImplemented, "/var/inventory", "test-playbook.yml", "/path/to/privkey.pem"}, - ExpectedArgs: []string{"-e", "packer_build_name=\"packerparty\"", "-e", "packer_builder_type=fakebuilder", "-e", "ansible_ssh_private_key_file=/path/to/privkey.pem", "--ssh-extra-args", "'-o IdentitiesOnly=yes'", "-e", "hello-world", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - }, - { - // SSH with private key and an extra argument. - TestName: "SSH with private key and an extra argument and a ssh extra argument", - PackerBuildName: "packerparty", - generatedData: basicGenData(nil), - ExtraArguments: []string{"-e", "hello-world"}, - AnsibleSSHExtraArgs: []string{"-o IdentitiesOnly=no"}, - AnsibleEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - callArgs: []string{commonsteps.HttpAddrNotImplemented, "/var/inventory", "test-playbook.yml", "/path/to/privkey.pem"}, - ExpectedArgs: []string{"-e", "packer_build_name=\"packerparty\"", "-e", "packer_builder_type=fakebuilder", "--ssh-extra-args", "'-o IdentitiesOnly=no'", "-e", "ansible_ssh_private_key_file=/path/to/privkey.pem", "-e", "hello-world", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - }, - { - TestName: "SSH with private key and an extra argument and UseProxy", - PackerBuildName: "packerparty", - UseProxy: confighelper.TriTrue, - generatedData: basicGenData(nil), - ExtraArguments: []string{"-e", "hello-world"}, - callArgs: []string{commonsteps.HttpAddrNotImplemented, "/var/inventory", "test-playbook.yml", "/path/to/privkey.pem"}, - ExpectedArgs: []string{"-e", "packer_build_name=\"packerparty\"", "-e", "packer_builder_type=fakebuilder", "-e", "ansible_ssh_private_key_file=/path/to/privkey.pem", "--ssh-extra-args", "'-o IdentitiesOnly=yes'", "-e", "hello-world", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{}, - }, - { - // Winrm, but no_proxy is unset so we don't do anything with ansible_password. - TestName: "Winrm, but no_proxy is unset so we don't do anything with ansible_password", - PackerBuildName: "packerparty", - generatedData: basicGenData(map[string]interface{}{ - "ConnType": "winrm", - }), - ExtraArguments: []string{"-e", "hello-world"}, - AnsibleEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - callArgs: []string{commonsteps.HttpAddrNotImplemented, "/var/inventory", "test-playbook.yml", ""}, - ExpectedArgs: []string{"-e", "packer_build_name=\"packerparty\"", "-e", "packer_builder_type=fakebuilder", "-e", "hello-world", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - }, - { - // HTTPAddr should be set. No env vars. - TestName: "HTTPAddr should be set. No env vars", - PackerBuildName: "packerparty", - ExtraArguments: []string{"-e", "hello-world"}, - generatedData: basicGenData(map[string]interface{}{ - "PackerHTTPAddr": "123.45.67.89", - }), - callArgs: []string{"123.45.67.89", "/var/inventory", "test-playbook.yml", ""}, - ExpectedArgs: []string{"-e", "packer_build_name=\"packerparty\"", "-e", "packer_builder_type=fakebuilder", "-e", "packer_http_addr=123.45.67.89", "-e", "hello-world", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{}, - }, - { - // Add ansible_password for proxyless winrm connection. - TestName: "Add ansible_password for proxyless winrm connection.", - UseProxy: confighelper.TriFalse, - generatedData: basicGenData(map[string]interface{}{ - "ConnType": "winrm", - "Password": "ilovebananapancakes", - "PackerHTTPAddr": "123.45.67.89", - }), - AnsibleEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - callArgs: []string{"123.45.67.89", "/var/inventory", "test-playbook.yml", ""}, - ExpectedArgs: []string{"-e", "packer_builder_type=fakebuilder", "-e", "packer_http_addr=123.45.67.89", "-e", "ansible_password=ilovebananapancakes", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - }, - { - // Neither special ssh stuff, nor special windows stuff. This is docker! - TestName: "Neither special ssh stuff, nor special windows stuff. This is docker!", - PackerBuildName: "packerparty", - generatedData: basicGenData(map[string]interface{}{ - "ConnType": "docker", - }), - ExtraArguments: []string{"-e", "hello-world"}, - AnsibleEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - callArgs: []string{commonsteps.HttpAddrNotImplemented, "/var/inventory", "test-playbook.yml", ""}, - ExpectedArgs: []string{"-e", "packer_build_name=\"packerparty\"", "-e", "packer_builder_type=fakebuilder", "-e", "hello-world", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - }, - { - // Windows, no proxy, with extra vars. - TestName: "Windows, no proxy, with extra vars.", - UseProxy: confighelper.TriFalse, - generatedData: basicGenData(map[string]interface{}{ - "ConnType": "winrm", - "Password": "ilovebananapancakes", - "PackerHTTPAddr": "123.45.67.89", - }), - ExtraArguments: []string{"-e", "hello-world"}, - AnsibleEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - callArgs: []string{"123.45.67.89", "/var/inventory", "test-playbook.yml", ""}, - ExpectedArgs: []string{"-e", "packer_builder_type=fakebuilder", "-e", "packer_http_addr=123.45.67.89", "-e", "ansible_password=ilovebananapancakes", "-e", "hello-world", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - }, - { - // SSH, use Password. - TestName: "SSH, use ansible_password.", - generatedData: basicGenData(map[string]interface{}{ - "ConnType": "ssh", - "Password": "ilovebananapancakes", - "PackerHTTPAddr": "123.45.67.89", - }), - ExtraArguments: []string{"-e", "hello-world", "-e", "ansible_password=ilovebananapancakes"}, - AnsibleEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - callArgs: []string{"123.45.67.89", "/var/inventory", "test-playbook.yml", ""}, - ExpectedArgs: []string{"-e", "packer_builder_type=fakebuilder", "-e", "packer_http_addr=123.45.67.89", "-e", "hello-world", "-e", "ansible_password=ilovebananapancakes", "-e", "ansible_host_key_checking=False", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas"}, - }, - { - // SSH, use Password . - TestName: "SSH, already in ENV ansible_host_key_checking.", - generatedData: basicGenData(map[string]interface{}{ - "ConnType": "ssh", - "Password": "ilovebananapancakes", - "PackerHTTPAddr": "123.45.67.89", - }), - ExtraArguments: []string{"-e", "hello-world", "-e", "ansible_password=ilovebananapancakes"}, - AnsibleEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas", "ANSIBLE_HOST_KEY_CHECKING=False"}, - callArgs: []string{"123.45.67.89", "/var/inventory", "test-playbook.yml", ""}, - ExpectedArgs: []string{"-e", "packer_builder_type=fakebuilder", "-e", "packer_http_addr=123.45.67.89", "-e", "hello-world", "-e", "ansible_password=ilovebananapancakes", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{"ENV_1=pancakes", "ENV_2=bananas", "ANSIBLE_HOST_KEY_CHECKING=False"}, - }, - { - TestName: "Use PrivateKey", - PackerBuildName: "packerparty", - UseProxy: confighelper.TriTrue, - generatedData: basicGenData(nil), - ExtraArguments: []string{"-e", "hello-world"}, - callArgs: []string{commonsteps.HttpAddrNotImplemented, "/var/inventory", "test-playbook.yml", "/path/to/privkey.pem"}, - ExpectedArgs: []string{"-e", "packer_build_name=\"packerparty\"", "-e", "packer_builder_type=fakebuilder", "-e", "ansible_ssh_private_key_file=/path/to/privkey.pem", "--ssh-extra-args", "'-o IdentitiesOnly=yes'", "-e", "hello-world", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{}, - }, - { - TestName: "Use PrivateKey and SSH Extra Arg", - PackerBuildName: "packerparty", - UseProxy: confighelper.TriTrue, - generatedData: basicGenData(nil), - AnsibleSSHExtraArgs: []string{"-o IdentitiesOnly=no"}, - ExtraArguments: []string{"-e", "hello-world"}, - callArgs: []string{commonsteps.HttpAddrNotImplemented, "/var/inventory", "test-playbook.yml", "/path/to/privkey.pem"}, - ExpectedArgs: []string{"-e", "packer_build_name=\"packerparty\"", "-e", "packer_builder_type=fakebuilder", "-e", "ansible_ssh_private_key_file=/path/to/privkey.pem", "--ssh-extra-args", "'-o IdentitiesOnly=no'", "-e", "hello-world", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{}, - }, - { - TestName: "Use SSH Agent", - UseProxy: confighelper.TriTrue, - generatedData: basicGenData(nil), - callArgs: []string{commonsteps.HttpAddrNotImplemented, "/var/inventory", "test-playbook.yml", ""}, - ExpectedArgs: []string{"-e", "packer_builder_type=fakebuilder", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{}, - }, - { - // No builder name. This shouldn't cause an error, it just shouldn't be set. HCL, yo. - TestName: "No builder name. This shouldn't cause an error, it just shouldn't be set. HCL, yo.", - generatedData: basicGenData(nil), - callArgs: []string{commonsteps.HttpAddrNotImplemented, "/var/inventory", "test-playbook.yml", ""}, - ExpectedArgs: []string{"-e", "packer_builder_type=fakebuilder", "-i", "/var/inventory", "test-playbook.yml"}, - ExpectedEnvVars: []string{}, - }, - } - - for _, tc := range TestCases { - var p Provisioner - p.Prepare(testConfig(t)) - defer os.Remove(p.config.Command) - p.config.UseProxy = tc.UseProxy - p.config.PackerBuilderType = "fakebuilder" - p.config.PackerBuildName = tc.PackerBuildName - p.generatedData = tc.generatedData - p.config.AnsibleSSHExtraArgs = tc.AnsibleSSHExtraArgs - p.config.ExtraArguments = tc.ExtraArguments - p.config.AnsibleEnvVars = tc.AnsibleEnvVars - - args, envVars := p.createCmdArgs(tc.callArgs[0], tc.callArgs[1], tc.callArgs[2], tc.callArgs[3]) - assert.ElementsMatch(t, args, tc.ExpectedArgs, - "TestName: %s\nArgs didn't match expected:\nexpected: \n%s\n; recieved: \n%s\n", tc.TestName, tc.ExpectedArgs, args) - assert.ElementsMatch(t, envVars, tc.ExpectedEnvVars, - "TestName: %s\nArgs didn't match expected:\n\nEnvVars didn't match expected:\n\n expected: \n%s\n; recieved: \n%s\n", tc.TestName, tc.ExpectedEnvVars, envVars) - assert.EqualValues(t, tc.callArgs[2], args[len(args)-1], - "TestName: %s\nPlayBook File Not Returned as last element: \nexpected: %s\nrecieved: %s\n", tc.TestName, tc.callArgs[2], args[len(args)-1]) - } -} - -func TestUseProxy(t *testing.T) { - type testcase struct { - UseProxy confighelper.Trilean - generatedData map[string]interface{} - expectedSetupAdapterCalled bool - explanation string - } - - tcs := []testcase{ - { - explanation: "use_proxy is true; we should set up adapter", - UseProxy: confighelper.TriTrue, - generatedData: basicGenData(nil), - expectedSetupAdapterCalled: true, - }, - { - explanation: "use_proxy is false but no IP addr is available; we should set up adapter anyway.", - UseProxy: confighelper.TriFalse, - generatedData: basicGenData(map[string]interface{}{ - "Host": "", - "Port": nil, - }), - expectedSetupAdapterCalled: true, - }, - { - explanation: "use_proxy is false; we shouldn't set up adapter.", - UseProxy: confighelper.TriFalse, - generatedData: basicGenData(nil), - expectedSetupAdapterCalled: false, - }, - { - explanation: "use_proxy is false but connType isn't ssh or winrm.", - UseProxy: confighelper.TriFalse, - generatedData: basicGenData(map[string]interface{}{ - "ConnType": "docker", - }), - expectedSetupAdapterCalled: true, - }, - { - explanation: "use_proxy is unset; we should default to setting up the adapter (for now).", - UseProxy: confighelper.TriUnset, - generatedData: basicGenData(nil), - expectedSetupAdapterCalled: true, - }, - { - explanation: "use_proxy is false and connType is winRM. we should not set up the adapter.", - UseProxy: confighelper.TriFalse, - generatedData: basicGenData(map[string]interface{}{ - "ConnType": "winrm", - }), - expectedSetupAdapterCalled: false, - }, - { - explanation: "use_proxy is unset and connType is winRM. we should set up the adapter.", - UseProxy: confighelper.TriUnset, - generatedData: basicGenData(map[string]interface{}{ - "ConnType": "winrm", - }), - expectedSetupAdapterCalled: true, - }, - } - - for _, tc := range tcs { - var p Provisioner - p.Prepare(testConfig(t)) - p.config.UseProxy = tc.UseProxy - defer os.Remove(p.config.Command) - p.ansibleMajVersion = 1 - - var l provisionLogicTracker - l.setupAdapterCalled = false - p.setupAdapterFunc = l.setupAdapter - p.executeAnsibleFunc = l.executeAnsible - ctx := context.TODO() - comm := new(packersdk.MockCommunicator) - ui := &packersdk.BasicUi{ - Reader: new(bytes.Buffer), - Writer: new(bytes.Buffer), - } - p.Provision(ctx, ui, comm, tc.generatedData) - - if l.setupAdapterCalled != tc.expectedSetupAdapterCalled { - t.Fatalf("%s", tc.explanation) - } - os.Remove(p.config.Command) - } -} diff --git a/provisioner/ansible/test-fixtures/exit1 b/provisioner/ansible/test-fixtures/exit1 deleted file mode 100755 index 2bb8d868b..000000000 --- a/provisioner/ansible/test-fixtures/exit1 +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -exit 1 diff --git a/provisioner/ansible/test-fixtures/long-debug-message.yml b/provisioner/ansible/test-fixtures/long-debug-message.yml deleted file mode 100644 index e633061bd..000000000 --- a/provisioner/ansible/test-fixtures/long-debug-message.yml +++ /dev/null @@ -1,8 +0,0 @@ -- name: Stub for Packer testing long Ansible messages - hosts: localhost - connection: local - - tasks: - - name: Very long Ansible output (>65535 chars) (Issue https://github.com/hashicorp/packer/issues/3268) - debug: - msg: "{{ lipsum(n=300, html=false) }}" diff --git a/provisioner/ansible/version/version.go b/provisioner/ansible/version/version.go deleted file mode 100644 index 09e4c48ee..000000000 --- a/provisioner/ansible/version/version.go +++ /dev/null @@ -1,13 +0,0 @@ -package version - -import ( - "github.com/hashicorp/packer-plugin-sdk/version" - packerVersion "github.com/hashicorp/packer/version" -) - -var AnsiblePluginVersion *version.PluginVersion - -func init() { - AnsiblePluginVersion = version.InitializePluginVersion( - packerVersion.Version, packerVersion.VersionPrerelease) -} diff --git a/test/fixtures/provisioner-ansible/all_options.json b/test/fixtures/provisioner-ansible/all_options.json deleted file mode 100644 index 561d8d275..000000000 --- a/test/fixtures/provisioner-ansible/all_options.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "variables": {}, - "provisioners": [ - { - "type": "shell-local", - "command": "echo 'TODO(bhcleek): write the public key to $HOME/.ssh/known_hosts and stop using ANSIBLE_HOST_KEY_CHECKING=False'" - }, { - "type": "ansible", - "playbook_file": "./playbook.yml", - "extra_arguments": [ - "--private-key", "ansible-test-id" - ], - "sftp_command": "/usr/lib/sftp-server -e -l INFO", - "use_sftp": true, - "ansible_env_vars": ["PACKER_ANSIBLE_TEST=1", "ANSIBLE_HOST_KEY_CHECKING=False"], - "groups": ["PACKER_TEST"], - "empty_groups": ["PACKER_EMPTY_GROUP"], - "host_alias": "packer-test", - "user": "packer", - "local_port": 2222, - "ssh_host_key_file": "ansible-server.key", - "ssh_authorized_key_file": "ansible-test-id.pub" - } - ], - "builders": [{ - "type": "googlecompute", - "account_file": "{{user `account_file`}}", - "project_id": "{{user `project_id`}}", - "image_name": "packerbats-alloptions-{{timestamp}}", - "source_image": "debian-8-jessie-v20161027", - "zone": "us-central1-a", - "ssh_username": "debian" - }] -} diff --git a/test/fixtures/provisioner-ansible/connection_plugins/packer.py b/test/fixtures/provisioner-ansible/connection_plugins/packer.py deleted file mode 100644 index f0bb10da8..000000000 --- a/test/fixtures/provisioner-ansible/connection_plugins/packer.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.connection.ssh import Connection as SSHConnection - -class Connection(SSHConnection): - ''' ssh based connections for powershell via packer''' - - transport = 'packer' - has_pipelining = True - become_methods = [] - allow_executable = False - module_implementation_preferences = ('.ps1', '') - - def __init__(self, *args, **kwargs): - super(Connection, self).__init__(*args, **kwargs) diff --git a/test/fixtures/provisioner-ansible/dir/contents-only/file.txt b/test/fixtures/provisioner-ansible/dir/contents-only/file.txt deleted file mode 100644 index 426dfd53e..000000000 --- a/test/fixtures/provisioner-ansible/dir/contents-only/file.txt +++ /dev/null @@ -1 +0,0 @@ -this file's parent directory should not be transferred to the node. diff --git a/test/fixtures/provisioner-ansible/dir/file.txt b/test/fixtures/provisioner-ansible/dir/file.txt deleted file mode 100644 index 0637880d7..000000000 --- a/test/fixtures/provisioner-ansible/dir/file.txt +++ /dev/null @@ -1 +0,0 @@ -This is a file diff --git a/test/fixtures/provisioner-ansible/dir/subdir/file.txt b/test/fixtures/provisioner-ansible/dir/subdir/file.txt deleted file mode 100644 index faba4c25d..000000000 --- a/test/fixtures/provisioner-ansible/dir/subdir/file.txt +++ /dev/null @@ -1 +0,0 @@ -This file and its parent directory should be transferred to the node. diff --git a/test/fixtures/provisioner-ansible/docker.json b/test/fixtures/provisioner-ansible/docker.json deleted file mode 100644 index 9cc788b51..000000000 --- a/test/fixtures/provisioner-ansible/docker.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "variables": {}, - "provisioners": [ - { - "type": "shell", - "inline": [ - "apt-get update", - "apt-get -y install python" - ] - }, { - "type": "ansible", - "playbook_file": "./playbook.yml", - "extra_arguments": [ - ], - "sftp_command": "/usr/bin/false", - "use_sftp": false - } - ], - "builders": [ - { - "type": "docker", - "image": "debian:jessie", - "discard": true - } - ] -} diff --git a/test/fixtures/provisioner-ansible/galaxy-playbook.yml b/test/fixtures/provisioner-ansible/galaxy-playbook.yml deleted file mode 100644 index b91a3dab4..000000000 --- a/test/fixtures/provisioner-ansible/galaxy-playbook.yml +++ /dev/null @@ -1,33 +0,0 @@ ---- -- 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 deleted file mode 100644 index 879c925f6..000000000 --- a/test/fixtures/provisioner-ansible/galaxy.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "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/largish-file.txt b/test/fixtures/provisioner-ansible/largish-file.txt deleted file mode 100644 index 56036f7500758c5431fe9fadae8c42643ea349cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66556 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 aViLPsk%G>#=Ey>Ut/dev/null 2>&1 || { - echo "'gcloud' must be installed" >&2 - exit 1 -} - -USER_VARS="${USER_VARS} -var account_file=${GC_ACCOUNT_FILE}" -USER_VARS="${USER_VARS} -var project_id=${GC_PROJECT_ID}" - -# This tests if GCE has an image that contains the given parameter. -gc_has_image() { - gcloud compute --format='table[no-heading](name)' --project=${GC_PROJECT_ID} images list \ - | grep $1 | wc -l -} - -setup(){ - rm -f $FIXTURE_ROOT/ansible-test-id - rm -f $FIXTURE_ROOT/ansible-server.key - ssh-keygen -N "" -f $FIXTURE_ROOT/ansible-test-id - ssh-keygen -N "" -f $FIXTURE_ROOT/ansible-server.key -} - -teardown() { - gcloud compute --format='table[no-heading](name)' --project=${GC_PROJECT_ID} images list \ - | grep packerbats \ - | xargs -n1 gcloud compute --project=${GC_PROJECT_ID} images delete - - rm -f $FIXTURE_ROOT/ansible-test-id - rm -f $FIXTURE_ROOT/ansible-test-id.pub - rm -f $FIXTURE_ROOT/ansible-server.key - rm -f $FIXTURE_ROOT/ansible-server.key.pub - rm -rf $FIXTURE_ROOT/fetched-dir -} - -@test "ansible provisioner: build docker.json" { - cd $FIXTURE_ROOT - run packer build ${USER_VARS} $FIXTURE_ROOT/docker.json - [ "$status" -eq 0 ] - diff -r dir fetched-dir/default/tmp/remote-dir > /dev/null -} -@test "ansible provisioner: build minimal.json" { - cd $FIXTURE_ROOT - run packer build ${USER_VARS} $FIXTURE_ROOT/minimal.json - [ "$status" -eq 0 ] - [ "$(gc_has_image "packerbats-minimal")" -eq 1 ] - diff -r dir fetched-dir/default/tmp/remote-dir > /dev/null -} - -@test "ansible provisioner: build all_options.json" { - cd $FIXTURE_ROOT - run packer build ${USER_VARS} $FIXTURE_ROOT/all_options.json - [ "$status" -eq 0 ] - [ "$(gc_has_image "packerbats-alloptions")" -eq 1 ] - 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 - [ "$status" -eq 0 ] - [ "$(gc_has_image "packerbats-scp")" -eq 1 ] - diff -r dir fetched-dir/default/tmp/remote-dir > /dev/null -} - -@test "ansible provisioner: build scp-to-sftp.json" { - cd $FIXTURE_ROOT - run packer build ${USER_VARS} $FIXTURE_ROOT/scp-to-sftp.json - [ "$status" -eq 0 ] - [ "$(gc_has_image "packerbats-scp-to-sftp")" -eq 1 ] - diff -r dir fetched-dir/default/tmp/remote-dir > /dev/null -} - -@test "ansible provisioner: build sftp.json" { - cd $FIXTURE_ROOT - run packer build ${USER_VARS} $FIXTURE_ROOT/sftp.json - [ "$status" -eq 0 ] - [ "$(gc_has_image "packerbats-sftp")" -eq 1 ] - diff -r dir fetched-dir/default/tmp/remote-dir > /dev/null -} - -@test "ansible provisioner: build winrm.json" { - cd $FIXTURE_ROOT - run packer build ${USER_VARS} $FIXTURE_ROOT/winrm.json - [ "$status" -eq 0 ] - [ "$(gc_has_image "packerbats-winrm")" -eq 1 ] - echo "packer does not support downloading files from download, skipping verification" - #diff -r dir fetched-dir/default/tmp/remote-dir > /dev/null -} diff --git a/vendor/github.com/hashicorp/packer-plugin-ansible/LICENSE b/vendor/github.com/hashicorp/packer-plugin-ansible/LICENSE new file mode 100644 index 000000000..a612ad981 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-ansible/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/provisioner/ansible-local/communicator_mock.go b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/communicator_mock.go similarity index 98% rename from provisioner/ansible-local/communicator_mock.go rename to vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/communicator_mock.go index 4cec79020..d13625b2b 100644 --- a/provisioner/ansible-local/communicator_mock.go +++ b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/communicator_mock.go @@ -36,5 +36,6 @@ func (c *communicatorMock) DownloadDir(src, dst string, exclude []string) error return nil } +//nolint:unused func (c *communicatorMock) verify() { } diff --git a/provisioner/ansible-local/provisioner.go b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/provisioner.go similarity index 100% rename from provisioner/ansible-local/provisioner.go rename to vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/provisioner.go diff --git a/provisioner/ansible-local/provisioner.hcl2spec.go b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/provisioner.hcl2spec.go similarity index 100% rename from provisioner/ansible-local/provisioner.hcl2spec.go rename to vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/provisioner.hcl2spec.go diff --git a/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/test-fixtures/docker_playbookdir_template.pkr.hcl b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/test-fixtures/docker_playbookdir_template.pkr.hcl new file mode 100644 index 000000000..c8aa349b1 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/test-fixtures/docker_playbookdir_template.pkr.hcl @@ -0,0 +1,31 @@ +packer { + required_version = ">1.7.0" + + required_plugins { + docker = { + source = "github.com/hashicorp/docker" + version = ">=0.0.7" + } + } +} + +source "docker" "autogenerated_1" { + discard = true + image = "williamyeh/ansible:centos7" +} + +build { + sources = ["source.docker.autogenerated_1"] + + provisioner "ansible-local" { + playbook_dir = "test-fixtures" + playbook_files = ["test-fixtures/hello.yml", "test-fixtures/world.yml"] + } + + provisioner "file" { + destination = "hello_world" + direction = "download" + source = "/tmp/hello_world" + } + +} diff --git a/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/test-fixtures/docker_playbookfiles_template.pkr.hcl b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/test-fixtures/docker_playbookfiles_template.pkr.hcl new file mode 100644 index 000000000..7e7f51a42 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local/test-fixtures/docker_playbookfiles_template.pkr.hcl @@ -0,0 +1,30 @@ +packer { + required_version = ">1.7.0" + + required_plugins { + docker = { + source = "github.com/hashicorp/docker" + version = ">=0.0.7" + } + } +} + +source "docker" "autogenerated_1" { + discard = true + image = "williamyeh/ansible:centos7" +} + +build { + sources = ["source.docker.autogenerated_1"] + + provisioner "ansible-local" { + playbook_files = ["test-fixtures/hello.yml", "test-fixtures/world.yml"] + } + + provisioner "file" { + destination = "hello_world" + direction = "download" + source = "/tmp/hello_world" + } + +} diff --git a/provisioner/ansible/mock_ansible.go b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible/mock_ansible.go similarity index 100% rename from provisioner/ansible/mock_ansible.go rename to vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible/mock_ansible.go diff --git a/provisioner/ansible/provisioner.go b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible/provisioner.go similarity index 99% rename from provisioner/ansible/provisioner.go rename to vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible/provisioner.go index e6a67071c..79b82a0a2 100644 --- a/provisioner/ansible/provisioner.go +++ b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible/provisioner.go @@ -485,7 +485,9 @@ func (p *Provisioner) createInventoryFile() error { } w := bufio.NewWriter(tf) - w.WriteString(host) + if _, err := w.WriteString(host); err != nil { + log.Printf("[TRACE] error writing the generated inventory file: %s", err) + } for _, group := range p.config.Groups { fmt.Fprintf(w, "[%s]\n%s", group, host) @@ -681,7 +683,7 @@ func (p *Provisioner) executeGalaxy(ui packersdk.Ui, comm packersdk.Communicator // 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 { - ui.Message(fmt.Sprintf("Executing Ansible Galaxy")) + ui.Message("Executing Ansible Galaxy") cmd := exec.Command(p.config.GalaxyCommand, args...) stdout, err := cmd.StdoutPipe() diff --git a/provisioner/ansible/provisioner.hcl2spec.go b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible/provisioner.hcl2spec.go similarity index 100% rename from provisioner/ansible/provisioner.hcl2spec.go rename to vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible/provisioner.hcl2spec.go diff --git a/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible/test-fixtures/docker_playbookfile_template.pkr.hcl b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible/test-fixtures/docker_playbookfile_template.pkr.hcl new file mode 100644 index 000000000..a07d0f1e8 --- /dev/null +++ b/vendor/github.com/hashicorp/packer-plugin-ansible/provisioner/ansible/test-fixtures/docker_playbookfile_template.pkr.hcl @@ -0,0 +1,23 @@ +packer { + required_version = ">1.7.0" + + required_plugins { + docker = { + source = "github.com/hashicorp/docker" + version = ">=0.0.7" + } + } +} + +source "docker" "autogenerated_1" { + discard = true + image = "williamyeh/ansible:centos7" +} + +build { + sources = ["source.docker.autogenerated_1"] + + provisioner "ansible" { + playbook_file = "test-fixtures/long-debug-message.yml" + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 89835e8a3..1002ef874 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -509,6 +509,10 @@ github.com/hashicorp/packer-plugin-amazon/builder/instance github.com/hashicorp/packer-plugin-amazon/datasource/ami github.com/hashicorp/packer-plugin-amazon/datasource/secretsmanager github.com/hashicorp/packer-plugin-amazon/post-processor/import +# github.com/hashicorp/packer-plugin-ansible v0.0.2 +## explicit +github.com/hashicorp/packer-plugin-ansible/provisioner/ansible +github.com/hashicorp/packer-plugin-ansible/provisioner/ansible-local # github.com/hashicorp/packer-plugin-docker v0.0.7 ## explicit github.com/hashicorp/packer-plugin-docker/builder/docker diff --git a/website/content/docs/provisioners/ansible-local.mdx b/website/content/docs/provisioners/ansible-local.mdx deleted file mode 100644 index bfeeb1bee..000000000 --- a/website/content/docs/provisioners/ansible-local.mdx +++ /dev/null @@ -1,136 +0,0 @@ ---- -description: > - The ansible-local Packer provisioner will run ansible in ansible's "local" - mode on the remote/guest VM using Playbook and Role files that exist on the - guest VM. This means Ansible must be installed on the remote/guest VM. - Playbooks and Roles can be uploaded from your build machine (the one running - Packer) to the vm. -page_title: Ansible Local - Provisioners ---- - -# Ansible Local Provisioner - -Type: `ansible-local` - -The `ansible-local` Packer provisioner will execute `ansible` in Ansible's "local" -mode on the remote/guest VM using Playbook and Role files that exist on the -guest VM. This means Ansible must be installed on the remote/guest VM. -Playbooks and Roles can be uploaded from your build machine (the one running -Packer) to the vm. Ansible is then run on the guest machine in [local -mode](https://docs.ansible.com/ansible/latest/playbooks_delegation.html#local-playbooks) -via the `ansible-playbook` command. - --> **Note:** Ansible will _not_ be installed automatically by this -provisioner. This provisioner expects that Ansible is already installed on the -guest/remote machine. It is common practice to use the [shell -provisioner](/docs/provisioners/shell) before the Ansible provisioner to -do this. - -## Basic Example - -The example below is fully functional. - - - - -```json -{ - "builders": [ - { - "type": "docker", - "image": "williamyeh/ansible:ubuntu14.04", - "export_path": "packer_example", - "run_command": ["-d", "-i", "-t", "--entrypoint=/bin/bash", "{{.Image}}"] - } - ], - "variables": { - "topping": "mushroom" - }, - "provisioners": [ - { - "type": "ansible-local", - "playbook_file": "./playbook.yml", - "extra_arguments": [ - "--extra-vars", - "\"pizza_toppings={{ user `topping`}}\"" - ] - } - ] -} -``` - - - - -```hcl -variable "topping" { - type = string - default = "mushroom" -} - -source "docker" "example" { - image = "williamyeh/ansible:ubuntu14.04" - export_path = "packer_example" - run_command = ["-d", "-i", "-t", "--entrypoint=/bin/bash", "{{.Image}}"] -} - -build { - sources = [ - "source.docker.example" - ] - - provisioner "ansible-local" { - playbook_file = "./playbook.yml" - extra_arguments = ["--extra-vars", "\"pizza_toppings=${var.topping}\""] - } -} -``` - - - - -where ./playbook.yml contains - -``` ---- -- name: hello world - hosts: 127.0.0.1 - connection: local - - tasks: - - command: echo {{ pizza_toppings }} - - debug: msg="{{ pizza_toppings }}" - -``` - -## Configuration Reference - -The reference of available configuration options is listed below. - -Note that one of `playbook_file` or `playbook_files` is required. - -@include '/provisioner/ansible/Config-not-required.mdx' - -@include 'provisioners/common-config.mdx' - -## Default Extra Variables - -In addition to being able to specify extra arguments using the -`extra_arguments` configuration, the provisioner automatically defines certain -commonly useful Ansible variables: - -- `packer_build_name` is set to the name of the build that Packer is running. - This is most useful when Packer is making multiple builds and you want to - distinguish them slightly when using a common playbook. - -- `packer_builder_type` is the type of the builder that was used to create - the machine that the script is running on. This is useful if you want to - run only certain parts of the playbook on systems built with certain - builders. - -- `packer_http_addr` If using a builder that provides an HTTP server for file - transfer (such as `hyperv`, `parallels`, `qemu`, `virtualbox`, and `vmware`), this - will be set to the address. You can use this address in your provisioner to - download large files over HTTP. This may be useful if you're experiencing - slower speeds using the default file provisioner. A file provisioner using - the `winrm` communicator may experience these types of difficulties. diff --git a/website/content/docs/provisioners/ansible.mdx b/website/content/docs/provisioners/ansible.mdx deleted file mode 100644 index 86228cd32..000000000 --- a/website/content/docs/provisioners/ansible.mdx +++ /dev/null @@ -1,805 +0,0 @@ ---- -description: | - The ansible Packer provisioner allows Ansible playbooks to be run to provision - the machine. -page_title: Ansible - Provisioners ---- - -# Ansible Provisioner - -Type: `ansible` - -The `ansible` Packer provisioner runs Ansible playbooks. It dynamically creates -an Ansible inventory file configured to use SSH, runs an SSH server, executes -`ansible-playbook`, and marshals Ansible plays through the SSH server to the -machine being provisioned by Packer. - --> **Note:** Any `remote_user` defined in tasks will be ignored. Packer -will always connect with the user given in the json config for this -provisioner. - --> **Note:** Options below that use the Packer template engine won't be able to -accept jinja2 `{{ function }}` macro syntax in a way that can be preserved to -the Ansible run. If you need to set variables using Ansible macros, you need to -do so inside your playbooks or inventory files. - -Please see the [Debugging](#debugging), [Limitations](#limitations), or [Troubleshooting](#troubleshooting) if you are having trouble -getting started. - -## Basic Example - -This is a fully functional template that will provision an image on -DigitalOcean. Replace the mock `api_token` value with your own. - -Example Packer template: - - - - -```hcl -source "digitalocean" "example"{ - api_token = "6a561151587389c7cf8faa2d83e94150a4202da0e2bad34dd2bf236018ffaeeb" - image = "ubuntu-20-04-x64" - region = "sfo1" -} - -build { - sources = [ - "source.digitalocean.example" - ] - - provisioner "ansible" { - playbook_file = "./playbook.yml" - } -} -``` - - - - -```json -{ - "builders": [ - { - "type": "digitalocean", - "api_token": "6a561151587389c7cf8faa2d83e94150a4202da0e2bad34dd2bf236018ffaeeb", - "image": "ubuntu-20-04-x64", - "region": "sfo1" - } - ], - "provisioners": [ - { - "type": "ansible", - "playbook_file": "./playbook.yml" - } - ] -} -``` - - - - -Example playbook: - -```yaml ---- -# playbook.yml -- name: 'Provision Image' - hosts: default - become: true - - tasks: - - name: install Apache - package: - name: 'httpd' - state: present -``` - -## Configuration Reference - -Required Parameters: - -@include 'provisioner/ansible/Config-required.mdx' - -Optional Parameters: - -@include '/provisioner/ansible/Config-not-required.mdx' - -@include 'provisioners/common-config.mdx' - -## Default Extra Variables - -In addition to being able to specify extra arguments using the -`extra_arguments` configuration, the provisioner automatically defines certain -commonly useful Ansible variables: - -- `packer_build_name` is set to the name of the build that Packer is running. - This is most useful when Packer is making multiple builds and you want to - distinguish them slightly when using a common playbook. - -- `packer_builder_type` is the type of the builder that was used to create - the machine that the script is running on. This is useful if you want to - run only certain parts of the playbook on systems built with certain - builders. - -- `packer_http_addr` If using a builder that provides an HTTP server for file - transfer (such as `hyperv`, `parallels`, `qemu`, `virtualbox`, and `vmware`), this - will be set to the address. You can use this address in your provisioner to - download large files over HTTP. This may be useful if you're experiencing - slower speeds using the default file provisioner. A file provisioner using - the `winrm` communicator may experience these types of difficulties. - -## Debugging - -To debug underlying issues with Ansible, add `"-vvvv"` to `"extra_arguments"` -to enable verbose logging. - - - - -```hcl - extra_arguments = [ "-vvvv" ] -``` - - - - -```json - "extra_arguments": [ "-vvvv" ] -``` - - - - -## Limitations - -### Redhat / CentOS - -Redhat / CentOS builds have been known to fail with the following error due to -`sftp_command`, which should be set to `/usr/libexec/openssh/sftp-server -e`: - -```text -==> virtualbox-ovf: starting sftp subsystem - virtualbox-ovf: fatal: [default]: UNREACHABLE! => {"changed": false, "msg": "SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh", "unreachable": true} -``` - -### chroot communicator - -Building within a chroot (e.g. `amazon-chroot`) requires changing the Ansible -connection to chroot and running Ansible as root/sudo. - - - - -```hcl -source "amazon-chroot" "example" { - mount_path = "/mnt/packer-amazon-chroot" - region = "us-east-1" - source_ami = "ami-123456" -} - -build { - sources = [ - "source.amazon-chroot.example" - ] - - provisioner "ansible" { - extra_arguments = [ - "--connection=chroot", - "--inventory-file=/mnt/packer-amazon-chroot" - ] - - playbook_file = "main.yml" - } -} -``` - - - - -```json -{ - "builders": [ - { - "type": "amazon-chroot", - "mount_path": "/mnt/packer-amazon-chroot", - "region": "us-east-1", - "source_ami": "ami-123456" - } - ], - "provisioners": [ - { - "type": "ansible", - "extra_arguments": [ - "--connection=chroot", - "--inventory-file=/mnt/packer-amazon-chroot" - ], - "playbook_file": "main.yml" - } - ] -} -``` - - - - -### WinRM Communicator - -There are two possible methods for using Ansible with the WinRM communicator. - -Please note that if you're having trouble getting Ansible to connect, you may -want to take a look at the script that the Ansible project provides to help -configure remoting for Ansible: -https://github.com/ansible/ansible/blob/devel/examples/scripts/ConfigureRemotingForAnsible.ps1 - -#### Method 1 (recommended) - -The recommended way to use the WinRM communicator is to set `"use_proxy": false` -and let the Ansible provisioner handle the rest for you. If you -are using WinRM with HTTPS, and you are using a self-signed certificate you -will also have to set `ansible_winrm_server_cert_validation=ignore` in your -extra_arguments. - -Below is a fully functioning Ansible example using WinRM: - - - - -```hcl -data "amazon-ami" "ubuntu" { - access_key = var.aws_access_key - - filters = { - name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*" - root-device-type = "ebs" - virtualization-type = "hvm" - } - - most_recent = true - owners = ["099720109477"] - region = var.aws_region - secret_key = var.aws_secret_key -} - -source "amazon-ebs" "example" { - region = "us-east-1" - instance_type = "t2.micro" - - source_ami = data.amazon-ami.ubuntu.id - - ami_name = "test-ansible-packer" - user_data_file = "windows_bootstrap.txt" - communicator = "winrm" - force_deregister = true - winrm_username = "Administrator" - winrm_insecure = true - winrm_use_ssl = true -} - -build { - sources = [ - "source.amazon-ebs.example", - ] - - provisioner "ansible" { - playbook_file = "./playbooks/playbook-windows.yml" - user = "Administrator" - use_proxy = false - extra_arguments = [ - "-e", - "ansible_winrm_server_cert_validation=ignore" - ] - } -} -``` - - - - -```json -{ - "builders": [ - { - "type": "amazon-ebs", - "region": "us-east-1", - "instance_type": "t2.micro", - "source_ami_filter": { - "filters": { - "virtualization-type": "hvm", - "name": "*Windows_Server-2012*English-64Bit-Base*", - "root-device-type": "ebs" - }, - "most_recent": true, - "owners": "amazon" - }, - "ami_name": "test-ansible-packer", - "user_data_file": "windows_bootstrap.txt", - "communicator": "winrm", - "force_deregister": true, - "winrm_insecure": true, - "winrm_username": "Administrator", - "winrm_use_ssl": true - } - ], - "provisioners": [ - { - "type": "ansible", - "playbook_file": "./playbook.yml", - "user": "Administrator", - "use_proxy": false, - "extra_arguments": ["-e", "ansible_winrm_server_cert_validation=ignore"] - } - ] -} -``` - - - - -Note that you do have to set the "Administrator" user, because otherwise Ansible -will default to using the user that is calling Packer, rather than the user -configured inside of the Packer communicator. For the contents of -windows_bootstrap.txt, see the WinRM docs for the amazon-ebs communicator. - -When running from OSX, you may see an error like: - -```text - amazon-ebs: objc[9752]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. - amazon-ebs: objc[9752]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug. - amazon-ebs: ERROR! A worker was found in a dead state -``` - -If you see this, you may be able to work around the issue by telling Ansible to -explicitly not use any proxying; you can do this by setting the template option - - - - -```hcl -ansible_env_vars = ["no_proxy=\"*\""] -``` - - - - -```json -"ansible_env_vars": ["no_proxy=\"*\""], -``` - - - - -in the above Ansible template. - -#### Method 2 (Not recommended) - -If you want to use the Packer SSH proxy, then you need a custom Ansible -connection plugin and a particular configuration. You need a directory named -`connection_plugins` next to the playbook which contains a file named -packer.py` which implements the connection plugin. On versions of Ansible -before 2.4.x, the following works as the connection plugin: - -```python -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.connection.ssh import Connection as SSHConnection - -class Connection(SSHConnection): - ''' ssh based connections for powershell via packer''' - - transport = 'packer' - has_pipelining = True - become_methods = [] - allow_executable = False - module_implementation_preferences = ('.ps1', '') - - def __init__(self, *args, **kwargs): - super(Connection, self).__init__(*args, **kwargs) -``` - -Newer versions of Ansible require all plugins to have a documentation string. -You can see if there is a plugin available for the version of Ansible you are -using -[here](https://github.com/hashicorp/packer/tree/master/provisioner/ansible/examples/connection-plugin). - -To create the plugin yourself, you will need to copy all of the `options` from -the `DOCUMENTATION` string from the [ssh.py Ansible connection -plugin](https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/connection/ssh.py) -of the Ansible version you are using and add it to a packer.py file similar to -as follows - -```python -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from ansible.plugins.connection.ssh import Connection as SSHConnection - -DOCUMENTATION = ''' - connection: packer - short_description: ssh based connections for powershell via packer - description: - - This connection plugin allows ansible to communicate to the target packer machines via ssh based connections for powershell. - author: Packer - version_added: na - options: - **** Copy ALL the options from - https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/connection/ssh.py - for the version of Ansible you are using **** -''' - -class Connection(SSHConnection): - ''' ssh based connections for powershell via packer''' - - transport = 'packer' - has_pipelining = True - become_methods = [] - allow_executable = False - module_implementation_preferences = ('.ps1', '') - - def __init__(self, *args, **kwargs): - super(Connection, self).__init__(*args, **kwargs) -``` - -This template should build a Windows Server 2012 image on Google Cloud -Platform: - -```json -{ - "variables": {}, - "provisioners": [ - { - "type": "ansible", - "playbook_file": "./win-playbook.yml", - "extra_arguments": [ - "--connection", - "packer", - "--extra-vars", - "\"ansible_shell_type=powershell ansible_shell_executable=None\"" - ] - } - ], - "builders": [ - { - "type": "googlecompute", - "account_file": "{{ user `account_file`}}", - "project_id": "{{user `project_id`}}", - "source_image": "windows-server-2012-r2-dc-v20160916", - "communicator": "winrm", - "zone": "us-central1-a", - "disk_size": 50, - "winrm_username": "packer", - "winrm_use_ssl": true, - "winrm_insecure": true, - "metadata": { - "sysprep-specialize-script-cmd": "winrm set winrm/config/service/auth @{Basic=\"true\"}" - } - } - ] -} -``` - --> **Warning:** Please note that if you're setting up WinRM for provisioning, you'll probably want to turn it off or restrict its permissions as part of a shutdown script at the end of Packer's provisioning process. For more details on the why/how, check out this useful blog post and the associated code: -https://cloudywindows.io/post/winrm-for-provisioning-close-the-door-on-the-way-out-eh/ - -### Post i/o timeout errors - -If you see -`unknown error: Post http://:/wsman:dial tcp :: i/o timeout` -errors while provisioning a Windows machine, try setting Ansible to copy files -over [ssh instead of -sftp](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#envvar-ANSIBLE_SCP_IF_SSH). - -### Too many SSH keys - -SSH servers only allow you to attempt to authenticate a certain number of -times. All of your loaded keys will be tried before the dynamically generated -key. If you have too many SSH keys loaded in your `ssh-agent`, the Ansible -provisioner may fail authentication with a message similar to this: - -```text - googlecompute: fatal: [default]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: Warning: Permanently added '[127.0.0.1]:62684' (RSA) to the list of known hosts.\r\nReceived disconnect from 127.0.0.1 port 62684:2: too many authentication failures\r\nAuthentication failed.\r\n", "unreachable": true} -``` - -To unload all keys from your `ssh-agent`, run: - -```shell-session -$ ssh-add -D -``` - -### Become: yes - -We recommend against running Packer as root; if you do then you won't be able -to successfully run your Ansible playbook as root; `become: yes` will fail. - -### Using a wrapping script for your Ansible call - -Sometimes, you may have extra setup that needs to be called as part of your -ansible run. The easiest way to do this is by writing a small bash script and -using that bash script in your "command" in place of the default -"ansible-playbook". For example, you may need to launch a Python virtualenv -before calling Ansible. To do this, you'd want to create a bash script like - -```shell -#!/bin/bash -source /tmp/venv/bin/activate && ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 /tmp/venv/bin/ansible-playbook "$@" -``` - -The Ansible provisioner template remains very simple. For example: - - - - -```hcl -provisioner "ansible" { - command = "/Path/To/call_ansible.sh" - playbook_file = "./playbook.yml" -} -``` - - - - -```json -{ - "type": "ansible", - "command": "/Path/To/call_ansible.sh", - "playbook_file": "./playbook.yml" -} -``` - - - - -Note that we're calling ansible-playbook at the end of this command and passing -all command line arguments through into this call; this is necessary for -making sure that --extra-vars and other important Ansible arguments get set. -Note the quoting around the bash array, too; if you don't use quotes, any -arguments with spaces will not be read properly. - -### Docker - -When trying to use Ansible with Docker, it should "just work" but if it doesn't -you may need to tweak a few options. - -- Change the ansible_connection from "ssh" to "docker" -- Set a Docker container name via the --name option. - -On a CI server you probably want to overwrite ansible_host with a random name. - -Example Packer template: - - - - -```hcl -variable "ansible_host" { - default = "default" -} - -variable "ansible_connection" { - default = "docker" -} - -source "docker" "example" { - image = "centos:7" - commit = true - run_command = [ "-d", "-i", "-t", "--name", var.ansible_host, "{{.Image}}", "/bin/bash" ] -} - -build { - sources = [ - "source.docker.example" - ] - - provisioner "ansible" { - groups = [ "webserver" ] - playbook_file = "./webserver.yml" - extra_arguments = [ - "--extra-vars", - "ansible_host=${var.ansible_host} ansible_connection=${var.ansible_connection}" - ] - } -} -``` - - - - -```json -{ - "variables": { - "ansible_host": "default", - "ansible_connection": "docker" - }, - "builders": [ - { - "type": "docker", - "image": "centos:7", - "commit": true, - "run_command": [ - "-d", - "-i", - "-t", - "--name", - "{{user `ansible_host`}}", - "{{.Image}}", - "/bin/bash" - ] - } - ], - "provisioners": [ - { - "type": "ansible", - "groups": ["webserver"], - "playbook_file": "./webserver.yml", - "extra_arguments": [ - "--extra-vars", - "ansible_host={{user `ansible_host`}} ansible_connection={{user `ansible_connection`}}" - ] - } - ] -} -``` - - - - -Example playbook: - -```yaml -- name: configure webserver - hosts: webserver - tasks: - - name: install Apache - yum: - name: httpd -``` - -### Amazon Session Manager - -When trying to use Ansible with Amazon's Session Manager, you may run into an error where Ansible -is unable to connect to the remote Amazon instance if the local proxy adapter for Ansible [use_proxy](#use_proxy) is false. - -The error may look something like the following: - -``` -amazon-ebs: fatal: [default]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 127.0.0.1 port 8362: Connection timed out", "unreachable": true} -``` - -The error is caused by a limitation on using Amazon's SSM default Port Forwarding session which only allows for one -remote connection on the forwarded port. Since Ansible's SSH communication is not using the local proxy adapter -it will try to make a new SSH connection to the same forwarded localhost port and fail. - -In order to workaround this issue Ansible can be configured via a custom inventory file to use the AWS session-manager-plugin -directly to create a new session, separate from the one created by Packer, at runtime to connect and remotely provision the instance. - --> **Warning:** Please note that the default region configured for the `aws` cli must match the build region where the instance is being -provisioned otherwise you may run into a TargetNotConnected error. Users can use `AWS_DEFAULT_REGION` to temporarily override -their configured region. - - - - -```hcl - provisioner "ansible" { - use_proxy = false - playbook_file = "./playbooks/playbook_remote.yml" - ansible_env_vars = ["PACKER_BUILD_NAME={{ build_name }}"] - inventory_file_template = "{{ .HostAlias }} ansible_host={{ .ID }} ansible_user={{ .User }} ansible_ssh_common_args='-o StrictHostKeyChecking=no -o ProxyCommand=\"sh -c \\\"aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters portNumber=%p\\\"\"'\n" - } -``` - - - - -```json - "provisioners": [ - { - "type": "ansible", - "use_proxy": false, - "ansible_env_vars": ["PACKER_BUILD_NAME={{ build_name }}"], - "playbook_file": "./playbooks/playbook_remote.yml", - "inventory_file_template": "{{ .HostAlias }} ansible_host={{ .ID }} ansible_user={{ .User }} ansible_ssh_common_args='-o StrictHostKeyChecking=no -o ProxyCommand=\"sh -c \\\"aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters portNumber=%p\\\"\"'\n" - } - ] -``` - - - - -Full Packer template example: - - - - -```hcl - -variables { - instance_role = "SSMInstanceProfile" -} - -source "amazon-ebs" "ansible-example" { - region = "us-east-1" - ami_name = "packer-ami-ansible" - instance_type = "t2.micro" - - source_ami_filter { - filters = { - name = "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*" - virtualization-type = "hvm" - root-device-type = "ebs" - } - owners = [ "099720109477" ] - most_recent = true - } - communicator = "ssh" - ssh_username = "ubuntu" - ssh_interface = "session_manager" - iam_instance_profile = var.instance_role -} - -build { - sources = ["source.amazon-ebs.ansible-example"] - - provisioner "ansible" { - use_proxy = false - playbook_file = "./playbooks/playbook_remote.yml" - ansible_env_vars = ["PACKER_BUILD_NAME={{ build_name }}"] - inventory_file_template = "{{ .HostAlias }} ansible_host={{ .ID }} ansible_user={{ .User }} ansible_ssh_common_args='-o StrictHostKeyChecking=no -o ProxyCommand=\"sh -c \\\"aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters portNumber=%p\\\"\"'\n" - } -} -``` - - - - -```json -{ - "variables": { - "instance_role": "SSMInstanceProfile" - }, - - "builders": [ - { - "type": "amazon-ebs", - "region": "us-east-1", - "ami_name": "packer-ami-ansible", - "instance_type": "t2.micro", - "source_ami_filter": { - "filters": { - "virtualization-type": "hvm", - "name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*", - "root-device-type": "ebs" - }, - "owners": ["099720109477"], - "most_recent": true - }, - "communicator": "ssh", - "ssh_username": "ubuntu", - "ssh_interface": "session_manager", - "iam_instance_profile": "{{user `instance_role`}}" - } - ], - "provisioners": [ - { - "type": "ansible", - "use_proxy": false, - "ansible_env_vars": ["PACKER_BUILD_NAME={{ build_name }}"], - "playbook_file": "./playbooks/playbook_remote.yml", - "inventory_file_template": "{{ .HostAlias }} ansible_host={{ .ID }} ansible_user={{ .User }} ansible_ssh_common_args='-o StrictHostKeyChecking=no -o ProxyCommand=\"sh -c \\\"aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters portNumber=%p\\\"\"'\n" - } - ] -} -``` - - - - -### Troubleshooting - -If you are using an Ansible version >= 2.8 and Packer hangs in the -"Gathering Facts" stage, this could be the result of a pipelineing issue with -the proxy adapter that Packer uses. Setting `use_proxy: false,` in your -Packer config should resolve the issue. In the future we will default to setting -this, so you won't have to but for now it is a manual change you must make. diff --git a/website/content/partials/provisioner/ansible-local/Config-not-required.mdx b/website/content/partials/provisioner/ansible-local/Config-not-required.mdx deleted file mode 100644 index 379d30798..000000000 --- a/website/content/partials/provisioner/ansible-local/Config-not-required.mdx +++ /dev/null @@ -1,124 +0,0 @@ - - -- `command` (string) - 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 - ansible, take a look at the ansible wrapper guide below for inspiration. - Please note that Packer expects Command to be a path to an executable. - Arbitrary bash scripting will not work and needs to go inside an - executable script. - -- `extra_arguments` ([]string) - Extra arguments to pass to Ansible. - These arguments _will not_ be passed through a shell and arguments should - not be quoted. Usage example: - - ```json - "extra_arguments": [ "--extra-vars", "Region={{user `Region`}} Stage={{user `Stage`}}" ] - ``` - In certain scenarios where you want to pass ansible command line arguments - that include parameter and value (for example `--vault-password-file pwfile`), - from ansible documentation this is correct format but that is NOT accepted here. - Instead you need to do it like `--vault-password-file=pwfile`. - - If you are running a Windows build on AWS, Azure, Google Compute, or OpenStack - and would like to access the auto-generated password that Packer uses to - connect to a Windows instance via WinRM, you can use the template variable - `{{.WinRMPassword}}` in this option. For example: - - ```json - "extra_arguments": [ - "--extra-vars", "winrm_password={{ .WinRMPassword }}" - ] - ``` - -- `group_vars` (string) - A path to the directory containing ansible group - variables on your local system to be copied to the remote machine. By - default, this is empty. - -- `host_vars` (string) - A path to the directory containing ansible host variables on your local - system to be copied to the remote machine. By default, this is empty. - -- `playbook_dir` (string) - A path to the complete ansible directory structure on your local system - to be copied to the remote machine as the `staging_directory` before all - other files and directories. - -- `playbook_file` (string) - The playbook file to be executed by ansible. This file must exist on your - local system and will be uploaded to the remote machine. This option is - exclusive with `playbook_files`. - -- `playbook_files` ([]string) - The playbook files to be executed by ansible. These files must exist on - your local system. If the files don't exist in the `playbook_dir` or you - don't set `playbook_dir` they will be uploaded to the remote machine. This - option is exclusive with `playbook_file`. - -- `playbook_paths` ([]string) - An array of directories of playbook files on your local system. These - will be uploaded to the remote machine under `staging_directory`/playbooks. - By default, this is empty. - -- `role_paths` ([]string) - An array of paths to role directories on your local system. These will be - uploaded to the remote machine under `staging_directory`/roles. By default, - this is empty. - -- `staging_directory` (string) - The directory where all the configuration of Ansible by Packer will be placed. - By default this is `/tmp/packer-provisioner-ansible-local/`, where - `` is replaced with a unique ID so that this provisioner can be run more - than once. If you'd like to know the location of the staging directory in - advance, you should set this to a known location. This directory doesn't need - to exist but must have proper permissions so that the SSH user that Packer uses - is able to create directories and write into this folder. If the permissions - are not correct, use a shell provisioner prior to this to configure it - properly. - -- `clean_staging_directory` (bool) - If set to `true`, the content of the `staging_directory` will be removed after - executing ansible. By default this is set to `false`. - -- `inventory_file` (string) - The inventory file to be used by ansible. This - file must exist on your local system and will be uploaded to the remote - machine. - - When using an inventory file, it's also required to `--limit` the hosts to the - specified host you're building. The `--limit` argument can be provided in the - `extra_arguments` option. - - An example inventory file may look like: - - ```text - [chi-dbservers] - db-01 ansible_connection=local - db-02 ansible_connection=local - - [chi-appservers] - app-01 ansible_connection=local - app-02 ansible_connection=local - - [chi:children] - chi-dbservers - chi-appservers - - [dbservers:children] - chi-dbservers - - [appservers:children] - chi-appservers - ``` - -- `inventory_groups` ([]string) - `inventory_groups` (string) - A comma-separated list of groups to which - packer will assign the host `127.0.0.1`. A value of `my_group_1,my_group_2` - will generate an Ansible inventory like: - - ```text - [my_group_1] - 127.0.0.1 - [my_group_2] - 127.0.0.1 - ``` - -- `galaxy_file` (string) - 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. - -- `galaxy_command` (string) - The command to invoke ansible-galaxy. By default, this is - `ansible-galaxy`. - - diff --git a/website/content/partials/provisioner/ansible/Config-not-required.mdx b/website/content/partials/provisioner/ansible/Config-not-required.mdx deleted file mode 100644 index 76c7e2dad..000000000 --- a/website/content/partials/provisioner/ansible/Config-not-required.mdx +++ /dev/null @@ -1,167 +0,0 @@ - - -- `command` (string) - 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 - ansible, take a look at the ansible wrapper guide below for inspiration. - Please note that Packer expects Command to be a path to an executable. - Arbitrary bash scripting will not work and needs to go inside an - executable script. - -- `extra_arguments` ([]string) - Extra arguments to pass to Ansible. - These arguments _will not_ be passed through a shell and arguments should - not be quoted. Usage example: - - ```json - "extra_arguments": [ "--extra-vars", "Region={{user `Region`}} Stage={{user `Stage`}}" ] - ``` - In certain scenarios where you want to pass ansible command line arguments - that include parameter and value (for example `--vault-password-file pwfile`), - from ansible documentation this is correct format but that is NOT accepted here. - Instead you need to do it like `--vault-password-file=pwfile`. - - If you are running a Windows build on AWS, Azure, Google Compute, or OpenStack - and would like to access the auto-generated password that Packer uses to - connect to a Windows instance via WinRM, you can use the template variable - `{{.WinRMPassword}}` in this option. For example: - - ```json - "extra_arguments": [ - "--extra-vars", "winrm_password={{ .WinRMPassword }}" - ] - ``` - -- `ansible_env_vars` ([]string) - Environment variables to set before - running Ansible. Usage example: - - ```json - "ansible_env_vars": [ "ANSIBLE_HOST_KEY_CHECKING=False", "ANSIBLE_SSH_ARGS='-o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s'", "ANSIBLE_NOCOLOR=True" ] - ``` - - This is a [template engine](/docs/templates/legacy_json_templates/engine). Therefore, you - may use user variables and template functions in this field. - - For example, if you are running a Windows build on AWS, Azure, - Google Compute, or OpenStack and would like to access the auto-generated - password that Packer uses to connect to a Windows instance via WinRM, you - can use the template variable `{{.WinRMPassword}}` in this option. Example: - - ```json - "ansible_env_vars": [ "WINRM_PASSWORD={{.WinRMPassword}}" ], - ``` - -- `ansible_ssh_extra_args` ([]string) - Specifies --ssh-extra-args on command line defaults to -o IdentitiesOnly=yes - -- `groups` ([]string) - The groups into which the Ansible host should - be placed. When unspecified, the host is not associated with any groups. - -- `empty_groups` ([]string) - The groups which should be present in - inventory file but remain empty. - -- `host_alias` (string) - The alias by which the Ansible host should be - known. Defaults to `default`. This setting is ignored when using a custom - inventory file. - -- `user` (string) - The `ansible_user` to use. Defaults to the user running - packer, NOT the user set for your communicator. If you want to use the same - user as the communicator, you will need to manually set it again in this - field. - -- `local_port` (int) - The port on which to attempt to listen for SSH - connections. This value is a starting point. The provisioner will attempt - listen for SSH connections on the first available of ten ports, starting at - `local_port`. A system-chosen port is used when `local_port` is missing or - empty. - -- `ssh_host_key_file` (string) - The SSH key that will be used to run the SSH - server on the host machine to forward commands to the target machine. - Ansible connects to this server and will validate the identity of the - server using the system known_hosts. The default behavior is to generate - and use a onetime key. Host key checking is disabled via the - `ANSIBLE_HOST_KEY_CHECKING` environment variable if the key is generated. - -- `ssh_authorized_key_file` (string) - The SSH public key of the Ansible - `ssh_user`. The default behavior is to generate and use a onetime key. If - this key is generated, the corresponding private key is passed to - `ansible-playbook` with the `-e ansible_ssh_private_key_file` option. - -- `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, - respectively. Defaults to `/usr/lib/sftp-server -e`. - -- `skip_version_check` (bool) - Check if ansible is installed prior to - running. Set this to `true`, for example, if you're going to install - ansible during the packer run. - -- `use_sftp` (bool) - Use SFTP - -- `inventory_directory` (string) - The directory in which to place the - 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. - -- `inventory_file_template` (string) - This template represents the format for the lines added to the temporary - inventory file that Packer will create to run Ansible against your image. - The default for recent versions of Ansible is: - "{{ .HostAlias }} ansible_host={{ .Host }} ansible_user={{ .User }} ansible_port={{ .Port }}\n" - Available template engines are: This option is a template engine; - variables available to you include the examples in the default (Host, - HostAlias, User, Port) as well as any variables available to you via the - "build" template engine. - -- `inventory_file` (string) - The inventory file to use during provisioning. - When unspecified, Packer will create a temporary inventory file and will - use the `host_alias`. - -- `keep_inventory_file` (bool) - If `true`, the Ansible provisioner will - not delete the temporary inventory file it creates in order to connect to - the instance. This is useful if you are trying to debug your ansible run - and using "--on-error=ask" in order to leave your instance running while you - 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 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 - `ansible-galaxy`. - -- `galaxy_force_install` (bool) - Force overwriting an existing role. - Adds `--force` option to `ansible-galaxy` command. By default, this is - `false`. - -- `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 - to use the Ansible provisioner. If you set this option to `false`, but - Packer cannot find an IP address to connect Ansible to, it will - automatically set up the adapter anyway. - - In order for Ansible to connect properly even when use_proxy is false, you - need to make sure that you are either providing a valid username and ssh key - to the ansible provisioner directly, or that the username and ssh key - being used by the ssh communicator will work for your needs. If you do not - provide a user to ansible, it will use the user associated with your - builder, not the user running Packer. - use_proxy=false is currently only supported for SSH and WinRM. - - Currently, this defaults to `true` for all connection types. In the future, - this option will be changed to default to `false` for SSH and WinRM - connections where the provisioner has access to a host IP. - - diff --git a/website/content/partials/provisioner/ansible/Config-required.mdx b/website/content/partials/provisioner/ansible/Config-required.mdx deleted file mode 100644 index dc067fd87..000000000 --- a/website/content/partials/provisioner/ansible/Config-required.mdx +++ /dev/null @@ -1,5 +0,0 @@ - - -- `playbook_file` (string) - The playbook to be run by Ansible. - - diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 1198fe8f2..c9b8ca2a2 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -945,14 +945,6 @@ "title": "Overview", "path": "provisioners" }, - { - "title": "Ansible Local", - "path": "provisioners/ansible-local" - }, - { - "title": "Ansible (Remote)", - "path": "provisioners/ansible" - }, { "title": "Breakpoint", "path": "provisioners/breakpoint" diff --git a/website/data/docs-remote-plugins.json b/website/data/docs-remote-plugins.json index 16cfd8daa..c7f9ea5d2 100644 --- a/website/data/docs-remote-plugins.json +++ b/website/data/docs-remote-plugins.json @@ -16,5 +16,11 @@ "path": "vsphere", "repo": "hashicorp/packer-plugin-vsphere", "version": "latest" + }, + { + "title": "Ansible", + "path": "ansible", + "repo": "hashicorp/packer-plugin-ansible", + "version": "latest" } ]