Merge pull request #10355 from hashicorp/clean_provisioners

Clean provisioners
This commit is contained in:
Megan Marsh 2020-12-08 11:14:31 -08:00 committed by GitHub
commit 7a05d0ce15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1015 additions and 542 deletions

View File

@ -301,15 +301,15 @@ You can run tests for individual packages using commands like this:
make test TEST=./builder/amazon/...
```
#### Running Acceptance Tests
#### Running Builder Acceptance Tests
Packer has [acceptance tests](https://en.wikipedia.org/wiki/Acceptance_testing)
for various builders. These typically require an API key (AWS, GCE), or
additional software to be installed on your computer (VirtualBox, VMware).
If you're working on a new builder or builder feature and want to verify it is
functioning (and also hasn't broken anything else), we recommend running the
acceptance tests.
functioning (and also hasn't broken anything else), we recommend creating or
running the acceptance tests.
**Warning:** The acceptance tests create/destroy/modify _real resources_, which
may incur costs for real money. In the presence of a bug, it is possible that
@ -340,7 +340,7 @@ Acceptance tests typically require other environment variables to be set for
things such as API tokens and keys. Each test should error and tell you which
credentials are missing, so those are not documented here.
#### Running Provisioners Acceptance Tests
#### Running Provisioner Acceptance Tests
**Warning:** The acceptance tests create/destroy/modify _real resources_, which
may incur costs for real money. In the presence of a bug, it is possible that
@ -351,124 +351,164 @@ resources are not accidentally destroyed or overwritten during testing.
Also, these typically require an API key (AWS, GCE), or additional software
to be installed on your computer (VirtualBox, VMware).
To run the Provisioners Acceptance Tests you should use both **ACC_TEST_BUILDERS** and **ACC_TEST_PROVISIONERS** variables to
tell which provisioner and builder the test should be run against.
To run the Provisioners Acceptance Tests you should use the
**ACC_TEST_BUILDERS** environment variable to tell the tests which builder the
test should be run against.
Examples of usage:
- Run the Shell provisioner acceptance tests against the Amazon EBS builder.
```
ACC_TEST_BUILDERS=amazon-ebs ACC_TEST_PROVISIONERS=shell go test ./provisioner/shell/... -v -timeout=1h
ACC_TEST_BUILDERS=amazon-ebs go test ./provisioner/shell/... -v -timeout=1h
```
- Do the same but using the Makefile
```
ACC_TEST_BUILDERS=amazon-ebs ACC_TEST_PROVISIONERS=shell make provisioners-acctest
ACC_TEST_BUILDERS=amazon-ebs make provisioners-acctest TEST=./provisioner/shell
```
- Run the all Shell and Powershell provisioners acceptance tests against the Amazon EBS builder.
- Run all provisioner acceptance tests against the Amazon EBS builder.
```
ACC_TEST_BUILDERS=amazon-ebs ACC_TEST_PROVISIONERS=shell,powershell make provisioners-acctest
ACC_TEST_BUILDERS=amazon-ebs make provisioners-acctest TEST=./...
```
- Run the all provisioners acceptance tests against the Amazon EBS builder.
- Run all provisioner acceptance tests against all builders whenever they are compatible.
```
ACC_TEST_BUILDERS=amazon-ebs ACC_TEST_PROVISIONERS=all make provisioners-acctest
```
- Run the all provisioners acceptance tests against all builders whenever they are compatible.
```
ACC_TEST_BUILDERS=all ACC_TEST_PROVISIONERS=all make provisioners-acctest
ACC_TEST_BUILDERS=all make provisioners-acctest TEST=./...
```
Both **ACC_TEST_BUILDERS** and **ACC_TEST_PROVISIONERS** allows defining a list of builders and provisioners separated by comma
(e.g. `ACC_TEST_BUILDERS=amazon-ebs,virtualbox-iso`)
The **ACC_TEST_BUILDERS** env variable accepts a list of builders separated by
commas. (e.g. `ACC_TEST_BUILDERS=amazon-ebs,virtualbox-iso`)
#### Writing Provisioner Acceptance Tests
Packer has an already implemented structure that will run the provisioner against builders and you can find it in `helper/tests/acc/provisioners.go`.
All provisioners should use this structure in their acceptance tests.
To start writing a new provisioner acceptance test, you should add a test file named as `provisioner_acc_test.go` in the provisioner folder
and the package should be `<provisioner>_test`. This file should have a struct that will implement the ProvisionerAcceptance interface.
Packer has implemented a `ProvisionerTestCase` structure to help write
provisioner acceptance tests.
```go
type ProvisionerAcceptance interface {
GetName() string
GetConfig() (string, error)
GetProvisionerStore() packer.MapOfProvisioner
IsCompatible(builder string, vmOS string) bool
RunTest(c *command.BuildCommand, args []string) error
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
}
```
- **GetName()** should return the provisioner type. For example for the Shell provisioner the method returns "shell".
To start writing a new provisioner acceptance test, you should add a test file
named `provisioner_acc_test.go` in the same folder as your provisioner is
defined. Create a test case by implementing the above struct, and run it
by calling `provisioneracc.TestProvisionersAgainstBuilders(testCase, t)`
- **GetConfig()** should read a text file with the json configuration block for the provisioner and any other necessary provisioner.
For the Shell one the file contains:
The following example has been adapted from a shell-local provisioner test:
```
{
"type": "shell",
"inline": [
"echo {{ build `ID`}} > provisioner.{{ build `PackerRunUUID`}}.txt"
]
```
import (
"github.com/hashicorp/packer/packer-plugin-sdk/acctest/provisioneracc"
"github.com/hashicorp/packer/packer-plugin-sdk/acctest/testutils"
)
// ...
func TestAccShellProvisioner_basic(t *testing.T) {
// Create a json template fragment containing just the provisioners you want
// to run.
templateString := `{
"type": "shell-local",
"script": "test-fixtures/script.sh",
"max_retries" : 5
}`
// instantiate a test case.
testCase := &provisioneracc.ProvisionerTestCase{
IsCompatible: func() bool {return true},
Name: "shell-local-provisioner-basic",
Teardown: func() error {
testutils.CleanupFiles("test-fixtures/file.txt")
return nil
},
{
"type": "file",
"source": "provisioner.{{ build `PackerRunUUID`}}.txt",
"destination": "provisioner.shell.{{ build `PackerRunUUID`}}.txt",
"direction": "download"
}
```
The file should be placed under the `test-fixtures` folder.
In this case, it's necessary to use the File provisioner to validate if the Shell provisioner test is successful or not.
This config should be returned as string that will be later merged with the builder config into a full template.
- **GetProvisionerStore()** this returns the provisioner store where we declare the available provisioners for running the build.
For the Shell provisioners this is:
```go
func (s *ShellProvisionerAccTest) GetProvisionerStore() packer.MapOfProvisioner {
return packer.MapOfProvisioner{
"shell": func() (packer.Provisioner, error) { return &shell.Provisioner{}, nil },
"file": func() (packer.Provisioner, error) { return &file.Provisioner{}, 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)
}
}
```
- **IsCompatible(builder string, vmOS string)** returns true or false whether the provisioner should run against a
specific builder or/and specific OS.
- **RunTest(c \*command.BuildCommand, args []string)** it will actually run the build and return any error if it fails the validations.
For the Shell provisioner this is:
```go
func (s *ShellProvisionerAccTest) RunTest(c *command.BuildCommand, args []string) error {
// Provisioner specific setup
UUID := os.Getenv("PACKER_RUN_UUID")
if UUID == "" {
UUID, _ = uuid.GenerateUUID()
os.Setenv("PACKER_RUN_UUID", UUID)
}
file := "provisioner.shell." + UUID + ".txt"
defer testutils.CleanupFiles(file)
// Run build
// All provisioner acc tests should contain this code and validation
if code := c.Run(args); code != 0 {
ui := c.Meta.Ui.(*packer.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())
}
// Any other extra specific validation
if !testutils.FileExists(file) {
return fmt.Errorf("Expected to find %s", file)
}
return nil
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)
}
```
```
After writing the struct and implementing the interface, now is time to write the test that will run all
of this code you wrote. Your test should be like:
@ -480,64 +520,101 @@ func TestShellProvisioner(t *testing.T) {
}
```
If the environment variable **ACC_TEST_PROVISIONERS** is set as `all` or contains the provisioner type, then the test should run, otherwise the test should skip.
In case of running it, you'll need to call the helper function `acc.TestProvisionersAgainstBuilders` passing a pointer to the test struct created above and the test testing pointer.
The method `TestProvisionersAgainstBuilders` will run the provisioner against
all available and compatible builders. If there are not builders compatible with
the test you want to run, you can add a builder using the following steps:
The method `TestProvisionersAgainstBuilders` will run the provisioner against all available and compatible builders. An available builder
is the one that has the necessary code for running this type of test. In case the builder you want to run against is not available for testing, you can write it following the next steps.
Create a subdirectory in provisioneracc/test-fixtures for the type of builder
you are adding. In this subdirectory, add one json file containing a single
builder fragment. For example, one of our amazon-ebs builders is defined in
provisioneracc/test-fixtures/amazon-ebs/amazon-ebs.txt and contains:
To add a new builder to the available builders for provisioners acc testing, you'll need to create a new folder under the builder folder
called `acceptance` and inside you create the `builder_acceptance.go` file and the package should be `<builder>_acc`. Like the provisioners, you'll need to create a struct that will
implement the BuilderAcceptance interface.
```go
type BuilderAcceptance interface {
GetConfigs() (map[string]string, error)
GetBuilderStore() packer.MapOfBuilder
CleanUp() error
}
```
- **GetConfigs()** should read a text file with the json configuration block for the builder and return a map of configs by OS type.
For the Amazon EBS builder the file contains:
```
{
"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"
}
}
```
The file should be placed under the `test-fixtures` folder.
In case you need to make references to another file, you'll need to add the relative path to provisioners folder like:
`../../builder/amazon/ebs/acceptance/test-fixtures/file.txt`.
- **GetBuilderStore()** this returns the builder store where we declare the available builders for running the build.
For the Amazon EBS builder this is:
```go
func (s *AmazonEBSAccTest) GetBuilderStore() packer.MapOfBuilder {
return packer.MapOfBuilder{
"amazon-ebs": func() (packer.Builder, error) { return &amazonebsbuilder.Builder{}, nil },
}
}
```
- **CleanUp()** cleans any resource created by the builder whether local or remote.
Once you created the builder necessary code, the last step is adding it to the `BuildersAccTest` map in `helper/tests/acc/provisioners.go`.
```go
var BuildersAccTest = map[string]BuilderAcceptance{
...
"amazon-ebs": new(amazonEBS.AmazonEBSAccTest),
...
```json
{
"type": "amazon-ebs",
"ami_name": "packer-acc-test",
"instance_type": "t2.micro",
"region": "us-east-1",
"ssh_username": "ubuntu",
"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
},
"force_deregister" : true,
"tags": {
"packer-test": "true"
}
}
```
Once you finish the steps, you should be ready to run your new provisioner acceptance test.
note that this fragment does not contain anything other than a single builder
definition. The testing framework will combine this with the provisioner
fragment to create a working json template.
In order to tell the testing framework how to use this builder fragment, you
need to implement a `BuilderFixture` struct:
```go
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
// 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
}
```
Implement this struct to the file "provisioneracc/builders.go", then add
the new implementation to the `BuildersAccTest` map in
`provisioneracc/provisioners.go`
Once you finish these steps, you should be ready to run your new provisioner
acceptance test by setting the name used in the BuildersAccTest map as your
`ACC_TEST_BUILDERS` environment variable.
#### Debugging Plugins

View File

@ -140,8 +140,8 @@ 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
ACC_TEST_BUILDERS=$(ACC_TEST_BUILDERS) ACC_TEST_PROVISIONERS=$(ACC_TEST_PROVISIONERS) go test ./provisioner/... -timeout=1h
provisioners-acctest: #install-build-deps generate
ACC_TEST_BUILDERS=$(ACC_TEST_BUILDERS) go test $(TEST) $(TESTARGS) -timeout=1h
# testacc runs acceptance tests
testacc: # install-build-deps generate ## Run acceptance tests

View File

@ -251,8 +251,10 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
if resp, e := ec2conn.DescribeInstances(describeInstance); e == nil {
if len(resp.Reservations) > 0 && len(resp.Reservations[0].Instances) > 0 {
instance := resp.Reservations[0].Instances[0]
ui.Error(fmt.Sprintf("Instance state change details: %s: %s",
*instance.StateTransitionReason, *instance.StateReason.Message))
if instance.StateTransitionReason != nil && instance.StateReason.Message != nil {
ui.Error(fmt.Sprintf("Instance state change details: %s: %s",
*instance.StateTransitionReason, *instance.StateReason.Message))
}
}
}
return multistep.ActionHalt

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,91 +3,259 @@ 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) {
TestProvisionersAgainstBuilders(testCase, t)
}
//nolint:errcheck
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,
}
err = provisionerAcc.RunTest(c, args)
// Cleanup created resources
testutils.CleanupFiles(fileName)
cleanErr := builderAcc.CleanUp()
if cleanErr != nil {
log.Printf("bad: failed to clean up resources: %s", cleanErr.Error())
}
// Make sure packer is installed:
packerbin, err := exec.LookPath("packer")
if err != nil {
t.Fatalf("bad: failed to to run build: %s", err.Error())
t.Fatalf("Couldn't find packer binary installed on system: %s", err.Error())
}
// Run build
buildCommand := exec.Command(packerbin, "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)
}
// Cleanup stuff created by builder.
cleanErr := buildFixture.Teardown()
if cleanErr != nil {
log.Printf("bad: failed to clean up builder-created resources: %s", cleanErr.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 {
cwd, _ := os.Getwd()
t.Fatalf(fmt.Sprintf("Error running provisioner acceptance"+
" tests: %s\nLogs can be found at %s\nand the "+
"acceptance test template can be found at %s",
checkErr.Error(), filepath.Join(cwd, logfile),
filepath.Join(cwd, templatePath)))
} else {
os.Remove(templatePath)
os.Remove(logfile)
}
})
}
}
}
// TestProvisionersPreCheck checks if the Provisioner with name is set in ACC_TEST_PROVISIONERS environment variable
func TestProvisionersPreCheck(name string, t *testing.T) {
p := os.Getenv("ACC_TEST_PROVISIONERS")
if p == "all" {
return
}
provisioners := strings.Split(p, ",")
for _, provisioner := range provisioners {
if provisioner == name {
return
}
}
msg := fmt.Sprintf("Provisioner %q not defined in ACC_TEST_PROVISIONERS", name)
t.Skip(msg)
}
// 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 +287,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 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,20 @@
{
"type": "amazon-ebs",
"ami_name": "packer-acc-test",
"instance_type": "t2.micro",
"region": "us-east-1",
"ssh_username": "ubuntu",
"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
},
"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

@ -49,6 +49,15 @@ type TestCase struct {
// If SkipArtifactTeardown is true, we will not attempt to destroy the
// artifact created in this test run.
SkipArtifactTeardown bool
// If set, overrides the default provisioner store with custom provisioners.
// This can be useful for running acceptance tests for a particular
// provisioner using a specific builder.
// Default provisioner store:
// ProvisionerStore: packersdk.MapOfProvisioner{
// "shell": func() (packersdk.Provisioner, error) { return &shellprovisioner.Provisioner{}, nil },
// "file": func() (packersdk.Provisioner, error) { return &file.Provisioner{}, nil },
// },
ProvisionerStore packersdk.MapOfProvisioner
}
// TestCheckFunc is the callback used for Check in TestStep.
@ -112,6 +121,12 @@ func Test(t TestT, c TestCase) {
return
}
if c.ProvisionerStore == nil {
c.ProvisionerStore = packersdk.MapOfProvisioner{
"shell": func() (packersdk.Provisioner, error) { return &shellprovisioner.Provisioner{}, nil },
"file": func() (packersdk.Provisioner, error) { return &file.Provisioner{}, nil },
}
}
// Build the core
log.Printf("[DEBUG] Initializing core...")
core := packer.NewCore(&packer.CoreConfig{
@ -125,10 +140,7 @@ func Test(t TestT, c TestCase) {
return nil, nil
},
},
ProvisionerStore: packersdk.MapOfProvisioner{
"shell": func() (packersdk.Provisioner, error) { return &shellprovisioner.Provisioner{}, nil },
"file": func() (packersdk.Provisioner, error) { return &file.Provisioner{}, nil },
},
ProvisionerStore: c.ProvisionerStore,
},
Template: tpl,
})
@ -163,7 +175,6 @@ func Test(t TestT, c TestCase) {
// Run it! We use a temporary directory for caching and discard
// any UI output. We discard since it shows up in logs anyways.
log.Printf("[DEBUG] Running 'test' build")
// ui := packersdk.TestUi(t)
ui := &packersdk.BasicUi{
Reader: os.Stdin,
Writer: ioutil.Discard,

View File

@ -10,7 +10,6 @@ import (
"testing"
"time"
"github.com/hashicorp/packer/packer"
packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
"golang.org/x/crypto/ssh"
@ -26,7 +25,7 @@ func TestAdapter_Serve(t *testing.T) {
config := &ssh.ServerConfig{}
ui := new(packer.NoopUi)
ui := packersdk.TestUi(t)
sut := NewAdapter(done, &l, config, "", ui, communicator{})
go func() {

View File

@ -151,6 +151,56 @@ func (rw *BasicUi) TrackProgress(src string, currentSize, totalSize int64, strea
return rw.PB.TrackProgress(src, currentSize, totalSize, stream)
}
// Safe is a UI that wraps another UI implementation and
// provides concurrency-safe access
type SafeUi struct {
Sem chan int
Ui Ui
PB getter.ProgressTracker
}
var _ Ui = new(SafeUi)
func (u *SafeUi) Ask(s string) (string, error) {
u.Sem <- 1
ret, err := u.Ui.Ask(s)
<-u.Sem
return ret, err
}
func (u *SafeUi) Say(s string) {
u.Sem <- 1
u.Ui.Say(s)
<-u.Sem
}
func (u *SafeUi) Message(s string) {
u.Sem <- 1
u.Ui.Message(s)
<-u.Sem
}
func (u *SafeUi) Error(s string) {
u.Sem <- 1
u.Ui.Error(s)
<-u.Sem
}
func (u *SafeUi) Machine(t string, args ...string) {
u.Sem <- 1
u.Ui.Machine(t, args...)
<-u.Sem
}
func (u *SafeUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) (body io.ReadCloser) {
u.Sem <- 1
ret := u.Ui.TrackProgress(src, currentSize, totalSize, stream)
<-u.Sem
return ret
}
// NoopProgressTracker is a progress tracker
// that displays nothing.
type NoopProgressTracker struct{}

View File

@ -7,6 +7,8 @@ import (
"testing"
)
// TestUi creates a simple UI for use in testing.
// It's not meant for "real" use.
func TestUi(t *testing.T) Ui {
var buf bytes.Buffer
return &BasicUi{

View File

@ -30,21 +30,6 @@ const (
UiColorCyan = 36
)
type NoopUi struct {
PB packersdk.NoopProgressTracker
}
var _ packersdk.Ui = new(NoopUi)
func (*NoopUi) Ask(string) (string, error) { return "", errors.New("this is a noop ui") }
func (*NoopUi) Say(string) { return }
func (*NoopUi) Message(string) { return }
func (*NoopUi) Error(string) { return }
func (*NoopUi) Machine(string, ...string) { return }
func (u *NoopUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser {
return u.PB.TrackProgress(src, currentSize, totalSize, stream)
}
// ColoredUi is a UI that is colored using terminal colors.
type ColoredUi struct {
Color UiColor
@ -267,53 +252,3 @@ func (u *TimestampedUi) TrackProgress(src string, currentSize, totalSize int64,
func (u *TimestampedUi) timestampLine(string string) string {
return fmt.Sprintf("%v: %v", time.Now().Format(time.RFC3339), string)
}
// Safe is a UI that wraps another UI implementation and
// provides concurrency-safe access
type SafeUi struct {
Sem chan int
Ui packersdk.Ui
PB getter.ProgressTracker
}
var _ packersdk.Ui = new(SafeUi)
func (u *SafeUi) Ask(s string) (string, error) {
u.Sem <- 1
ret, err := u.Ui.Ask(s)
<-u.Sem
return ret, err
}
func (u *SafeUi) Say(s string) {
u.Sem <- 1
u.Ui.Say(s)
<-u.Sem
}
func (u *SafeUi) Message(s string) {
u.Sem <- 1
u.Ui.Message(s)
<-u.Sem
}
func (u *SafeUi) Error(s string) {
u.Sem <- 1
u.Ui.Error(s)
<-u.Sem
}
func (u *SafeUi) Machine(t string, args ...string) {
u.Sem <- 1
u.Ui.Machine(t, args...)
<-u.Sem
}
func (u *SafeUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) (body io.ReadCloser) {
u.Sem <- 1
ret := u.Ui.TrackProgress(src, currentSize, totalSize, stream)
<-u.Sem
return ret
}

View File

@ -12,9 +12,8 @@ import (
"os/exec"
"github.com/hashicorp/packer/builder/docker"
"github.com/hashicorp/packer/packer"
builderT "github.com/hashicorp/packer/packer-plugin-sdk/acctest"
packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/packer-plugin-sdk/template"
"github.com/hashicorp/packer/provisioner/file"
)
@ -135,7 +134,7 @@ func TestProvisionerProvision_PlaybookFiles(t *testing.T) {
}
comm := &communicatorMock{}
if err := p.Provision(context.Background(), new(packer.NoopUi), comm, make(map[string]interface{})); err != nil {
if err := p.Provision(context.Background(), packersdk.TestUi(t), comm, make(map[string]interface{})); err != nil {
t.Fatalf("err: %s", err)
}
@ -169,7 +168,7 @@ func TestProvisionerProvision_PlaybookFilesWithPlaybookDir(t *testing.T) {
}
comm := &communicatorMock{}
if err := p.Provision(context.Background(), new(packer.NoopUi), comm, make(map[string]interface{})); err != nil {
if err := p.Provision(context.Background(), packersdk.TestUi(t), comm, make(map[string]interface{})); err != nil {
t.Fatalf("err: %s", err)
}
@ -329,69 +328,38 @@ func testProvisionerProvisionDockerWithPlaybookFiles(t *testing.T, templateStrin
t.Skip("This test is only run with PACKER_ACC=1")
}
ui := packersdk.TestUi(t)
tpl, err := template.Parse(strings.NewReader(templateString))
if err != nil {
t.Fatalf("Unable to parse config: %s", err)
}
// Check if docker executable can be found.
_, err = exec.LookPath("docker")
// 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")
}
// Setup the builder
builder := &docker.Builder{}
_, warnings, err := builder.Prepare(tpl.Builders["docker"].Config)
if err != nil {
t.Fatalf("Error preparing configuration %s", err)
}
if len(warnings) > 0 {
t.Fatal("Encountered configuration warnings; aborting")
}
builderT.Test(t, builderT.TestCase{
Builder: &docker.Builder{},
Template: templateString,
Check: func(a []packersdk.Artifact) error {
ansible := &Provisioner{}
err = ansible.Prepare(tpl.Provisioners[0].Config)
if err != nil {
t.Fatalf("Error preparing ansible-local provisioner: %s", err)
}
actualContent, err := ioutil.ReadFile("hello_world")
if err != nil {
return fmt.Errorf("Expected file not found: %s", err)
}
download := &file.Provisioner{}
err = download.Prepare(tpl.Provisioners[1].Config)
if err != nil {
t.Fatalf("Error preparing download: %s", err)
}
// Add hooks so the provisioners run during the build
hooks := map[string][]packersdk.Hook{}
hooks[packersdk.HookProvision] = []packersdk.Hook{
&packer.ProvisionHook{
Provisioners: []*packer.HookedProvisioner{
{Provisioner: ansible, Config: nil, TypeName: ""},
{Provisioner: download, Config: nil, TypeName: ""},
},
expectedContent := "Hello world!"
if string(actualContent) != expectedContent {
return fmt.Errorf(`Unexpected file content: expected="%s", actual="%s"`, expectedContent, actualContent)
}
return nil
},
}
hook := &packersdk.DispatchHook{Mapping: hooks}
artifact, err := builder.Run(context.Background(), ui, hook)
if err != nil {
t.Fatalf("Error running build %s", err)
}
defer os.Remove("hello_world")
defer artifact.Destroy()
actualContent, err := ioutil.ReadFile("hello_world")
if err != nil {
t.Fatalf("Expected file not found: %s", err)
}
expectedContent := "Hello world!"
if string(actualContent) != expectedContent {
t.Fatalf(`Unexpected file content: expected="%s", actual="%s"`, expectedContent, actualContent)
}
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) {
@ -477,7 +445,7 @@ const playbookFilesDockerTemplate = `
{
"builders": [
{
"type": "docker",
"type": "test",
"image": "williamyeh/ansible:centos7",
"discard": true
}
@ -504,7 +472,7 @@ const playbookFilesWithPlaybookDirDockerTemplate = `
{
"builders": [
{
"type": "docker",
"type": "test",
"image": "williamyeh/ansible:centos7",
"discard": true
}

View File

@ -30,7 +30,6 @@ import (
"golang.org/x/crypto/ssh"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer-plugin-sdk/adapter"
"github.com/hashicorp/packer/packer-plugin-sdk/common"
"github.com/hashicorp/packer/packer-plugin-sdk/multistep/commonsteps"
@ -433,7 +432,7 @@ func (p *Provisioner) setupAdapter(ui packersdk.Ui, comm packersdk.Communicator)
return "", err
}
ui = &packer.SafeUi{
ui = &packersdk.SafeUi{
Sem: make(chan int, 1),
Ui: ui,
}

View File

@ -28,7 +28,6 @@ import (
"golang.org/x/crypto/ssh"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer-plugin-sdk/adapter"
"github.com/hashicorp/packer/packer-plugin-sdk/common"
packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
@ -304,7 +303,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packersdk.Ui, comm packe
return err
}
ui = &packer.SafeUi{
ui = &packersdk.SafeUi{
Sem: make(chan int, 1),
Ui: ui,
}

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\n", 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"
}