Refactor provisioner acceptance tests to shell directly out to Packer rather than import the core. Modify test case formatting to more closely reflect the builder test cases.

This commit is contained in:
Megan Marsh 2020-12-04 15:14:45 -08:00
parent bc7dae2dff
commit 76177b50ce
14 changed files with 667 additions and 232 deletions

View File

@ -140,7 +140,7 @@ test: mode-check vet ## Run unit tests
@go test -count $(COUNT) $(TEST) $(TESTARGS) -timeout=3m
# acctest runs provisioners acceptance tests
provisioners-acctest: install-build-deps generate
provisioners-acctest: #install-build-deps generate
ACC_TEST_BUILDERS=$(ACC_TEST_BUILDERS) ACC_TEST_PROVISIONERS=$(ACC_TEST_PROVISIONERS) go test ./provisioner/... -timeout=1h
# testacc runs acceptance tests

View File

@ -27,7 +27,7 @@ func (v *VirtualBoxISOAccTest) GetConfigs() (map[string]string, error) {
file, err := ioutil.ReadAll(config)
if err != nil {
return nil, fmt.Errorf("Uneble to read %s", filePath)
return nil, fmt.Errorf("Unable to read %s", filePath)
}
return map[string]string{"linux": string(file)}, nil
}

View File

@ -0,0 +1,49 @@
package provisioneracc
import (
"github.com/hashicorp/packer/packer-plugin-sdk/acctest/testutils"
)
var AmasonEBSBuilderFixtureLinux = &BuilderFixture{
Name: "Amazon-ebs Linux builder",
TemplatePath: "amazon-ebs/amazon-ebs.txt",
GuestOS: "linux",
HostOS: "any",
Teardown: func() error {
// TODO
// helper := AWSHelper{
// Region: "us-east-1",
// AMIName: "packer-acc-test",
// }
// return helper.CleanUpAmi()
return nil
},
}
var AmasonEBSBuilderFixtureWindows = &BuilderFixture{
Name: "Amazon-ebs Windows builder",
TemplatePath: "amazon-ebs/amazon-ebs_windows.txt",
GuestOS: "windows",
HostOS: "any",
Teardown: func() error {
// TODO
// helper := AWSHelper{
// Region: "us-east-1",
// AMIName: "packer-acc-test",
// }
// return helper.CleanUpAmi()
return nil
},
}
var VirtualboxBuilderFixtureWindows = &BuilderFixture{
Name: "Virtualbox Windows builder",
TemplatePath: "virtualbox/virtualbox-iso.txt",
GuestOS: "linux",
HostOS: "any",
Teardown: func() error {
testutils.CleanupFiles("virtualbox-iso-packer-acc-test")
testutils.CleanupFiles("packer_cache")
return nil
},
}

View File

@ -3,65 +3,243 @@ package provisioneracc
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
amazonEBS "github.com/hashicorp/packer/builder/amazon/ebs/acceptance"
virtualboxISO "github.com/hashicorp/packer/builder/virtualbox/iso/acceptance"
"github.com/hashicorp/packer/command"
"github.com/hashicorp/packer/packer-plugin-sdk/acctest/testutils"
builderT "github.com/hashicorp/packer/packer-plugin-sdk/acctest"
packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
)
func TestProvisionersAgainstBuilders(provisionerAcc ProvisionerAcceptance, t *testing.T) {
provisioner := provisionerAcc.GetName()
builders := checkBuilders(t)
// ProvisionerTestCase is a single set of tests to run for a provisioner.
// A ProvisionerTestCase should generally map 1:1 to each test method for your
// acceptance tests.
type ProvisionerTestCase struct {
// Check is called after this step is executed in order to test that
// the step executed successfully. If this is not set, then the next
// step will be called
Check func(*exec.Cmd, string) error
// IsCompatible checks whether a provisioner is able to run against a
// given builder type and guest operating system, and returns a boolean.
// if it returns true, the test combination is okay to run. If false, the
// test combination is not okay to run.
IsCompatible func(builderType string, BuilderGuestOS string) bool
// Name is the name of the test case. Be simple but unique and descriptive.
Name string
// Setup, if non-nil, will be called once before the test case
// runs. This can be used for some setup like setting environment
// variables, or for validation prior to the
// test running. For example, you can use this to make sure certain
// binaries are installed, or text fixtures are in place.
Setup func() error
// Teardown will be called before the test case is over regardless
// of if the test succeeded or failed. This should return an error
// in the case that the test can't guarantee all resources were
// properly cleaned up.
Teardown builderT.TestTeardownFunc
// Template is the provisioner template to use.
// The provisioner template fragment must be a json-formatted string
// containing the provisioner definition but no other portions of a packer
// template. For
// example:
//
// ```json
// {
// "type": "shell-local",
// "inline", ["echo hello world"]
// }
//```
//
// is a valid entry for "template" here, but the complete Packer template:
//
// ```json
// {
// "provisioners": [
// {
// "type": "shell-local",
// "inline", ["echo hello world"]
// }
// ]
// }
// ```
//
// is invalid as input.
//
// You may provide multiple provisioners in the same template. For example:
// ```json
// {
// "type": "shell-local",
// "inline", ["echo hello world"]
// },
// {
// "type": "shell-local",
// "inline", ["echo hello world 2"]
// }
// ```
Template string
// Type is the type of provisioner.
Type string
}
// build template file and run a build for each builder with the provisioner
for _, builder := range builders {
builderAcc := BuildersAccTest[builder]
builderConfigs, err := builderAcc.GetConfigs()
if err != nil {
t.Fatalf("bad: failed to read builder config: %s", err.Error())
}
// BuilderFixtures are basic builder test configurations and metadata used
// in provisioner acceptance testing. These are frameworks to be used by
// provisioner tests, not tests in and of themselves. BuilderFixtures should
// generally be simple and not contain excessive or complex configurations.
// Instantiations of this struct are stored in the builders.go file in this
// module.
type BuilderFixture struct {
// Name is the name of the builder fixture.
// Be simple and descriptive.
Name string
// Setup creates necessary extra test fixtures, and renders their values
// into the BuilderFixture.Template.
Setup func()
// Template is the path to a builder template fragment.
// The builder template fragment must be a json-formatted file containing
// the builder definition but no other portions of a packer template. For
// example:
//
// ```json
// {
// "type": "null",
// "communicator", "none"
// }
//```
//
// is a valid entry for "template" here, but the complete Packer template:
//
// ```json
// {
// "builders": [
// "type": "null",
// "communicator": "none"
// ]
// }
// ```
//
// is invalid as input.
//
// Only provide one builder template fragment per file.
TemplatePath string
for vmOS, builderConfig := range builderConfigs {
if !provisionerAcc.IsCompatible(builder, vmOS) {
// GuestOS says what guest os type the builder template fragment creates.
// Valid values are "windows", "linux" or "darwin" guests.
GuestOS string
// HostOS says what host os type the builder is capable of running on.
// Valid values are "any", windows", or "posix". If you set "posix", then
// this builder can run on a "linux" or "darwin" platform. If you set
// "any", then this builder can be used on any platform.
HostOS string
Teardown builderT.TestTeardownFunc
}
func fixtureDir() string {
_, file, _, _ := runtime.Caller(0)
return filepath.Join(filepath.Dir(file), "test-fixtures")
}
func LoadBuilderFragment(templateFragmentPath string) (string, error) {
dir := fixtureDir()
fragmentAbsPath := filepath.Join(dir, templateFragmentPath)
fragmentFile, err := os.Open(fragmentAbsPath)
if err != nil {
return "", fmt.Errorf("Unable find %s", fragmentAbsPath)
}
defer fragmentFile.Close()
fragmentString, err := ioutil.ReadAll(fragmentFile)
if err != nil {
return "", fmt.Errorf("Unable to read %s", fragmentAbsPath)
}
return string(fragmentString), nil
}
func RunProvisionerAccTest(testCase *ProvisionerTestCase, t *testing.T) {
TestProvisionersPreCheck(testCase.Type, t)
TestProvisionersAgainstBuilders(testCase, t)
}
func TestProvisionersAgainstBuilders(testCase *ProvisionerTestCase, t *testing.T) {
// retrieve user-desired builders.
builderTypes := checkBuilders(t)
// Run this provisioner test case against each builder type requested.
for _, builderType := range builderTypes {
buildFixtures := BuildersAccTest[builderType]
// loop over individual build templates, merge with provisioner
// templates, and shell out to run test.
for _, buildFixture := range buildFixtures {
if !testCase.IsCompatible(builderType, buildFixture.GuestOS) {
continue
}
testName := fmt.Sprintf("testing %s builder against %s provisioner", builder, provisioner)
testName := fmt.Sprintf("%s on %s", testCase.Name, buildFixture.Name)
if testCase.Setup != nil {
err := testCase.Setup()
if err != nil {
t.Fatalf("test %s setup failed: %s", testName, err)
}
}
t.Run(testName, func(t *testing.T) {
provisionerConfig, err := provisionerAcc.GetConfig()
builderFragment, err := LoadBuilderFragment(buildFixture.TemplatePath)
if err != nil {
t.Fatalf("bad: failed to read provisioner config: %s", err.Error())
t.Fatalf("failed to load builder fragment: %s", err)
}
// Write json template
// Combine provisioner and builder template fragments; write to
// file.
out := bytes.NewBuffer(nil)
fmt.Fprintf(out, `{"builders": [%s],"provisioners": [%s]}`, builderConfig, provisionerConfig)
fileName := fmt.Sprintf("%s_%s.json", builder, provisioner)
filePath := filepath.Join("./", fileName)
writeJsonTemplate(out, filePath, t)
fmt.Fprintf(out, `{"builders": [%s],"provisioners": [%s]}`,
builderFragment, testCase.Template)
templateName := fmt.Sprintf("%s_%s.json", builderType, testCase.Type)
templatePath := filepath.Join("./", templateName)
writeJsonTemplate(out, templatePath, t)
logfile := fmt.Sprintf("packer_log_%s_%s.txt", builderType, testCase.Type)
// set pre-config with necessary builder and provisioner
c := buildCommand(t, builderAcc, provisionerAcc)
args := []string{
filePath,
// Run build
// TODO: stream logs _and_ store in a logfile.
buildCommand := exec.Command("packer", "build", "--machine-readable", templatePath)
buildCommand.Env = append(buildCommand.Env, os.Environ()...)
buildCommand.Env = append(buildCommand.Env, "PACKER_LOG=1",
fmt.Sprintf("PACKER_LOG_PATH=%s", logfile))
buildCommand.Run()
// Check for test custom pass/fail before we clean up
var checkErr error
if testCase.Check != nil {
checkErr = testCase.Check(buildCommand, logfile)
}
err = provisionerAcc.RunTest(c, args)
// Cleanup created resources
testutils.CleanupFiles(fileName)
cleanErr := builderAcc.CleanUp()
// preemptive cleanup
defer os.Remove(templatePath)
defer os.Remove(logfile)
// Cleanup stuff created by builder.
cleanErr := buildFixture.Teardown()
if cleanErr != nil {
log.Printf("bad: failed to clean up resources: %s", cleanErr.Error())
log.Printf("bad: failed to clean up builder-created resources: %s", cleanErr.Error())
}
if err != nil {
t.Fatalf("bad: failed to to run build: %s", err.Error())
// Clean up anything created in provisioner run
if testCase.Teardown != nil {
cleanErr = testCase.Teardown()
if cleanErr != nil {
log.Printf("bad: failed to clean up test-created resources: %s", cleanErr.Error())
}
}
// Fail test if check failed.
if checkErr != nil {
t.Fatalf(checkErr.Error())
}
})
}
@ -88,6 +266,8 @@ func TestProvisionersPreCheck(name string, t *testing.T) {
}
// checkBuilders retrieves all of the builders that the user has requested to
// run acceptance tests against.
func checkBuilders(t *testing.T) []string {
b := os.Getenv("ACC_TEST_BUILDERS")
// validate if we want to run provisioners acc tests
@ -119,32 +299,52 @@ func writeJsonTemplate(out *bytes.Buffer, filePath string, t *testing.T) {
outputFile.Sync()
}
func buildCommand(t *testing.T, builder BuilderAcceptance, provisioner ProvisionerAcceptance) *command.BuildCommand {
c := &command.BuildCommand{
Meta: TestMetaFile(t),
}
c.CoreConfig.Components.BuilderStore = builder.GetBuilderStore()
c.CoreConfig.Components.ProvisionerStore = provisioner.GetProvisionerStore()
return c
}
type ProvisionerAcceptance interface {
GetName() string
GetConfig() (string, error)
GetProvisionerStore() packersdk.MapOfProvisioner
IsCompatible(builder string, vmOS string) bool
RunTest(c *command.BuildCommand, args []string) error
}
// BuilderAcceptance is specialized tooling implemented by individual builders
// To add your builder to the provisioner testing framework, create a struct
// that implements the this interface, add it to the BuildersAccTest map below.
// TODO add this interface to the plugin server so that Packer can request it
// From the plugin rather than importing it here.
type BuilderAcceptance interface {
// GetConfigs provides a mapping of guest OS architecture to builder
// template fragment.
// The builder template fragment must be a json-formatted string containing
// the builder definition but no other portions of a packer template. For
// example:
//
// ```json
// {
// "type": "null",
// "communicator", "none"
// }
//```
//
// is a valid entry for "template" here, but the complete Packer template:
//
// ```json
// {
// "builders": [
// "type": "null",
// "communicator": "none"
// ]
// }
// ```
//
// is invalid as input.
//
// Valid keys for the map are "linux" and "windows". These keys will be used
// to determine whether a given builder template is compatible with a given
// provisioner template.
GetConfigs() (map[string]string, error)
// GetBuilderStore() returns a MapOfBuilder that contains the actual builder
// struct definition being used for this test.
GetBuilderStore() packersdk.MapOfBuilder
// CleanUp cleans up any side-effects of the builder not already cleaned up
// by the builderT framework.
CleanUp() error
}
// List of all builders available for acceptance test
var BuildersAccTest = map[string]BuilderAcceptance{
"virtualbox-iso": new(virtualboxISO.VirtualBoxISOAccTest),
"amazon-ebs": new(amazonEBS.AmazonEBSAccTest),
// Mapping of all builder fixtures defined for a given builder type.
var BuildersAccTest = map[string][]*BuilderFixture{
"virtualbox-iso": []*BuilderFixture{VirtualboxBuilderFixtureWindows},
"amazon-ebs": []*BuilderFixture{AmasonEBSBuilderFixtureLinux, AmasonEBSBuilderFixtureWindows},
}

View File

@ -0,0 +1,12 @@
{
"type": "amazon-ebs",
"ami_name": "packer-acc-test",
"instance_type": "m1.small",
"region": "us-east-1",
"ssh_username": "ubuntu",
"source_ami": "ami-0568456c",
"force_deregister" : true,
"tags": {
"packer-test": "true"
}
}

View File

@ -0,0 +1,23 @@
{
"type": "amazon-ebs",
"region": "us-east-1",
"instance_type": "t2.micro",
"source_ami_filter": {
"filters": {
"virtualization-type": "hvm",
"name": "*Windows_Server-2012-R2*English-64Bit-Base*",
"root-device-type": "ebs"
},
"most_recent": true,
"owners": "amazon"
},
"ami_name": "packer-acc-test",
"user_data_file": "../../builder/amazon/ebs/acceptance/test-fixtures/scripts/bootstrap_win.txt",
"communicator": "winrm",
"winrm_username": "Administrator",
"winrm_password": "SuperS3cr3t!!!!",
"force_deregister" : true,
"tags": {
"packer-test": "true"
}
}

View File

@ -0,0 +1,41 @@
<powershell>
# Set administrator password
net user Administrator SuperS3cr3t!!!!
wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE
# First, make sure WinRM can't be connected to
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block
# Delete any existing WinRM listeners
winrm delete winrm/config/listener?Address=*+Transport=HTTP 2>$Null
winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null
# Disable group policies which block basic authentication and unencrypted login
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowBasic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowUnencryptedTraffic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowBasic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowUnencryptedTraffic -Value 1
# Create a new WinRM listener and configure
winrm create winrm/config/listener?Address=*+Transport=HTTP
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}'
winrm set winrm/config '@{MaxTimeoutms="7200000"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
winrm set winrm/config/client/auth '@{Basic="true"}'
# Configure UAC to allow privilege elevation in remote shells
$Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
$Setting = 'LocalAccountTokenFilterPolicy'
Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force
# Configure and restart the WinRM Service; Enable the required firewall exception
Stop-Service -Name WinRM
Set-Service -Name WinRM -StartupType Automatic
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
Start-Service -Name WinRM
</powershell>

View File

@ -0,0 +1,42 @@
# Preseeding only locale sets language, country and locale.
d-i debian-installer/locale string en_US
# Keyboard selection.
d-i console-setup/ask_detect boolean false
d-i keyboard-configuration/xkb-keymap select us
choose-mirror-bin mirror/http/proxy string
d-i base-installer/kernel/override-image string linux-server
d-i clock-setup/utc boolean true
d-i clock-setup/utc-auto boolean true
d-i finish-install/reboot_in_progress note
d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
d-i mirror/country string manual
d-i mirror/http/directory string /ubuntu/
d-i mirror/http/hostname string archive.ubuntu.com
d-i mirror/http/proxy string
d-i partman-auto-lvm/guided_size string max
d-i partman-auto/choose_recipe select atomic
d-i partman-auto/method string lvm
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
d-i partman-lvm/device_remove_lvm boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i partman/confirm_write_new_label boolean true
d-i passwd/user-fullname string vagrant
d-i passwd/user-uid string 1000
d-i passwd/user-password password vagrant
d-i passwd/user-password-again password vagrant
d-i passwd/username string vagrant
d-i pkgsel/include string openssh-server cryptsetup build-essential libssl-dev libreadline-dev zlib1g-dev linux-source dkms nfs-kernel-server nfs-common linux-headers-$(uname -r) perl
d-i pkgsel/install-language-support boolean false
d-i pkgsel/update-policy select none
d-i pkgsel/upgrade select full-upgrade
d-i time/zone string UTC
d-i user-setup/allow-password-weak boolean true
d-i user-setup/encrypt-home boolean false
tasksel tasksel/first multiselect standard, server

View File

@ -0,0 +1,43 @@
{
"type": "virtualbox-iso",
"iso_checksum": "sha256:946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2",
"iso_url": "http://old-releases.ubuntu.com/releases/14.04.1/ubuntu-14.04.1-server-amd64.iso",
"disk_size": "40960",
"guest_os_type": "Ubuntu_64",
"headless" : "true",
"ssh_password": "vagrant",
"ssh_username": "vagrant",
"ssh_wait_timeout": "10000s",
"http_directory": "../../builder/virtualbox/iso/acceptance/test-fixtures/http",
"boot_wait": "10s",
"boot_command": [
"<esc><wait>",
"<esc><wait>",
"<enter><wait>",
"/install/vmlinuz<wait>",
" auto<wait>",
" console-setup/ask_detect=false<wait>",
" console-setup/layoutcode=us<wait>",
" console-setup/modelcode=pc105<wait>",
" debconf/frontend=noninteractive<wait>",
" debian-installer=en_US.UTF-8<wait>",
" fb=false<wait>",
" initrd=/install/initrd.gz<wait>",
" kbd-chooser/method=us<wait>",
" keyboard-configuration/layout=USA<wait>",
" keyboard-configuration/variant=USA<wait>",
" locale=en_US.UTF-8<wait>",
" netcfg/get_domain=vm<wait>",
" netcfg/get_hostname=vagrant<wait>",
" noapic<wait>",
" preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg<wait>",
" -- <wait>",
"<enter><wait>"
],
"output_directory": "virtualbox-iso-packer-acc-test",
"shutdown_command": "echo 'vagrant' | sudo -S shutdown -P now",
"post_shutdown_delay": "60s"
}

View File

@ -1,94 +1,110 @@
package powershell_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/packer/command"
"github.com/hashicorp/packer/packer-plugin-sdk/acctest/provisioneracc"
packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/provisioner/powershell"
windowsshellprovisioner "github.com/hashicorp/packer/provisioner/windows-shell"
)
const TestProvisionerName = "powershell"
const TestProvisionerType = "powershell"
func TestAccPowershellProvisioner_basic(t *testing.T) {
provisioneracc.TestProvisionersPreCheck(TestProvisionerName, t)
testProvisioner := PowershellProvisionerAccTest{"powershell-provisioner-cleanup.txt"}
provisioneracc.TestProvisionersAgainstBuilders(&testProvisioner, t)
}
func TestAccPowershellProvisioner_Inline(t *testing.T) {
provisioneracc.TestProvisionersPreCheck(TestProvisionerName, t)
testProvisioner := PowershellProvisionerAccTest{"powershell-inline-provisioner.txt"}
provisioneracc.TestProvisionersAgainstBuilders(&testProvisioner, t)
}
func TestAccPowershellProvisioner_Script(t *testing.T) {
provisioneracc.TestProvisionersPreCheck(TestProvisionerName, t)
testProvisioner := PowershellProvisionerAccTest{"powershell-script-provisioner.txt"}
provisioneracc.TestProvisionersAgainstBuilders(&testProvisioner, t)
}
type PowershellProvisionerAccTest struct {
ConfigName string
}
func (s *PowershellProvisionerAccTest) GetName() string {
return TestProvisionerName
}
func (s *PowershellProvisionerAccTest) GetConfig() (string, error) {
filePath := filepath.Join("./test-fixtures", s.ConfigName)
config, err := os.Open(filePath)
if err != nil {
return "", fmt.Errorf("os.Open:%v", err)
}
defer config.Close()
file, err := ioutil.ReadAll(config)
if err != nil {
return "", fmt.Errorf("ioutil.ReadAll:%v", err)
}
return string(file), nil
}
func (s *PowershellProvisionerAccTest) GetProvisionerStore() packersdk.MapOfProvisioner {
return packersdk.MapOfProvisioner{
TestProvisionerName: func() (packersdk.Provisioner, error) { return &powershell.Provisioner{}, nil },
"windows-shell": func() (packersdk.Provisioner, error) { return &windowsshellprovisioner.Provisioner{}, nil },
}
}
func (s *PowershellProvisionerAccTest) IsCompatible(builder string, vmOS string) bool {
func powershellIsCompatible(builder string, vmOS string) bool {
return vmOS == "windows"
}
func (s *PowershellProvisionerAccTest) RunTest(c *command.BuildCommand, args []string) error {
UUID := os.Getenv("PACKER_RUN_UUID")
if UUID == "" {
UUID, _ = uuid.GenerateUUID()
os.Setenv("PACKER_RUN_UUID", UUID)
}
if code := c.Run(args); code != 0 {
ui := c.Meta.Ui.(*packersdk.BasicUi)
out := ui.Writer.(*bytes.Buffer)
err := ui.ErrorWriter.(*bytes.Buffer)
return fmt.Errorf(
"Bad exit code.\n\nStdout:\n\n%s\n\nStderr:\n\n%s",
out.String(),
err.String())
}
return nil
func fixtureDir() string {
_, file, _, _ := runtime.Caller(0)
return filepath.Join(filepath.Dir(file), "test-fixtures")
}
func LoadProvisionerFragment(templateFragmentPath string) (string, error) {
dir := fixtureDir()
fragmentAbsPath := filepath.Join(dir, templateFragmentPath)
fragmentFile, err := os.Open(fragmentAbsPath)
if err != nil {
return "", fmt.Errorf("Unable find %s", fragmentAbsPath)
}
defer fragmentFile.Close()
fragmentString, err := ioutil.ReadAll(fragmentFile)
if err != nil {
return "", fmt.Errorf("Unable to read %s", fragmentAbsPath)
}
return string(fragmentString), nil
}
func TestAccPowershellProvisioner_basic(t *testing.T) {
templateString, err := LoadProvisionerFragment("powershell-provisioner-cleanup.txt")
if err != nil {
t.Fatalf("Couldn't load test fixture; %s", err.Error())
}
testCase := &provisioneracc.ProvisionerTestCase{
IsCompatible: powershellIsCompatible,
Name: "powershell-provisioner-cleanup",
Template: templateString,
Type: TestProvisionerType,
Check: func(buildcommand *exec.Cmd, logfile string) error {
if buildcommand.ProcessState != nil {
if buildcommand.ProcessState.ExitCode() != 0 {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}
}
return nil
},
}
provisioneracc.TestProvisionersAgainstBuilders(testCase, t)
}
func TestAccPowershellProvisioner_Inline(t *testing.T) {
templateString, err := LoadProvisionerFragment("powershell-inline-provisioner.txt")
if err != nil {
t.Fatalf("Couldn't load test fixture; %s", err.Error())
}
testCase := &provisioneracc.ProvisionerTestCase{
IsCompatible: powershellIsCompatible,
Name: "powershell-provisioner-inline",
Template: templateString,
Type: TestProvisionerType,
Check: func(buildcommand *exec.Cmd, logfile string) error {
if buildcommand.ProcessState != nil {
if buildcommand.ProcessState.ExitCode() != 0 {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}
}
return nil
},
}
provisioneracc.TestProvisionersAgainstBuilders(testCase, t)
}
func TestAccPowershellProvisioner_Script(t *testing.T) {
templateString, err := LoadProvisionerFragment("powershell-script-provisioner.txt")
if err != nil {
t.Fatalf("Couldn't load test fixture; %s", err.Error())
}
testCase := &provisioneracc.ProvisionerTestCase{
IsCompatible: powershellIsCompatible,
Name: "powershell-provisioner-script",
Template: templateString,
Type: TestProvisionerType,
Check: func(buildcommand *exec.Cmd, logfile string) error {
if buildcommand.ProcessState != nil {
if buildcommand.ProcessState.ExitCode() != 0 {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}
}
return nil
},
}
provisioneracc.TestProvisionersAgainstBuilders(testCase, t)
}

View File

@ -1,65 +1,76 @@
package shell_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/hashicorp/packer/packer-plugin-sdk/acctest/provisioneracc"
"github.com/hashicorp/packer/provisioner/shell"
packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/command"
"github.com/hashicorp/packer/packer-plugin-sdk/acctest/testutils"
)
func TestShellLocalProvisionerWithRetryOption(t *testing.T) {
provisioneracc.TestProvisionersPreCheck("shell-local", t)
provisioneracc.TestProvisionersAgainstBuilders(new(ShellLocalProvisionerAccTest), t)
func fixtureDir() string {
_, file, _, _ := runtime.Caller(0)
return filepath.Join(filepath.Dir(file), "test-fixtures")
}
type ShellLocalProvisionerAccTest struct{}
func (s *ShellLocalProvisionerAccTest) GetName() string {
return "file"
}
func (s *ShellLocalProvisionerAccTest) GetConfig() (string, error) {
filePath := filepath.Join("./test-fixtures", "shell-local-provisioner.txt")
config, err := os.Open(filePath)
func loadFile(templateFragmentPath string) (string, error) {
dir := fixtureDir()
fragmentAbsPath := filepath.Join(dir, templateFragmentPath)
fragmentFile, err := os.Open(fragmentAbsPath)
if err != nil {
return "", fmt.Errorf("Expected to find %s", filePath)
return "", fmt.Errorf("Unable find %s", fragmentAbsPath)
}
defer config.Close()
defer fragmentFile.Close()
file, err := ioutil.ReadAll(config)
return string(file), err
fragmentString, err := ioutil.ReadAll(fragmentFile)
if err != nil {
return "", fmt.Errorf("Unable to read %s", fragmentAbsPath)
}
return string(fragmentString), nil
}
func (s *ShellLocalProvisionerAccTest) GetProvisionerStore() packersdk.MapOfProvisioner {
return packersdk.MapOfProvisioner{
"shell-local": func() (packersdk.Provisioner, error) { return &shell.Provisioner{}, nil },
}
}
func (s *ShellLocalProvisionerAccTest) IsCompatible(builder string, vmOS string) bool {
func IsCompatible(builder string, vmOS string) bool {
return vmOS == "linux"
}
func (s *ShellLocalProvisionerAccTest) RunTest(c *command.BuildCommand, args []string) error {
if code := c.Run(args); code != 0 {
ui := c.Meta.Ui.(*packersdk.BasicUi)
out := ui.Writer.(*bytes.Buffer)
err := ui.ErrorWriter.(*bytes.Buffer)
return fmt.Errorf(
"Bad exit code.\n\nStdout:\n\n%s\n\nStderr:\n\n%s",
out.String(),
err.String())
func TestAccShellProvisioner_basic(t *testing.T) {
templateString, err := loadFile("shell-local-provisioner.txt")
if err != nil {
t.Fatalf("Couldn't load test fixture; %s", err.Error())
}
return nil
testCase := &provisioneracc.ProvisionerTestCase{
IsCompatible: IsCompatible,
Name: "shell-local-provisioner-basic",
Teardown: func() error {
testutils.CleanupFiles("test-fixtures/file.txt")
return nil
},
Template: templateString,
Type: "shell-local",
Check: func(buildcommand *exec.Cmd, logfile string) error {
if buildcommand.ProcessState != nil {
if buildcommand.ProcessState.ExitCode() != 0 {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}
}
filecontents, err := loadFile("file.txt")
if err != nil {
return err
}
if !strings.Contains(filecontents, "hello") {
return fmt.Errorf("file contents were wrong: %s", filecontents)
}
return nil
},
}
provisioneracc.TestProvisionersAgainstBuilders(testCase, t)
}

5
provisioner/shell-local/test-fixtures/script.sh Normal file → Executable file
View File

@ -1,6 +1,7 @@
#!/bin/bash
if [[ ! -f file.txt ]] ; then
echo 'hello' > file.txt
# On first try, exits 1; on second try, passes.
if [[ ! -f test-fixtures/file.txt ]] ; then
echo 'hello' > test-fixtures/file.txt
exit 1
fi

View File

@ -1,80 +1,77 @@
package shell_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"testing"
"github.com/hashicorp/packer/packer-plugin-sdk/acctest/provisioneracc"
"github.com/hashicorp/packer/packer-plugin-sdk/acctest/testutils"
"github.com/hashicorp/packer/provisioner/file"
"github.com/hashicorp/packer/provisioner/shell"
packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/packer/command"
)
func TestShellProvisioner(t *testing.T) {
provisioneracc.TestProvisionersPreCheck("shell", t)
provisioneracc.TestProvisionersAgainstBuilders(new(ShellProvisionerAccTest), t)
func fixtureDir() string {
_, file, _, _ := runtime.Caller(0)
return filepath.Join(filepath.Dir(file), "test-fixtures")
}
type ShellProvisionerAccTest struct{}
func (s *ShellProvisionerAccTest) GetName() string {
return "shell"
}
func (s *ShellProvisionerAccTest) GetConfig() (string, error) {
filePath := filepath.Join("./test-fixtures", "shell-provisioner.txt")
config, err := os.Open(filePath)
func loadFile(templateFragmentPath string) (string, error) {
dir := fixtureDir()
fragmentAbsPath := filepath.Join(dir, templateFragmentPath)
fragmentFile, err := os.Open(fragmentAbsPath)
if err != nil {
return "", fmt.Errorf("Expected to find %s", filePath)
return "", fmt.Errorf("Unable find %s", fragmentAbsPath)
}
defer config.Close()
defer fragmentFile.Close()
file, err := ioutil.ReadAll(config)
return string(file), err
fragmentString, err := ioutil.ReadAll(fragmentFile)
if err != nil {
return "", fmt.Errorf("Unable to read %s", fragmentAbsPath)
}
return string(fragmentString), nil
}
func (s *ShellProvisionerAccTest) GetProvisionerStore() packersdk.MapOfProvisioner {
return packersdk.MapOfProvisioner{
"shell": func() (packersdk.Provisioner, error) { return &shell.Provisioner{}, nil },
"file": func() (packersdk.Provisioner, error) { return &file.Provisioner{}, nil },
}
}
func (s *ShellProvisionerAccTest) IsCompatible(builder string, vmOS string) bool {
func IsCompatible(builder string, vmOS string) bool {
return vmOS == "linux"
}
func (s *ShellProvisionerAccTest) RunTest(c *command.BuildCommand, args []string) error {
UUID := os.Getenv("PACKER_RUN_UUID")
if UUID == "" {
UUID, _ = uuid.GenerateUUID()
os.Setenv("PACKER_RUN_UUID", UUID)
func TestAccShellProvisioner_basic(t *testing.T) {
templateString, err := loadFile("shell-provisioner.txt")
if err != nil {
t.Fatalf("Couldn't load test fixture; %s", err.Error())
}
file := "provisioner.shell." + UUID + ".txt"
defer testutils.CleanupFiles(file)
if code := c.Run(args); code != 0 {
ui := c.Meta.Ui.(*packersdk.BasicUi)
out := ui.Writer.(*bytes.Buffer)
err := ui.ErrorWriter.(*bytes.Buffer)
return fmt.Errorf(
"Bad exit code.\n\nStdout:\n\n%s\n\nStderr:\n\n%s",
out.String(),
err.String())
testCase := &provisioneracc.ProvisionerTestCase{
IsCompatible: IsCompatible,
Name: "shell-provisioner-basic",
Teardown: func() error {
testutils.CleanupFiles("test-fixtures/provisioner.shell.txt")
return nil
},
Template: templateString,
Type: "shell",
Check: func(buildcommand *exec.Cmd, logfile string) error {
if buildcommand.ProcessState != nil {
if buildcommand.ProcessState.ExitCode() != 0 {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}
}
filecontents, err := loadFile("provisioner.shell.txt")
if err != nil {
return err
}
re := regexp.MustCompile(`build ID is .* and build UUID is [[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}`)
if !re.MatchString(filecontents) {
return fmt.Errorf("Bad file contents \"%s\"", filecontents)
}
return nil
},
}
if !testutils.FileExists(file) {
return fmt.Errorf("Expected to find %s", file)
}
return nil
provisioneracc.TestProvisionersAgainstBuilders(testCase, t)
}

View File

@ -1,12 +1,12 @@
{
"type": "shell",
"inline": [
"echo {{ build `ID`}} > provisioner.{{ build `PackerRunUUID`}}.txt"
"echo build ID is {{ build `ID`}} and build UUID is {{ build `PackerRunUUID` }}> provisioner.{{ build `PackerRunUUID`}}.txt"
]
},
{
"type": "file",
"source": "provisioner.{{ build `PackerRunUUID`}}.txt",
"destination": "provisioner.shell.{{ build `PackerRunUUID`}}.txt",
"destination": "./test-fixtures/provisioner.shell.txt",
"direction": "download"
}