Compare commits

..

50 Commits

Author SHA1 Message Date
Adrien Delorme 780f9c8b41 empty commit 2021-03-24 11:22:30 +01:00
Adrien Delorme 3629aeaa01 Merge remote-tracking branch 'origin/master' into azr_implicit_requried_plugin 2021-03-24 11:08:05 +01:00
Adrien Delorme c207e4acad Update init.mdx 2021-03-24 10:49:03 +01:00
Adrien Delorme 519dfa1959 Update init.mdx
improve Implicit required plugin section
2021-03-24 10:45:18 +01:00
Adrien Delorme 51024ed507 Update website/content/docs/commands/init.mdx
Co-authored-by: Sylvia Moss <moss@hashicorp.com>
2021-03-23 13:35:05 +01:00
Adrien Delorme a060cd81a5 Update init.mdx 2021-03-23 13:26:26 +01:00
Adrien Delorme 4ab0d14f8c Update init.mdx 2021-03-23 13:12:22 +01:00
Adrien Delorme 4cd4616641 Update init.mdx 2021-03-23 13:12:05 +01:00
Adrien Delorme e470ec0ffc Merge remote-tracking branch 'origin/master' into azr_implicit_requried_plugin 2021-03-23 13:09:41 +01:00
Adrien Delorme e976c30964 Update init.mdx 2021-03-23 13:09:16 +01:00
Adrien Delorme 7ed9e672a9 Update command/init.go
clearer and shorter warning ( using docs link )
2021-03-23 13:07:59 +01:00
Adrien Delorme ff5b5221c4 Update init.mdx 2021-03-23 13:06:20 +01:00
Adrien Delorme b345a44a07 Update init.mdx 2021-03-23 13:05:16 +01:00
Adrien Delorme d8cbfcc075 Update website/content/docs/commands/init.mdx 2021-03-19 11:36:12 +01:00
Adrien Delorme eba081a155 Update types.required_plugins.go
more comments
2021-03-19 11:18:39 +01:00
Adrien Delorme 7ccfe38475 Update types.required_plugins.go
up comment
2021-03-19 11:16:13 +01:00
Adrien Delorme dacf52b5b3 check for pre-existing component definition first 2021-03-19 11:13:43 +01:00
Adrien Delorme 1926fd6176 Update command/init.go
better warning

Co-authored-by: Sylvia Moss <moss@hashicorp.com>
2021-03-18 16:43:29 +01:00
Adrien Delorme 15f4b93cd2 init.mdx: add short text on implicit plugin requirements 2021-03-17 11:46:42 +01:00
Adrien Delorme 46b6a21a9f Update types.required_plugins.go
better comments
2021-03-17 11:24:37 +01:00
Adrien Delorme 53dc1eb87b better comments 2021-03-17 11:03:42 +01:00
Adrien Delorme 0c45f75bf6 better comments 2021-03-17 11:02:20 +01:00
Adrien Delorme 9b41474ab5 better comments 2021-03-17 10:59:55 +01:00
Adrien Delorme 2f4c5c0a24 test that already defined components won't create implicit required plugins 2021-03-17 10:30:37 +01:00
Adrien Delorme 85aef9d3a6 skip a implictly requiring a plugin if the plugin name of the plugin is being defined in a required_plugin 2021-03-17 10:17:03 +01:00
Adrien Delorme ad21a101c8 add comments 2021-03-17 10:16:24 +01:00
Adrien Delorme ef974dd75c add comments 2021-03-17 09:56:00 +01:00
Adrien Delorme 69de942647 requirePluginImplicitly => decodeImplicitRequiredPluginsBlock 2021-03-17 09:54:45 +01:00
Adrien Delorme 2e9307de3f define ComponentKind that help enumerate what kind of components exist in hcl 2021-03-17 09:53:26 +01:00
Adrien Delorme 27fb9b2525 rimplify requirePluginImplicitly a little 2021-03-17 09:52:59 +01:00
Adrien Delorme 47fa09a9c9 hcl2template: Rename Datasource type to DatasourceBlock 2021-03-17 09:42:47 +01:00
Adrien Delorme 7e4948502f add test for a forked plugin 2021-03-17 09:29:36 +01:00
Adrien Delorme f00ea7a166 more docs 2021-03-17 09:19:14 +01:00
Adrien Delorme bdc8ac2813 document redirects 2021-03-16 17:59:55 +01:00
Adrien Delorme 7ce5a2b9cf comment redirects for now 2021-03-16 17:59:47 +01:00
Adrien Delorme 2c48e04f1f add all plugins that we plan on moving out 2021-03-16 17:29:29 +01:00
Adrien Delorme dba5171fa2 improve wording some more 2021-03-16 13:59:24 +01:00
Adrien Delorme a1de1559bf improve implicit plugin wording 2021-03-16 13:55:01 +01:00
Adrien Delorme 5497139f2a better init wording 2021-03-16 13:36:42 +01:00
Adrien Delorme 172cec7223 better wording for implicitly required plugins 2021-03-15 17:13:58 +01:00
Adrien Delorme 08b0884521 Vet command/init.go 2021-03-09 16:16:08 +01:00
Adrien Delorme 3224438a34 vet 2021-03-09 16:16:08 +01:00
Adrien Delorme 2b4812837a Update main.go
show where to add maps
2021-03-09 16:16:07 +01:00
Adrien Delorme 123517aec1 add warning with what to when calling init on an implicitly required plugin 2021-03-09 16:16:07 +01:00
Adrien Delorme c4535b2552 Update types.required_plugins_test.go 2021-03-09 16:16:07 +01:00
Adrien Delorme 1d10da1126 better docs 2021-03-09 16:16:07 +01:00
Adrien Delorme 8f7b148cbb Update types.required_plugins.go 2021-03-09 16:16:07 +01:00
Adrien Delorme 924fb0f8f8 make sure renamed plugins don't match if not defined 2021-03-09 16:16:07 +01:00
Adrien Delorme df4c5a1314 actually parse hcl blocks 2021-03-09 16:16:07 +01:00
Adrien Delorme 37e494a6e8 add basic inferImplicitRequiredPluginFromBlocks + tests
this is not done; I think testing with an actual parsing will be better
2021-03-09 16:16:06 +01:00
1707 changed files with 25200 additions and 66659 deletions
+3 -16
View File
@@ -37,19 +37,15 @@ commands:
parameters:
GOOS:
type: string
GOARCH:
default: "amd64"
type: string
steps:
- checkout
- run: GOOS=<< parameters.GOOS >> GOARCH=<<parameters.GOARCH>> go build -ldflags="-s -w -X github.com/hashicorp/packer/version.GitCommit=${CIRCLE_SHA1}" -o ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >> .
- run: zip ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >>.zip ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >>
- run: rm ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >>
- run: GOOS=<< parameters.GOOS >> go build -ldflags="-s -w -X github.com/hashicorp/packer/version.GitCommit=${CIRCLE_SHA1}" -o ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH) .
- run: zip ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH).zip ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH)
- run: rm ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH)
- persist_to_workspace:
root: .
paths:
- ./pkg/
# Golang CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-go/ for more details
@@ -127,13 +123,6 @@ jobs:
steps:
- build-and-persist-packer-binary:
GOOS: darwin
build_darwin_arm64:
executor: golang
working_directory: /go/src/github.com/hashicorp/packer
steps:
- build-and-persist-packer-binary:
GOOS: darwin
GOARCH: arm64
build_freebsd:
executor: golang
working_directory: /go/src/github.com/hashicorp/packer
@@ -214,7 +203,6 @@ workflows:
jobs:
- build_linux
- build_darwin
- build_darwin_arm64
- build_windows
- build_freebsd
- build_openbsd
@@ -223,7 +211,6 @@ workflows:
requires:
- build_linux
- build_darwin
- build_darwin_arm64
- build_windows
- build_freebsd
- build_openbsd
+5 -49
View File
@@ -1,44 +1,21 @@
## 1.7.2 (Upcoming)
### IMPROVEMENTS:
* builder/alicloud: Add `ramrole` configuration to ECS instance. [GH-10845]
### BUG FIXES:
* builder/proxmox: Update Proxmox Go API to ensure only the first non-loopback
IPv4 address gets returned. [GH-10858]
* builder/vsphere: Fix primary disk resize on clone. [GH-10848]
* core: Fix bug where call to "packer version" sent output to stderr instead of
stdout. [GH-10850]
## 1.7.1 (March 31, 2021)
## 1.7.1 (Upcoming)
### NOTES:
* builder/amazon: Has been vendored in this release and will no longer be
updated with Packer core. In Packer v1.8.0 the plugin will be removed
entirely. The `amazon` components will continue to work as expected until
then, but for the latest offerings of the Amazon plugin, users are
encourage to use the `packer init` command to install the latest release
version. For more details see [Installing Packer
Plugins](https://www.packer.io/docs/plugins#installing-plugins)
* builder/docker: Has been vendored in this release and will no longer be
updated with Packer core. In Packer v1.8.0 the plugin will be removed
entirely. The `docker` builder will continue to work as expected until
then, but for the latest offerings of the Docker plugin, users are
encourage to use the `packer init` command to install the latest release
version. For more details see [Installing Packer
Plugins](https://www.packer.io/docs/plugins#installing-plugins)
* darwin/arm64: Packer now includes the darwin/arm64 binary to its releases to
supports the new OSX M1. [GH-10804]
Plugins](https://www.packer.io/docs/plugins#installing- plugins)
* post-processor/docker-\*: Have been vendored in this release and will no
longer be updated with Packer core. In Packer v1.8.0 the plugin will be
removed entirely. The `docker` builder will continue to work as expected
until then, but for the latest offerings of the Docker plugin, users are
encourage to use the `packer init` command to install the latest release
version. For more details see [Installing Packer
Plugins](https://www.packer.io/docs/plugins#installing-plugins)
Plugins](https://www.packer.io/docs/plugins#installing- plugins)
* post-processor/exoscale-import: Has been vendored in this release and will no
longer be updated with Packer core. In Packer v1.8.0 the plugin will be
removed entirely. The `exoscale-import` post-processor will continue to
@@ -48,17 +25,13 @@
Repostiroy](https://github.com/exoscale/packer-plugin-exoscale). [GH-10709]
### IMPROVEMENTS
* builder/amazon: allow creation of ebs snapshots without volumes. [GH-9591]
* builder/amazon: allow creation of ebs snapshots wihtout volumes. [GH-9591]
* builder/amazon: Fix issue for multi-region AMI build that fail when
encrypting with KMS and sharing across accounts. [GH-10754]
* builder/azure: Add client_cert_token_timeout option. [GH-10528]
* builder/google: Make Windows password timeout configurable. [GH-10727]
* builder/google: Update public GCP image project as gce-uefi-images are
deprecated. [GH-10724]
* builder/oracle-oci: Update Oracle Go SDK to add support for OCI flexible
shapes. [GH-10833]
* builder/proxmox: Allow using API tokens for Proxmox authentication.
[GH-10797]
* builder/qemu: Added firmware option. [GH-10683]
* builder/scaleway: add support for timeout in shutdown step. [GH-10503]
* builder/vagrant: Fix logging to be clearer when Vagrant builder overrides
@@ -74,21 +47,11 @@
[GH-10651]
* command/fmt: Adding recursive flag to formatter to format subdirectories.
[GH-10457]
* core/hcl2: Add legacy_isotime function. [GH-10780]
* core/hcl2: Add support for generating `dynamic` blocks within a `build`
block. [GH-10825]
* core/hcl2: Add templatefile function. [GH-10776]
* core/hcl2_upgrade: hcl2_upgrade command can now upgrade json var-files.
[GH-10676]
* core/init: Add implicit required_plugin blocks feature. [GH-10732]
* core: Add http_content option to serve variables from HTTP at preseed.
[GH-10801]
* core: Change template parsing error to include warning about file extensions.
[GH-10652]
* core: Update to gopsutil v3.21.1 to allow builds to work for darwin arm64.
[GH-10697]
* provisioner/inspec: Allow non-zero exit codes for inspec provisioner.
[GH-10723]
* hcl2_upgrade: hcl2_upgrade command can now upgrade json var-files [GH-10676]
### BUG FIXES
* buider/azure: Update builder to ensure a proper clean up Azure temporary
@@ -105,8 +68,6 @@
[GH-10748]
* builder/oracle-oci: Update Oracle Go SDK to fix issue with reading key file.
[GH-10560] [GH-10774]
* builder/outscale: Fix omi_description that was ignored in Osc builder
[GH-10792]
* builder/parallels: Make Packer respect winrm_host flag in winrm connect func.
[GH-10748]
* builder/proxmox: Fixes issue when using `additional_iso_files` in HCL enabled
@@ -117,14 +78,11 @@
func. [GH-10748]
* builder/vmware: Added a fallback file check when trying to determine the
network-mapping configuration. [GH-10543]
* builder/vsphere: Fix invalid device configuration issue when creating a
vm with multiple disk on the same controller. [GH-10844]
* builder/vsphere: Fix issue where boot command would fail the build do to a
key typing error. This change will now retry to type the key on error
before giving up. [GH-10541]
* core/hcl2_upgrade: Check for nil config map when provisioner/post-processor
doesn't have config. [GH-10730]
* core/hcl2_upgrade: Fix escaped quotes in template functions [GH-10794]
* core/hcl2_upgrade: Make hcl2_upgrade command correctly translate
pause_before. [GH-10654]
* core/hcl2_upgrade: Make json variables using template engines get stored as
@@ -136,8 +94,6 @@
* core: Pin Packer to Golang 1.16 to fix code generation issues. [GH-10702]
* core: Templates previously could not interpolate the environment variable
PACKER_LOG_PATH. [GH-10660]
* post-processor/vagrant-cloud: Override direct upload based on box size
[GH-10820]
* provisioner/chef-solo: HCL2 templates can support the json_string option.
[GH-10655]
* provisioner/inspec: Add new configuration field `valid_exit_codes` to allow
+1 -1
View File
@@ -49,7 +49,7 @@ package:
@sh -c "$(CURDIR)/scripts/dist.sh $(VERSION)"
install-build-deps: ## Install dependencies for bin build
@go install github.com/mitchellh/gox@v1.0.1
@go get github.com/mitchellh/gox
install-gen-deps: ## Install dependencies for code generation
# to avoid having to tidy our go deps, we `go get` our binaries from a temp
-1
View File
@@ -135,7 +135,6 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
RamRoleName: b.config.RamRoleName,
RegionId: b.config.AlicloudRegion,
InternetChargeType: b.config.InternetChargeType,
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
-2
View File
@@ -88,7 +88,6 @@ type FlatConfig struct {
AlicloudSourceImage *string `mapstructure:"source_image" required:"true" cty:"source_image" hcl:"source_image"`
ForceStopInstance *bool `mapstructure:"force_stop_instance" required:"false" cty:"force_stop_instance" hcl:"force_stop_instance"`
DisableStopInstance *bool `mapstructure:"disable_stop_instance" required:"false" cty:"disable_stop_instance" hcl:"disable_stop_instance"`
RamRoleName *string `mapstructure:"ram_role_name" required:"false" cty:"ram_role_name" hcl:"ram_role_name"`
SecurityGroupId *string `mapstructure:"security_group_id" required:"false" cty:"security_group_id" hcl:"security_group_id"`
SecurityGroupName *string `mapstructure:"security_group_name" required:"false" cty:"security_group_name" hcl:"security_group_name"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
@@ -206,7 +205,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"source_image": &hcldec.AttrSpec{Name: "source_image", Type: cty.String, Required: false},
"force_stop_instance": &hcldec.AttrSpec{Name: "force_stop_instance", Type: cty.Bool, Required: false},
"disable_stop_instance": &hcldec.AttrSpec{Name: "disable_stop_instance", Type: cty.Bool, Required: false},
"ram_role_name": &hcldec.AttrSpec{Name: "ram_role_name", Type: cty.String, Required: false},
"security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false},
"security_group_name": &hcldec.AttrSpec{Name: "security_group_name", Type: cty.String, Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
-2
View File
@@ -47,8 +47,6 @@ type RunConfig struct {
// E.g., Sysprep a windows which may shutdown the instance within its command.
// The default value is false.
DisableStopInstance bool `mapstructure:"disable_stop_instance" required:"false"`
// Ram Role to apply when launching the instance.
RamRoleName string `mapstructure:"ram_role_name" required:"false"`
// ID of the security group to which a newly
// created instance belongs. Mutual access is allowed between instances in one
// security group. If not specified, the newly created instance will be added
@@ -23,7 +23,6 @@ type stepCreateAlicloudInstance struct {
UserData string
UserDataFile string
instanceId string
RamRoleName string
RegionId string
InternetChargeType string
InternetMaxBandwidthOut int
@@ -116,7 +115,6 @@ func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep.
request.RegionId = s.RegionId
request.InstanceType = s.InstanceType
request.InstanceName = s.InstanceName
request.RamRoleName = s.RamRoleName
request.ZoneId = s.ZoneId
sourceImage := state.Get("source_image").(*ecs.Image)
@@ -15,7 +15,6 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/hcl/v2/hcldec"
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
"github.com/hashicorp/packer-plugin-sdk/chroot"
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/multistep"
@@ -24,6 +23,7 @@ import (
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
)
// The unique ID for this builder
@@ -4,8 +4,8 @@ package chroot
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-amazon/builder/common"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer/builder/amazon/common"
"github.com/zclconf/go-cty/cty"
)
+251
View File
@@ -0,0 +1,251 @@
package chroot
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"ami_name": "foo",
"source_ami": "foo",
"region": "us-east-1",
// region validation logic is checked in ami_config_test
"skip_region_validation": true,
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packersdk.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilderPrepare_AMIName(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["ami_name"] = "foo"
config["skip_region_validation"] = true
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["ami_name"] = "foo {{"
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
// Test bad
delete(config, "ami_name")
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_ChrootMounts(t *testing.T) {
b := &Builder{}
config := testConfig()
config["chroot_mounts"] = nil
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_ChrootMountsBadDefaults(t *testing.T) {
b := &Builder{}
config := testConfig()
config["chroot_mounts"] = [][]string{
{"bad"},
}
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_SourceAmi(t *testing.T) {
b := &Builder{}
config := testConfig()
config["source_ami"] = ""
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
config["source_ami"] = "foo"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_CommandWrapper(t *testing.T) {
b := &Builder{}
config := testConfig()
config["command_wrapper"] = "echo hi; {{.Command}}"
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_CopyFiles(t *testing.T) {
b := &Builder{}
config := testConfig()
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
if len(b.config.CopyFiles) != 1 && b.config.CopyFiles[0] != "/etc/resolv.conf" {
t.Errorf("Was expecting default value for copy_files.")
}
}
func TestBuilderPrepare_CopyFilesNoDefault(t *testing.T) {
b := &Builder{}
config := testConfig()
config["copy_files"] = []string{}
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
if len(b.config.CopyFiles) > 0 {
t.Errorf("Was expecting no default value for copy_files. Found %v",
b.config.CopyFiles)
}
}
func TestBuilderPrepare_RootDeviceNameAndAMIMappings(t *testing.T) {
var b Builder
config := testConfig()
config["root_device_name"] = "/dev/sda"
config["ami_block_device_mappings"] = []interface{}{map[string]string{}}
config["root_volume_size"] = 15
_, warnings, err := b.Prepare(config)
if len(warnings) == 0 {
t.Fatal("Missing warning, stating block device mappings will be overwritten")
} else if len(warnings) > 1 {
t.Fatalf("excessive warnings: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_AMIMappingsNoRootDeviceName(t *testing.T) {
var b Builder
config := testConfig()
config["ami_block_device_mappings"] = []interface{}{map[string]string{}}
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("should have error")
}
}
func TestBuilderPrepare_RootDeviceNameNoAMIMappings(t *testing.T) {
var b Builder
config := testConfig()
config["root_device_name"] = "/dev/sda"
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("should have error")
}
}
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
var b Builder
config := testConfig()
generatedData, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if generatedData[0] != "SourceAMIName" {
t.Fatalf("Generated data should contain SourceAMIName")
}
if generatedData[1] != "BuildRegion" {
t.Fatalf("Generated data should contain BuildRegion")
}
if generatedData[2] != "SourceAMI" {
t.Fatalf("Generated data should contain SourceAMI")
}
if generatedData[3] != "SourceAMICreationDate" {
t.Fatalf("Generated data should contain SourceAMICreationDate")
}
if generatedData[4] != "SourceAMIOwner" {
t.Fatalf("Generated data should contain SourceAMIOwner")
}
if generatedData[5] != "SourceAMIOwnerName" {
t.Fatalf("Generated data should contain SourceAMIOwnerName")
}
if generatedData[6] != "Device" {
t.Fatalf("Generated data should contain Device")
}
if generatedData[7] != "MountPath" {
t.Fatalf("Generated data should contain MountPath")
}
}
+51
View File
@@ -0,0 +1,51 @@
package chroot
import (
"fmt"
"io/ioutil"
"os"
"runtime"
"testing"
"github.com/hashicorp/packer-plugin-sdk/common"
)
func TestCopyFile(t *testing.T) {
if runtime.GOOS == "windows" {
return
}
first, err := ioutil.TempFile("", "copy_files_test")
if err != nil {
t.Fatalf("couldn't create temp file.")
}
defer os.Remove(first.Name())
newName := first.Name() + "-new"
payload := "copy_files_test.go payload"
if _, err = first.WriteString(payload); err != nil {
t.Fatalf("Couldn't write payload to first file.")
}
first.Sync()
cmd := common.ShellCommand(fmt.Sprintf("cp %s %s", first.Name(), newName))
if err := cmd.Run(); err != nil {
t.Fatalf("Couldn't copy file")
}
defer os.Remove(newName)
second, err := os.Open(newName)
if err != nil {
t.Fatalf("Couldn't open copied file.")
}
defer second.Close()
var copiedPayload = make([]byte, len(payload))
if _, err := second.Read(copiedPayload); err != nil {
t.Fatalf("Couldn't open copied file for reading.")
}
if string(copiedPayload) != payload {
t.Fatalf("payload not copied.")
}
}
+10
View File
@@ -0,0 +1,10 @@
package chroot
import "testing"
func TestDevicePrefixMatch(t *testing.T) {
/*
if devicePrefixMatch("nvme0n1") != "" {
}
*/
}
@@ -7,9 +7,9 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
)
// StepAttachVolume attaches the previously created volume to an
@@ -0,0 +1,15 @@
package chroot
import (
"testing"
"github.com/hashicorp/packer-plugin-sdk/chroot"
)
func TestAttachVolumeCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
var raw interface{}
raw = new(StepAttachVolume)
if _, ok := raw.(chroot.Cleanup); !ok {
t.Fatalf("cleanup func should be a CleanupFunc")
}
}
@@ -8,11 +8,11 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
)
// StepCreateVolume creates a new volume from the snapshot of the root
@@ -0,0 +1,97 @@
package chroot
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
confighelper "github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/stretchr/testify/assert"
)
func buildTestRootDevice() *ec2.BlockDeviceMapping {
return &ec2.BlockDeviceMapping{
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(10),
SnapshotId: aws.String("snap-1234"),
VolumeType: aws.String("gp2"),
Encrypted: aws.Bool(false),
},
}
}
func TestCreateVolume_Default(t *testing.T) {
stepCreateVolume := new(StepCreateVolume)
_, err := stepCreateVolume.buildCreateVolumeInput("test-az", buildTestRootDevice())
assert.NoError(t, err)
}
func TestCreateVolume_Shrink(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeSize: 1}
testRootDevice := buildTestRootDevice()
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
// Ensure that the new value is equal to the size of the old root device
assert.Equal(t, *ret.Size, *testRootDevice.Ebs.VolumeSize)
}
func TestCreateVolume_Expand(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeSize: 25}
testRootDevice := buildTestRootDevice()
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
// Ensure that the new value is equal to the size of the value passed in
assert.Equal(t, *ret.Size, stepCreateVolume.RootVolumeSize)
}
func TestCreateVolume_io1_to_io1(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeType: "io1"}
testRootDevice := buildTestRootDevice()
testRootDevice.Ebs.VolumeType = aws.String("io1")
testRootDevice.Ebs.Iops = aws.Int64(1000)
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
assert.Equal(t, *ret.VolumeType, stepCreateVolume.RootVolumeType)
assert.Equal(t, *ret.Iops, *testRootDevice.Ebs.Iops)
}
func TestCreateVolume_io1_to_gp2(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeType: "gp2"}
testRootDevice := buildTestRootDevice()
testRootDevice.Ebs.VolumeType = aws.String("io1")
testRootDevice.Ebs.Iops = aws.Int64(1000)
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
assert.Equal(t, *ret.VolumeType, stepCreateVolume.RootVolumeType)
assert.Nil(t, ret.Iops)
}
func TestCreateVolume_gp2_to_io1(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeType: "io1"}
testRootDevice := buildTestRootDevice()
_, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.Error(t, err)
}
func TestCreateVolume_Encrypted(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeEncryptBoot: confighelper.TrileanFromBool(true)}
testRootDevice := buildTestRootDevice()
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
// Ensure that the new value is equal to the the value passed in
assert.Equal(t, confighelper.TrileanFromBool(*ret.Encrypted), stepCreateVolume.RootVolumeEncryptBoot)
}
func TestCreateVolume_Custom_KMS_Key_Encrypted(t *testing.T) {
stepCreateVolume := StepCreateVolume{
RootVolumeEncryptBoot: confighelper.TrileanFromBool(true),
RootVolumeKmsKeyId: "alias/1234",
}
testRootDevice := buildTestRootDevice()
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
// Ensure that the new value is equal to the value passed in
assert.Equal(t, *ret.KmsKeyId, stepCreateVolume.RootVolumeKmsKeyId)
}
+15
View File
@@ -0,0 +1,15 @@
package chroot
import (
"testing"
"github.com/hashicorp/packer-plugin-sdk/chroot"
)
func TestFlockCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
var raw interface{}
raw = new(StepFlock)
if _, ok := raw.(chroot.Cleanup); !ok {
t.Fatalf("cleanup func should be a CleanupFunc")
}
}
@@ -0,0 +1,15 @@
package chroot
import (
"testing"
"github.com/hashicorp/packer-plugin-sdk/chroot"
)
func TestMountDeviceCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
var raw interface{}
raw = new(StepMountDevice)
if _, ok := raw.(chroot.Cleanup); !ok {
t.Fatalf("cleanup func should be a CleanupFunc")
}
}
@@ -6,11 +6,11 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/random"
confighelper "github.com/hashicorp/packer-plugin-sdk/template/config"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
)
// StepRegisterAMI creates the AMI.
@@ -0,0 +1,216 @@
package chroot
import (
"testing"
"github.com/hashicorp/packer-plugin-sdk/common"
amazon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
)
func testImage() ec2.Image {
return ec2.Image{
ImageId: aws.String("ami-abcd1234"),
Name: aws.String("ami_test_name"),
Architecture: aws.String("x86_64"),
KernelId: aws.String("aki-abcd1234"),
}
}
func TestStepRegisterAmi_buildRegisterOpts_pv(t *testing.T) {
config := Config{}
config.AMIName = "test_ami_name"
config.AMIDescription = "test_ami_description"
config.AMIVirtType = "paravirtual"
rootDeviceName := "foo"
image := testImage()
blockDevices := []*ec2.BlockDeviceMapping{}
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
expected := config.AMIVirtType
if *opts.VirtualizationType != expected {
t.Fatalf("Unexpected VirtType value: expected %s got %s\n", expected, *opts.VirtualizationType)
}
expected = config.AMIName
if *opts.Name != expected {
t.Fatalf("Unexpected Name value: expected %s got %s\n", expected, *opts.Name)
}
expected = *image.KernelId
if *opts.KernelId != expected {
t.Fatalf("Unexpected KernelId value: expected %s got %s\n", expected, *opts.KernelId)
}
expected = rootDeviceName
if *opts.RootDeviceName != expected {
t.Fatalf("Unexpected RootDeviceName value: expected %s got %s\n", expected, *opts.RootDeviceName)
}
}
func TestStepRegisterAmi_buildRegisterOpts_hvm(t *testing.T) {
config := Config{}
config.AMIName = "test_ami_name"
config.AMIDescription = "test_ami_description"
config.AMIVirtType = "hvm"
rootDeviceName := "foo"
image := testImage()
blockDevices := []*ec2.BlockDeviceMapping{}
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
expected := config.AMIVirtType
if *opts.VirtualizationType != expected {
t.Fatalf("Unexpected VirtType value: expected %s got %s\n", expected, *opts.VirtualizationType)
}
expected = config.AMIName
if *opts.Name != expected {
t.Fatalf("Unexpected Name value: expected %s got %s\n", expected, *opts.Name)
}
if opts.KernelId != nil {
t.Fatalf("Unexpected KernelId value: expected nil got %s\n", *opts.KernelId)
}
expected = rootDeviceName
if *opts.RootDeviceName != expected {
t.Fatalf("Unexpected RootDeviceName value: expected %s got %s\n", expected, *opts.RootDeviceName)
}
}
func TestStepRegisterAmi_buildRegisterOptsFromScratch(t *testing.T) {
rootDeviceName := "/dev/sda"
snapshotID := "foo"
config := Config{
FromScratch: true,
PackerConfig: common.PackerConfig{},
AMIMappings: []amazon.BlockDevice{
{
DeviceName: rootDeviceName,
},
},
RootDeviceName: rootDeviceName,
}
registerOpts := buildBaseRegisterOpts(&config, nil, 10, snapshotID, config.AMIName)
if len(registerOpts.BlockDeviceMappings) != 1 {
t.Fatal("Expected block device mapping of length 1")
}
if *registerOpts.BlockDeviceMappings[0].Ebs.SnapshotId != snapshotID {
t.Fatalf("Snapshot ID of root disk not set to snapshot id %s", rootDeviceName)
}
}
func TestStepRegisterAmi_buildRegisterOptFromExistingImage(t *testing.T) {
rootDeviceName := "/dev/sda"
snapshotID := "foo"
config := Config{
FromScratch: false,
PackerConfig: common.PackerConfig{},
}
sourceImage := ec2.Image{
RootDeviceName: &rootDeviceName,
BlockDeviceMappings: []*ec2.BlockDeviceMapping{
{
DeviceName: &rootDeviceName,
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(10),
},
},
// Throw in an ephemeral device, it seems like all devices in the return struct in a source AMI have
// a size, even if it's for ephemeral
{
DeviceName: aws.String("/dev/sdb"),
VirtualName: aws.String("ephemeral0"),
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(0),
},
},
},
}
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotID, config.AMIName)
if len(registerOpts.BlockDeviceMappings) != 2 {
t.Fatal("Expected block device mapping of length 2")
}
for _, dev := range registerOpts.BlockDeviceMappings {
if dev.Ebs.SnapshotId != nil && *dev.Ebs.SnapshotId == snapshotID {
// Even though root volume size is in config, it isn't used, instead we use the root volume size
// that's derived when we build the step
if *dev.Ebs.VolumeSize != 15 {
t.Fatalf("Root volume size not 15 GB instead %d", *dev.Ebs.VolumeSize)
}
return
}
}
t.Fatalf("Could not find device with snapshot ID %s", snapshotID)
}
func TestStepRegisterAmi_buildRegisterOptFromExistingImageWithBlockDeviceMappings(t *testing.T) {
const (
rootDeviceName = "/dev/xvda"
oldRootDevice = "/dev/sda1"
)
snapshotId := "foo"
config := Config{
FromScratch: false,
PackerConfig: common.PackerConfig{},
AMIMappings: []amazon.BlockDevice{
{
DeviceName: rootDeviceName,
},
},
RootDeviceName: rootDeviceName,
}
// Intentionally try to use a different root devicename
sourceImage := ec2.Image{
RootDeviceName: aws.String(oldRootDevice),
BlockDeviceMappings: []*ec2.BlockDeviceMapping{
{
DeviceName: aws.String(oldRootDevice),
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(10),
},
},
// Throw in an ephemeral device, it seems like all devices in the return struct in a source AMI have
// a size, even if it's for ephemeral
{
DeviceName: aws.String("/dev/sdb"),
VirtualName: aws.String("ephemeral0"),
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(0),
},
},
},
}
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotId, config.AMIName)
if len(registerOpts.BlockDeviceMappings) != 1 {
t.Fatal("Expected block device mapping of length 1")
}
if *registerOpts.BlockDeviceMappings[0].Ebs.SnapshotId != snapshotId {
t.Fatalf("Snapshot ID of root disk set to '%s' expected '%s'", *registerOpts.BlockDeviceMappings[0].Ebs.SnapshotId, rootDeviceName)
}
if *registerOpts.RootDeviceName != rootDeviceName {
t.Fatalf("Root device set to '%s' expected %s", *registerOpts.RootDeviceName, rootDeviceName)
}
if *registerOpts.BlockDeviceMappings[0].Ebs.VolumeSize != 15 {
t.Fatalf("Size of root disk not set to 15 GB, instead %d", *registerOpts.BlockDeviceMappings[0].Ebs.VolumeSize)
}
}
@@ -6,9 +6,9 @@ import (
"time"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
)
// StepSnapshot creates a snapshot of the created volume.
@@ -17,7 +17,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
awsbase "github.com/hashicorp/aws-sdk-go-base"
cleanhttp "github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
vaultapi "github.com/hashicorp/vault/api"
)
@@ -0,0 +1,47 @@
package common
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
)
func TestAccessConfigPrepare_Region(t *testing.T) {
c := FakeAccessConfig()
c.RawRegion = "us-east-12"
err := c.ValidateRegion(c.RawRegion)
if err == nil {
t.Fatalf("should have region validation err: %s", c.RawRegion)
}
c.RawRegion = "us-east-1"
err = c.ValidateRegion(c.RawRegion)
if err != nil {
t.Fatalf("shouldn't have region validation err: %s", c.RawRegion)
}
c.RawRegion = "custom"
err = c.ValidateRegion(c.RawRegion)
if err == nil {
t.Fatalf("should have region validation err: %s", c.RawRegion)
}
}
func TestAccessConfigPrepare_RegionRestricted(t *testing.T) {
c := FakeAccessConfig()
// Create a Session with a custom region
c.session = session.Must(session.NewSession(&aws.Config{
Region: aws.String("us-gov-west-1"),
}))
if err := c.Prepare(); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
if !c.IsGovCloud() {
t.Fatal("We should be in gov region.")
}
}
+244
View File
@@ -0,0 +1,244 @@
package common
import (
"fmt"
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer-plugin-sdk/template/config"
)
func testAMIConfig() *AMIConfig {
return &AMIConfig{
AMIName: "foo",
}
}
func getFakeAccessConfig(region string) *AccessConfig {
c := FakeAccessConfig()
c.RawRegion = region
return c
}
func TestAMIConfigPrepare_name(t *testing.T) {
c := testAMIConfig()
accessConf := FakeAccessConfig()
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AMIName = ""
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("should have error")
}
}
func (m *mockEC2Client) DescribeRegions(*ec2.DescribeRegionsInput) (*ec2.DescribeRegionsOutput, error) {
return &ec2.DescribeRegionsOutput{
Regions: []*ec2.Region{
{RegionName: aws.String("us-east-1")},
{RegionName: aws.String("us-east-2")},
{RegionName: aws.String("us-west-1")},
},
}, nil
}
func TestAMIConfigPrepare_regions(t *testing.T) {
c := testAMIConfig()
c.AMIRegions = nil
var errs []error
var err error
accessConf := FakeAccessConfig()
mockConn := &mockEC2Client{}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatalf("shouldn't have err: %#v", errs)
}
c.AMIRegions, err = listEC2Regions(mockConn)
if err != nil {
t.Fatalf("shouldn't have err: %s", err.Error())
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatalf("shouldn't have err: %#v", errs)
}
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-1"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatalf("bad: %s", errs[0])
}
expected := []string{"us-east-1", "us-west-1"}
if !reflect.DeepEqual(c.AMIRegions, expected) {
t.Fatalf("bad: %#v", c.AMIRegions)
}
c.AMIRegions = []string{"custom"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("shouldn't have error")
}
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "456-789-0123",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal(fmt.Sprintf("shouldn't have error: %s", errs[0]))
}
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have passed; we are able to use default KMS key if not sharing")
}
c.SnapshotUsers = []string{"user-foo", "user-bar"}
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have an error b/c can't use default KMS key if sharing")
}
c.AMIRegions = []string{"us-east-1", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "456-789-0123",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have error b/c theres a region in the key map that isn't in ami_regions")
}
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
}
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
}
c.SnapshotUsers = []string{"foo", "bar"}
c.AMIKmsKeyId = "123-abc-456"
c.AMIEncryptBootVolume = config.TriTrue
c.AMIRegions = []string{"us-east-1", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
}
// allow rawregion to exist in ami_regions list.
accessConf = getFakeAccessConfig("us-east-1")
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
c.AMIRegionKMSKeyIDs = nil
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should allow user to have the raw region in ami_regions")
}
}
func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
c := testAMIConfig()
c.AMIUsers = []string{"testAccountID"}
c.AMIEncryptBootVolume = config.TriTrue
accessConf := FakeAccessConfig()
c.AMIKmsKeyId = ""
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
}
c.AMIKmsKeyId = "89c3fb9a-de87-4f2a-aedc-fddc5138193c"
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatal("should be able to share ami with encrypted boot volume")
}
}
func TestAMIConfigPrepare_ValidateKmsKey(t *testing.T) {
c := testAMIConfig()
c.AMIEncryptBootVolume = config.TriTrue
accessConf := FakeAccessConfig()
validCases := []string{
"abcd1234-e567-890f-a12b-a123b4cd56ef",
"alias/foo/bar",
"arn:aws:kms:us-east-1:012345678910:key/abcd1234-a123-456a-a12b-a123b4cd56ef",
"arn:aws:kms:us-east-1:012345678910:alias/foo/bar",
"arn:aws-us-gov:kms:us-gov-east-1:123456789012:key/12345678-1234-abcd-0000-123456789012",
}
for _, validCase := range validCases {
c.AMIKmsKeyId = validCase
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatalf("%s should not have failed KMS key validation", validCase)
}
}
invalidCases := []string{
"ABCD1234-e567-890f-a12b-a123b4cd56ef",
"ghij1234-e567-890f-a12b-a123b4cd56ef",
"ghij1234+e567_890f-a12b-a123b4cd56ef",
"foo/bar",
"arn:aws:kms:us-east-1:012345678910:foo/bar",
"arn:foo:kms:us-east-1:012345678910:key/abcd1234-a123-456a-a12b-a123b4cd56ef",
}
for _, invalidCase := range invalidCases {
c.AMIKmsKeyId = invalidCase
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatalf("%s should have failed KMS key validation", invalidCase)
}
}
}
func TestAMINameValidation(t *testing.T) {
c := testAMIConfig()
accessConf := FakeAccessConfig()
c.AMIName = "aa"
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("shouldn't be able to have an ami name with less than 3 characters")
}
var longAmiName string
for i := 0; i < 129; i++ {
longAmiName += "a"
}
c.AMIName = longAmiName
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("shouldn't be able to have an ami name with great than 128 characters")
}
c.AMIName = "+aaa"
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("shouldn't be able to have an ami name with invalid characters")
}
c.AMIName = "fooBAR1()[] ./-'@_"
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatal("should be able to use all of the allowed AMI characters")
}
c.AMIName = `xyz-base-2017-04-05-1934`
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatalf("expected `xyz-base-2017-04-05-1934` to pass validation.")
}
}
+90
View File
@@ -0,0 +1,90 @@
package common
import (
"reflect"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestArtifact_Impl(t *testing.T) {
var _ packersdk.Artifact = new(Artifact)
}
func TestArtifactId(t *testing.T) {
expected := `east:foo,west:bar`
amis := make(map[string]string)
amis["east"] = "foo"
amis["west"] = "bar"
a := &Artifact{
Amis: amis,
}
result := a.Id()
if result != expected {
t.Fatalf("bad: %s", result)
}
}
func TestArtifactState_atlasMetadata(t *testing.T) {
a := &Artifact{
Amis: map[string]string{
"east": "foo",
"west": "bar",
},
}
actual := a.State("atlas.artifact.metadata")
expected := map[string]string{
"region.east": "foo",
"region.west": "bar",
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestArtifactString(t *testing.T) {
expected := `AMIs were created:
east: foo
west: bar
`
amis := make(map[string]string)
amis["east"] = "foo"
amis["west"] = "bar"
a := &Artifact{Amis: amis}
result := a.String()
if result != expected {
t.Fatalf("bad: %s", result)
}
}
func TestArtifactState(t *testing.T) {
expectedData := "this is the data"
artifact := &Artifact{
StateData: map[string]interface{}{"state_data": expectedData},
}
// Valid state
result := artifact.State("state_data")
if result != expectedData {
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
}
// Invalid state
result = artifact.State("invalid_key")
if result != nil {
t.Fatalf("Bad: State should be nil for invalid state data name")
}
// Nil StateData should not fail and should return nil
artifact = &Artifact{}
result = artifact.State("key")
if result != nil {
t.Fatalf("Bad: State should be nil for nil StateData")
}
}
+401
View File
@@ -0,0 +1,401 @@
package common
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
func TestBlockDevice(t *testing.T) {
cases := []struct {
Config *BlockDevice
Result *ec2.BlockDeviceMapping
}{
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
SnapshotId: "snap-1234",
VolumeType: "standard",
VolumeSize: 8,
DeleteOnTermination: true,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
SnapshotId: aws.String("snap-1234"),
VolumeType: aws.String("standard"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeSize: 8,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(false),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io1",
VolumeSize: 8,
DeleteOnTermination: true,
IOPS: aws.Int64(1000),
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("io1"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
Iops: aws.Int64(1000),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 8,
DeleteOnTermination: true,
IOPS: aws.Int64(1000),
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("io2"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
Iops: aws.Int64(1000),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnTermination: true,
Encrypted: config.TriTrue,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("gp2"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
Encrypted: aws.Bool(true),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnTermination: true,
Encrypted: config.TriTrue,
KmsKeyId: "2Fa48a521f-3aff-4b34-a159-376ac5d37812",
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("gp2"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
Encrypted: aws.Bool(true),
KmsKeyId: aws.String("2Fa48a521f-3aff-4b34-a159-376ac5d37812"),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "standard",
DeleteOnTermination: true,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("standard"),
DeleteOnTermination: aws.Bool(true),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VirtualName: "ephemeral0",
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
VirtualName: aws.String("ephemeral0"),
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
NoDevice: true,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
NoDevice: aws.String(""),
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
VolumeSize: 8,
Throughput: aws.Int64(125),
IOPS: aws.Int64(3000),
DeleteOnTermination: true,
Encrypted: config.TriTrue,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("gp3"),
VolumeSize: aws.Int64(8),
Throughput: aws.Int64(125),
Iops: aws.Int64(3000),
DeleteOnTermination: aws.Bool(true),
Encrypted: aws.Bool(true),
},
},
},
}
for _, tc := range cases {
var amiBlockDevices BlockDevices = []BlockDevice{*tc.Config}
var launchBlockDevices BlockDevices = []BlockDevice{*tc.Config}
expected := []*ec2.BlockDeviceMapping{tc.Result}
amiResults := amiBlockDevices.BuildEC2BlockDeviceMappings()
if diff := cmp.Diff(expected, amiResults); diff != "" {
t.Fatalf("Bad block device: %s", diff)
}
launchResults := launchBlockDevices.BuildEC2BlockDeviceMappings()
if diff := cmp.Diff(expected, launchResults); diff != "" {
t.Fatalf("Bad block device: %s", diff)
}
}
}
func TestIOPSValidation(t *testing.T) {
cases := []struct {
device BlockDevice
ok bool
msg string
}{
// volume size unknown
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io1",
IOPS: aws.Int64(1000),
},
ok: true,
},
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
IOPS: aws.Int64(1000),
},
ok: true,
},
// ratio requirement satisfied
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io1",
VolumeSize: 50,
IOPS: aws.Int64(1000),
},
ok: true,
},
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 100,
IOPS: aws.Int64(1000),
},
ok: true,
},
// ratio requirement not satisfied
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io1",
VolumeSize: 10,
IOPS: aws.Int64(2000),
},
ok: false,
msg: "/dev/sdb: the maximum ratio of provisioned IOPS to requested volume size (in GiB) is 50:1 for io1 volumes",
},
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 50,
IOPS: aws.Int64(30000),
},
ok: false,
msg: "/dev/sdb: the maximum ratio of provisioned IOPS to requested volume size (in GiB) is 500:1 for io2 volumes",
},
// exceed max iops
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 500,
IOPS: aws.Int64(99999),
},
ok: false,
msg: "IOPS must be between 100 and 64000 for device /dev/sdb",
},
// lower than min iops
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 50,
IOPS: aws.Int64(10),
},
ok: false,
msg: "IOPS must be between 100 and 64000 for device /dev/sdb",
},
// exceed max iops
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
VolumeSize: 50,
Throughput: aws.Int64(125),
IOPS: aws.Int64(99999),
},
ok: false,
msg: "IOPS must be between 3000 and 16000 for device /dev/sdb",
},
// lower than min iops
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
VolumeSize: 50,
Throughput: aws.Int64(125),
IOPS: aws.Int64(10),
},
ok: false,
msg: "IOPS must be between 3000 and 16000 for device /dev/sdb",
},
}
ctx := interpolate.Context{}
for _, testCase := range cases {
err := testCase.device.Prepare(&ctx)
if testCase.ok && err != nil {
t.Fatalf("should not error, but: %v", err)
}
if !testCase.ok {
if err == nil {
t.Fatalf("should error")
} else if err.Error() != testCase.msg {
t.Fatalf("wrong error: expected %s, found: %v", testCase.msg, err)
}
}
}
}
func TestThroughputValidation(t *testing.T) {
cases := []struct {
device BlockDevice
ok bool
msg string
}{
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
Throughput: aws.Int64(125),
IOPS: aws.Int64(3000),
},
ok: true,
},
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
Throughput: aws.Int64(1000),
IOPS: aws.Int64(3000),
},
ok: true,
},
// exceed max Throughput
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
Throughput: aws.Int64(1001),
IOPS: aws.Int64(3000),
},
ok: false,
msg: "Throughput must be between 125 and 1000 for device /dev/sdb",
},
// lower than min Throughput
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
Throughput: aws.Int64(124),
IOPS: aws.Int64(3000),
},
ok: false,
msg: "Throughput must be between 125 and 1000 for device /dev/sdb",
},
}
ctx := interpolate.Context{}
for _, testCase := range cases {
err := testCase.device.Prepare(&ctx)
if testCase.ok && err != nil {
t.Fatalf("should not error, but: %v", err)
}
if !testCase.ok {
if err == nil {
t.Fatalf("should error")
} else if err.Error() != testCase.msg {
t.Fatalf("wrong error: expected %s, found: %v", testCase.msg, err)
}
}
}
}
@@ -0,0 +1,31 @@
package common
import (
"testing"
)
func TestStepSourceAmiInfo_BuildFilter(t *testing.T) {
filter_key := "name"
filter_value := "foo"
filter_key2 := "name2"
filter_value2 := "foo2"
inputFilter := map[string]string{filter_key: filter_value, filter_key2: filter_value2}
outputFilter := buildEc2Filters(inputFilter)
// deconstruct filter back into things we can test
foundMap := map[string]bool{filter_key: false, filter_key2: false}
for _, filter := range outputFilter {
for key, value := range inputFilter {
if *filter.Name == key && *filter.Values[0] == value {
foundMap[key] = true
}
}
}
for k, v := range foundMap {
if !v {
t.Fatalf("Fail: should have found value for key: %s", k)
}
}
}
+70
View File
@@ -0,0 +1,70 @@
package common
import (
"fmt"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/aws/aws-sdk-go/aws/awserr"
)
type mockSTS struct {
}
func (m *mockSTS) DecodeAuthorizationMessage(input *sts.DecodeAuthorizationMessageInput) (*sts.DecodeAuthorizationMessageOutput, error) {
return &sts.DecodeAuthorizationMessageOutput{
DecodedMessage: aws.String(`{
"allowed": false,
"explicitDeny": true,
"matchedStatements": {}
}`),
}, nil
}
func TestErrorsParsing_RequestFailure(t *testing.T) {
ae := awserr.New("UnauthorizedOperation",
`You are not authorized to perform this operation. Encoded authorization failure message: D9Q7oicjOMr9l2CC-NPP1FiZXK9Ijia1k-3l0siBFCcrK3oSuMFMkBIO5TNj0HdXE-WfwnAcdycFOohfKroNO6toPJEns8RFVfy_M_IjNGmrEFJ6E62pnmBW0OLrMsXxR9FQE4gB4gJzSM0AD6cV6S3FOfqYzWBRX-sQdOT4HryGkFNRoFBr9Xbp-tRwiadwkbdHdfnV9fbRkXmnwCdULml16NBSofC4ZPepLMKmIB5rKjwk-m179UUh2XA-J5no0si6XcRo5GbHQB5QfCIwSHL4vsro2wLZUd16-8OWKyr3tVlTbQe0ERZskqRqRQ5E28QuiBCVV6XstUyo-T4lBSr75Fgnyr3wCO-dS3b_5Ns3WzA2JD4E2AJOAStXIU8IH5YuKkAg7C-dJMuBMPpmKCBEXhNoHDwCyOo5PsV3xMlc0jSb0qYGpfst_TDDtejcZfn7NssUjxVq9qkdH-OXz2gPoQB-hX8ycmZCL5UZwKc3TCLUr7TGnudHjmnMrE9cUo-yTCWfyHPLprhiYhTCKW18EikJ0O1EKI3FJ_b4F19_jFBPARjSwQc7Ut6MNCVzrPdZGYSF6acj5gPaxdy9uSkVQwWXK7Pd5MFP7EBDE1_DgYbzodgwDO2PXeVFUbSLBHKWo_ebZS9ZX2nYPcGss_sYaly0ZVSIJXp7G58B5BoFVhvVH6jYnF9XiAOjMltuP_ycu1pQP1lki500RY3baLvfeYeAsB38XZHKEgWZzq7Fei-uh89q0cjJTmlVyrfRU3q6`,
fmt.Errorf("You can't do it!!"))
rf := awserr.NewRequestFailure(ae, 400, "abc-def-123-456")
result := decodeAWSError(&mockSTS{}, rf)
if result == nil {
t.Error("Expected resulting error")
}
if !strings.Contains(result.Error(), "Authorization failure message:") {
t.Error("Expected authorization failure message")
}
}
func TestErrorsParsing_NonAuthorizationFailure(t *testing.T) {
ae := awserr.New("BadRequest",
`You did something wrong. Try again`,
fmt.Errorf("Request was no good."))
rf := awserr.NewRequestFailure(ae, 400, "abc-def-123-456")
result := decodeAWSError(&mockSTS{}, rf)
if result == nil {
t.Error("Expected resulting error")
}
if result != rf {
t.Error("Expected original error to be returned unchanged")
}
}
func TestErrorsParsing_NonAWSError(t *testing.T) {
err := fmt.Errorf("Random error occurred")
result := decodeAWSError(&mockSTS{}, err)
if result == nil {
t.Error("Expected resulting error")
}
if result != err {
t.Error("Expected original error to be returned unchanged")
}
}
@@ -8,8 +8,8 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
"github.com/hashicorp/packer-plugin-sdk/retry"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
)
// DestroyAMIs deregisters the AWS machine images in imageids from an active AWS account
@@ -0,0 +1,91 @@
package common
import (
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
)
func testImage() *ec2.Image {
return &ec2.Image{
ImageId: aws.String("ami-abcd1234"),
CreationDate: aws.String("ami_test_creation_date"),
Name: aws.String("ami_test_name"),
OwnerId: aws.String("ami_test_owner_id"),
ImageOwnerAlias: aws.String("ami_test_owner_alias"),
RootDeviceType: aws.String("ebs"),
Tags: []*ec2.Tag{
{
Key: aws.String("key-1"),
Value: aws.String("value-1"),
},
{
Key: aws.String("key-2"),
Value: aws.String("value-2"),
},
},
}
}
func testState() multistep.StateBag {
state := new(multistep.BasicStateBag)
return state
}
func testGeneratedData(state multistep.StateBag) packerbuilderdata.GeneratedData {
generatedData := packerbuilderdata.GeneratedData{State: state}
return generatedData
}
func TestInterpolateBuildInfo_extractBuildInfo_noSourceImage(t *testing.T) {
state := testState()
generatedData := testGeneratedData(state)
buildInfo := extractBuildInfo("foo", state, &generatedData)
expected := BuildInfoTemplate{
BuildRegion: "foo",
}
if !reflect.DeepEqual(*buildInfo, expected) {
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
}
}
func TestInterpolateBuildInfo_extractBuildInfo_withSourceImage(t *testing.T) {
state := testState()
state.Put("source_image", testImage())
generatedData := testGeneratedData(state)
buildInfo := extractBuildInfo("foo", state, &generatedData)
expected := BuildInfoTemplate{
BuildRegion: "foo",
SourceAMI: "ami-abcd1234",
SourceAMICreationDate: "ami_test_creation_date",
SourceAMIName: "ami_test_name",
SourceAMIOwner: "ami_test_owner_id",
SourceAMIOwnerName: "ami_test_owner_alias",
SourceAMITags: map[string]string{
"key-1": "value-1",
"key-2": "value-2",
},
}
if !reflect.DeepEqual(*buildInfo, expected) {
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
}
}
func TestInterpolateBuildInfo_extractBuildInfo_GeneratedDataWithSourceImageName(t *testing.T) {
state := testState()
state.Put("source_image", testImage())
generatedData := testGeneratedData(state)
extractBuildInfo("foo", state, &generatedData)
generatedDataState := state.Get("generated_data").(map[string]interface{})
if generatedDataState["SourceAMIName"] != "ami_test_name" {
t.Fatalf("Unexpected state SourceAMIName: expected %#v got %#v\n", "ami_test_name", generatedDataState["SourceAMIName"])
}
}
+251
View File
@@ -0,0 +1,251 @@
package common
import (
"io/ioutil"
"os"
"regexp"
"testing"
"github.com/hashicorp/packer-plugin-sdk/communicator"
)
func init() {
// Clear out the AWS access key env vars so they don't
// affect our tests.
os.Setenv("AWS_ACCESS_KEY_ID", "")
os.Setenv("AWS_ACCESS_KEY", "")
os.Setenv("AWS_SECRET_ACCESS_KEY", "")
os.Setenv("AWS_SECRET_KEY", "")
}
func testConfig() *RunConfig {
return &RunConfig{
SourceAmi: "abcd",
InstanceType: "m1.small",
Comm: communicator.Config{
SSH: communicator.SSH{
SSHUsername: "foo",
},
},
}
}
func testConfigFilter() *RunConfig {
config := testConfig()
config.SourceAmi = ""
config.SourceAmiFilter = AmiFilterOptions{}
return config
}
func TestRunConfigPrepare(t *testing.T) {
c := testConfig()
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_InstanceType(t *testing.T) {
c := testConfig()
c.InstanceType = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if an instance_type is not specified")
}
}
func TestRunConfigPrepare_SourceAmi(t *testing.T) {
c := testConfig()
c.SourceAmi = ""
if err := c.Prepare(nil); len(err) != 2 {
t.Fatalf("Should error if a source_ami (or source_ami_filter) is not specified")
}
}
func TestRunConfigPrepare_SourceAmiFilterBlank(t *testing.T) {
c := testConfigFilter()
if err := c.Prepare(nil); len(err) != 2 {
t.Fatalf("Should error if source_ami_filter is empty or not specified (and source_ami is not specified)")
}
}
func TestRunConfigPrepare_SourceAmiFilterOwnersBlank(t *testing.T) {
c := testConfigFilter()
filter_key := "name"
filter_value := "foo"
c.SourceAmiFilter.Filters = map[string]string{filter_key: filter_value}
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if Owners is not specified)")
}
}
func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) {
c := testConfigFilter()
owner := "123"
filter_key := "name"
filter_value := "foo"
goodFilter := AmiFilterOptions{
Owners: []string{owner},
Filters: map[string]string{filter_key: filter_value},
}
c.SourceAmiFilter = goodFilter
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_EnableT2UnlimitedGood(t *testing.T) {
c := testConfig()
// Must have a T2 instance type if T2 Unlimited is enabled
c.InstanceType = "t2.micro"
c.EnableT2Unlimited = true
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_EnableT2UnlimitedBadInstanceType(t *testing.T) {
c := testConfig()
// T2 Unlimited cannot be used with instance types other than T2
c.InstanceType = "m5.large"
c.EnableT2Unlimited = true
err := c.Prepare(nil)
if len(err) != 1 {
t.Fatalf("Should error if T2 Unlimited is enabled with non-T2 instance_type")
}
}
func TestRunConfigPrepare_EnableT2UnlimitedBadWithSpotInstanceRequest(t *testing.T) {
c := testConfig()
// T2 Unlimited cannot be used with Spot Instances
c.InstanceType = "t2.micro"
c.EnableT2Unlimited = true
c.SpotPrice = "auto"
err := c.Prepare(nil)
if len(err) != 1 {
t.Fatalf("Should error if T2 Unlimited has been used in conjuntion with a Spot Price request")
}
}
func TestRunConfigPrepare_SpotAuto(t *testing.T) {
c := testConfig()
c.SpotPrice = "auto"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
// Shouldn't error (YET) even though SpotPriceAutoProduct is deprecated
c.SpotPriceAutoProduct = "Linux/Unix"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SSHPort(t *testing.T) {
c := testConfig()
c.Comm.SSHPort = 0
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHPort != 22 {
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
}
c.Comm.SSHPort = 44
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHPort != 44 {
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
}
}
func TestRunConfigPrepare_UserData(t *testing.T) {
c := testConfig()
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
c.UserData = "foo"
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if user_data string and user_data_file have both been specified")
}
}
func TestRunConfigPrepare_UserDataFile(t *testing.T) {
c := testConfig()
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
c.UserDataFile = "idontexistidontthink"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if the file specified by user_data_file does not exist")
}
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
c := testConfig()
c.Comm.SSHTemporaryKeyPairName = ""
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHTemporaryKeyPairName == "" {
t.Fatal("keypair name is empty")
}
// Match prefix and UUID, e.g. "packer_5790d491-a0b8-c84c-c9d2-2aea55086550".
r := regexp.MustCompile(`\Apacker_(?:(?i)[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}?)\z`)
if !r.MatchString(c.Comm.SSHTemporaryKeyPairName) {
t.Fatal("keypair name is not valid")
}
c.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
t.Fatal("keypair name does not match")
}
}
func TestRunConfigPrepare_TenancyBad(t *testing.T) {
c := testConfig()
c.Tenancy = "not_real"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatal("Should error if tenancy is set to an invalid type")
}
}
func TestRunConfigPrepare_TenancyGood(t *testing.T) {
validTenancy := []string{"", "default", "dedicated", "host"}
for _, vt := range validTenancy {
c := testConfig()
c.Tenancy = vt
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("Should not error if tenancy is set to %s", vt)
}
}
}
+141
View File
@@ -0,0 +1,141 @@
package common
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
const (
privateIP = "10.0.0.1"
publicIP = "192.168.1.1"
privateDNS = "private.dns.test"
publicDNS = "public.dns.test"
localhost = "localhost"
sshHostTemplate = "custom.host.value"
)
func TestSSHHost(t *testing.T) {
origSshHostSleepDuration := sshHostSleepDuration
defer func() { sshHostSleepDuration = origSshHostSleepDuration }()
sshHostSleepDuration = 0
var cases = []struct {
allowTries int
vpcId string
sshInterface string
ok bool
wantHost string
sshHostOverride string
}{
{1, "", "", true, publicDNS, ""},
{1, "", "private_ip", true, privateIP, ""},
{1, "", "session_manager", true, localhost, ""},
{1, "vpc-id", "", true, publicIP, ""},
{1, "vpc-id", "private_ip", true, privateIP, ""},
{1, "vpc-id", "private_dns", true, privateDNS, ""},
{1, "vpc-id", "public_dns", true, publicDNS, ""},
{1, "vpc-id", "public_ip", true, publicIP, ""},
{1, "vpc-id", "session_manager", true, localhost, ""},
{2, "", "", true, publicDNS, ""},
{2, "", "private_ip", true, privateIP, ""},
{2, "vpc-id", "", true, publicIP, ""},
{2, "vpc-id", "private_ip", true, privateIP, ""},
{2, "vpc-id", "private_dns", true, privateDNS, ""},
{2, "vpc-id", "public_dns", true, publicDNS, ""},
{2, "vpc-id", "public_ip", true, publicIP, ""},
{3, "", "", false, "", ""},
{3, "", "private_ip", false, "", ""},
{3, "vpc-id", "", false, "", ""},
{3, "vpc-id", "private_ip", false, "", ""},
{3, "vpc-id", "private_dns", false, "", ""},
{3, "vpc-id", "public_dns", false, "", ""},
{3, "vpc-id", "public_ip", false, "", ""},
{1, "", "", true, sshHostTemplate, sshHostTemplate},
{1, "vpc-id", "", true, sshHostTemplate, sshHostTemplate},
{2, "vpc-id", "private_dns", true, sshHostTemplate, sshHostTemplate},
}
for _, c := range cases {
testSSHHost(t, c.allowTries, c.vpcId, c.sshInterface, c.ok, c.wantHost,
c.sshHostOverride)
}
}
func testSSHHost(t *testing.T, allowTries int, vpcId string, sshInterface string,
ok bool, wantHost string, sshHostOverride string) {
t.Logf("allowTries=%d vpcId=%s sshInterface=%s ok=%t wantHost=%q sshHostOverride=%s",
allowTries, vpcId, sshInterface, ok, wantHost, sshHostOverride)
e := &fakeEC2Describer{
allowTries: allowTries,
vpcId: vpcId,
privateIP: privateIP,
publicIP: publicIP,
privateDNS: privateDNS,
publicDNS: publicDNS,
}
f := SSHHost(e, sshInterface, sshHostOverride)
st := &multistep.BasicStateBag{}
st.Put("instance", &ec2.Instance{
InstanceId: aws.String("instance-id"),
})
host, err := f(st)
if e.tries > allowTries {
t.Fatalf("got %d ec2 DescribeInstances tries, want %d", e.tries, allowTries)
}
switch {
case ok && err != nil:
t.Fatalf("expected no error, got %+v", err)
case !ok && err == nil:
t.Fatalf("expected error, got none and host %s", host)
}
if host != wantHost {
t.Fatalf("got host %s, want %s", host, wantHost)
}
}
type fakeEC2Describer struct {
allowTries int
tries int
vpcId string
privateIP, publicIP, privateDNS, publicDNS string
}
func (d *fakeEC2Describer) DescribeInstances(in *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
d.tries++
instance := &ec2.Instance{
InstanceId: aws.String("instance-id"),
}
if d.vpcId != "" {
instance.VpcId = aws.String(d.vpcId)
}
if d.tries >= d.allowTries {
instance.PublicIpAddress = aws.String(d.publicIP)
instance.PrivateIpAddress = aws.String(d.privateIP)
instance.PublicDnsName = aws.String(d.publicDNS)
instance.PrivateDnsName = aws.String(d.privateDNS)
}
out := &ec2.DescribeInstancesOutput{
Reservations: []*ec2.Reservation{
{
Instances: []*ec2.Instance{instance},
},
},
}
return out, nil
}
@@ -12,10 +12,10 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/retry"
"github.com/hashicorp/packer-plugin-sdk/shell-local/localexec"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
)
type Session struct {
+66
View File
@@ -0,0 +1,66 @@
package common
import (
"reflect"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws/request"
)
func testGetWaiterOptions(t *testing.T) {
// no vars are set
envValues := overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
envInfo{"AWS_TIMEOUT_SECONDS", 300, false},
}
options := applyEnvOverrides(envValues)
if len(options) > 0 {
t.Fatalf("Did not expect any waiter options to be generated; actual: %#v", options)
}
// all vars are set
envValues = overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 1, true},
envInfo{"AWS_MAX_ATTEMPTS", 800, true},
envInfo{"AWS_TIMEOUT_SECONDS", 20, true},
}
options = applyEnvOverrides(envValues)
expected := []request.WaiterOption{
request.WithWaiterDelay(request.ConstantWaiterDelay(time.Duration(1) * time.Second)),
request.WithWaiterMaxAttempts(800),
}
if !reflect.DeepEqual(options, expected) {
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
}
// poll delay is not set
envValues = overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
envInfo{"AWS_MAX_ATTEMPTS", 800, true},
envInfo{"AWS_TIMEOUT_SECONDS", 300, false},
}
options = applyEnvOverrides(envValues)
expected = []request.WaiterOption{
request.WithWaiterMaxAttempts(800),
}
if !reflect.DeepEqual(options, expected) {
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
}
// poll delay is not set but timeout seconds is
envValues = overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
envInfo{"AWS_TIMEOUT_SECONDS", 20, true},
}
options = applyEnvOverrides(envValues)
expected = []request.WaiterOption{
request.WithWaiterDelay(request.ConstantWaiterDelay(time.Duration(2) * time.Second)),
request.WithWaiterMaxAttempts(10),
}
if !reflect.DeepEqual(options, expected) {
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
}
}
@@ -0,0 +1,404 @@
package common
import (
"bytes"
"context"
"fmt"
"sync"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
)
// Define a mock struct to be used in unit tests for common aws steps.
type mockEC2Conn struct {
ec2iface.EC2API
Config *aws.Config
// Counters to figure out what code path was taken
copyImageCount int
describeImagesCount int
deregisterImageCount int
deleteSnapshotCount int
waitCount int
lock sync.Mutex
}
func (m *mockEC2Conn) CopyImage(copyInput *ec2.CopyImageInput) (*ec2.CopyImageOutput, error) {
m.lock.Lock()
m.copyImageCount++
m.lock.Unlock()
copiedImage := fmt.Sprintf("%s-copied-%d", *copyInput.SourceImageId, m.copyImageCount)
output := &ec2.CopyImageOutput{
ImageId: &copiedImage,
}
return output, nil
}
// functions we have to create mock responses for in order for test to run
func (m *mockEC2Conn) DescribeImages(*ec2.DescribeImagesInput) (*ec2.DescribeImagesOutput, error) {
m.lock.Lock()
m.describeImagesCount++
m.lock.Unlock()
output := &ec2.DescribeImagesOutput{
Images: []*ec2.Image{{}},
}
return output, nil
}
func (m *mockEC2Conn) DeregisterImage(*ec2.DeregisterImageInput) (*ec2.DeregisterImageOutput, error) {
m.lock.Lock()
m.deregisterImageCount++
m.lock.Unlock()
output := &ec2.DeregisterImageOutput{}
return output, nil
}
func (m *mockEC2Conn) DeleteSnapshot(*ec2.DeleteSnapshotInput) (*ec2.DeleteSnapshotOutput, error) {
m.lock.Lock()
m.deleteSnapshotCount++
m.lock.Unlock()
output := &ec2.DeleteSnapshotOutput{}
return output, nil
}
func (m *mockEC2Conn) WaitUntilImageAvailableWithContext(aws.Context, *ec2.DescribeImagesInput, ...request.WaiterOption) error {
m.lock.Lock()
m.waitCount++
m.lock.Unlock()
return nil
}
func getMockConn(config *AccessConfig, target string) (ec2iface.EC2API, error) {
mockConn := &mockEC2Conn{
Config: aws.NewConfig(),
}
return mockConn, nil
}
// Create statebag for running test
func tState() multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
state.Put("amis", map[string]string{"us-east-1": "ami-12345"})
state.Put("snapshots", map[string][]string{"us-east-1": {"snap-0012345"}})
conn, _ := getMockConn(&AccessConfig{}, "us-east-2")
state.Put("ec2", conn)
return state
}
func TestStepAMIRegionCopy_duplicates(t *testing.T) {
// ------------------------------------------------------------------------
// Test that if the original region is added to both Regions and Region,
// the ami is only copied once (with encryption).
// ------------------------------------------------------------------------
stepAMIRegionCopy := StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-east-1"},
AMIKmsKeyId: "12345",
// Original region key in regionkeyids is different than in amikmskeyid
RegionKeyIds: map[string]string{"us-east-1": "12345"},
EncryptBootVolume: config.TriTrue,
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state := tState()
state.Put("intermediary_image", true)
stepAMIRegionCopy.Run(context.Background(), state)
if len(stepAMIRegionCopy.Regions) != 1 {
t.Fatalf("Should have added original ami to Regions one time only")
}
// ------------------------------------------------------------------------
// Both Region and Regions set, but no encryption - shouldn't copy anything
// ------------------------------------------------------------------------
// the ami is only copied once.
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-east-1"},
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
state.Put("intermediary_image", false)
stepAMIRegionCopy.getRegionConn = getMockConn
stepAMIRegionCopy.Run(context.Background(), state)
if len(stepAMIRegionCopy.Regions) != 0 {
t.Fatalf("Should not have added original ami to Regions; not encrypting")
}
// ------------------------------------------------------------------------
// Both Region and Regions set, but no encryption - shouldn't copy anything,
// this tests false as opposed to nil value above.
// ------------------------------------------------------------------------
// the ami is only copied once.
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-east-1"},
EncryptBootVolume: config.TriFalse,
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
state.Put("intermediary_image", false)
stepAMIRegionCopy.getRegionConn = getMockConn
stepAMIRegionCopy.Run(context.Background(), state)
if len(stepAMIRegionCopy.Regions) != 0 {
t.Fatalf("Should not have added original ami to Regions once; not" +
"encrypting")
}
// ------------------------------------------------------------------------
// Multiple regions, many duplicates, and encryption (this shouldn't ever
// happen because of our template validation, but good to test it.)
// ------------------------------------------------------------------------
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
// Many duplicates for only 3 actual values
Regions: []string{"us-east-1", "us-west-2", "us-west-2", "ap-east-1", "ap-east-1", "ap-east-1"},
AMIKmsKeyId: "IlikePancakes",
// Original region key in regionkeyids is different than in amikmskeyid
RegionKeyIds: map[string]string{"us-east-1": "12345", "us-west-2": "abcde", "ap-east-1": "xyz"},
EncryptBootVolume: config.TriTrue,
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state.Put("intermediary_image", true)
stepAMIRegionCopy.Run(context.Background(), state)
if len(stepAMIRegionCopy.Regions) != 3 {
t.Fatalf("Each AMI should have been added to Regions one time only.")
}
// Also verify that we respect RegionKeyIds over AMIKmsKeyIds:
if stepAMIRegionCopy.RegionKeyIds["us-east-1"] != "12345" {
t.Fatalf("RegionKeyIds should take precedence over AmiKmsKeyIds")
}
// ------------------------------------------------------------------------
// Multiple regions, many duplicates, NO encryption
// ------------------------------------------------------------------------
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
// Many duplicates for only 3 actual values
Regions: []string{"us-east-1", "us-west-2", "us-west-2", "ap-east-1", "ap-east-1", "ap-east-1"},
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state.Put("intermediary_image", false)
stepAMIRegionCopy.Run(context.Background(), state)
if len(stepAMIRegionCopy.Regions) != 2 {
t.Fatalf("Each AMI should have been added to Regions one time only, " +
"and original region shouldn't be added at all")
}
}
func TestStepAmiRegionCopy_nil_encryption(t *testing.T) {
// create step
stepAMIRegionCopy := StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: make([]string, 0),
AMIKmsKeyId: "",
RegionKeyIds: make(map[string]string),
EncryptBootVolume: config.TriUnset,
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state := tState()
state.Put("intermediary_image", false)
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete != "" {
t.Fatalf("Shouldn't have an intermediary ami if encrypt is nil")
}
if len(stepAMIRegionCopy.Regions) != 0 {
t.Fatalf("Should not have added original ami to original region")
}
}
func TestStepAmiRegionCopy_true_encryption(t *testing.T) {
// create step
stepAMIRegionCopy := StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: make([]string, 0),
AMIKmsKeyId: "",
RegionKeyIds: make(map[string]string),
EncryptBootVolume: config.TriTrue,
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state := tState()
state.Put("intermediary_image", true)
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete == "" {
t.Fatalf("Should delete original AMI if encrypted=true")
}
if len(stepAMIRegionCopy.Regions) == 0 {
t.Fatalf("Should have added original ami to Regions")
}
}
func TestStepAmiRegionCopy_nil_intermediary(t *testing.T) {
// create step
stepAMIRegionCopy := StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: make([]string, 0),
AMIKmsKeyId: "",
RegionKeyIds: make(map[string]string),
EncryptBootVolume: config.TriFalse,
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state := tState()
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete != "" {
t.Fatalf("Should not delete original AMI if no intermediary")
}
if len(stepAMIRegionCopy.Regions) != 0 {
t.Fatalf("Should not have added original ami to Regions")
}
}
func TestStepAmiRegionCopy_AMISkipBuildRegion(t *testing.T) {
// ------------------------------------------------------------------------
// skip build region is true
// ------------------------------------------------------------------------
stepAMIRegionCopy := StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-west-1"},
AMIKmsKeyId: "",
RegionKeyIds: map[string]string{"us-west-1": "abcde"},
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
AMISkipBuildRegion: true,
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state := tState()
state.Put("intermediary_image", true)
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete == "" {
t.Fatalf("Should delete original AMI if skip_save_build_region=true")
}
if len(stepAMIRegionCopy.Regions) != 1 {
t.Fatalf("Should not have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
}
// ------------------------------------------------------------------------
// skip build region is false.
// ------------------------------------------------------------------------
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-west-1"},
AMIKmsKeyId: "",
RegionKeyIds: make(map[string]string),
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
AMISkipBuildRegion: false,
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state.Put("intermediary_image", false) // not encrypted
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete != "" {
t.Fatalf("Shouldn't have an intermediary AMI, so dont delete original ami")
}
if len(stepAMIRegionCopy.Regions) != 1 {
t.Fatalf("Should not have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
}
// ------------------------------------------------------------------------
// skip build region is false, but encrypt is true
// ------------------------------------------------------------------------
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-west-1"},
AMIKmsKeyId: "",
RegionKeyIds: map[string]string{"us-west-1": "abcde"},
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
AMISkipBuildRegion: false,
EncryptBootVolume: config.TriTrue,
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state.Put("intermediary_image", true) //encrypted
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete == "" {
t.Fatalf("Have to delete intermediary AMI")
}
if len(stepAMIRegionCopy.Regions) != 2 {
t.Fatalf("Should have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
}
// ------------------------------------------------------------------------
// skip build region is true, and encrypt is true
// ------------------------------------------------------------------------
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-west-1"},
AMIKmsKeyId: "",
RegionKeyIds: map[string]string{"us-west-1": "abcde"},
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
AMISkipBuildRegion: true,
EncryptBootVolume: config.TriTrue,
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state.Put("intermediary_image", true) //encrypted
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete == "" {
t.Fatalf("Have to delete intermediary AMI")
}
if len(stepAMIRegionCopy.Regions) != 1 {
t.Fatalf("Should not have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
}
}
@@ -9,10 +9,10 @@ import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ssm"
pssm "github.com/hashicorp/packer-plugin-amazon/builder/common/ssm"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/net"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
pssm "github.com/hashicorp/packer/builder/amazon/common/ssm"
)
type StepCreateSSMTunnel struct {
@@ -8,11 +8,11 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/retry"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
)
type StepCreateTags struct {
@@ -9,10 +9,10 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/retry"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
)
// StepPreValidate provides an opportunity to pre-validate any configuration for
@@ -0,0 +1,70 @@
package common
import (
"fmt"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
)
//DescribeVpcs mocks an ec2.DescribeVpcsOutput for a given input
func (m *mockEC2Conn) DescribeVpcs(input *ec2.DescribeVpcsInput) (*ec2.DescribeVpcsOutput, error) {
if input == nil || aws.StringValue(input.VpcIds[0]) == "" {
return nil, fmt.Errorf("oops looks like we need more input")
}
var isDefault bool
vpcID := aws.StringValue(input.VpcIds[0])
//only one default VPC per region
if strings.Contains("vpc-default-id", vpcID) {
isDefault = true
}
output := &ec2.DescribeVpcsOutput{
Vpcs: []*ec2.Vpc{
{IsDefault: aws.Bool(isDefault),
VpcId: aws.String(vpcID),
},
},
}
return output, nil
}
func TestStepPreValidate_checkVpc(t *testing.T) {
tt := []struct {
name string
step StepPreValidate
errorExpected bool
}{
{"DefaultVpc", StepPreValidate{VpcId: "vpc-default-id"}, false},
{"NonDefaultVpcNoSubnet", StepPreValidate{VpcId: "vpc-1234567890"}, true},
{"NonDefaultVpcWithSubnet", StepPreValidate{VpcId: "vpc-1234567890", SubnetId: "subnet-1234567890"}, false},
{"SubnetWithNoVpc", StepPreValidate{SubnetId: "subnet-1234567890"}, false},
{"NoVpcInformation", StepPreValidate{}, false},
{"NonDefaultVpcWithSubnetFilter", StepPreValidate{VpcId: "vpc-1234567890", HasSubnetFilter: true}, false},
}
mockConn, err := getMockConn(nil, "")
if err != nil {
t.Fatal("unable to get a mock connection")
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
err := tc.step.checkVpc(mockConn)
if tc.errorExpected && err == nil {
t.Errorf("expected a validation error for %q but got %q", tc.name, err)
}
if !tc.errorExpected && err != nil {
t.Errorf("expected a validation to pass for %q but got %q", tc.name, err)
}
})
}
}
@@ -11,12 +11,12 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/retry"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
)
type StepRunSourceInstance struct {
@@ -12,13 +12,13 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/random"
"github.com/hashicorp/packer-plugin-sdk/retry"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
)
type EC2BlockDeviceMappingsBuilder interface {
@@ -0,0 +1,366 @@
package common
import (
"bytes"
"context"
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// Create statebag for running test
func tStateSpot() multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
state.Put("availability_zone", "us-east-1c")
state.Put("securityGroupIds", []string{"sg-0b8984db72f213dc3"})
state.Put("iamInstanceProfile", "packer-123")
state.Put("subnet_id", "subnet-077fde4e")
state.Put("source_image", "")
return state
}
func getBasicStep() *StepRunSpotInstance {
stepRunSpotInstance := StepRunSpotInstance{
PollingConfig: new(AWSPollingConfig),
AssociatePublicIpAddress: false,
LaunchMappings: BlockDevices{},
BlockDurationMinutes: 0,
Debug: false,
Comm: &communicator.Config{
SSH: communicator.SSH{
SSHKeyPairName: "foo",
},
},
EbsOptimized: false,
ExpectedRootDevice: "ebs",
InstanceInitiatedShutdownBehavior: "stop",
InstanceType: "t2.micro",
Region: "us-east-1",
SourceAMI: "",
SpotPrice: "auto",
SpotTags: nil,
Tags: map[string]string{},
VolumeTags: nil,
UserData: "",
UserDataFile: "",
}
return &stepRunSpotInstance
}
func TestCreateTemplateData(t *testing.T) {
state := tStateSpot()
stepRunSpotInstance := getBasicStep()
template := stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
// expected := []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{
// &ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{
// DeleteOnTermination: aws.Bool(true),
// DeviceIndex: aws.Int64(0),
// Groups: aws.StringSlice([]string{"sg-0b8984db72f213dc3"}),
// SubnetId: aws.String("subnet-077fde4e"),
// },
// }
// if expected != template.NetworkInterfaces {
if template.NetworkInterfaces == nil {
t.Fatalf("Template should have contained a networkInterface object: recieved %#v", template.NetworkInterfaces)
}
if *template.IamInstanceProfile.Name != state.Get("iamInstanceProfile") {
t.Fatalf("Template should have contained a InstanceProfile name: recieved %#v", template.IamInstanceProfile.Name)
}
// Rerun, this time testing that we set security group IDs
state.Put("subnet_id", "")
template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
if template.NetworkInterfaces != nil {
t.Fatalf("Template shouldn't contain network interfaces object if subnet_id is unset.")
}
// Rerun, this time testing that instance doesn't have instance profile is iamInstanceProfile is unset
state.Put("iamInstanceProfile", "")
template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
fmt.Println(template.IamInstanceProfile)
if *template.IamInstanceProfile.Name != "" {
t.Fatalf("Template shouldn't contain instance profile if iamInstanceProfile is unset.")
}
}
func TestCreateTemplateData_NoEphemeral(t *testing.T) {
state := tStateSpot()
stepRunSpotInstance := getBasicStep()
stepRunSpotInstance.NoEphemeral = true
template := stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
if len(template.BlockDeviceMappings) != 26 {
t.Fatalf("Should have created 26 mappings to keep ephemeral drives from appearing.")
}
// Now check that noEphemeral doesn't mess with the mappings in real life.
// state = tStateSpot()
// stepRunSpotInstance = getBasicStep()
// stepRunSpotInstance.NoEphemeral = true
// mappings := []*ec2.InstanceBlockDeviceMapping{
// &ec2.InstanceBlockDeviceMapping{
// DeviceName: "xvda",
// Ebs: {
// DeleteOnTermination: true,
// Status: "attaching",
// VolumeId: "vol-044cd49c330f21c05",
// },
// },
// &ec2.InstanceBlockDeviceMapping{
// DeviceName: "/dev/xvdf",
// Ebs: {
// DeleteOnTermination: false,
// Status: "attaching",
// VolumeId: "vol-0eefaf2d6ae35827e",
// },
// },
// }
// template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
// &ec2.LaunchTemplateInstanceMarketOptionsRequest{})
// if len(*template.BlockDeviceMappings) != 26 {
// t.Fatalf("Should have created 26 mappings to keep ephemeral drives from appearing.")
// }
}
type runSpotEC2ConnMock struct {
ec2iface.EC2API
CreateLaunchTemplateParams []*ec2.CreateLaunchTemplateInput
CreateLaunchTemplateFn func(*ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error)
CreateFleetParams []*ec2.CreateFleetInput
CreateFleetFn func(*ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error)
CreateTagsParams []*ec2.CreateTagsInput
CreateTagsFn func(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error)
DescribeInstancesParams []*ec2.DescribeInstancesInput
DescribeInstancesFn func(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
}
func (m *runSpotEC2ConnMock) CreateLaunchTemplate(req *ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error) {
m.CreateLaunchTemplateParams = append(m.CreateLaunchTemplateParams, req)
resp, err := m.CreateLaunchTemplateFn(req)
return resp, err
}
func (m *runSpotEC2ConnMock) CreateFleet(req *ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error) {
m.CreateFleetParams = append(m.CreateFleetParams, req)
if m.CreateFleetFn != nil {
resp, err := m.CreateFleetFn(req)
return resp, err
} else {
return nil, nil
}
}
func (m *runSpotEC2ConnMock) DescribeInstances(req *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
m.DescribeInstancesParams = append(m.DescribeInstancesParams, req)
if m.DescribeInstancesFn != nil {
resp, err := m.DescribeInstancesFn(req)
return resp, err
} else {
return nil, nil
}
}
func (m *runSpotEC2ConnMock) CreateTags(req *ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) {
m.CreateTagsParams = append(m.CreateTagsParams, req)
if m.CreateTagsFn != nil {
resp, err := m.CreateTagsFn(req)
return resp, err
} else {
return nil, nil
}
}
func defaultEc2Mock(instanceId, spotRequestId, volumeId, launchTemplateId *string) *runSpotEC2ConnMock {
instance := &ec2.Instance{
InstanceId: instanceId,
SpotInstanceRequestId: spotRequestId,
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
{
Ebs: &ec2.EbsInstanceBlockDevice{
VolumeId: volumeId,
},
},
},
}
return &runSpotEC2ConnMock{
CreateLaunchTemplateFn: func(in *ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error) {
return &ec2.CreateLaunchTemplateOutput{
LaunchTemplate: &ec2.LaunchTemplate{
LaunchTemplateId: launchTemplateId,
},
Warning: nil,
}, nil
},
CreateFleetFn: func(*ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error) {
return &ec2.CreateFleetOutput{
Errors: nil,
FleetId: nil,
Instances: []*ec2.CreateFleetInstance{
{
InstanceIds: []*string{instanceId},
},
},
}, nil
},
DescribeInstancesFn: func(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
return &ec2.DescribeInstancesOutput{
NextToken: nil,
Reservations: []*ec2.Reservation{
{
Instances: []*ec2.Instance{instance},
},
},
}, nil
},
}
}
func TestRun(t *testing.T) {
instanceId := aws.String("test-instance-id")
spotRequestId := aws.String("spot-id")
volumeId := aws.String("volume-id")
launchTemplateId := aws.String("launchTemplateId")
ec2Mock := defaultEc2Mock(instanceId, spotRequestId, volumeId, launchTemplateId)
uiMock := packersdk.TestUi(t)
state := tStateSpot()
state.Put("ec2", ec2Mock)
state.Put("ui", uiMock)
state.Put("source_image", testImage())
stepRunSpotInstance := getBasicStep()
stepRunSpotInstance.Tags["Name"] = "Packer Builder"
stepRunSpotInstance.Tags["test-tag"] = "test-value"
stepRunSpotInstance.SpotTags = map[string]string{
"spot-tag": "spot-tag-value",
}
stepRunSpotInstance.VolumeTags = map[string]string{
"volume-tag": "volume-tag-value",
}
ctx := context.TODO()
action := stepRunSpotInstance.Run(ctx, state)
if err := state.Get("error"); err != nil {
t.Fatalf("should not error, but: %v", err)
}
if action != multistep.ActionContinue {
t.Fatalf("shoul continue, but: %v", action)
}
if len(ec2Mock.CreateLaunchTemplateParams) != 1 {
t.Fatalf("createLaunchTemplate should be invoked once, but invoked %v", len(ec2Mock.CreateLaunchTemplateParams))
}
launchTemplateName := ec2Mock.CreateLaunchTemplateParams[0].LaunchTemplateName
if len(ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications) != 1 {
t.Fatalf("exactly one launch template tag specification expected")
}
if *ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications[0].ResourceType != "launch-template" {
t.Fatalf("resource type 'launch-template' expected")
}
if len(ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications[0].Tags) != 1 {
t.Fatalf("1 launch template tag expected")
}
nameTag := ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications[0].Tags[0]
if *nameTag.Key != "spot-tag" || *nameTag.Value != "spot-tag-value" {
t.Fatalf("expected spot-tag: spot-tag-value")
}
if len(ec2Mock.CreateFleetParams) != 1 {
t.Fatalf("createFleet should be invoked once, but invoked %v", len(ec2Mock.CreateLaunchTemplateParams))
}
if *ec2Mock.CreateFleetParams[0].TargetCapacitySpecification.DefaultTargetCapacityType != "spot" {
t.Fatalf("capacity type should be spot")
}
if *ec2Mock.CreateFleetParams[0].TargetCapacitySpecification.TotalTargetCapacity != 1 {
t.Fatalf("target capacity should be 1")
}
if len(ec2Mock.CreateFleetParams[0].LaunchTemplateConfigs) != 1 {
t.Fatalf("exactly one launch config template expected")
}
if *ec2Mock.CreateFleetParams[0].LaunchTemplateConfigs[0].LaunchTemplateSpecification.LaunchTemplateName != *launchTemplateName {
t.Fatalf("launchTemplateName should match in createLaunchTemplate and createFleet requests")
}
if len(ec2Mock.DescribeInstancesParams) != 1 {
t.Fatalf("describeInstancesParams should be invoked once, but invoked %v", len(ec2Mock.DescribeInstancesParams))
}
if *ec2Mock.DescribeInstancesParams[0].InstanceIds[0] != *instanceId {
t.Fatalf("instanceId should match from createFleet response")
}
uiMock.Say(fmt.Sprintf("%v", ec2Mock.CreateTagsParams))
if len(ec2Mock.CreateTagsParams) != 3 {
t.Fatalf("createTags should be invoked 3 times")
}
if len(ec2Mock.CreateTagsParams[0].Resources) != 1 || *ec2Mock.CreateTagsParams[0].Resources[0] != *spotRequestId {
t.Fatalf("should create tags for spot request")
}
if len(ec2Mock.CreateTagsParams[1].Resources) != 1 || *ec2Mock.CreateTagsParams[1].Resources[0] != *instanceId {
t.Fatalf("should create tags for instance")
}
if len(ec2Mock.CreateTagsParams[2].Resources) != 1 || ec2Mock.CreateTagsParams[2].Resources[0] != volumeId {
t.Fatalf("should create tags for volume")
}
}
func TestRun_NoSpotTags(t *testing.T) {
instanceId := aws.String("test-instance-id")
spotRequestId := aws.String("spot-id")
volumeId := aws.String("volume-id")
launchTemplateId := aws.String("lt-id")
ec2Mock := defaultEc2Mock(instanceId, spotRequestId, volumeId, launchTemplateId)
uiMock := packersdk.TestUi(t)
state := tStateSpot()
state.Put("ec2", ec2Mock)
state.Put("ui", uiMock)
state.Put("source_image", testImage())
stepRunSpotInstance := getBasicStep()
stepRunSpotInstance.Tags["Name"] = "Packer Builder"
stepRunSpotInstance.Tags["test-tag"] = "test-value"
stepRunSpotInstance.VolumeTags = map[string]string{
"volume-tag": "volume-tag-value",
}
ctx := context.TODO()
action := stepRunSpotInstance.Run(ctx, state)
if err := state.Get("error"); err != nil {
t.Fatalf("should not error, but: %v", err)
}
if action != multistep.ActionContinue {
t.Fatalf("shoul continue, but: %v", action)
}
if len(ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications) != 0 {
t.Fatalf("0 launch template tags expected")
}
}
@@ -0,0 +1,43 @@
package common
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/stretchr/testify/assert"
)
func TestStepSourceAmiInfo_PVImage(t *testing.T) {
err := new(StepSourceAMIInfo).canEnableEnhancedNetworking(&ec2.Image{
VirtualizationType: aws.String("paravirtual"),
})
assert.Error(t, err)
}
func TestStepSourceAmiInfo_HVMImage(t *testing.T) {
err := new(StepSourceAMIInfo).canEnableEnhancedNetworking(&ec2.Image{
VirtualizationType: aws.String("hvm"),
})
assert.NoError(t, err)
}
func TestStepSourceAmiInfo_PVImageWithAMIVirtPV(t *testing.T) {
stepSourceAMIInfo := StepSourceAMIInfo{
AMIVirtType: "paravirtual",
}
err := stepSourceAMIInfo.canEnableEnhancedNetworking(&ec2.Image{
VirtualizationType: aws.String("paravirtual"),
})
assert.Error(t, err)
}
func TestStepSourceAmiInfo_PVImageWithAMIVirtHVM(t *testing.T) {
stepSourceAMIInfo := StepSourceAMIInfo{
AMIVirtType: "hvm",
}
err := stepSourceAMIInfo.canEnableEnhancedNetworking(&ec2.Image{
VirtualizationType: aws.String("paravirtual"),
})
assert.NoError(t, err)
}
@@ -6,10 +6,10 @@ import (
"time"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/retry"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
)
type StepStopEBSBackedInstance struct {
@@ -0,0 +1,16 @@
package common
import (
"testing"
)
func TestAMITemplatePrepare_clean(t *testing.T) {
origName := "AMZamz09()./-_:&^ $%[]#'@"
expected := "AMZamz09()./-_--- --[]-'@"
name := templateCleanAMIName(origName)
if name != expected {
t.Fatalf("template names do not match: expected %s got %s\n", expected, name)
}
}
+46
View File
@@ -0,0 +1,46 @@
package amazon_acc
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
)
type AWSHelper struct {
Region string
AMIName string
}
func (a *AWSHelper) CleanUpAmi() error {
accessConfig := &awscommon.AccessConfig{}
session, err := accessConfig.Session()
if err != nil {
return fmt.Errorf("AWSAMICleanUp: Unable to create aws session %s", err.Error())
}
regionconn := ec2.New(session.Copy(&aws.Config{
Region: aws.String(a.Region),
}))
resp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
Owners: aws.StringSlice([]string{"self"}),
Filters: []*ec2.Filter{{
Name: aws.String("name"),
Values: aws.StringSlice([]string{a.AMIName}),
}}})
if err != nil {
return fmt.Errorf("AWSAMICleanUp: Unable to find Image %s: %s", a.AMIName, err.Error())
}
if resp != nil && len(resp.Images) > 0 {
_, err = regionconn.DeregisterImage(&ec2.DeregisterImageInput{
ImageId: resp.Images[0].ImageId,
})
if err != nil {
return fmt.Errorf("AWSAMICleanUp: Unable to Deregister Image %s", err.Error())
}
}
return nil
}
@@ -0,0 +1,59 @@
package amazon_acc
// This is the code necessary for running the provisioner acceptance tests.
// It provides the builder config and cleans up created resource.
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
amazonebsbuilder "github.com/hashicorp/packer/builder/amazon/ebs"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type AmazonEBSAccTest struct{}
func (s *AmazonEBSAccTest) GetConfigs() (map[string]string, error) {
fixtures := map[string]string{
"linux": "amazon-ebs.txt",
"windows": "amazon-ebs_windows.txt",
}
configs := make(map[string]string)
for distro, fixture := range fixtures {
fileName := fixture
filePath := filepath.Join("../../builder/amazon/ebs/acceptance/test-fixtures/", fileName)
config, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("Expected to find %s", filePath)
}
defer config.Close()
file, err := ioutil.ReadAll(config)
if err != nil {
return nil, fmt.Errorf("Unable to read %s", filePath)
}
configs[distro] = string(file)
}
return configs, nil
}
func (s *AmazonEBSAccTest) CleanUp() error {
helper := AWSHelper{
Region: "us-east-1",
AMIName: "packer-acc-test",
}
return helper.CleanUpAmi()
}
func (s *AmazonEBSAccTest) GetBuilderStore() packersdk.MapOfBuilder {
return packersdk.MapOfBuilder{
"amazon-ebs": func() (packersdk.Builder, error) { return &amazonebsbuilder.Builder{}, nil },
}
}
@@ -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"
}
}
@@ -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"
}
}
@@ -0,0 +1,40 @@
<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>
@@ -15,7 +15,6 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/hcl/v2/hcldec"
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
@@ -24,6 +23,7 @@ import (
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
)
// The unique ID for this builder
@@ -4,8 +4,8 @@ package ebs
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-amazon/builder/common"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer/builder/amazon/common"
"github.com/zclconf/go-cty/cty"
)
+396
View File
@@ -0,0 +1,396 @@
/*
Deregister the test image with
aws ec2 deregister-image --image-id $(aws ec2 describe-images --output text --filters "Name=name,Values=packer-test-packer-test-dereg" --query 'Images[*].{ID:ImageId}')
*/
package ebs
import (
"fmt"
"os"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/amazon/common"
)
func TestBuilderAcc_basic(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccBasic,
})
}
func TestBuilderAcc_regionCopy(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccRegionCopy,
Check: checkRegionCopy([]string{"us-east-1", "us-west-2"}),
})
}
func TestBuilderAcc_forceDeregister(t *testing.T) {
// Build the same AMI name twice, with force_deregister on the second run
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: buildForceDeregisterConfig("false", "dereg"),
SkipArtifactTeardown: true,
})
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: buildForceDeregisterConfig("true", "dereg"),
})
}
func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) {
amiName := "packer-test-dereg"
// Build the same AMI name twice, with force_delete_snapshot on the second run
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: buildForceDeleteSnapshotConfig("false", amiName),
SkipArtifactTeardown: true,
})
// Get image data by AMI name
ec2conn, _ := testEC2Conn()
describeInput := &ec2.DescribeImagesInput{Filters: []*ec2.Filter{
{
Name: aws.String("name"),
Values: []*string{aws.String(amiName)},
},
}}
ec2conn.WaitUntilImageExists(describeInput)
imageResp, _ := ec2conn.DescribeImages(describeInput)
image := imageResp.Images[0]
// Get snapshot ids for image
snapshotIds := []*string{}
for _, device := range image.BlockDeviceMappings {
if device.Ebs != nil && device.Ebs.SnapshotId != nil {
snapshotIds = append(snapshotIds, device.Ebs.SnapshotId)
}
}
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: buildForceDeleteSnapshotConfig("true", amiName),
Check: checkSnapshotsDeleted(snapshotIds),
})
}
func checkSnapshotsDeleted(snapshotIds []*string) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
// Verify the snapshots are gone
ec2conn, _ := testEC2Conn()
snapshotResp, _ := ec2conn.DescribeSnapshots(
&ec2.DescribeSnapshotsInput{SnapshotIds: snapshotIds},
)
if len(snapshotResp.Snapshots) > 0 {
return fmt.Errorf("Snapshots weren't successfully deleted by `force_delete_snapshot`")
}
return nil
}
}
func TestBuilderAcc_amiSharing(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccSharingPreCheck(t) },
Builder: &Builder{},
Template: buildSharingConfig(os.Getenv("TESTACC_AWS_ACCOUNT_ID")),
Check: checkAMISharing(2, os.Getenv("TESTACC_AWS_ACCOUNT_ID"), "all"),
})
}
func TestBuilderAcc_encryptedBoot(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccEncrypted,
Check: checkBootEncrypted(),
})
}
func checkAMISharing(count int, uid, group string) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
if len(artifacts) > 1 {
return fmt.Errorf("more than 1 artifact")
}
// Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0]
artifact, ok := artifactRaw.(*common.Artifact)
if !ok {
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
}
// describe the image, get block devices with a snapshot
ec2conn, _ := testEC2Conn()
imageResp, err := ec2conn.DescribeImageAttribute(&ec2.DescribeImageAttributeInput{
Attribute: aws.String("launchPermission"),
ImageId: aws.String(artifact.Amis["us-east-1"]),
})
if err != nil {
return fmt.Errorf("Error retrieving Image Attributes for AMI Artifact (%#v) in AMI Sharing Test: %s", artifact, err)
}
// Launch Permissions are in addition to the userid that created it, so if
// you add 3 additional ami_users, you expect 2 Launch Permissions here
if len(imageResp.LaunchPermissions) != count {
return fmt.Errorf("Error in Image Attributes, expected (%d) Launch Permissions, got (%d)", count, len(imageResp.LaunchPermissions))
}
userFound := false
for _, lp := range imageResp.LaunchPermissions {
if lp.UserId != nil && uid == *lp.UserId {
userFound = true
}
}
if !userFound {
return fmt.Errorf("Error in Image Attributes, expected User ID (%s) to have Launch Permissions, but was not found", uid)
}
groupFound := false
for _, lp := range imageResp.LaunchPermissions {
if lp.Group != nil && group == *lp.Group {
groupFound = true
}
}
if !groupFound {
return fmt.Errorf("Error in Image Attributes, expected Group ID (%s) to have Launch Permissions, but was not found", group)
}
return nil
}
}
func checkRegionCopy(regions []string) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
if len(artifacts) > 1 {
return fmt.Errorf("more than 1 artifact")
}
// Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0]
artifact, ok := artifactRaw.(*common.Artifact)
if !ok {
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
}
// Verify that we copied to only the regions given
regionSet := make(map[string]struct{})
for _, r := range regions {
regionSet[r] = struct{}{}
}
for r := range artifact.Amis {
if _, ok := regionSet[r]; !ok {
return fmt.Errorf("unknown region: %s", r)
}
delete(regionSet, r)
}
if len(regionSet) > 0 {
return fmt.Errorf("didn't copy to: %#v", regionSet)
}
return nil
}
}
func checkBootEncrypted() builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
// Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0]
artifact, ok := artifactRaw.(*common.Artifact)
if !ok {
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
}
// describe the image, get block devices with a snapshot
ec2conn, _ := testEC2Conn()
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{aws.String(artifact.Amis["us-east-1"])},
})
if err != nil {
return fmt.Errorf("Error retrieving Image Attributes for AMI (%s) in AMI Encrypted Boot Test: %s", artifact, err)
}
image := imageResp.Images[0] // Only requested a single AMI ID
rootDeviceName := image.RootDeviceName
for _, bd := range image.BlockDeviceMappings {
if *bd.DeviceName == *rootDeviceName {
if *bd.Ebs.Encrypted != true {
return fmt.Errorf("volume not encrypted: %s", *bd.Ebs.SnapshotId)
}
}
}
return nil
}
}
func TestBuilderAcc_SessionManagerInterface(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccSessionManagerInterface,
})
}
func testAccPreCheck(t *testing.T) {
}
func testAccSharingPreCheck(t *testing.T) {
if v := os.Getenv("TESTACC_AWS_ACCOUNT_ID"); v == "" {
t.Fatal(fmt.Sprintf("TESTACC_AWS_ACCOUNT_ID must be set for acceptance tests"))
}
}
func testEC2Conn() (*ec2.EC2, error) {
access := &common.AccessConfig{RawRegion: "us-east-1"}
session, err := access.Session()
if err != nil {
return nil, err
}
return ec2.New(session), nil
}
const testBuilderAccBasic = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"ami_name": "packer-test {{timestamp}}"
}]
}
`
const testBuilderAccRegionCopy = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"ami_name": "packer-test {{timestamp}}",
"ami_regions": ["us-east-1", "us-west-2"]
}]
}
`
const testBuilderAccForceDeregister = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"force_deregister": "%s",
"ami_name": "%s"
}]
}
`
const testBuilderAccForceDeleteSnapshot = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"force_deregister": "%s",
"force_delete_snapshot": "%s",
"ami_name": "%s"
}]
}
`
const testBuilderAccSharing = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"ami_name": "packer-test {{timestamp}}",
"ami_users":["%s"],
"ami_groups":["all"]
}]
}
`
const testBuilderAccEncrypted = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami":"ami-c15bebaa",
"ssh_username": "ubuntu",
"ami_name": "packer-enc-test {{timestamp}}",
"encrypt_boot": true
}]
}
`
const testBuilderAccSessionManagerInterface = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"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
},
"ssh_username": "ubuntu",
"ssh_interface": "session_manager",
"iam_instance_profile": "SSMInstanceProfile",
"ami_name": "packer-ssm-test-{{timestamp}}"
}]
}
`
func buildForceDeregisterConfig(val, name string) string {
return fmt.Sprintf(testBuilderAccForceDeregister, val, name)
}
func buildForceDeleteSnapshotConfig(val, name string) string {
return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name)
}
func buildSharingConfig(val string) string {
return fmt.Sprintf(testBuilderAccSharing, val)
}
+166
View File
@@ -0,0 +1,166 @@
package ebs
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"access_key": "foo",
"secret_key": "bar",
"source_ami": "foo",
"instance_type": "foo",
"region": "us-east-1",
"ssh_username": "root",
"ami_name": "foo",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packersdk.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
"access_key": []string{},
}
_, warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("prepare should fail")
}
}
func TestBuilderPrepare_AMIName(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["ami_name"] = "foo"
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["ami_name"] = "foo {{"
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
// Test bad
delete(config, "ami_name")
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["shutdown_behavior"] = "terminate"
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test good
config["shutdown_behavior"] = "stop"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["shutdown_behavior"] = "foobar"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
var b Builder
config := testConfig()
generatedData, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if generatedData[0] != "SourceAMIName" {
t.Fatalf("Generated data should contain SourceAMIName")
}
if generatedData[1] != "BuildRegion" {
t.Fatalf("Generated data should contain BuildRegion")
}
if generatedData[2] != "SourceAMI" {
t.Fatalf("Generated data should contain SourceAMI")
}
if generatedData[3] != "SourceAMICreationDate" {
t.Fatalf("Generated data should contain SourceAMICreationDate")
}
if generatedData[4] != "SourceAMIOwner" {
t.Fatalf("Generated data should contain SourceAMIOwner")
}
if generatedData[5] != "SourceAMIOwnerName" {
t.Fatalf("Generated data should contain SourceAMIOwnerName")
}
}
@@ -8,12 +8,12 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/random"
"github.com/hashicorp/packer-plugin-sdk/retry"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
)
type stepCreateAMI struct {
+137
View File
@@ -0,0 +1,137 @@
package ebs
import (
"encoding/json"
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/amazon/common"
)
type TFBuilder struct {
Type string `json:"type"`
Region string `json:"region"`
SourceAmi string `json:"source_ami"`
InstanceType string `json:"instance_type"`
SshUsername string `json:"ssh_username"`
AmiName string `json:"ami_name"`
Tags map[string]string `json:"tags"`
SnapshotTags map[string]string `json:"snapshot_tags"`
}
type TFConfig struct {
Builders []TFBuilder `json:"builders"`
}
func TestBuilderTagsAcc_basic(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderTagsAccBasic,
Check: checkTags(),
})
}
func checkTags() builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
if len(artifacts) > 1 {
return fmt.Errorf("more than 1 artifact")
}
config := TFConfig{}
json.Unmarshal([]byte(testBuilderTagsAccBasic), &config)
tags := config.Builders[0].Tags
snapshotTags := config.Builders[0].SnapshotTags
// Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0]
artifact, ok := artifactRaw.(*common.Artifact)
if !ok {
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
}
// Describe the image, get block devices with a snapshot
ec2conn, _ := testEC2Conn()
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{aws.String(artifact.Amis["us-east-1"])},
})
if err != nil {
return fmt.Errorf("Error retrieving details for AMI Artifact (%#v) in Tags Test: %s", artifact, err)
}
if len(imageResp.Images) == 0 {
return fmt.Errorf("No images found for AMI Artifact (%#v) in Tags Test: %s", artifact, err)
}
image := imageResp.Images[0]
// Check only those with a Snapshot ID, i.e. not Ephemeral
var snapshots []*string
for _, device := range image.BlockDeviceMappings {
if device.Ebs != nil && device.Ebs.SnapshotId != nil {
snapshots = append(snapshots, device.Ebs.SnapshotId)
}
}
// Grab matching snapshot info
resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{
SnapshotIds: snapshots,
})
if err != nil {
return fmt.Errorf("Error retrieving Snapshots for AMI Artifact (%#v) in Tags Test: %s", artifact, err)
}
if len(resp.Snapshots) == 0 {
return fmt.Errorf("No Snapshots found for AMI Artifact (%#v) in Tags Test", artifact)
}
// Grab the snapshots, check the tags
for _, s := range resp.Snapshots {
expected := len(tags)
for _, t := range s.Tags {
for key, value := range tags {
if val, ok := snapshotTags[key]; ok && val == *t.Value {
expected--
} else if key == *t.Key && value == *t.Value {
expected--
}
}
}
if expected > 0 {
return fmt.Errorf("Not all tags found")
}
}
return nil
}
}
const testBuilderTagsAccBasic = `
{
"builders": [
{
"type": "test",
"region": "us-east-1",
"source_ami": "ami-9eaa1cf6",
"instance_type": "t2.micro",
"ssh_username": "ubuntu",
"ami_name": "packer-tags-testing-{{timestamp}}",
"tags": {
"OS_Version": "Ubuntu",
"Release": "Latest",
"Name": "Bleep"
},
"snapshot_tags": {
"Name": "Foobar"
}
}
]
}
`
@@ -4,8 +4,8 @@ package ebssurrogate
import (
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
)
type BlockDevice struct {
@@ -13,7 +13,6 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/hcl/v2/hcldec"
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
@@ -22,6 +21,7 @@ import (
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
)
const BuilderId = "mitchellh.amazon.ebssurrogate"

Some files were not shown because too many files have changed in this diff Show More