Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f834e229a | |||
| 4417f8b3bf | |||
| 8db540a935 | |||
| e8780bf7b8 | |||
| 3b0226d496 | |||
| 4c08789642 | |||
| 634bf87d99 | |||
| d566419c45 | |||
| cce1f5c1e3 | |||
| 7f26429a2a | |||
| d81c02b456 | |||
| 794e83b171 | |||
| 58fb58c2ea | |||
| c3e78d2c32 | |||
| fb04fa7a25 | |||
| 830140157d | |||
| 1b8e71ca1f | |||
| 3e497e3712 | |||
| 030da4b6b9 | |||
| 3a437d4891 | |||
| 8c2f26718e | |||
| 076596cd3b | |||
| f541cd59ed | |||
| 4b6891d6d5 | |||
| 4746fc682d | |||
| f6dbc3e78a | |||
| 03d79a2c39 | |||
| 349a300213 | |||
| dfc5d76108 | |||
| 77a29fc2f8 | |||
| a588808270 | |||
| b9b1cdf75f | |||
| 2ac5fe894c | |||
| fbb9429910 | |||
| cb359e8064 | |||
| a9bec7945e | |||
| 505cbd2591 | |||
| 25fddf3199 | |||
| 166df2ce1d | |||
| 755395faf8 | |||
| b958fe7a54 | |||
| cc133ea250 | |||
| 4ea4c0570f | |||
| a665e6b822 | |||
| 2de91e4862 | |||
| 3a11820a41 | |||
| 0e3fcb589b | |||
| e6596a0a1d | |||
| a915ec8e05 | |||
| 9b641c9bfd |
+16
-3
@@ -37,15 +37,19 @@ commands:
|
||||
parameters:
|
||||
GOOS:
|
||||
type: string
|
||||
GOARCH:
|
||||
default: "amd64"
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- 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)
|
||||
- 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 >>
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- ./pkg/
|
||||
|
||||
# Golang CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
||||
@@ -123,6 +127,13 @@ 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
|
||||
@@ -203,6 +214,7 @@ workflows:
|
||||
jobs:
|
||||
- build_linux
|
||||
- build_darwin
|
||||
- build_darwin_arm64
|
||||
- build_windows
|
||||
- build_freebsd
|
||||
- build_openbsd
|
||||
@@ -211,6 +223,7 @@ workflows:
|
||||
requires:
|
||||
- build_linux
|
||||
- build_darwin
|
||||
- build_darwin_arm64
|
||||
- build_windows
|
||||
- build_freebsd
|
||||
- build_openbsd
|
||||
|
||||
+49
-5
@@ -1,21 +1,44 @@
|
||||
## 1.7.1 (Upcoming)
|
||||
## 1.7.2 (April 05, 2021)
|
||||
|
||||
### 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)
|
||||
|
||||
### 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)
|
||||
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]
|
||||
* 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
|
||||
@@ -25,13 +48,17 @@
|
||||
Repostiroy](https://github.com/exoscale/packer-plugin-exoscale). [GH-10709]
|
||||
|
||||
### IMPROVEMENTS
|
||||
* builder/amazon: allow creation of ebs snapshots wihtout volumes. [GH-9591]
|
||||
* builder/amazon: allow creation of ebs snapshots without 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
|
||||
@@ -47,11 +74,21 @@
|
||||
[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]
|
||||
* hcl2_upgrade: hcl2_upgrade command can now upgrade json var-files [GH-10676]
|
||||
* provisioner/inspec: Allow non-zero exit codes for inspec provisioner.
|
||||
[GH-10723]
|
||||
|
||||
### BUG FIXES
|
||||
* buider/azure: Update builder to ensure a proper clean up Azure temporary
|
||||
@@ -68,6 +105,8 @@
|
||||
[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
|
||||
@@ -78,11 +117,14 @@
|
||||
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
|
||||
@@ -94,6 +136,8 @@
|
||||
* 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
|
||||
|
||||
@@ -49,7 +49,7 @@ package:
|
||||
@sh -c "$(CURDIR)/scripts/dist.sh $(VERSION)"
|
||||
|
||||
install-build-deps: ## Install dependencies for bin build
|
||||
@go get github.com/mitchellh/gox
|
||||
@go install github.com/mitchellh/gox@v1.0.1
|
||||
|
||||
install-gen-deps: ## Install dependencies for code generation
|
||||
# to avoid having to tidy our go deps, we `go get` our binaries from a temp
|
||||
|
||||
@@ -135,6 +135,7 @@ 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,
|
||||
|
||||
@@ -88,6 +88,7 @@ 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"`
|
||||
@@ -205,6 +206,7 @@ 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},
|
||||
|
||||
@@ -47,6 +47,8 @@ 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,6 +23,7 @@ type stepCreateAlicloudInstance struct {
|
||||
UserData string
|
||||
UserDataFile string
|
||||
instanceId string
|
||||
RamRoleName string
|
||||
RegionId string
|
||||
InternetChargeType string
|
||||
InternetMaxBandwidthOut int
|
||||
@@ -115,6 +116,7 @@ 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)
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
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.")
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package chroot
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDevicePrefixMatch(t *testing.T) {
|
||||
/*
|
||||
if devicePrefixMatch("nvme0n1") != "" {
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
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.")
|
||||
}
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
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.")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,401 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
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"])
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,404 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
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 },
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<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>
|
||||
@@ -1,396 +0,0 @@
|
||||
/*
|
||||
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)
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
@@ -1,110 +0,0 @@
|
||||
package ebssurrogate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/builder/amazon/common"
|
||||
|
||||
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",
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packersdk.Builder); !ok {
|
||||
t.Fatal("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.Fatal("prepare should fail")
|
||||
}
|
||||
}
|
||||
|
||||
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_ReturnGeneratedData(t *testing.T) {
|
||||
var b Builder
|
||||
// Basic configuration
|
||||
b.config.RootDevice = RootBlockDevice{
|
||||
SourceDeviceName: "device name",
|
||||
DeviceName: "device name",
|
||||
}
|
||||
b.config.LaunchMappings = BlockDevices{
|
||||
BlockDevice{
|
||||
BlockDevice: common.BlockDevice{
|
||||
DeviceName: "device name",
|
||||
},
|
||||
OmitFromArtifact: false,
|
||||
},
|
||||
}
|
||||
b.config.AMIVirtType = "type"
|
||||
config := testConfig()
|
||||
config["ami_name"] = "name"
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
package ebssurrogate
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/builder/amazon/common"
|
||||
)
|
||||
|
||||
const sourceDeviceName = "/dev/xvdf"
|
||||
const rootDeviceName = "/dev/xvda"
|
||||
|
||||
func newStepRegisterAMI(amiDevices, launchDevices []*ec2.BlockDeviceMapping) *StepRegisterAMI {
|
||||
return &StepRegisterAMI{
|
||||
RootDevice: RootBlockDevice{
|
||||
SourceDeviceName: sourceDeviceName,
|
||||
DeviceName: rootDeviceName,
|
||||
DeleteOnTermination: true,
|
||||
VolumeType: "ebs",
|
||||
VolumeSize: 10,
|
||||
},
|
||||
AMIDevices: amiDevices,
|
||||
LaunchDevices: launchDevices,
|
||||
PollingConfig: new(common.AWSPollingConfig),
|
||||
}
|
||||
}
|
||||
|
||||
func sorted(devices []*ec2.BlockDeviceMapping) []*ec2.BlockDeviceMapping {
|
||||
sort.SliceStable(devices, func(i, j int) bool {
|
||||
return *devices[i].DeviceName < *devices[j].DeviceName
|
||||
})
|
||||
return devices
|
||||
}
|
||||
|
||||
func TestStepRegisterAmi_combineDevices(t *testing.T) {
|
||||
cases := []struct {
|
||||
snapshotIds map[string]string
|
||||
amiDevices []*ec2.BlockDeviceMapping
|
||||
launchDevices []*ec2.BlockDeviceMapping
|
||||
allDevices []*ec2.BlockDeviceMapping
|
||||
}{
|
||||
{
|
||||
snapshotIds: map[string]string{},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{},
|
||||
allDevices: []*ec2.BlockDeviceMapping{},
|
||||
},
|
||||
{
|
||||
snapshotIds: map[string]string{},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Minimal single device
|
||||
snapshotIds: map[string]string{
|
||||
sourceDeviceName: "snap-0123456789abcdef1",
|
||||
},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Single launch device with AMI device
|
||||
snapshotIds: map[string]string{
|
||||
sourceDeviceName: "snap-0123456789abcdef1",
|
||||
},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Multiple launch devices
|
||||
snapshotIds: map[string]string{
|
||||
sourceDeviceName: "snap-0123456789abcdef1",
|
||||
"/dev/xvdg": "snap-0123456789abcdef2",
|
||||
},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef2"),
|
||||
},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Multiple launch devices with encryption
|
||||
snapshotIds: map[string]string{
|
||||
sourceDeviceName: "snap-0123456789abcdef1",
|
||||
"/dev/xvdg": "snap-0123456789abcdef2",
|
||||
},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
// Encrypted: true stripped from snapshotted devices
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef2"),
|
||||
},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Multiple launch devices and AMI devices with encryption
|
||||
snapshotIds: map[string]string{
|
||||
sourceDeviceName: "snap-0123456789abcdef1",
|
||||
"/dev/xvdg": "snap-0123456789abcdef2",
|
||||
},
|
||||
amiDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
KmsKeyId: aws.String("keyId"),
|
||||
},
|
||||
// Source device name can be used in AMI devices
|
||||
// since launch device of same name gets renamed
|
||||
// to root device name
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
},
|
||||
launchDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
allDevices: []*ec2.BlockDeviceMapping{
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
Encrypted: aws.Bool(true),
|
||||
KmsKeyId: aws.String("keyId"),
|
||||
},
|
||||
DeviceName: aws.String(sourceDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef1"),
|
||||
},
|
||||
DeviceName: aws.String(rootDeviceName),
|
||||
},
|
||||
{
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
SnapshotId: aws.String("snap-0123456789abcdef2"),
|
||||
},
|
||||
DeviceName: aws.String("/dev/xvdg"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
stepRegisterAmi := newStepRegisterAMI(tc.amiDevices, tc.launchDevices)
|
||||
allDevices := stepRegisterAmi.combineDevices(tc.snapshotIds)
|
||||
if !reflect.DeepEqual(sorted(allDevices), sorted(tc.allDevices)) {
|
||||
t.Fatalf("Unexpected output from combineDevices")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package ebsvolume
|
||||
|
||||
import "testing"
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
package ebsvolume
|
||||
|
||||
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",
|
||||
}
|
||||
}
|
||||
|
||||
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_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 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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuidler_ConfigBlockdevicemapping(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
//Set some snapshot settings
|
||||
config["ebs_volumes"] = []map[string]interface{}{
|
||||
{
|
||||
"device_name": "/dev/xvdb",
|
||||
"volume_size": "32",
|
||||
"delete_on_termination": true,
|
||||
},
|
||||
{
|
||||
"device_name": "/dev/xvdc",
|
||||
"volume_size": "32",
|
||||
"delete_on_termination": true,
|
||||
"snapshot_tags": map[string]string{
|
||||
"Test_Tag": "tag_value",
|
||||
"another tag": "another value",
|
||||
},
|
||||
"snapshot_users": []string{
|
||||
"123", "456",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
t.Logf("Test gen %+v", b.config.VolumeMappings)
|
||||
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package ebsvolume
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"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"
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/amazon/common"
|
||||
)
|
||||
|
||||
// Define a mock struct to be used in unit tests for common aws steps.
|
||||
type mockEC2Conn struct {
|
||||
ec2iface.EC2API
|
||||
Config *aws.Config
|
||||
}
|
||||
|
||||
func (m *mockEC2Conn) CreateSnapshot(input *ec2.CreateSnapshotInput) (*ec2.Snapshot, error) {
|
||||
snap := &ec2.Snapshot{
|
||||
// This isn't typical amazon format, but injecting the volume id into
|
||||
// this field lets us verify that the right volume was snapshotted with
|
||||
// a simple string comparison
|
||||
SnapshotId: aws.String(fmt.Sprintf("snap-of-%s", *input.VolumeId)),
|
||||
}
|
||||
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
func (m *mockEC2Conn) WaitUntilSnapshotCompletedWithContext(aws.Context, *ec2.DescribeSnapshotsInput, ...request.WaiterOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMockConn(config *common.AccessConfig, target string) (ec2iface.EC2API, error) {
|
||||
mockConn := &mockEC2Conn{
|
||||
Config: aws.NewConfig(),
|
||||
}
|
||||
return mockConn, nil
|
||||
}
|
||||
|
||||
// Create statebag for running test
|
||||
func tState(t *testing.T) multistep.StateBag {
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("ui", &packer.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(&common.AccessConfig{}, "us-east-2")
|
||||
|
||||
state.Put("ec2", conn)
|
||||
// Store a fake instance that contains a block device that matches the
|
||||
// volumes defined in the config above
|
||||
state.Put("instance", &ec2.Instance{
|
||||
InstanceId: aws.String("instance-id"),
|
||||
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
|
||||
{
|
||||
DeviceName: aws.String("/dev/xvda"),
|
||||
Ebs: &ec2.EbsInstanceBlockDevice{
|
||||
VolumeId: aws.String("vol-1234"),
|
||||
},
|
||||
},
|
||||
{
|
||||
DeviceName: aws.String("/dev/xvdb"),
|
||||
Ebs: &ec2.EbsInstanceBlockDevice{
|
||||
VolumeId: aws.String("vol-5678"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return state
|
||||
}
|
||||
|
||||
func TestStepSnapshot_run_simple(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig() //from builder_test
|
||||
|
||||
//Set some snapshot settings
|
||||
config["ebs_volumes"] = []map[string]interface{}{
|
||||
{
|
||||
"device_name": "/dev/xvdb",
|
||||
"volume_size": "32",
|
||||
"delete_on_termination": true,
|
||||
"snapshot_volume": true,
|
||||
},
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
state := tState(t)
|
||||
|
||||
accessConfig := common.FakeAccessConfig()
|
||||
|
||||
step := stepSnapshotEBSVolumes{
|
||||
PollingConfig: new(common.AWSPollingConfig),
|
||||
AccessConfig: accessConfig,
|
||||
VolumeMapping: b.config.VolumeMappings,
|
||||
Ctx: b.config.ctx,
|
||||
}
|
||||
|
||||
step.Run(context.Background(), state)
|
||||
|
||||
if len(step.snapshotMap) != 1 {
|
||||
t.Fatalf("Missing Snapshot from step")
|
||||
}
|
||||
|
||||
if volmapping := step.snapshotMap["snap-of-vol-5678"]; volmapping == nil {
|
||||
t.Fatalf("Didn't snapshot correct volume: Map is %#v", step.snapshotMap)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepSnapshot_run_no_snaps(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig() //from builder_test
|
||||
|
||||
//Set some snapshot settings
|
||||
config["ebs_volumes"] = []map[string]interface{}{
|
||||
{
|
||||
"device_name": "/dev/xvdb",
|
||||
"volume_size": "32",
|
||||
"delete_on_termination": true,
|
||||
"snapshot_volume": false,
|
||||
},
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
state := tState(t)
|
||||
|
||||
accessConfig := common.FakeAccessConfig()
|
||||
|
||||
step := stepSnapshotEBSVolumes{
|
||||
PollingConfig: new(common.AWSPollingConfig),
|
||||
AccessConfig: accessConfig,
|
||||
VolumeMapping: b.config.VolumeMappings,
|
||||
Ctx: b.config.ctx,
|
||||
}
|
||||
|
||||
step.Run(context.Background(), state)
|
||||
|
||||
if len(step.snapshotMap) != 0 {
|
||||
t.Fatalf("Shouldn't have snapshotted any volumes")
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
command: packer build -var "accesskey=*" -var "secretkey=" -var "shellpath=packages.sh" .\apache.json
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"variables":
|
||||
{
|
||||
"accesskey": "",
|
||||
"secretkey": "",
|
||||
"shellpath": "packages.sh"
|
||||
},
|
||||
"builders":[
|
||||
{
|
||||
"type": "amazon-ebs",
|
||||
"access_key": "{{user `accesskey`}}",
|
||||
"secret_key": "{{user `secretkey`}}",
|
||||
"region": "ap-south-1",
|
||||
"source_ami": "ami-sa7608343426b",
|
||||
"instance_type": "t2.micro",
|
||||
"ssh_username": "ubuntu",
|
||||
"ami_name": "apache",
|
||||
"tags": {
|
||||
"OS_Version": "Ubuntu",
|
||||
"Release": "Latest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"provisioners":[
|
||||
{
|
||||
"type": "shell",
|
||||
"script": "{{user `shellpath`}}"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
echo "installing apache "
|
||||
sudo apt-get update
|
||||
sudo apt-get install apache2 -y
|
||||
sudo apt-get update
|
||||
sudo service apache2 restart
|
||||
sudo apache2 --version
|
||||
@@ -1 +0,0 @@
|
||||
command: packer build -var "accesskey=*" -var "secretkey=" -var "shellpath=packages.sh" .\nginx.json
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"variables":
|
||||
{
|
||||
"accesskey": "",
|
||||
"secretkey": "",
|
||||
"shellpath": "packages.sh"
|
||||
},
|
||||
"builders":[
|
||||
{
|
||||
"type": "amazon-ebs",
|
||||
"access_key": "{{user `accesskey`}}",
|
||||
"secret_key": "{{user `secretkey`}}",
|
||||
"region": "ap-south-1",
|
||||
"source_ami": "ami-sa7608343426b",
|
||||
"instance_type": "t2.micro",
|
||||
"ssh_username": "ubuntu",
|
||||
"ami_name": "nginx",
|
||||
"tags": {
|
||||
"OS_Version": "Ubuntu",
|
||||
"Release": "Latest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"provisioners":[
|
||||
{
|
||||
"type": "shell",
|
||||
"script": "{{user `shellpath`}}"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
echo "installing nginx "
|
||||
sudo apt-get update
|
||||
sudo apt-get install nginx -y
|
||||
sudo service nginx restart
|
||||
@@ -1,343 +0,0 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func testConfig() (config map[string]interface{}, tf *os.File) {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
config = map[string]interface{}{
|
||||
"account_id": "foo",
|
||||
"ami_name": "foo",
|
||||
"instance_type": "m1.small",
|
||||
"region": "us-east-1",
|
||||
"s3_bucket": "foo",
|
||||
"source_ami": "foo",
|
||||
"ssh_username": "bob",
|
||||
"x509_cert_path": tf.Name(),
|
||||
"x509_key_path": tf.Name(),
|
||||
"x509_upload_path": "/foo",
|
||||
}
|
||||
|
||||
return config, tf
|
||||
}
|
||||
|
||||
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_AccountId(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
config["account_id"] = ""
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["account_id"] = "foo"
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
|
||||
config["account_id"] = "0123-0456-7890"
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AccountId != "012304567890" {
|
||||
t.Errorf("should strip hyphens: %s", b.config.AccountId)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
var b Builder
|
||||
config, tempfile := testConfig()
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
// 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_BundleDestination(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
config["bundle_destination"] = ""
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if b.config.BundleDestination != "/tmp" {
|
||||
t.Fatalf("bad: %s", b.config.BundleDestination)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_BundlePrefix(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if b.config.BundlePrefix == "" {
|
||||
t.Fatalf("bad: %s", b.config.BundlePrefix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
var b Builder
|
||||
config, tempfile := testConfig()
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
// 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_S3Bucket(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
config["s3_bucket"] = ""
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["s3_bucket"] = "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_X509CertPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
config["x509_cert_path"] = ""
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["x509_cert_path"] = "i/am/a/file/that/doesnt/exist"
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("should have error")
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
config["x509_cert_path"] = tf.Name()
|
||||
_, 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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_X509KeyPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
config["x509_key_path"] = ""
|
||||
_, warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["x509_key_path"] = "i/am/a/file/that/doesnt/exist"
|
||||
_, warnings, err = b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
t.Fatalf("bad: %#v", warnings)
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("should have error")
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
config["x509_key_path"] = tf.Name()
|
||||
_, 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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_X509UploadPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config, tempfile := testConfig()
|
||||
config["skip_region_validation"] = true
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
config["x509_upload_path"] = ""
|
||||
_, 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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
|
||||
var b Builder
|
||||
config, tempfile := testConfig()
|
||||
defer os.Remove(tempfile.Name())
|
||||
defer tempfile.Close()
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
||||
packerVersion "github.com/hashicorp/packer/version"
|
||||
)
|
||||
|
||||
var AWSPluginVersion *version.PluginVersion
|
||||
|
||||
func init() {
|
||||
AWSPluginVersion = version.InitializePluginVersion(
|
||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/oracle/oci-go-sdk/core"
|
||||
"github.com/oracle/oci-go-sdk/v36/core"
|
||||
)
|
||||
|
||||
// Artifact is an artifact implementation that contains a built Custom Image.
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
ocommon "github.com/hashicorp/packer/builder/oracle/common"
|
||||
"github.com/oracle/oci-go-sdk/core"
|
||||
"github.com/oracle/oci-go-sdk/v36/core"
|
||||
)
|
||||
|
||||
// BuilderId uniquely identifies the builder
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest
|
||||
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest,FlexShapeConfig
|
||||
|
||||
package oci
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/pathing"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||
ocicommon "github.com/oracle/oci-go-sdk/common"
|
||||
ociauth "github.com/oracle/oci-go-sdk/common/auth"
|
||||
ocicommon "github.com/oracle/oci-go-sdk/v36/common"
|
||||
ociauth "github.com/oracle/oci-go-sdk/v36/common/auth"
|
||||
)
|
||||
|
||||
type CreateVNICDetails struct {
|
||||
@@ -46,6 +46,11 @@ type ListImagesRequest struct {
|
||||
Shape *string `mapstructure:"shape"`
|
||||
}
|
||||
|
||||
type FlexShapeConfig struct {
|
||||
Ocpus *float32 `mapstructure:"ocpus" required:"false"`
|
||||
MemoryInGBs *float32 `mapstructure:"memory_in_gbs" required:"false"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
@@ -91,6 +96,7 @@ type Config struct {
|
||||
InstanceTags map[string]string `mapstructure:"instance_tags"`
|
||||
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
|
||||
Shape string `mapstructure:"shape"`
|
||||
ShapeConfig FlexShapeConfig `mapstructure:"shape_config"`
|
||||
BootVolumeSizeInGBs int64 `mapstructure:"disk_size"`
|
||||
|
||||
// Metadata optionally contains custom metadata key/value pairs provided in the
|
||||
@@ -222,7 +228,7 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
providers := []ocicommon.ConfigurationProvider{
|
||||
NewRawConfigurationProvider(c.TenancyID, c.UserID, c.Region, c.Fingerprint, string(keyContent), &c.PassPhrase),
|
||||
ocicommon.NewRawConfigurationProvider(c.TenancyID, c.UserID, c.Region, c.Fingerprint, string(keyContent), &c.PassPhrase),
|
||||
}
|
||||
|
||||
if fileProvider != nil {
|
||||
@@ -277,6 +283,18 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||
errs, errors.New("'shape' must be specified"))
|
||||
}
|
||||
|
||||
if strings.HasSuffix(c.Shape, "Flex") {
|
||||
if c.ShapeConfig.Ocpus == nil {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("'Ocpus' must be specified when using flexible shapes"))
|
||||
}
|
||||
}
|
||||
|
||||
if c.ShapeConfig.MemoryInGBs != nil && c.ShapeConfig.Ocpus == nil {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("'Ocpus' must be specified if memory_in_gbs is specified"))
|
||||
}
|
||||
|
||||
if (c.SubnetID == "") && (c.CreateVnicDetails.SubnetId == nil) {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("'subnet_ocid' must be specified"))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest,FlexShapeConfig"; DO NOT EDIT.
|
||||
|
||||
package oci
|
||||
|
||||
@@ -88,6 +88,7 @@ type FlatConfig struct {
|
||||
InstanceTags map[string]string `mapstructure:"instance_tags" cty:"instance_tags" hcl:"instance_tags"`
|
||||
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags" cty:"instance_defined_tags" hcl:"instance_defined_tags"`
|
||||
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
|
||||
ShapeConfig *FlatFlexShapeConfig `mapstructure:"shape_config" cty:"shape_config" hcl:"shape_config"`
|
||||
BootVolumeSizeInGBs *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
|
||||
Metadata map[string]string `mapstructure:"metadata" cty:"metadata" hcl:"metadata"`
|
||||
UserData *string `mapstructure:"user_data" cty:"user_data" hcl:"user_data"`
|
||||
@@ -188,6 +189,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"instance_tags": &hcldec.AttrSpec{Name: "instance_tags", Type: cty.Map(cty.String), Required: false},
|
||||
"instance_defined_tags": &hcldec.AttrSpec{Name: "instance_defined_tags", Type: cty.Map(cty.String), Required: false},
|
||||
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
|
||||
"shape_config": &hcldec.BlockSpec{TypeName: "shape_config", Nested: hcldec.ObjectSpec((*FlatFlexShapeConfig)(nil).HCL2Spec())},
|
||||
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
|
||||
"metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(cty.String), Required: false},
|
||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||
@@ -239,6 +241,31 @@ func (*FlatCreateVNICDetails) HCL2Spec() map[string]hcldec.Spec {
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatFlexShapeConfig is an auto-generated flat version of FlexShapeConfig.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatFlexShapeConfig struct {
|
||||
Ocpus *float32 `mapstructure:"ocpus" required:"false" cty:"ocpus" hcl:"ocpus"`
|
||||
MemoryInGBs *float32 `mapstructure:"memory_in_gbs" required:"false" cty:"memory_in_gbs" hcl:"memory_in_gbs"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatFlexShapeConfig.
|
||||
// FlatFlexShapeConfig is an auto-generated flat version of FlexShapeConfig.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*FlexShapeConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatFlexShapeConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a FlexShapeConfig.
|
||||
// This spec is used by HCL to read the fields of FlexShapeConfig.
|
||||
// The decoded values from this spec will then be applied to a FlatFlexShapeConfig.
|
||||
func (*FlatFlexShapeConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"ocpus": &hcldec.AttrSpec{Name: "ocpus", Type: cty.Number, Required: false},
|
||||
"memory_in_gbs": &hcldec.AttrSpec{Name: "memory_in_gbs", Type: cty.Number, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatListImagesRequest is an auto-generated flat version of ListImagesRequest.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatListImagesRequest struct {
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
package oci
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/oracle/oci-go-sdk/common"
|
||||
)
|
||||
|
||||
// rawConfigurationProvider allows a user to simply construct a configuration
|
||||
// provider from raw values. It errors on access when those values are empty.
|
||||
type rawConfigurationProvider struct {
|
||||
tenancy string
|
||||
user string
|
||||
region string
|
||||
fingerprint string
|
||||
privateKey string
|
||||
privateKeyPassphrase *string
|
||||
}
|
||||
|
||||
// NewRawConfigurationProvider will create a rawConfigurationProvider.
|
||||
func NewRawConfigurationProvider(tenancy, user, region, fingerprint, privateKey string, privateKeyPassphrase *string) common.ConfigurationProvider {
|
||||
return rawConfigurationProvider{tenancy, user, region, fingerprint, privateKey, privateKeyPassphrase}
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
|
||||
return common.PrivateKeyFromBytes([]byte(p.privateKey), p.privateKeyPassphrase)
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) KeyID() (keyID string, err error) {
|
||||
tenancy, err := p.TenancyOCID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := p.UserOCID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fingerprint, err := p.KeyFingerprint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) TenancyOCID() (string, error) {
|
||||
if p.tenancy == "" {
|
||||
return "", errors.New("no tenancy provided")
|
||||
}
|
||||
return p.tenancy, nil
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) UserOCID() (string, error) {
|
||||
if p.user == "" {
|
||||
return "", errors.New("no user provided")
|
||||
}
|
||||
return p.user, nil
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) KeyFingerprint() (string, error) {
|
||||
if p.fingerprint == "" {
|
||||
return "", errors.New("no fingerprint provided")
|
||||
}
|
||||
return p.fingerprint, nil
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) Region() (string, error) {
|
||||
if p.region == "" {
|
||||
return "", errors.New("no region provided")
|
||||
}
|
||||
return p.region, nil
|
||||
}
|
||||
@@ -131,7 +131,7 @@ func TestConfig(t *testing.T) {
|
||||
|
||||
t.Run("NoAccessConfig", func(t *testing.T) {
|
||||
raw := testConfig(cfgFile)
|
||||
delete(raw, "access_cfg_file")
|
||||
raw["access_cfg_file"] = "/tmp/random/access/config/file/should/not/exist"
|
||||
|
||||
var c Config
|
||||
errs := c.Prepare(raw)
|
||||
@@ -140,6 +140,10 @@ func TestConfig(t *testing.T) {
|
||||
"'user_ocid'", "'tenancy_ocid'", "'fingerprint'", "'key_file'",
|
||||
}
|
||||
|
||||
if errs == nil {
|
||||
t.Fatalf("Expected errors %q but got none", expectedErrors)
|
||||
}
|
||||
|
||||
s := errs.Error()
|
||||
for _, expected := range expectedErrors {
|
||||
if !strings.Contains(s, expected) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package oci
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/oracle/oci-go-sdk/core"
|
||||
"github.com/oracle/oci-go-sdk/v36/core"
|
||||
)
|
||||
|
||||
// Driver interfaces between the builder steps and the OCI SDK.
|
||||
|
||||
@@ -3,7 +3,7 @@ package oci
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/oracle/oci-go-sdk/core"
|
||||
"github.com/oracle/oci-go-sdk/v36/core"
|
||||
)
|
||||
|
||||
// driverMock implements the Driver interface and communicates with Oracle
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/oracle/oci-go-sdk/common"
|
||||
core "github.com/oracle/oci-go-sdk/core"
|
||||
"github.com/oracle/oci-go-sdk/v36/common"
|
||||
core "github.com/oracle/oci-go-sdk/v36/core"
|
||||
)
|
||||
|
||||
// driverOCI implements the Driver interface and communicates with Oracle
|
||||
@@ -158,6 +158,14 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
if d.cfg.ShapeConfig.Ocpus != nil {
|
||||
LaunchInstanceShapeConfigDetails := core.LaunchInstanceShapeConfigDetails{
|
||||
Ocpus: d.cfg.ShapeConfig.Ocpus,
|
||||
MemoryInGBs: d.cfg.ShapeConfig.MemoryInGBs,
|
||||
}
|
||||
instanceDetails.ShapeConfig = &LaunchInstanceShapeConfigDetails
|
||||
}
|
||||
|
||||
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{
|
||||
LaunchInstanceDetails: instanceDetails,
|
||||
RequestMetadata: requestMetadata,
|
||||
|
||||
@@ -3,6 +3,8 @@ package oci
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
|
||||
"github.com/oracle/oci-go-sdk/v36/common"
|
||||
)
|
||||
|
||||
// Mock struct to be used during testing to obtain Instance Principals.
|
||||
@@ -32,3 +34,10 @@ func (p instancePrincipalConfigurationProviderMock) KeyFingerprint() (string, er
|
||||
func (p instancePrincipalConfigurationProviderMock) Region() (string, error) {
|
||||
return "some_random_region", nil
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProviderMock) AuthType() (common.AuthConfig, error) {
|
||||
return common.AuthConfig{
|
||||
AuthType: common.InstancePrincipal,
|
||||
IsFromConfigFile: false,
|
||||
OboToken: nil}, nil
|
||||
}
|
||||
|
||||
@@ -158,6 +158,9 @@ func getVMIP(state multistep.StateBag) (string, error) {
|
||||
if addr.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
if addr.To4() == nil {
|
||||
continue
|
||||
}
|
||||
return addr.String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ type StepCloneVM struct {
|
||||
|
||||
func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
d := state.Get("driver").(*driver.VCenterDriver)
|
||||
d := state.Get("driver").(driver.Driver)
|
||||
vmPath := path.Join(s.Location.Folder, s.Location.VMName)
|
||||
|
||||
err := d.PreCleanVM(ui, vmPath, s.Force)
|
||||
@@ -102,17 +102,18 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist
|
||||
}
|
||||
|
||||
vm, err := template.Clone(ctx, &driver.CloneConfig{
|
||||
Name: s.Location.VMName,
|
||||
Folder: s.Location.Folder,
|
||||
Cluster: s.Location.Cluster,
|
||||
Host: s.Location.Host,
|
||||
ResourcePool: s.Location.ResourcePool,
|
||||
Datastore: s.Location.Datastore,
|
||||
LinkedClone: s.Config.LinkedClone,
|
||||
Network: s.Config.Network,
|
||||
MacAddress: s.Config.MacAddress,
|
||||
Annotation: s.Config.Notes,
|
||||
VAppProperties: s.Config.VAppConfig.Properties,
|
||||
Name: s.Location.VMName,
|
||||
Folder: s.Location.Folder,
|
||||
Cluster: s.Location.Cluster,
|
||||
Host: s.Location.Host,
|
||||
ResourcePool: s.Location.ResourcePool,
|
||||
Datastore: s.Location.Datastore,
|
||||
LinkedClone: s.Config.LinkedClone,
|
||||
Network: s.Config.Network,
|
||||
MacAddress: s.Config.MacAddress,
|
||||
Annotation: s.Config.Notes,
|
||||
VAppProperties: s.Config.VAppConfig.Properties,
|
||||
PrimaryDiskSize: s.Config.DiskSize,
|
||||
StorageConfig: driver.StorageConfig{
|
||||
DiskControllerType: s.Config.StorageConfig.DiskControllerType,
|
||||
Storage: disks,
|
||||
@@ -127,14 +128,6 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist
|
||||
}
|
||||
state.Put("vm", vm)
|
||||
|
||||
if s.Config.DiskSize > 0 {
|
||||
err = vm.ResizeDisk(s.Config.DiskSize)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
package clone
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/vsphere/common"
|
||||
"github.com/hashicorp/packer/builder/vsphere/driver"
|
||||
)
|
||||
|
||||
func TestCreateConfig_Prepare(t *testing.T) {
|
||||
tc := []struct {
|
||||
name string
|
||||
config *CloneConfig
|
||||
fail bool
|
||||
expectedErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "Valid config",
|
||||
config: &CloneConfig{
|
||||
Template: "template name",
|
||||
StorageConfig: common.StorageConfig{
|
||||
DiskControllerType: []string{"test"},
|
||||
Storage: []common.DiskConfig{
|
||||
{
|
||||
DiskSize: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fail: true,
|
||||
expectedErrMsg: "storage[0].'disk_size' is required",
|
||||
},
|
||||
{
|
||||
name: "Storage validate disk_size",
|
||||
config: &CloneConfig{
|
||||
StorageConfig: common.StorageConfig{
|
||||
Storage: []common.DiskConfig{
|
||||
{
|
||||
DiskSize: 0,
|
||||
DiskThinProvisioned: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fail: true,
|
||||
expectedErrMsg: "storage[0].'disk_size' is required",
|
||||
},
|
||||
{
|
||||
name: "Storage validate disk_controller_index",
|
||||
config: &CloneConfig{
|
||||
StorageConfig: common.StorageConfig{
|
||||
Storage: []common.DiskConfig{
|
||||
{
|
||||
DiskSize: 32768,
|
||||
DiskControllerIndex: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fail: true,
|
||||
expectedErrMsg: "storage[0].'disk_controller_index' references an unknown disk controller",
|
||||
},
|
||||
{
|
||||
name: "Validate template is set",
|
||||
config: &CloneConfig{
|
||||
StorageConfig: common.StorageConfig{
|
||||
DiskControllerType: []string{"test"},
|
||||
Storage: []common.DiskConfig{
|
||||
{
|
||||
DiskSize: 32768,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fail: true,
|
||||
expectedErrMsg: "'template' is required",
|
||||
},
|
||||
{
|
||||
name: "Validate LinkedClone and DiskSize set at the same time",
|
||||
config: &CloneConfig{
|
||||
Template: "template name",
|
||||
LinkedClone: true,
|
||||
DiskSize: 32768,
|
||||
StorageConfig: common.StorageConfig{
|
||||
DiskControllerType: []string{"test"},
|
||||
Storage: []common.DiskConfig{
|
||||
{
|
||||
DiskSize: 32768,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fail: true,
|
||||
expectedErrMsg: "'linked_clone' and 'disk_size' cannot be used together",
|
||||
},
|
||||
{
|
||||
name: "Validate MacAddress and Network not set at the same time",
|
||||
config: &CloneConfig{
|
||||
Template: "template name",
|
||||
MacAddress: "some mac address",
|
||||
StorageConfig: common.StorageConfig{
|
||||
DiskControllerType: []string{"test"},
|
||||
Storage: []common.DiskConfig{
|
||||
{
|
||||
DiskSize: 32768,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fail: true,
|
||||
expectedErrMsg: "'network' is required when 'mac_address' is specified",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range tc {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
errs := c.config.Prepare()
|
||||
if c.fail {
|
||||
if len(errs) == 0 {
|
||||
t.Fatalf("Config preprare should fail")
|
||||
}
|
||||
if errs[0].Error() != c.expectedErrMsg {
|
||||
t.Fatalf("Expected error message: %s but was '%s'", c.expectedErrMsg, errs[0].Error())
|
||||
}
|
||||
} else {
|
||||
if len(errs) != 0 {
|
||||
t.Fatalf("Config preprare should not fail: %s", errs[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateVM_Run(t *testing.T) {
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("ui", &packersdk.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
})
|
||||
driverMock := driver.NewDriverMock()
|
||||
state.Put("driver", driverMock)
|
||||
step := basicStepCloneVM()
|
||||
step.Force = true
|
||||
vmPath := path.Join(step.Location.Folder, step.Location.VMName)
|
||||
vmMock := new(driver.VirtualMachineMock)
|
||||
driverMock.VM = vmMock
|
||||
|
||||
if action := step.Run(context.TODO(), state); action == multistep.ActionHalt {
|
||||
t.Fatalf("Should not halt.")
|
||||
}
|
||||
|
||||
// Pre clean VM
|
||||
if !driverMock.PreCleanVMCalled {
|
||||
t.Fatalf("driver.PreCleanVM should be called.")
|
||||
}
|
||||
if driverMock.PreCleanForce != step.Force {
|
||||
t.Fatalf("Force PreCleanVM should be %t but was %t.", step.Force, driverMock.PreCleanForce)
|
||||
}
|
||||
if driverMock.PreCleanVMPath != vmPath {
|
||||
t.Fatalf("VM path expected to be %s but was %s", vmPath, driverMock.PreCleanVMPath)
|
||||
}
|
||||
|
||||
if !driverMock.FindVMCalled {
|
||||
t.Fatalf("driver.FindVM should be called.")
|
||||
}
|
||||
if !vmMock.CloneCalled {
|
||||
t.Fatalf("vm.Clone should be called.")
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(vmMock.CloneConfig, driverCreateConfig(step.Config, step.Location)); diff != "" {
|
||||
t.Fatalf("wrong driver.CreateConfig: %s", diff)
|
||||
}
|
||||
vm, ok := state.GetOk("vm")
|
||||
if !ok {
|
||||
t.Fatal("state must contain the VM")
|
||||
}
|
||||
if vm != driverMock.VM {
|
||||
t.Fatalf("state doesn't contain the created VM.")
|
||||
}
|
||||
}
|
||||
|
||||
func basicStepCloneVM() *StepCloneVM {
|
||||
step := &StepCloneVM{
|
||||
Config: createConfig(),
|
||||
Location: basicLocationConfig(),
|
||||
}
|
||||
return step
|
||||
}
|
||||
|
||||
func basicLocationConfig() *common.LocationConfig {
|
||||
return &common.LocationConfig{
|
||||
VMName: "test-vm",
|
||||
Folder: "test-folder",
|
||||
Cluster: "test-cluster",
|
||||
Host: "test-host",
|
||||
ResourcePool: "test-resource-pool",
|
||||
Datastore: "test-datastore",
|
||||
}
|
||||
}
|
||||
|
||||
func createConfig() *CloneConfig {
|
||||
return &CloneConfig{
|
||||
Template: "template name",
|
||||
StorageConfig: common.StorageConfig{
|
||||
DiskControllerType: []string{"pvscsi"},
|
||||
Storage: []common.DiskConfig{
|
||||
{
|
||||
DiskSize: 32768,
|
||||
DiskThinProvisioned: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func driverCreateConfig(config *CloneConfig, location *common.LocationConfig) *driver.CloneConfig {
|
||||
var disks []driver.Disk
|
||||
for _, disk := range config.StorageConfig.Storage {
|
||||
disks = append(disks, driver.Disk{
|
||||
DiskSize: disk.DiskSize,
|
||||
DiskEagerlyScrub: disk.DiskEagerlyScrub,
|
||||
DiskThinProvisioned: disk.DiskThinProvisioned,
|
||||
ControllerIndex: disk.DiskControllerIndex,
|
||||
})
|
||||
}
|
||||
|
||||
return &driver.CloneConfig{
|
||||
StorageConfig: driver.StorageConfig{
|
||||
DiskControllerType: config.StorageConfig.DiskControllerType,
|
||||
Storage: disks,
|
||||
},
|
||||
Annotation: config.Notes,
|
||||
Name: location.VMName,
|
||||
Folder: location.Folder,
|
||||
Cluster: location.Cluster,
|
||||
Host: location.Host,
|
||||
ResourcePool: location.ResourcePool,
|
||||
Datastore: location.Datastore,
|
||||
LinkedClone: config.LinkedClone,
|
||||
Network: config.Network,
|
||||
MacAddress: config.MacAddress,
|
||||
VAppProperties: config.VAppConfig.Properties,
|
||||
PrimaryDiskSize: config.DiskSize,
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ func (c *StorageConfig) AddStorageDevices(existingDevices object.VirtualDeviceLi
|
||||
}
|
||||
|
||||
existingDevices.AssignController(disk, controllers[dc.ControllerIndex])
|
||||
existingDevices = append(existingDevices, disk)
|
||||
newDevices = append(newDevices, disk)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
)
|
||||
|
||||
func TestAddStorageDevices(t *testing.T) {
|
||||
config := &StorageConfig{
|
||||
DiskControllerType: []string{"pvscsi"},
|
||||
Storage: []Disk{
|
||||
{
|
||||
DiskSize: 3072,
|
||||
DiskThinProvisioned: true,
|
||||
ControllerIndex: 0,
|
||||
},
|
||||
{
|
||||
DiskSize: 20480,
|
||||
DiskThinProvisioned: true,
|
||||
ControllerIndex: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
noExistingDevices := object.VirtualDeviceList{}
|
||||
storageConfigSpec, err := config.AddStorageDevices(noExistingDevices)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected erro: %q", err.Error())
|
||||
}
|
||||
if len(storageConfigSpec) != 3 {
|
||||
t.Fatalf("Expecting VirtualDeviceList to have 3 storage devices but had %d", len(storageConfigSpec))
|
||||
}
|
||||
|
||||
existingDevices := object.VirtualDeviceList{}
|
||||
device, err := existingDevices.CreateNVMEController()
|
||||
existingDevices = append(existingDevices, device)
|
||||
|
||||
storageConfigSpec, err = config.AddStorageDevices(existingDevices)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected erro: %q", err.Error())
|
||||
}
|
||||
if len(storageConfigSpec) != 3 {
|
||||
t.Fatalf("Expecting VirtualDeviceList to have 3 storage devices but had %d", len(storageConfigSpec))
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,9 @@ type DriverMock struct {
|
||||
CreateVMCalled bool
|
||||
CreateConfig *CreateConfig
|
||||
VM VirtualMachine
|
||||
|
||||
FindVMCalled bool
|
||||
FindVMName string
|
||||
}
|
||||
|
||||
func NewDriverMock() *DriverMock {
|
||||
@@ -45,7 +48,12 @@ func (d *DriverMock) NewVM(ref *types.ManagedObjectReference) VirtualMachine {
|
||||
}
|
||||
|
||||
func (d *DriverMock) FindVM(name string) (VirtualMachine, error) {
|
||||
return nil, nil
|
||||
d.FindVMCalled = true
|
||||
if d.VM == nil {
|
||||
d.VM = new(VirtualMachineMock)
|
||||
}
|
||||
d.FindVMName = name
|
||||
return d.VM, d.FindDatastoreErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) FindCluster(name string) (*Cluster, error) {
|
||||
|
||||
@@ -32,7 +32,7 @@ type VirtualMachine interface {
|
||||
Destroy() error
|
||||
Configure(config *HardwareConfig) error
|
||||
Customize(spec types.CustomizationSpec) error
|
||||
ResizeDisk(diskSize int64) error
|
||||
ResizeDisk(diskSize int64) ([]types.BaseVirtualDeviceConfigSpec, error)
|
||||
WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error)
|
||||
PowerOn() error
|
||||
PowerOff() error
|
||||
@@ -68,18 +68,19 @@ type VirtualMachineDriver struct {
|
||||
}
|
||||
|
||||
type CloneConfig struct {
|
||||
Name string
|
||||
Folder string
|
||||
Cluster string
|
||||
Host string
|
||||
ResourcePool string
|
||||
Datastore string
|
||||
LinkedClone bool
|
||||
Network string
|
||||
MacAddress string
|
||||
Annotation string
|
||||
VAppProperties map[string]string
|
||||
StorageConfig StorageConfig
|
||||
Name string
|
||||
Folder string
|
||||
Cluster string
|
||||
Host string
|
||||
ResourcePool string
|
||||
Datastore string
|
||||
LinkedClone bool
|
||||
Network string
|
||||
MacAddress string
|
||||
Annotation string
|
||||
VAppProperties map[string]string
|
||||
PrimaryDiskSize int64
|
||||
StorageConfig StorageConfig
|
||||
}
|
||||
|
||||
type HardwareConfig struct {
|
||||
@@ -339,6 +340,15 @@ func (vm *VirtualMachineDriver) Clone(ctx context.Context, config *CloneConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.PrimaryDiskSize > 0 {
|
||||
deviceResizeSpec, err := vm.ResizeDisk(config.PrimaryDiskSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resize primary disk: %s", err.Error())
|
||||
}
|
||||
configSpec.DeviceChange = append(configSpec.DeviceChange, deviceResizeSpec...)
|
||||
}
|
||||
|
||||
virtualDisks := devices.SelectByType((*types.VirtualDisk)(nil))
|
||||
virtualControllers := devices.SelectByType((*types.VirtualController)(nil))
|
||||
|
||||
@@ -349,7 +359,7 @@ func (vm *VirtualMachineDriver) Clone(ctx context.Context, config *CloneConfig)
|
||||
|
||||
storageConfigSpec, err := config.StorageConfig.AddStorageDevices(existingDevices)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to add storage devices: %s", err.Error())
|
||||
}
|
||||
configSpec.DeviceChange = append(configSpec.DeviceChange, storageConfigSpec...)
|
||||
|
||||
@@ -597,35 +607,25 @@ func (vm *VirtualMachineDriver) Customize(spec types.CustomizationSpec) error {
|
||||
return task.Wait(vm.driver.ctx)
|
||||
}
|
||||
|
||||
func (vm *VirtualMachineDriver) ResizeDisk(diskSize int64) error {
|
||||
var confSpec types.VirtualMachineConfigSpec
|
||||
|
||||
func (vm *VirtualMachineDriver) ResizeDisk(diskSize int64) ([]types.BaseVirtualDeviceConfigSpec, error) {
|
||||
devices, err := vm.vm.Device(vm.driver.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
disk, err := findDisk(devices)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
disk.CapacityInKB = diskSize * 1024
|
||||
|
||||
confSpec.DeviceChange = []types.BaseVirtualDeviceConfigSpec{
|
||||
return []types.BaseVirtualDeviceConfigSpec{
|
||||
&types.VirtualDeviceConfigSpec{
|
||||
Device: disk,
|
||||
Operation: types.VirtualDeviceConfigSpecOperationEdit,
|
||||
},
|
||||
}
|
||||
|
||||
task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = task.WaitForResult(vm.driver.ctx, nil)
|
||||
return err
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (vm *VirtualMachineDriver) PowerOn() error {
|
||||
|
||||
@@ -55,6 +55,9 @@ type VirtualMachineMock struct {
|
||||
|
||||
RemoveCdromsCalled bool
|
||||
RemoveCdromsErr error
|
||||
CloneCalled bool
|
||||
CloneConfig *CloneConfig
|
||||
CloneError error
|
||||
}
|
||||
|
||||
func (vm *VirtualMachineMock) Info(params ...string) (*mo.VirtualMachine, error) {
|
||||
@@ -71,7 +74,9 @@ func (vm *VirtualMachineMock) FloppyDevices() (object.VirtualDeviceList, error)
|
||||
}
|
||||
|
||||
func (vm *VirtualMachineMock) Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error) {
|
||||
return nil, nil
|
||||
vm.CloneCalled = true
|
||||
vm.CloneConfig = config
|
||||
return vm, vm.CloneError
|
||||
}
|
||||
|
||||
func (vm *VirtualMachineMock) updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error) {
|
||||
@@ -107,8 +112,8 @@ func (vm *VirtualMachineMock) Customize(spec types.CustomizationSpec) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VirtualMachineMock) ResizeDisk(diskSize int64) error {
|
||||
return nil
|
||||
func (vm *VirtualMachineMock) ResizeDisk(diskSize int64) ([]types.BaseVirtualDeviceConfigSpec, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (vm *VirtualMachineMock) PowerOn() error {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/govmomi/simulator"
|
||||
@@ -60,3 +61,127 @@ func TestVirtualMachineDriver_Configure(t *testing.T) {
|
||||
t.Fatalf("Configure should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVirtualMachineDriver_CreateVMWithMultipleDisks(t *testing.T) {
|
||||
sim, err := NewVCenterSimulator()
|
||||
if err != nil {
|
||||
t.Fatalf("should not fail: %s", err.Error())
|
||||
}
|
||||
defer sim.Close()
|
||||
|
||||
_, datastore := sim.ChooseSimulatorPreCreatedDatastore()
|
||||
|
||||
config := &CreateConfig{
|
||||
Name: "mock name",
|
||||
Host: "DC0_H0",
|
||||
Datastore: datastore.Name,
|
||||
NICs: []NIC{
|
||||
{
|
||||
Network: "VM Network",
|
||||
NetworkCard: "vmxnet3",
|
||||
},
|
||||
},
|
||||
StorageConfig: StorageConfig{
|
||||
DiskControllerType: []string{"pvscsi"},
|
||||
Storage: []Disk{
|
||||
{
|
||||
DiskSize: 3072,
|
||||
DiskThinProvisioned: true,
|
||||
ControllerIndex: 0,
|
||||
},
|
||||
{
|
||||
DiskSize: 20480,
|
||||
DiskThinProvisioned: true,
|
||||
ControllerIndex: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
vm, err := sim.driver.CreateVM(config)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err.Error())
|
||||
}
|
||||
|
||||
devices, err := vm.Devices()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err.Error())
|
||||
}
|
||||
|
||||
var disks []*types.VirtualDisk
|
||||
for _, device := range devices {
|
||||
switch d := device.(type) {
|
||||
case *types.VirtualDisk:
|
||||
disks = append(disks, d)
|
||||
}
|
||||
}
|
||||
|
||||
if len(disks) != 2 {
|
||||
t.Fatalf("unexpected number of devices")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVirtualMachineDriver_CloneWithPrimaryDiskResize(t *testing.T) {
|
||||
sim, err := NewVCenterSimulator()
|
||||
if err != nil {
|
||||
t.Fatalf("should not fail: %s", err.Error())
|
||||
}
|
||||
defer sim.Close()
|
||||
|
||||
_, datastore := sim.ChooseSimulatorPreCreatedDatastore()
|
||||
vm, _ := sim.ChooseSimulatorPreCreatedVM()
|
||||
|
||||
config := &CloneConfig{
|
||||
Name: "mock name",
|
||||
Host: "DC0_H0",
|
||||
Datastore: datastore.Name,
|
||||
PrimaryDiskSize: 204800,
|
||||
StorageConfig: StorageConfig{
|
||||
DiskControllerType: []string{"pvscsi"},
|
||||
Storage: []Disk{
|
||||
{
|
||||
DiskSize: 3072,
|
||||
DiskThinProvisioned: true,
|
||||
ControllerIndex: 0,
|
||||
},
|
||||
{
|
||||
DiskSize: 20480,
|
||||
DiskThinProvisioned: true,
|
||||
ControllerIndex: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
clonedVM, err := vm.Clone(context.TODO(), config)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err.Error())
|
||||
}
|
||||
|
||||
devices, err := clonedVM.Devices()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err.Error())
|
||||
}
|
||||
|
||||
var disks []*types.VirtualDisk
|
||||
for _, device := range devices {
|
||||
switch d := device.(type) {
|
||||
case *types.VirtualDisk:
|
||||
disks = append(disks, d)
|
||||
}
|
||||
}
|
||||
|
||||
if len(disks) != 3 {
|
||||
t.Fatalf("unexpected number of devices")
|
||||
}
|
||||
|
||||
if disks[0].CapacityInKB != config.PrimaryDiskSize*1024 {
|
||||
t.Fatalf("unexpected disk size for primary disk: %d", disks[0].CapacityInKB)
|
||||
}
|
||||
if disks[1].CapacityInKB != config.StorageConfig.Storage[0].DiskSize*1024 {
|
||||
t.Fatalf("unexpected disk size for primary disk: %d", disks[1].CapacityInKB)
|
||||
}
|
||||
if disks[2].CapacityInKB != config.StorageConfig.Storage[1].DiskSize*1024 {
|
||||
t.Fatalf("unexpected disk size for primary disk: %d", disks[2].CapacityInKB)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/plugin"
|
||||
"github.com/hashicorp/packer/builder/amazon/ebs"
|
||||
"github.com/hashicorp/packer/builder/amazon/ebssurrogate"
|
||||
"github.com/hashicorp/packer/builder/amazon/ebsvolume"
|
||||
"github.com/hashicorp/packer/builder/osc/chroot"
|
||||
amazonami "github.com/hashicorp/packer/datasource/amazon/ami"
|
||||
"github.com/hashicorp/packer/datasource/amazon/secretsmanager"
|
||||
amazonimport "github.com/hashicorp/packer/post-processor/amazon-import"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pps := plugin.NewSet()
|
||||
pps.RegisterBuilder("ebs", new(ebs.Builder))
|
||||
pps.RegisterBuilder("chroot", new(chroot.Builder))
|
||||
pps.RegisterBuilder("ebssurrogate", new(ebssurrogate.Builder))
|
||||
pps.RegisterBuilder("ebsvolume", new(ebsvolume.Builder))
|
||||
pps.RegisterPostProcessor("import", new(amazonimport.PostProcessor))
|
||||
pps.RegisterDatasource("ami", new(amazonami.Datasource))
|
||||
pps.RegisterDatasource("secretsmanager", new(secretsmanager.Datasource))
|
||||
err := pps.Run()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -383,6 +383,19 @@ func TestBuild(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hcl - dynamic source blocks in a build block",
|
||||
args: []string{
|
||||
testFixture("hcl", "dynamic", "build.pkr.hcl"),
|
||||
},
|
||||
fileCheck: fileCheck{
|
||||
expectedContent: map[string]string{
|
||||
"dummy.txt": "layers/base/main/files",
|
||||
"postgres/13.txt": "layers/base/main/files\nlayers/base/init/files\nlayers/postgres/files",
|
||||
},
|
||||
expected: []string{"dummy-fooo.txt", "dummy-baar.txt", "postgres/13-fooo.txt", "postgres/13-baar.txt"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tc {
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-amazon/builder/ebs"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer/builder/amazon/ebs"
|
||||
"github.com/hashicorp/packer/builder/file"
|
||||
"github.com/hashicorp/packer/builder/null"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/hcl/v2/hclwrite"
|
||||
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
|
||||
hcl2shim "github.com/hashicorp/packer-plugin-sdk/hcl2helper"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/posener/complete"
|
||||
|
||||
+44
-2
@@ -121,12 +121,54 @@ func (c *InitCommand) RunContext(buildCtx context.Context, cla *InitArgs) int {
|
||||
Getters: getters,
|
||||
})
|
||||
if err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
ret = 1
|
||||
if pluginRequirement.Implicit {
|
||||
msg := fmt.Sprintf(`
|
||||
Warning! At least one component used in your config file(s) has moved out of
|
||||
Packer into the %q plugin.
|
||||
For that reason, Packer init tried to install the latest version of the %s
|
||||
plugin. Unfortunately, this failed :
|
||||
%s`,
|
||||
pluginRequirement.Identifier,
|
||||
pluginRequirement.Identifier.Type,
|
||||
err)
|
||||
c.Ui.Say(msg)
|
||||
} else {
|
||||
c.Ui.Error(err.Error())
|
||||
ret = 1
|
||||
}
|
||||
}
|
||||
if newInstall != nil {
|
||||
if pluginRequirement.Implicit {
|
||||
msg := fmt.Sprintf("Installed implicitly required plugin %s %s in %q", pluginRequirement.Identifier, newInstall.Version, newInstall.BinaryPath)
|
||||
ui.Say(msg)
|
||||
|
||||
warn := fmt.Sprintf(`
|
||||
Warning, at least one component used in your config file(s) has moved out of
|
||||
Packer into the %[2]q plugin and is now being implicitly required.
|
||||
For more details on implicitly required plugins see https://packer.io/docs/commands/init#implicit-required-plugin
|
||||
|
||||
To avoid any backward incompatible changes with your
|
||||
config file you may want to lock the plugin version by pasting the following to your config:
|
||||
|
||||
packer {
|
||||
required_plugins {
|
||||
%[1]s = {
|
||||
source = "%[2]s"
|
||||
version = "~> %[3]s"
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
pluginRequirement.Identifier.Type,
|
||||
pluginRequirement.Identifier,
|
||||
newInstall.Version,
|
||||
)
|
||||
ui.Error(warn)
|
||||
continue
|
||||
}
|
||||
msg := fmt.Sprintf("Installed plugin %s %s in %q", pluginRequirement.Identifier, newInstall.Version, newInstall.BinaryPath)
|
||||
ui.Say(msg)
|
||||
|
||||
}
|
||||
}
|
||||
return ret
|
||||
|
||||
+47
-64
@@ -14,11 +14,6 @@ import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/plugin"
|
||||
|
||||
alicloudecsbuilder "github.com/hashicorp/packer/builder/alicloud/ecs"
|
||||
amazonchrootbuilder "github.com/hashicorp/packer/builder/amazon/chroot"
|
||||
amazonebsbuilder "github.com/hashicorp/packer/builder/amazon/ebs"
|
||||
amazonebssurrogatebuilder "github.com/hashicorp/packer/builder/amazon/ebssurrogate"
|
||||
amazonebsvolumebuilder "github.com/hashicorp/packer/builder/amazon/ebsvolume"
|
||||
amazoninstancebuilder "github.com/hashicorp/packer/builder/amazon/instance"
|
||||
azurearmbuilder "github.com/hashicorp/packer/builder/azure/arm"
|
||||
azurechrootbuilder "github.com/hashicorp/packer/builder/azure/chroot"
|
||||
azuredtlbuilder "github.com/hashicorp/packer/builder/azure/dtl"
|
||||
@@ -64,10 +59,7 @@ import (
|
||||
vsphereclonebuilder "github.com/hashicorp/packer/builder/vsphere/clone"
|
||||
vsphereisobuilder "github.com/hashicorp/packer/builder/vsphere/iso"
|
||||
yandexbuilder "github.com/hashicorp/packer/builder/yandex"
|
||||
amazonamidatasource "github.com/hashicorp/packer/datasource/amazon/ami"
|
||||
amazonsecretsmanagerdatasource "github.com/hashicorp/packer/datasource/amazon/secretsmanager"
|
||||
alicloudimportpostprocessor "github.com/hashicorp/packer/post-processor/alicloud-import"
|
||||
amazonimportpostprocessor "github.com/hashicorp/packer/post-processor/amazon-import"
|
||||
artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice"
|
||||
checksumpostprocessor "github.com/hashicorp/packer/post-processor/checksum"
|
||||
compresspostprocessor "github.com/hashicorp/packer/post-processor/compress"
|
||||
@@ -108,57 +100,52 @@ type PluginCommand struct {
|
||||
}
|
||||
|
||||
var Builders = map[string]packersdk.Builder{
|
||||
"alicloud-ecs": new(alicloudecsbuilder.Builder),
|
||||
"amazon-chroot": new(amazonchrootbuilder.Builder),
|
||||
"amazon-ebs": new(amazonebsbuilder.Builder),
|
||||
"amazon-ebssurrogate": new(amazonebssurrogatebuilder.Builder),
|
||||
"amazon-ebsvolume": new(amazonebsvolumebuilder.Builder),
|
||||
"amazon-instance": new(amazoninstancebuilder.Builder),
|
||||
"azure-arm": new(azurearmbuilder.Builder),
|
||||
"azure-chroot": new(azurechrootbuilder.Builder),
|
||||
"azure-dtl": new(azuredtlbuilder.Builder),
|
||||
"cloudstack": new(cloudstackbuilder.Builder),
|
||||
"digitalocean": new(digitaloceanbuilder.Builder),
|
||||
"file": new(filebuilder.Builder),
|
||||
"googlecompute": new(googlecomputebuilder.Builder),
|
||||
"hcloud": new(hcloudbuilder.Builder),
|
||||
"hyperone": new(hyperonebuilder.Builder),
|
||||
"hyperv-iso": new(hypervisobuilder.Builder),
|
||||
"hyperv-vmcx": new(hypervvmcxbuilder.Builder),
|
||||
"jdcloud": new(jdcloudbuilder.Builder),
|
||||
"linode": new(linodebuilder.Builder),
|
||||
"lxc": new(lxcbuilder.Builder),
|
||||
"lxd": new(lxdbuilder.Builder),
|
||||
"ncloud": new(ncloudbuilder.Builder),
|
||||
"null": new(nullbuilder.Builder),
|
||||
"oneandone": new(oneandonebuilder.Builder),
|
||||
"openstack": new(openstackbuilder.Builder),
|
||||
"oracle-classic": new(oracleclassicbuilder.Builder),
|
||||
"oracle-oci": new(oracleocibuilder.Builder),
|
||||
"osc-bsu": new(oscbsubuilder.Builder),
|
||||
"osc-bsusurrogate": new(oscbsusurrogatebuilder.Builder),
|
||||
"osc-bsuvolume": new(oscbsuvolumebuilder.Builder),
|
||||
"osc-chroot": new(oscchrootbuilder.Builder),
|
||||
"parallels-iso": new(parallelsisobuilder.Builder),
|
||||
"parallels-pvm": new(parallelspvmbuilder.Builder),
|
||||
"profitbricks": new(profitbricksbuilder.Builder),
|
||||
"proxmox": new(proxmoxbuilder.Builder),
|
||||
"proxmox-clone": new(proxmoxclonebuilder.Builder),
|
||||
"proxmox-iso": new(proxmoxisobuilder.Builder),
|
||||
"qemu": new(qemubuilder.Builder),
|
||||
"scaleway": new(scalewaybuilder.Builder),
|
||||
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),
|
||||
"triton": new(tritonbuilder.Builder),
|
||||
"ucloud-uhost": new(uclouduhostbuilder.Builder),
|
||||
"vagrant": new(vagrantbuilder.Builder),
|
||||
"virtualbox-iso": new(virtualboxisobuilder.Builder),
|
||||
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
|
||||
"virtualbox-vm": new(virtualboxvmbuilder.Builder),
|
||||
"vmware-iso": new(vmwareisobuilder.Builder),
|
||||
"vmware-vmx": new(vmwarevmxbuilder.Builder),
|
||||
"vsphere-clone": new(vsphereclonebuilder.Builder),
|
||||
"vsphere-iso": new(vsphereisobuilder.Builder),
|
||||
"yandex": new(yandexbuilder.Builder),
|
||||
"alicloud-ecs": new(alicloudecsbuilder.Builder),
|
||||
"azure-arm": new(azurearmbuilder.Builder),
|
||||
"azure-chroot": new(azurechrootbuilder.Builder),
|
||||
"azure-dtl": new(azuredtlbuilder.Builder),
|
||||
"cloudstack": new(cloudstackbuilder.Builder),
|
||||
"digitalocean": new(digitaloceanbuilder.Builder),
|
||||
"file": new(filebuilder.Builder),
|
||||
"googlecompute": new(googlecomputebuilder.Builder),
|
||||
"hcloud": new(hcloudbuilder.Builder),
|
||||
"hyperone": new(hyperonebuilder.Builder),
|
||||
"hyperv-iso": new(hypervisobuilder.Builder),
|
||||
"hyperv-vmcx": new(hypervvmcxbuilder.Builder),
|
||||
"jdcloud": new(jdcloudbuilder.Builder),
|
||||
"linode": new(linodebuilder.Builder),
|
||||
"lxc": new(lxcbuilder.Builder),
|
||||
"lxd": new(lxdbuilder.Builder),
|
||||
"ncloud": new(ncloudbuilder.Builder),
|
||||
"null": new(nullbuilder.Builder),
|
||||
"oneandone": new(oneandonebuilder.Builder),
|
||||
"openstack": new(openstackbuilder.Builder),
|
||||
"oracle-classic": new(oracleclassicbuilder.Builder),
|
||||
"oracle-oci": new(oracleocibuilder.Builder),
|
||||
"osc-bsu": new(oscbsubuilder.Builder),
|
||||
"osc-bsusurrogate": new(oscbsusurrogatebuilder.Builder),
|
||||
"osc-bsuvolume": new(oscbsuvolumebuilder.Builder),
|
||||
"osc-chroot": new(oscchrootbuilder.Builder),
|
||||
"parallels-iso": new(parallelsisobuilder.Builder),
|
||||
"parallels-pvm": new(parallelspvmbuilder.Builder),
|
||||
"profitbricks": new(profitbricksbuilder.Builder),
|
||||
"proxmox": new(proxmoxbuilder.Builder),
|
||||
"proxmox-clone": new(proxmoxclonebuilder.Builder),
|
||||
"proxmox-iso": new(proxmoxisobuilder.Builder),
|
||||
"qemu": new(qemubuilder.Builder),
|
||||
"scaleway": new(scalewaybuilder.Builder),
|
||||
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),
|
||||
"triton": new(tritonbuilder.Builder),
|
||||
"ucloud-uhost": new(uclouduhostbuilder.Builder),
|
||||
"vagrant": new(vagrantbuilder.Builder),
|
||||
"virtualbox-iso": new(virtualboxisobuilder.Builder),
|
||||
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
|
||||
"virtualbox-vm": new(virtualboxvmbuilder.Builder),
|
||||
"vmware-iso": new(vmwareisobuilder.Builder),
|
||||
"vmware-vmx": new(vmwarevmxbuilder.Builder),
|
||||
"vsphere-clone": new(vsphereclonebuilder.Builder),
|
||||
"vsphere-iso": new(vsphereisobuilder.Builder),
|
||||
"yandex": new(yandexbuilder.Builder),
|
||||
}
|
||||
|
||||
var Provisioners = map[string]packersdk.Provisioner{
|
||||
@@ -184,7 +171,6 @@ var Provisioners = map[string]packersdk.Provisioner{
|
||||
|
||||
var PostProcessors = map[string]packersdk.PostProcessor{
|
||||
"alicloud-import": new(alicloudimportpostprocessor.PostProcessor),
|
||||
"amazon-import": new(amazonimportpostprocessor.PostProcessor),
|
||||
"artifice": new(artificepostprocessor.PostProcessor),
|
||||
"checksum": new(checksumpostprocessor.PostProcessor),
|
||||
"compress": new(compresspostprocessor.PostProcessor),
|
||||
@@ -202,10 +188,7 @@ var PostProcessors = map[string]packersdk.PostProcessor{
|
||||
"yandex-import": new(yandeximportpostprocessor.PostProcessor),
|
||||
}
|
||||
|
||||
var Datasources = map[string]packersdk.Datasource{
|
||||
"amazon-ami": new(amazonamidatasource.Datasource),
|
||||
"amazon-secretsmanager": new(amazonsecretsmanagerdatasource.Datasource),
|
||||
}
|
||||
var Datasources = map[string]packersdk.Datasource{}
|
||||
|
||||
var pluginRegexp = regexp.MustCompile("packer-(builder|post-processor|provisioner|datasource)-(.+)")
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
source "file" "base" {
|
||||
}
|
||||
|
||||
variables {
|
||||
images = {
|
||||
dummy = {
|
||||
image = "dummy"
|
||||
layers = ["base/main"]
|
||||
}
|
||||
postgres = {
|
||||
image = "postgres/13"
|
||||
layers = ["base/main", "base/init", "postgres"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
files = {
|
||||
foo = {
|
||||
destination = "fooo"
|
||||
}
|
||||
bar = {
|
||||
destination = "baar"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
build {
|
||||
dynamic "source" {
|
||||
for_each = var.images
|
||||
labels = ["file.base"]
|
||||
content {
|
||||
name = source.key
|
||||
target = "${source.value.image}.txt"
|
||||
content = join("\n", formatlist("layers/%s/files", var.images[source.key].layers))
|
||||
}
|
||||
}
|
||||
|
||||
dynamic "provisioner" {
|
||||
for_each = local.files
|
||||
labels = ["shell-local"]
|
||||
content {
|
||||
inline = ["echo '' > ${var.images[source.name].image}-${provisioner.value.destination}.txt"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,14 @@ import (
|
||||
// still vendored with Packer for now. Importing as library instead of
|
||||
// forcing use of packer init, until packer v1.8.0
|
||||
exoscaleimportpostprocessor "github.com/exoscale/packer-plugin-exoscale/post-processor/exoscale-import"
|
||||
amazonchrootbuilder "github.com/hashicorp/packer-plugin-amazon/builder/chroot"
|
||||
amazonebsbuilder "github.com/hashicorp/packer-plugin-amazon/builder/ebs"
|
||||
amazonebssurrogatebuilder "github.com/hashicorp/packer-plugin-amazon/builder/ebssurrogate"
|
||||
amazonebsvolumebuilder "github.com/hashicorp/packer-plugin-amazon/builder/ebsvolume"
|
||||
amazoninstancebuilder "github.com/hashicorp/packer-plugin-amazon/builder/instance"
|
||||
amazonamidatasource "github.com/hashicorp/packer-plugin-amazon/datasource/ami"
|
||||
amazonsecretsmanagerdatasource "github.com/hashicorp/packer-plugin-amazon/datasource/secretsmanager"
|
||||
anazibimportpostprocessor "github.com/hashicorp/packer-plugin-amazon/post-processor/import"
|
||||
dockerbuilder "github.com/hashicorp/packer-plugin-docker/builder/docker"
|
||||
dockerimportpostprocessor "github.com/hashicorp/packer-plugin-docker/post-processor/docker-import"
|
||||
dockerpushpostprocessor "github.com/hashicorp/packer-plugin-docker/post-processor/docker-push"
|
||||
@@ -14,10 +22,22 @@ import (
|
||||
dockertagpostprocessor "github.com/hashicorp/packer-plugin-docker/post-processor/docker-tag"
|
||||
)
|
||||
|
||||
// VendoredDatasources are datasource components that were once bundled with the
|
||||
// Packer core, but are now being imported from their counterpart plugin repos
|
||||
var VendoredDatasources = map[string]packersdk.Datasource{
|
||||
"amazon-ami": new(amazonamidatasource.Datasource),
|
||||
"amazon-secretsmanager": new(amazonsecretsmanagerdatasource.Datasource),
|
||||
}
|
||||
|
||||
// VendoredBuilders are builder components that were once bundled with the
|
||||
// Packer core, but are now being imported from their counterpart plugin repos
|
||||
var VendoredBuilders = map[string]packersdk.Builder{
|
||||
"docker": new(dockerbuilder.Builder),
|
||||
"docker": new(dockerbuilder.Builder),
|
||||
"amazon-ebs": new(amazonebsbuilder.Builder),
|
||||
"amazon-chroot": new(amazonchrootbuilder.Builder),
|
||||
"amazon-ebssurrogate": new(amazonebssurrogatebuilder.Builder),
|
||||
"amazon-ebsvolume": new(amazonebsvolumebuilder.Builder),
|
||||
"amazon-instance": new(amazoninstancebuilder.Builder),
|
||||
}
|
||||
|
||||
// VendoredProvisioners are provisioner components that were once bundled with the
|
||||
@@ -32,11 +52,19 @@ var VendoredPostProcessors = map[string]packersdk.PostProcessor{
|
||||
"docker-save": new(dockersavepostprocessor.PostProcessor),
|
||||
"docker-tag": new(dockertagpostprocessor.PostProcessor),
|
||||
"exoscale-import": new(exoscaleimportpostprocessor.PostProcessor),
|
||||
"amazon-import": new(anazibimportpostprocessor.PostProcessor),
|
||||
}
|
||||
|
||||
// Upon init lets load up any plugins that were vendored manually into the default
|
||||
// set of plugins.
|
||||
func init() {
|
||||
for k, v := range VendoredDatasources {
|
||||
if _, ok := Datasources[k]; ok {
|
||||
continue
|
||||
}
|
||||
Datasources[k] = v
|
||||
}
|
||||
|
||||
for k, v := range VendoredBuilders {
|
||||
if _, ok := Builders[k]; ok {
|
||||
continue
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package ami
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
amazonacc "github.com/hashicorp/packer/builder/amazon/ebs/acceptance"
|
||||
)
|
||||
|
||||
func TestAmazonAmi(t *testing.T) {
|
||||
testCase := &acctest.DatasourceTestCase{
|
||||
Name: "amazon_ami_datasource_basic_test",
|
||||
Teardown: func() error {
|
||||
helper := amazonacc.AWSHelper{
|
||||
Region: "us-west-2",
|
||||
AMIName: "packer-amazon-ami-test",
|
||||
}
|
||||
return helper.CleanUpAmi()
|
||||
},
|
||||
Template: testDatasourceBasic,
|
||||
Type: "amazon-ami",
|
||||
Check: func(buildCommand *exec.Cmd, logfile string) error {
|
||||
if buildCommand.ProcessState != nil {
|
||||
if buildCommand.ProcessState.ExitCode() != 0 {
|
||||
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
acctest.TestDatasource(t, testCase)
|
||||
}
|
||||
|
||||
const testDatasourceBasic = `
|
||||
data "amazon-ami" "test" {
|
||||
filters = {
|
||||
virtualization-type = "hvm"
|
||||
name = "Windows_Server-2016-English-Full-Base-*"
|
||||
root-device-type = "ebs"
|
||||
}
|
||||
most_recent = true
|
||||
owners = ["801119661308"]
|
||||
}
|
||||
|
||||
source "amazon-ebs" "basic-example" {
|
||||
user_data_file = "./test-fixtures/configure-source-ssh.ps1"
|
||||
region = "us-west-2"
|
||||
source_ami = data.amazon-ami.test.id
|
||||
instance_type = "t2.small"
|
||||
ssh_agent_auth = false
|
||||
ami_name = "packer-amazon-ami-test"
|
||||
communicator = "ssh"
|
||||
ssh_timeout = "10m"
|
||||
ssh_username = "Administrator"
|
||||
}
|
||||
|
||||
build {
|
||||
sources = [
|
||||
"source.amazon-ebs.basic-example"
|
||||
]
|
||||
}
|
||||
`
|
||||
@@ -1,45 +0,0 @@
|
||||
package ami
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
)
|
||||
|
||||
func TestDatasourceConfigure_FilterBlank(t *testing.T) {
|
||||
datasource := Datasource{
|
||||
config: Config{
|
||||
AmiFilterOptions: awscommon.AmiFilterOptions{},
|
||||
},
|
||||
}
|
||||
if err := datasource.Configure(nil); err == nil {
|
||||
t.Fatalf("Should error if filters map is empty or not specified")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceAmiFilterOwnersBlank(t *testing.T) {
|
||||
datasource := Datasource{
|
||||
config: Config{
|
||||
AmiFilterOptions: awscommon.AmiFilterOptions{
|
||||
Filters: map[string]string{"foo": "bar"},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := datasource.Configure(nil); err == nil {
|
||||
t.Fatalf("Should error if Owners is not specified)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) {
|
||||
datasource := Datasource{
|
||||
config: Config{
|
||||
AmiFilterOptions: awscommon.AmiFilterOptions{
|
||||
Owners: []string{"1234567"},
|
||||
Filters: map[string]string{"foo": "bar"},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := datasource.Configure(nil); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
<powershell>
|
||||
# Version and download URL
|
||||
$openSSHVersion = "8.1.0.0p1-Beta"
|
||||
$openSSHURL = "https://github.com/PowerShell/Win32-OpenSSH/releases/download/v$openSSHVersion/OpenSSH-Win64.zip"
|
||||
|
||||
Set-ExecutionPolicy Unrestricted
|
||||
|
||||
# GitHub became TLS 1.2 only on Feb 22, 2018
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
|
||||
|
||||
# Function to unzip an archive to a given destination
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
Function Unzip
|
||||
{
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true, Position=0)]
|
||||
[string] $ZipFile,
|
||||
[Parameter(Mandatory=$true, Position=1)]
|
||||
[string] $OutPath
|
||||
)
|
||||
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory($zipFile, $outPath)
|
||||
}
|
||||
|
||||
# Set various known paths
|
||||
$openSSHZip = Join-Path $env:TEMP 'OpenSSH.zip'
|
||||
$openSSHInstallDir = Join-Path $env:ProgramFiles 'OpenSSH'
|
||||
$openSSHInstallScript = Join-Path $openSSHInstallDir 'install-sshd.ps1'
|
||||
$openSSHDownloadKeyScript = Join-Path $openSSHInstallDir 'download-key-pair.ps1'
|
||||
$openSSHDaemon = Join-Path $openSSHInstallDir 'sshd.exe'
|
||||
$openSSHDaemonConfig = [io.path]::combine($env:ProgramData, 'ssh', 'sshd_config')
|
||||
|
||||
# Download and unpack the binary distribution of OpenSSH
|
||||
Invoke-WebRequest -Uri $openSSHURL `
|
||||
-OutFile $openSSHZip `
|
||||
-ErrorAction Stop
|
||||
|
||||
Unzip -ZipFile $openSSHZip `
|
||||
-OutPath "$env:TEMP" `
|
||||
-ErrorAction Stop
|
||||
|
||||
Remove-Item $openSSHZip `
|
||||
-ErrorAction SilentlyContinue
|
||||
|
||||
# Move into Program Files
|
||||
Move-Item -Path (Join-Path $env:TEMP 'OpenSSH-Win64') `
|
||||
-Destination $openSSHInstallDir `
|
||||
-ErrorAction Stop
|
||||
|
||||
# Run the install script, terminate if it fails
|
||||
& Powershell.exe -ExecutionPolicy Bypass -File $openSSHInstallScript
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw("Failed to install OpenSSH Server")
|
||||
}
|
||||
|
||||
# Add a firewall rule to allow inbound SSH connections to sshd.exe
|
||||
New-NetFirewallRule -Name sshd `
|
||||
-DisplayName "OpenSSH Server (sshd)" `
|
||||
-Group "Remote Access" `
|
||||
-Description "Allow access via TCP port 22 to the OpenSSH Daemon" `
|
||||
-Enabled True `
|
||||
-Direction Inbound `
|
||||
-Protocol TCP `
|
||||
-LocalPort 22 `
|
||||
-Program "$openSSHDaemon" `
|
||||
-Action Allow `
|
||||
-ErrorAction Stop
|
||||
|
||||
# Ensure sshd automatically starts on boot
|
||||
Set-Service sshd -StartupType Automatic `
|
||||
-ErrorAction Stop
|
||||
|
||||
# Set the default login shell for SSH connections to Powershell
|
||||
New-Item -Path HKLM:\SOFTWARE\OpenSSH -Force
|
||||
New-ItemProperty -Path HKLM:\SOFTWARE\OpenSSH `
|
||||
-Name DefaultShell `
|
||||
-Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" `
|
||||
-ErrorAction Stop
|
||||
|
||||
$keyDownloadScript = @'
|
||||
# Download the instance key pair and authorize Administrator logins using it
|
||||
$openSSHAdminUser = 'c:\ProgramData\ssh'
|
||||
$openSSHAuthorizedKeys = Join-Path $openSSHAdminUser 'authorized_keys'
|
||||
|
||||
If (-Not (Test-Path $openSSHAdminUser)) {
|
||||
New-Item -Path $openSSHAdminUser -Type Directory
|
||||
}
|
||||
|
||||
$keyUrl = "http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key"
|
||||
$keyReq = [System.Net.WebRequest]::Create($keyUrl)
|
||||
$keyResp = $keyReq.GetResponse()
|
||||
$keyRespStream = $keyResp.GetResponseStream()
|
||||
$streamReader = New-Object System.IO.StreamReader $keyRespStream
|
||||
$keyMaterial = $streamReader.ReadToEnd()
|
||||
|
||||
$keyMaterial | Out-File -Append -FilePath $openSSHAuthorizedKeys -Encoding ASCII
|
||||
|
||||
# Ensure access control on authorized_keys meets the requirements
|
||||
$acl = Get-ACL -Path $openSSHAuthorizedKeys
|
||||
$acl.SetAccessRuleProtection($True, $True)
|
||||
Set-Acl -Path $openSSHAuthorizedKeys -AclObject $acl
|
||||
|
||||
$acl = Get-ACL -Path $openSSHAuthorizedKeys
|
||||
$ar = New-Object System.Security.AccessControl.FileSystemAccessRule( `
|
||||
"NT Authority\Authenticated Users", "ReadAndExecute", "Allow")
|
||||
$acl.RemoveAccessRule($ar)
|
||||
$ar = New-Object System.Security.AccessControl.FileSystemAccessRule( `
|
||||
"BUILTIN\Administrators", "FullControl", "Allow")
|
||||
$acl.RemoveAccessRule($ar)
|
||||
$ar = New-Object System.Security.AccessControl.FileSystemAccessRule( `
|
||||
"BUILTIN\Users", "FullControl", "Allow")
|
||||
$acl.RemoveAccessRule($ar)
|
||||
Set-Acl -Path $openSSHAuthorizedKeys -AclObject $acl
|
||||
|
||||
Disable-ScheduledTask -TaskName "Download Key Pair"
|
||||
|
||||
$sshdConfigContent = @"
|
||||
# Modified sshd_config, created by Packer provisioner
|
||||
|
||||
PasswordAuthentication yes
|
||||
PubKeyAuthentication yes
|
||||
PidFile __PROGRAMDATA__/ssh/logs/sshd.pid
|
||||
AuthorizedKeysFile __PROGRAMDATA__/ssh/authorized_keys
|
||||
AllowUsers Administrator
|
||||
|
||||
Subsystem sftp sftp-server.exe
|
||||
"@
|
||||
|
||||
Set-Content -Path C:\ProgramData\ssh\sshd_config `
|
||||
-Value $sshdConfigContent
|
||||
|
||||
'@
|
||||
$keyDownloadScript | Out-File $openSSHDownloadKeyScript
|
||||
|
||||
# Create Task - Ensure the name matches the verbatim version above
|
||||
$taskName = "Download Key Pair"
|
||||
$principal = New-ScheduledTaskPrincipal `
|
||||
-UserID "NT AUTHORITY\SYSTEM" `
|
||||
-LogonType ServiceAccount `
|
||||
-RunLevel Highest
|
||||
$action = New-ScheduledTaskAction -Execute 'Powershell.exe' `
|
||||
-Argument "-NoProfile -File ""$openSSHDownloadKeyScript"""
|
||||
$trigger = New-ScheduledTaskTrigger -AtStartup
|
||||
Register-ScheduledTask -Action $action `
|
||||
-Trigger $trigger `
|
||||
-Principal $principal `
|
||||
-TaskName $taskName `
|
||||
-Description $taskName
|
||||
Disable-ScheduledTask -TaskName $taskName
|
||||
|
||||
# Run the install script, terminate if it fails
|
||||
& Powershell.exe -ExecutionPolicy Bypass -File $openSSHDownloadKeyScript
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw("Failed to download key pair")
|
||||
}
|
||||
|
||||
# Restart to ensure public key authentication works and SSH comes up
|
||||
Restart-Computer
|
||||
</powershell>
|
||||
<runAsLocalSystem>true</runAsLocalSystem>
|
||||
@@ -1,179 +0,0 @@
|
||||
package secretsmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/secretsmanager"
|
||||
"github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
"github.com/hashicorp/packer-plugin-sdk/retry"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||
)
|
||||
|
||||
func TestAmazonSecretsManager(t *testing.T) {
|
||||
secret := &AmazonSecret{
|
||||
Name: "packer_datasource_secretsmanager_test_secret",
|
||||
Key: "packer_test_key",
|
||||
Value: "this_is_the_packer_test_secret_value",
|
||||
Description: "this is a secret used in a packer acc test",
|
||||
}
|
||||
|
||||
testCase := &acctest.DatasourceTestCase{
|
||||
Name: "amazon_secretsmanager_datasource_basic_test",
|
||||
Setup: func() error {
|
||||
return secret.Create()
|
||||
},
|
||||
Teardown: func() error {
|
||||
return secret.Delete()
|
||||
},
|
||||
Template: testDatasourceBasic,
|
||||
Type: "amazon-secrestmanager",
|
||||
Check: func(buildCommand *exec.Cmd, logfile string) error {
|
||||
if buildCommand.ProcessState != nil {
|
||||
if buildCommand.ProcessState.ExitCode() != 0 {
|
||||
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
|
||||
}
|
||||
}
|
||||
|
||||
logs, err := os.Open(logfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable find %s", logfile)
|
||||
}
|
||||
defer logs.Close()
|
||||
|
||||
logsBytes, err := ioutil.ReadAll(logs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read %s", logfile)
|
||||
}
|
||||
logsString := string(logsBytes)
|
||||
|
||||
valueLog := fmt.Sprintf("null.basic-example: secret value: %s", secret.Value)
|
||||
secretStringLog := fmt.Sprintf("null.basic-example: secret secret_string: %s", fmt.Sprintf("{%s:%s}", secret.Key, secret.Value))
|
||||
versionIdLog := fmt.Sprintf("null.basic-example: secret version_id: %s", aws.StringValue(secret.Info.VersionId))
|
||||
secretValueLog := fmt.Sprintf("null.basic-example: secret value: %s", secret.Value)
|
||||
|
||||
if matched, _ := regexp.MatchString(valueLog+".*", logsString); !matched {
|
||||
t.Fatalf("logs doesn't contain expected arn %q", logsString)
|
||||
}
|
||||
if matched, _ := regexp.MatchString(secretStringLog+".*", logsString); !matched {
|
||||
t.Fatalf("logs doesn't contain expected secret_string %q", logsString)
|
||||
}
|
||||
if matched, _ := regexp.MatchString(versionIdLog+".*", logsString); !matched {
|
||||
t.Fatalf("logs doesn't contain expected version_id %q", logsString)
|
||||
}
|
||||
if matched, _ := regexp.MatchString(secretValueLog+".*", logsString); !matched {
|
||||
t.Fatalf("logs doesn't contain expected value %q", logsString)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
acctest.TestDatasource(t, testCase)
|
||||
}
|
||||
|
||||
const testDatasourceBasic = `
|
||||
data "amazon-secretsmanager" "test" {
|
||||
name = "packer_datasource_secretsmanager_test_secret"
|
||||
key = "packer_test_key"
|
||||
}
|
||||
|
||||
locals {
|
||||
value = data.amazon-secretsmanager.test.value
|
||||
secret_string = data.amazon-secretsmanager.test.secret_string
|
||||
version_id = data.amazon-secretsmanager.test.version_id
|
||||
secret_value = jsondecode(data.amazon-secretsmanager.test.secret_string)["packer_test_key"]
|
||||
}
|
||||
|
||||
source "null" "basic-example" {
|
||||
communicator = "none"
|
||||
}
|
||||
|
||||
build {
|
||||
sources = [
|
||||
"source.null.basic-example"
|
||||
]
|
||||
|
||||
provisioner "shell-local" {
|
||||
inline = [
|
||||
"echo secret value: ${local.value}",
|
||||
"echo secret secret_string: ${local.secret_string}",
|
||||
"echo secret version_id: ${local.version_id}",
|
||||
"echo secret value: ${local.secret_value}"
|
||||
]
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
type AmazonSecret struct {
|
||||
Name string
|
||||
Key string
|
||||
Value string
|
||||
Description string
|
||||
|
||||
Info *secretsmanager.CreateSecretOutput
|
||||
manager *secretsmanager.SecretsManager
|
||||
}
|
||||
|
||||
func (as *AmazonSecret) Create() error {
|
||||
if as.manager == nil {
|
||||
accessConfig := &awscommon.AccessConfig{}
|
||||
session, err := accessConfig.Session()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create aws session %s", err.Error())
|
||||
}
|
||||
as.manager = secretsmanager.New(session)
|
||||
}
|
||||
|
||||
newSecret := &secretsmanager.CreateSecretInput{
|
||||
Description: aws.String(as.Description),
|
||||
Name: aws.String(as.Name),
|
||||
SecretString: aws.String(fmt.Sprintf(`{%q:%q}`, as.Key, as.Value)),
|
||||
}
|
||||
|
||||
secret := new(secretsmanager.CreateSecretOutput)
|
||||
var err error
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(err error) bool {
|
||||
if awserrors.Matches(err, "ResourceExistsException", "") {
|
||||
_ = as.Delete()
|
||||
return true
|
||||
}
|
||||
if awserrors.Matches(err, "InvalidRequestException", "already scheduled for deletion") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(context.TODO(), func(_ context.Context) error {
|
||||
secret, err = as.manager.CreateSecret(newSecret)
|
||||
return err
|
||||
})
|
||||
as.Info = secret
|
||||
return err
|
||||
}
|
||||
|
||||
func (as *AmazonSecret) Delete() error {
|
||||
if as.manager == nil {
|
||||
accessConfig := &awscommon.AccessConfig{}
|
||||
session, err := accessConfig.Session()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create aws session %s", err.Error())
|
||||
}
|
||||
as.manager = secretsmanager.New(session)
|
||||
}
|
||||
|
||||
secret := &secretsmanager.DeleteSecretInput{
|
||||
ForceDeleteWithoutRecovery: aws.Bool(true),
|
||||
SecretId: aws.String(as.Name),
|
||||
}
|
||||
_, err := as.manager.DeleteSecret(secret)
|
||||
return err
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package secretsmanager
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDatasourceConfigure_EmptySecretId(t *testing.T) {
|
||||
datasource := Datasource{
|
||||
config: Config{},
|
||||
}
|
||||
if err := datasource.Configure(nil); err == nil {
|
||||
t.Fatalf("Should error if secret id is not specified")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatasourceConfigure_Dafaults(t *testing.T) {
|
||||
datasource := Datasource{
|
||||
config: Config{
|
||||
Name: "arn:1223",
|
||||
},
|
||||
}
|
||||
if err := datasource.Configure(nil); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if datasource.config.VersionStage != "AWSCURRENT" {
|
||||
t.Fatalf("VersionStage not set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatasourceConfigure(t *testing.T) {
|
||||
datasource := Datasource{
|
||||
config: Config{
|
||||
Name: "arn:1223",
|
||||
},
|
||||
}
|
||||
if err := datasource.Configure(nil); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,12 @@ require (
|
||||
github.com/Azure/go-autorest/autorest/to v0.3.0
|
||||
github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022
|
||||
github.com/NaverCloudPlatform/ncloud-sdk-go-v2 v1.1.0
|
||||
github.com/Telmate/proxmox-api-go v0.0.0-20210320143302-fea68269e6b0
|
||||
github.com/Telmate/proxmox-api-go v0.0.0-20210331182840-ff89a0cebcfa
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f
|
||||
github.com/antihax/optional v1.0.0
|
||||
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43
|
||||
github.com/aws/aws-sdk-go v1.37.15
|
||||
github.com/aws/aws-sdk-go v1.38.0
|
||||
github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3
|
||||
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee
|
||||
github.com/cheggaaa/pb v1.0.27
|
||||
@@ -32,26 +32,26 @@ require (
|
||||
github.com/go-resty/resty/v2 v2.3.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||
github.com/google/go-cmp v0.5.2
|
||||
github.com/google/go-cmp v0.5.5
|
||||
github.com/google/go-github/v33 v33.0.1-0.20210113204525-9318e629ec69
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/gophercloud/gophercloud v0.12.0
|
||||
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
|
||||
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026
|
||||
github.com/hashicorp/aws-sdk-go-base v0.6.0
|
||||
github.com/hashicorp/errwrap v1.0.0
|
||||
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840
|
||||
github.com/hashicorp/go-getter/v2 v2.0.0-20200604122502-a6995fa1edad
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-oracle-terraform v0.0.0-20181016190316-007121241b79
|
||||
github.com/hashicorp/go-uuid v1.0.2
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/hashicorp/hcl/v2 v2.8.0
|
||||
github.com/hashicorp/packer-plugin-docker v0.0.2
|
||||
github.com/hashicorp/packer-plugin-sdk v0.1.1-0.20210323111710-5a7ab7f7a1c0
|
||||
github.com/hashicorp/hcl/v2 v2.9.1
|
||||
github.com/hashicorp/packer-plugin-amazon v0.0.1
|
||||
github.com/hashicorp/packer-plugin-docker v0.0.7
|
||||
github.com/hashicorp/packer-plugin-sdk v0.1.1
|
||||
github.com/hashicorp/vault/api v1.0.4
|
||||
github.com/hetznercloud/hcloud-go v1.15.1
|
||||
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4
|
||||
@@ -64,13 +64,12 @@ require (
|
||||
github.com/mitchellh/cli v1.1.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
|
||||
github.com/mitchellh/gox v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.0
|
||||
github.com/mitchellh/panicwrap v1.0.0
|
||||
github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784
|
||||
github.com/mitchellh/reflectwalk v1.0.0
|
||||
github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible
|
||||
github.com/oracle/oci-go-sdk/v36 v36.2.0
|
||||
github.com/outscale/osc-sdk-go/osc v0.0.0-20200722135656-d654809d0699
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
@@ -87,7 +86,7 @@ require (
|
||||
github.com/xanzy/go-cloudstack v0.0.0-20190526095453-42f262b63ed0
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20200915125933-33de72a328bd
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20200921111412-ef15ded2014c
|
||||
github.com/zclconf/go-cty v1.7.0
|
||||
github.com/zclconf/go-cty v1.8.1
|
||||
github.com/zclconf/go-cty-yaml v1.0.1
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/mobile v0.0.0-20201208152944-da85bec010a2
|
||||
@@ -95,7 +94,7 @@ require (
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
|
||||
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d
|
||||
golang.org/x/tools v0.0.0-20201111133315-69daaf961d65
|
||||
google.golang.org/api v0.32.0
|
||||
google.golang.org/grpc v1.32.0
|
||||
|
||||
@@ -82,8 +82,8 @@ github.com/NaverCloudPlatform/ncloud-sdk-go-v2 v1.1.0/go.mod h1:P+3VS0ETiQPyWOx3
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/Telmate/proxmox-api-go v0.0.0-20200715182505-ec97c70ba887/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ=
|
||||
github.com/Telmate/proxmox-api-go v0.0.0-20210320143302-fea68269e6b0 h1:LeBf+Ex12uqA6dWZp73Qf3dzpV/LvB9SRmHgPBwnXrQ=
|
||||
github.com/Telmate/proxmox-api-go v0.0.0-20210320143302-fea68269e6b0/go.mod h1:ayPkdmEKnlssqLQ9K1BE1jlsaYhXVwkoduXI30oQF0I=
|
||||
github.com/Telmate/proxmox-api-go v0.0.0-20210331182840-ff89a0cebcfa h1:n4g0+o4DDX6WGTRfdj1Ux+49vSwtxtqFGB5XtxoDphI=
|
||||
github.com/Telmate/proxmox-api-go v0.0.0-20210331182840-ff89a0cebcfa/go.mod h1:ayPkdmEKnlssqLQ9K1BE1jlsaYhXVwkoduXI30oQF0I=
|
||||
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14=
|
||||
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw=
|
||||
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
|
||||
@@ -106,8 +106,9 @@ github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhi
|
||||
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
|
||||
github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0=
|
||||
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
|
||||
github.com/apparentlymart/go-textseg/v12 v12.0.0 h1:bNEQyAGak9tojivJNkoqWErVCQbjdL7GzRt3F8NvfJ0=
|
||||
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
||||
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43 h1:ePCAQPf5tUc5IMcUvu6euhSGna7jzs7eiXtJXHig6Zc=
|
||||
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43/go.mod h1:S6puKjZ9ZeqUPBv2hEBnMZGcM2J6mOsDRQcmxkMAND0=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
@@ -123,8 +124,8 @@ github.com/aws/aws-sdk-go v1.30.8/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU
|
||||
github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.36.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.36.5/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.37.15 h1:W7l7gLLMcYRlg6a+uvf3Zz4jYwdqYzhe5ymqwWoOhp4=
|
||||
github.com/aws/aws-sdk-go v1.37.15/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.38.0 h1:mqnmtdW8rGIQmp2d0WRFLua0zW0Pel0P6/vd3gJuViY=
|
||||
github.com/aws/aws-sdk-go v1.38.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||
@@ -268,8 +269,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
|
||||
github.com/google/go-github/v33 v33.0.1-0.20210113204525-9318e629ec69 h1:zL0/Ug5CMhV0XRb3A6vnK1SQ9kJM3VIyRxPQ5t9w8Bg=
|
||||
github.com/google/go-github/v33 v33.0.1-0.20210113204525-9318e629ec69/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
|
||||
@@ -310,8 +312,9 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdR
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
|
||||
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 h1:BpJ2o0OR5FV7vrkDYfXYVJQeMNWa8RhklZOpW2ITAIQ=
|
||||
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE=
|
||||
github.com/hashicorp/aws-sdk-go-base v0.6.0 h1:qmUbzM36msbBF59YctwuO5w0M2oNXjlilgKpnEhx1uw=
|
||||
github.com/hashicorp/aws-sdk-go-base v0.6.0/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY=
|
||||
github.com/hashicorp/aws-sdk-go-base v0.7.0 h1:Umcq11kcoARameDgxPiYBbyltTZqO7GgBVSdq4pzX/w=
|
||||
github.com/hashicorp/aws-sdk-go-base v0.7.0/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY=
|
||||
github.com/hashicorp/consul/api v1.4.0 h1:jfESivXnO5uLdH650JU/6AnjRoHrLhULq0FnC3Kp9EY=
|
||||
github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU=
|
||||
github.com/hashicorp/consul/sdk v0.4.0 h1:zBtCfKJZcJDBvSCkQJch4ulp59m1rATFLKwNo/LYY30=
|
||||
@@ -321,8 +324,9 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv
|
||||
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSythtg8aWSRSO29uwhgh7b127fWr+m5SemqjSUL8=
|
||||
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840 h1:kgvybwEeu0SXktbB2y3uLHX9lklLo+nzUwh59A3jzQc=
|
||||
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840/go.mod h1:Abjk0jbRkDaNCzsRhOv2iDCofYpX1eVsjozoiK63qLA=
|
||||
github.com/hashicorp/go-getter v1.4.1 h1:3A2Mh8smGFcf5M+gmcv898mZdrxpseik45IpcyISLsA=
|
||||
@@ -345,8 +349,9 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP
|
||||
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
||||
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-oracle-terraform v0.0.0-20181016190316-007121241b79 h1:RKu7yAXZTaQsxj1K9GDsh+QVw0+Wu1SWHxtbFN0n+hE=
|
||||
github.com/hashicorp/go-oracle-terraform v0.0.0-20181016190316-007121241b79/go.mod h1:09jT3Y/OIsjTjQ2+3bkVNPDKqWcGIYYvjB2BEKVUdvc=
|
||||
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||
@@ -378,8 +383,9 @@ github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY=
|
||||
github.com/hashicorp/hcl/v2 v2.8.0 h1:iHLEAsNDp3N2MtqroP1wf0nF/zB2+McHN5YCzwqIm80=
|
||||
github.com/hashicorp/hcl/v2 v2.8.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY=
|
||||
github.com/hashicorp/hcl/v2 v2.9.1 h1:eOy4gREY0/ZQHNItlfuEZqtcQbXIxzojlP301hDpnac=
|
||||
github.com/hashicorp/hcl/v2 v2.9.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
||||
@@ -395,8 +401,10 @@ github.com/hashicorp/packer v1.6.7-0.20210126105722-aef4ced967ec/go.mod h1:2+Vo/
|
||||
github.com/hashicorp/packer v1.6.7-0.20210208125835-f616955ebcb6/go.mod h1:7f5ZpTTRG53rQ58BcTADuTnpiBcB3wapuxl4sF2sGMM=
|
||||
github.com/hashicorp/packer v1.6.7-0.20210217093213-201869d627bf/go.mod h1:+EWPPcqee4h8S/y913Dnta1eJkgiqsGXBQgB75A2qV0=
|
||||
github.com/hashicorp/packer v1.7.0/go.mod h1:3KRJcwOctl2JaAGpQMI1bWQRArfWNWqcYjO6AOsVVGQ=
|
||||
github.com/hashicorp/packer-plugin-docker v0.0.2 h1:j/hQTogaN2pZfZohlZTRu5YvNZg2/qtYYHkxPBxv2Oo=
|
||||
github.com/hashicorp/packer-plugin-docker v0.0.2/go.mod h1:A2p9qztS4n88KsNF+qBM7BWw2HndW636GpFIjNSvbKM=
|
||||
github.com/hashicorp/packer-plugin-amazon v0.0.1 h1:EuyjNK9bL7WhQeIJzhBJxOx8nyc61ai5UbOsb1PIVwI=
|
||||
github.com/hashicorp/packer-plugin-amazon v0.0.1/go.mod h1:12c9msibyHdId+Mk/pCbdRb1KaLIhaNyxeJ6n8bZt30=
|
||||
github.com/hashicorp/packer-plugin-docker v0.0.7 h1:hMTrH7vrkFIjphtbbtpuzffTzSjMNgxayo2DPLz9y+c=
|
||||
github.com/hashicorp/packer-plugin-docker v0.0.7/go.mod h1:IpeKlwOSy2kdgQcysqd3gCsoqjME9jtmpFoKxn7RRNI=
|
||||
github.com/hashicorp/packer-plugin-sdk v0.0.6/go.mod h1:Nvh28f+Jmpp2rcaN79bULTouNkGNDRfHckhHKTAXtyU=
|
||||
github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210111224258-fd30ebb797f0/go.mod h1:YdWTt5w6cYfaQG7IOi5iorL+3SXnz8hI0gJCi8Db/LI=
|
||||
github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210120105339-f6fd68d2570a/go.mod h1:exN0C+Pe+3zu18l4nxueNjX5cfmslxUX/m/xk4IVmZQ=
|
||||
@@ -405,8 +413,9 @@ github.com/hashicorp/packer-plugin-sdk v0.0.10-0.20210126105622-8e1648006d93/go.
|
||||
github.com/hashicorp/packer-plugin-sdk v0.0.11/go.mod h1:GNb0WNs7zibb8vzUZce1As64z2AW0FEMwhe2J7/NW5I=
|
||||
github.com/hashicorp/packer-plugin-sdk v0.0.12/go.mod h1:hs82OYeufirGG6KRENMpjBWomnIlte99X6wXAPThJ5I=
|
||||
github.com/hashicorp/packer-plugin-sdk v0.0.14/go.mod h1:tNb3XzJPnjMl3QuUdKmF47B5ImerdTakalHzUAvW0aw=
|
||||
github.com/hashicorp/packer-plugin-sdk v0.1.1-0.20210323111710-5a7ab7f7a1c0 h1:nw4RqF7C4jUWGo5PGDG4dSclU+G/vXyVBHIu5j7akd4=
|
||||
github.com/hashicorp/packer-plugin-sdk v0.1.1-0.20210323111710-5a7ab7f7a1c0/go.mod h1:1d3nqB9LUsXMQaNUiL67Q+WYEtjsVcLNTX8ikVlpBrc=
|
||||
github.com/hashicorp/packer-plugin-sdk v0.1.0/go.mod h1:CFsC20uZjtER/EnTn/CSMKD0kEdkqOVev8mtOmfnZiI=
|
||||
github.com/hashicorp/packer-plugin-sdk v0.1.1 h1:foqSy6m+2MsEf9ygNoBlIoi3SPvlkInS+yT0Uyj3yvw=
|
||||
github.com/hashicorp/packer-plugin-sdk v0.1.1/go.mod h1:1d3nqB9LUsXMQaNUiL67Q+WYEtjsVcLNTX8ikVlpBrc=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hashicorp/serf v0.9.2 h1:yJoyfZXo4Pk2p/M/viW+YLibBFiIbKoP79gu7kDAFP0=
|
||||
github.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
@@ -529,7 +538,6 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZX
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
|
||||
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
|
||||
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
@@ -556,9 +564,10 @@ github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQ
|
||||
github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b h1:LGItPaClbzopugAomw5VFKnG3h1dUr9QW5KOU+m8gu0=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/oracle/oci-go-sdk v18.0.0+incompatible h1:FLV4KixsVfF3rwyVTMI6Ryp/Q+OSb9sR5TawbfjFLN4=
|
||||
github.com/oracle/oci-go-sdk v18.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU=
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/oracle/oci-go-sdk/v36 v36.2.0 h1:oBaN/FnBDy3ohMyVZ/rKfekYxnyksG2KK0YAhT5HSnk=
|
||||
github.com/oracle/oci-go-sdk/v36 v36.2.0/go.mod h1:t8Y/M3Lh8X4BOJhtThJKe1skRTg7qom7oWyHiNjo4RM=
|
||||
github.com/outscale/osc-sdk-go/osc v0.0.0-20200722135656-d654809d0699 h1:SHe9i7h5cHe+cB77fQ6lsEgIwKg3ckNU90P03CjGMnI=
|
||||
github.com/outscale/osc-sdk-go/osc v0.0.0-20200722135656-d654809d0699/go.mod h1:5AqqNH1X8zCHescKVlpSHRzrat1KCKDXqZoQPe8fY3A=
|
||||
github.com/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a h1:A3QMuteviunoaY/8ex+RKFqwhcZJ/Cf3fCW3IwL2wx4=
|
||||
@@ -665,8 +674,11 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
github.com/zclconf/go-cty v1.4.0/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ=
|
||||
github.com/zclconf/go-cty v1.7.0 h1:yMqLinUwNCYkmiHjEH+luio1yGl35cjqVzjvdRg2WlY=
|
||||
github.com/zclconf/go-cty v1.7.0/go.mod h1:VDR4+I79ubFBGm1uJac1226K5yANQFHeauxPBoP54+o=
|
||||
github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
|
||||
github.com/zclconf/go-cty v1.8.1 h1:SI0LqNeNxAgv2WWqWJMlG2/Ad/6aYJ7IVYYMigmfkuI=
|
||||
github.com/zclconf/go-cty v1.8.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
|
||||
github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8=
|
||||
github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@@ -839,8 +851,9 @@ golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d h1:jbzgAvDZn8aEnytae+4ou0J0GwFZoHR0hOrTg4qH8GA=
|
||||
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
|
||||
const lockedVersion = "v1.5.0"
|
||||
|
||||
func getBasicParser() *Parser {
|
||||
return &Parser{
|
||||
func getBasicParser(opts ...getParserOption) *Parser {
|
||||
parser := &Parser{
|
||||
CorePackerVersion: version.Must(version.NewSemver(lockedVersion)),
|
||||
CorePackerVersionString: lockedVersion,
|
||||
Parser: hclparse.NewParser(),
|
||||
@@ -44,8 +44,14 @@ func getBasicParser() *Parser {
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, configure := range opts {
|
||||
configure(parser)
|
||||
}
|
||||
return parser
|
||||
}
|
||||
|
||||
type getParserOption func(*Parser)
|
||||
|
||||
type parseTestArgs struct {
|
||||
filename string
|
||||
vars map[string]string
|
||||
@@ -338,7 +344,7 @@ var cmpOpts = []cmp.Option{
|
||||
PackerConfig{},
|
||||
Variable{},
|
||||
SourceBlock{},
|
||||
Datasource{},
|
||||
DatasourceBlock{},
|
||||
ProvisionerBlock{},
|
||||
PostProcessorBlock{},
|
||||
packer.CoreBuild{},
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package hcl2template
|
||||
|
||||
// ComponentKind helps enumerate what kind of components exist in this Package.
|
||||
type ComponentKind int
|
||||
|
||||
const (
|
||||
Builder ComponentKind = iota
|
||||
Provisioner
|
||||
PostProcessor
|
||||
Datasource
|
||||
)
|
||||
+16
-11
@@ -76,12 +76,15 @@ const (
|
||||
|
||||
// Parse will Parse all HCL files in filename. Path can be a folder or a file.
|
||||
//
|
||||
// Parse will first Parse variables and then the rest; so that interpolation
|
||||
// can happen.
|
||||
// Parse will first Parse packer and variables blocks, omitting the rest, which
|
||||
// can be expanded with dynamic blocks. We need to evaluate all variables for
|
||||
// that, so that data sources can expand dynamic blocks too.
|
||||
//
|
||||
// Parse returns a PackerConfig that contains configuration layout of a packer
|
||||
// build; sources(builders)/provisioners/posts-processors will not be started
|
||||
// and their contents wont be verified; Most syntax errors will cause an error.
|
||||
// and their contents wont be verified; Most syntax errors will cause an error,
|
||||
// init should be called next to expand dynamic blocks and verify that used
|
||||
// things do exist.
|
||||
func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]string) (*PackerConfig, hcl.Diagnostics) {
|
||||
var files []*hcl.File
|
||||
var diags hcl.Diagnostics
|
||||
@@ -235,10 +238,6 @@ func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]st
|
||||
diags = append(diags, cfg.collectInputVariableValues(os.Environ(), varFiles, argVars)...)
|
||||
}
|
||||
|
||||
// parse the actual content // rest
|
||||
for _, file := range cfg.files {
|
||||
diags = append(diags, cfg.parser.parseConfig(file, cfg)...)
|
||||
}
|
||||
return cfg, diags
|
||||
}
|
||||
|
||||
@@ -291,7 +290,7 @@ func filterVarsFromLogs(inputOrLocal Variables) {
|
||||
if !variable.Sensitive {
|
||||
continue
|
||||
}
|
||||
value, _ := variable.Value()
|
||||
value := variable.Value()
|
||||
_ = cty.Walk(value, func(_ cty.Path, nested cty.Value) (bool, error) {
|
||||
if nested.IsWhollyKnown() && !nested.IsNull() && nested.Type().Equals(cty.String) {
|
||||
packersdk.LogSecretFilter.Set(nested.AsString())
|
||||
@@ -311,9 +310,9 @@ func (cfg *PackerConfig) Initialize(opts packer.InitializeOptions) hcl.Diagnosti
|
||||
return diags
|
||||
}
|
||||
|
||||
_, moreDiags = cfg.InputVariables.Values()
|
||||
moreDiags = cfg.InputVariables.ValidateValues()
|
||||
diags = append(diags, moreDiags...)
|
||||
_, moreDiags = cfg.LocalVariables.Values()
|
||||
moreDiags = cfg.LocalVariables.ValidateValues()
|
||||
diags = append(diags, moreDiags...)
|
||||
diags = append(diags, cfg.evaluateDatasources(opts.SkipDatasourcesExecution)...)
|
||||
diags = append(diags, cfg.evaluateLocalVariables(cfg.LocalBlocks)...)
|
||||
@@ -321,6 +320,11 @@ func (cfg *PackerConfig) Initialize(opts packer.InitializeOptions) hcl.Diagnosti
|
||||
filterVarsFromLogs(cfg.InputVariables)
|
||||
filterVarsFromLogs(cfg.LocalVariables)
|
||||
|
||||
// parse the actual content // rest
|
||||
for _, file := range cfg.files {
|
||||
diags = append(diags, cfg.parser.parseConfig(file, cfg)...)
|
||||
}
|
||||
|
||||
diags = append(diags, cfg.initializeBlocks()...)
|
||||
|
||||
return diags
|
||||
@@ -332,6 +336,7 @@ func (p *Parser) parseConfig(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
body := f.Body
|
||||
body = dynblock.Expand(body, cfg.EvalContext(DatasourceContext, nil))
|
||||
content, moreDiags := body.Content(configSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
@@ -380,7 +385,7 @@ func (p *Parser) parseConfig(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
|
||||
func (p *Parser) decodeDatasources(file *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
body := dynblock.Expand(file.Body, cfg.EvalContext(DatasourceContext, nil))
|
||||
body := file.Body
|
||||
content, moreDiags := body.Content(configSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
|
||||
+10
-13
@@ -7,7 +7,7 @@ import (
|
||||
"runtime"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/ext/dynblock"
|
||||
"github.com/hashicorp/packer-plugin-sdk/didyoumean"
|
||||
pluginsdk "github.com/hashicorp/packer-plugin-sdk/plugin"
|
||||
plugingetter "github.com/hashicorp/packer/packer/plugin-getter"
|
||||
)
|
||||
@@ -41,6 +41,7 @@ func (cfg *PackerConfig) PluginRequirements() (plugingetter.Requirements, hcl.Di
|
||||
Accessor: name,
|
||||
Identifier: block.Type,
|
||||
VersionConstraints: block.Requirement.Required,
|
||||
Implicit: block.PluginDependencyReason == PluginDependencyImplicit,
|
||||
})
|
||||
uniq[name] = block
|
||||
}
|
||||
@@ -108,7 +109,7 @@ func (cfg *PackerConfig) detectPluginBinaries() hcl.Diagnostics {
|
||||
}
|
||||
|
||||
func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics {
|
||||
// verify that all used plugins do exist and expand dynamic bodies
|
||||
// verify that all used plugins do exist
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
for _, build := range cfg.Builds {
|
||||
@@ -128,12 +129,16 @@ func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics {
|
||||
|
||||
sourceDefinition, found := cfg.Sources[srcUsage.SourceRef]
|
||||
if !found {
|
||||
availableSrcs := listAvailableSourceNames(cfg.Sources)
|
||||
detail := fmt.Sprintf("Known: %v", availableSrcs)
|
||||
if sugg := didyoumean.NameSuggestion(srcUsage.SourceRef.String(), availableSrcs); sugg != "" {
|
||||
detail = fmt.Sprintf("Did you mean to use %q?", sugg)
|
||||
}
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Summary: "Unknown " + sourceLabel + " " + srcUsage.String(),
|
||||
Summary: "Unknown " + sourceLabel + " " + srcUsage.SourceRef.String(),
|
||||
Subject: build.HCL2Ref.DefRange.Ptr(),
|
||||
Severity: hcl.DiagError,
|
||||
Detail: fmt.Sprintf("Known: %v", cfg.Sources),
|
||||
// TODO: show known sources as a string slice here ^.
|
||||
Detail: detail,
|
||||
})
|
||||
continue
|
||||
}
|
||||
@@ -143,8 +148,6 @@ func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics {
|
||||
// merge additions into source definition to get a new body.
|
||||
body = hcl.MergeBodies([]hcl.Body{body, srcUsage.Body})
|
||||
}
|
||||
// expand any dynamic block.
|
||||
body = dynblock.Expand(body, cfg.EvalContext(BuildContext, nil))
|
||||
|
||||
srcUsage.Body = body
|
||||
}
|
||||
@@ -158,8 +161,6 @@ func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics {
|
||||
Severity: hcl.DiagError,
|
||||
})
|
||||
}
|
||||
// Allow rest of the body to have dynamic blocks
|
||||
provBlock.HCL2Ref.Rest = dynblock.Expand(provBlock.HCL2Ref.Rest, cfg.EvalContext(BuildContext, nil))
|
||||
}
|
||||
|
||||
if build.ErrorCleanupProvisionerBlock != nil {
|
||||
@@ -171,8 +172,6 @@ func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics {
|
||||
Severity: hcl.DiagError,
|
||||
})
|
||||
}
|
||||
// Allow rest of the body to have dynamic blocks
|
||||
build.ErrorCleanupProvisionerBlock.HCL2Ref.Rest = dynblock.Expand(build.ErrorCleanupProvisionerBlock.HCL2Ref.Rest, cfg.EvalContext(BuildContext, nil))
|
||||
}
|
||||
|
||||
for _, ppList := range build.PostProcessorsLists {
|
||||
@@ -185,8 +184,6 @@ func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics {
|
||||
Severity: hcl.DiagError,
|
||||
})
|
||||
}
|
||||
// Allow the rest of the body to have dynamic blocks
|
||||
ppBlock.HCL2Ref.Rest = dynblock.Expand(ppBlock.HCL2Ref.Rest, cfg.EvalContext(BuildContext, nil))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ type Builds []*BuildBlock
|
||||
// load the references to the contents of the build block.
|
||||
func (p *Parser) decodeBuildConfig(block *hcl.Block, cfg *PackerConfig) (*BuildBlock, hcl.Diagnostics) {
|
||||
build := &BuildBlock{}
|
||||
body := block.Body
|
||||
|
||||
var b struct {
|
||||
Name string `hcl:"name,optional"`
|
||||
@@ -88,7 +89,7 @@ func (p *Parser) decodeBuildConfig(block *hcl.Block, cfg *PackerConfig) (*BuildB
|
||||
FromSources []string `hcl:"sources,optional"`
|
||||
Config hcl.Body `hcl:",remain"`
|
||||
}
|
||||
diags := gohcl.DecodeBody(block.Body, nil, &b)
|
||||
diags := gohcl.DecodeBody(body, nil, &b)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
@@ -118,7 +119,8 @@ func (p *Parser) decodeBuildConfig(block *hcl.Block, cfg *PackerConfig) (*BuildB
|
||||
build.Sources = append(build.Sources, SourceUseBlock{SourceRef: ref})
|
||||
}
|
||||
|
||||
content, moreDiags := b.Config.Content(buildSchema)
|
||||
body = b.Config
|
||||
content, moreDiags := body.Content(buildSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// DataBlock references an HCL 'data' block.
|
||||
type Datasource struct {
|
||||
// DatasourceBlock references an HCL 'data' block.
|
||||
type DatasourceBlock struct {
|
||||
Type string
|
||||
Name string
|
||||
|
||||
@@ -25,9 +25,9 @@ type DatasourceRef struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type Datasources map[DatasourceRef]Datasource
|
||||
type Datasources map[DatasourceRef]DatasourceBlock
|
||||
|
||||
func (data *Datasource) Ref() DatasourceRef {
|
||||
func (data *DatasourceBlock) Ref() DatasourceRef {
|
||||
return DatasourceRef{
|
||||
Type: data.Type,
|
||||
Name: data.Name,
|
||||
@@ -124,9 +124,9 @@ func (cfg *PackerConfig) startDatasource(dataSourceStore packer.DatasourceStore,
|
||||
return datasource, diags
|
||||
}
|
||||
|
||||
func (p *Parser) decodeDataBlock(block *hcl.Block) (*Datasource, hcl.Diagnostics) {
|
||||
func (p *Parser) decodeDataBlock(block *hcl.Block) (*DatasourceBlock, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
r := &Datasource{
|
||||
r := &DatasourceBlock{
|
||||
Type: block.Labels[0],
|
||||
Name: block.Labels[1],
|
||||
block: block,
|
||||
|
||||
@@ -91,8 +91,8 @@ const (
|
||||
// decoder in order to tell what is the actual value of a var or a local and
|
||||
// the list of defined functions.
|
||||
func (cfg *PackerConfig) EvalContext(ctx BlockContext, variables map[string]cty.Value) *hcl.EvalContext {
|
||||
inputVariables, _ := cfg.InputVariables.Values()
|
||||
localVariables, _ := cfg.LocalVariables.Values()
|
||||
inputVariables := cfg.InputVariables.Values()
|
||||
localVariables := cfg.LocalVariables.Values()
|
||||
ectx := &hcl.EvalContext{
|
||||
Functions: Functions(cfg.Basedir),
|
||||
Variables: map[string]cty.Value{
|
||||
@@ -594,7 +594,7 @@ func (p *PackerConfig) printVariables() string {
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
v := p.InputVariables[key]
|
||||
val, _ := v.Value()
|
||||
val := v.Value()
|
||||
fmt.Fprintf(out, "var.%s: %q\n", v.Name, PrintableCtyValue(val))
|
||||
}
|
||||
out.WriteString("\n> local-variables:\n\n")
|
||||
@@ -602,7 +602,7 @@ func (p *PackerConfig) printVariables() string {
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
v := p.LocalVariables[key]
|
||||
val, _ := v.Value()
|
||||
val := v.Value()
|
||||
fmt.Fprintf(out, "local.%s: %q\n", v.Name, PrintableCtyValue(val))
|
||||
}
|
||||
return out.String()
|
||||
|
||||
@@ -131,7 +131,7 @@ func TestParser_complete(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Datasources: Datasources{
|
||||
DatasourceRef{Type: "amazon-ami", Name: "test"}: Datasource{
|
||||
DatasourceRef{Type: "amazon-ami", Name: "test"}: DatasourceBlock{
|
||||
Type: "amazon-ami",
|
||||
Name: "test",
|
||||
value: cty.StringVal("foo"),
|
||||
@@ -538,65 +538,8 @@ func TestParser_no_init(t *testing.T) {
|
||||
Type: cty.List(cty.String),
|
||||
},
|
||||
},
|
||||
Sources: map[SourceRef]SourceBlock{
|
||||
refVBIsoUbuntu1204: {Type: "virtualbox-iso", Name: "ubuntu-1204"},
|
||||
refAWSV3MyImage: {Type: "amazon-v3-ebs", Name: "my-image"},
|
||||
},
|
||||
Builds: Builds{
|
||||
&BuildBlock{
|
||||
Sources: []SourceUseBlock{
|
||||
{
|
||||
SourceRef: refVBIsoUbuntu1204,
|
||||
},
|
||||
{
|
||||
SourceRef: refAWSV3MyImage,
|
||||
},
|
||||
},
|
||||
ProvisionerBlocks: []*ProvisionerBlock{
|
||||
{
|
||||
PType: "shell",
|
||||
PName: "provisioner that does something",
|
||||
},
|
||||
{
|
||||
PType: "file",
|
||||
},
|
||||
},
|
||||
PostProcessorsLists: [][]*PostProcessorBlock{
|
||||
{
|
||||
{
|
||||
PType: "amazon-import",
|
||||
PName: "something",
|
||||
KeepInputArtifact: pTrue,
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
PType: "amazon-import",
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
PType: "amazon-import",
|
||||
PName: "first-nested-post-processor",
|
||||
},
|
||||
{
|
||||
PType: "amazon-import",
|
||||
PName: "second-nested-post-processor",
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
PType: "amazon-import",
|
||||
PName: "third-nested-post-processor",
|
||||
},
|
||||
{
|
||||
PType: "amazon-import",
|
||||
PName: "fourth-nested-post-processor",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Sources: nil,
|
||||
Builds: nil,
|
||||
},
|
||||
false, false,
|
||||
[]packersdk.Build{},
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/packer/hcl2template/addrs"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
@@ -41,9 +42,13 @@ func (cfg *PackerConfig) decodeRequiredPluginsBlock(f *hcl.File) hcl.Diagnostics
|
||||
}
|
||||
|
||||
func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlocks(f *hcl.File) hcl.Diagnostics {
|
||||
// when a plugin is used but not defined in the required plugin blocks, it
|
||||
// is 'implicitly used'. Here we read common configuration blocks to try to
|
||||
// guess plugins.
|
||||
// when a plugin is used but not available it should be 'implicitly
|
||||
// required'. Here we read common configuration blocks to try to guess
|
||||
// plugin usages.
|
||||
|
||||
// decodeRequiredPluginsBlock needs to be called before
|
||||
// decodeImplicitRequiredPluginsBlocks; otherwise all required plugins will
|
||||
// be implicitly required too.
|
||||
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
@@ -51,14 +56,112 @@ func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlocks(f *hcl.File) hcl.Di
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
for _, block := range content.Blocks {
|
||||
|
||||
switch block.Type {
|
||||
case sourceLabel:
|
||||
// TODO
|
||||
diags = append(diags, cfg.decodeImplicitRequiredPluginsBlock(Builder, block)...)
|
||||
case dataSourceLabel:
|
||||
diags = append(diags, cfg.decodeImplicitRequiredPluginsBlock(Datasource, block)...)
|
||||
case buildLabel:
|
||||
content, _, moreDiags := block.Body.PartialContent(buildSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
for _, block := range content.Blocks {
|
||||
|
||||
switch block.Type {
|
||||
case buildProvisionerLabel:
|
||||
diags = append(diags, cfg.decodeImplicitRequiredPluginsBlock(Provisioner, block)...)
|
||||
case buildPostProcessorLabel:
|
||||
diags = append(diags, cfg.decodeImplicitRequiredPluginsBlock(PostProcessor, block)...)
|
||||
case buildPostProcessorsLabel:
|
||||
content, _, moreDiags := block.Body.PartialContent(postProcessorsSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
for _, block := range content.Blocks {
|
||||
|
||||
switch block.Type {
|
||||
case buildPostProcessorLabel:
|
||||
diags = append(diags, cfg.decodeImplicitRequiredPluginsBlock(PostProcessor, block)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlock(k ComponentKind, block *hcl.Block) hcl.Diagnostics {
|
||||
if len(block.Labels) == 0 {
|
||||
// malformed block ? Let's not panic :)
|
||||
return nil
|
||||
}
|
||||
// Currently all block types are `type "component-kind" ["name"] {`
|
||||
// this makes this simple.
|
||||
componentName := block.Labels[0]
|
||||
|
||||
store := map[ComponentKind]packer.BasicStore{
|
||||
Builder: cfg.parser.PluginConfig.Builders,
|
||||
PostProcessor: cfg.parser.PluginConfig.PostProcessors,
|
||||
Provisioner: cfg.parser.PluginConfig.Provisioners,
|
||||
Datasource: cfg.parser.PluginConfig.DataSources,
|
||||
}[k]
|
||||
if store.Has(componentName) {
|
||||
// If any core or pre-loaded plugin defines the `happycloud-uploader`
|
||||
// pp, skip. This happens for core and manually installed plugins, as
|
||||
// they will be listed in the PluginConfig before parsing any HCL.
|
||||
return nil
|
||||
}
|
||||
|
||||
redirect := map[ComponentKind]map[string]string{
|
||||
Builder: cfg.parser.PluginConfig.BuilderRedirects,
|
||||
PostProcessor: cfg.parser.PluginConfig.PostProcessorRedirects,
|
||||
Provisioner: cfg.parser.PluginConfig.ProvisionerRedirects,
|
||||
Datasource: cfg.parser.PluginConfig.DatasourceRedirects,
|
||||
}[k][componentName]
|
||||
|
||||
if redirect == "" {
|
||||
// no known redirect for this component
|
||||
return nil
|
||||
}
|
||||
|
||||
redirectAddr, diags := addrs.ParsePluginSourceString(redirect)
|
||||
if diags.HasErrors() {
|
||||
// This should never happen, since the map is manually filled.
|
||||
return diags
|
||||
}
|
||||
|
||||
for _, req := range cfg.Packer.RequiredPlugins {
|
||||
if _, found := req.RequiredPlugins[redirectAddr.Type]; found {
|
||||
// This could happen if a plugin was forked. For example, I forked
|
||||
// the github.com/hashicorp/happycloud plugin into
|
||||
// github.com/azr/happycloud that is required in my config file; and
|
||||
// am using the `happycloud-uploader` pp component from it. In that
|
||||
// case - and to avoid miss-requires - we won't implicitly import
|
||||
// any other `happycloud` plugin.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
cfg.implicitlyRequirePlugin(redirectAddr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *PackerConfig) implicitlyRequirePlugin(plugin *addrs.Plugin) {
|
||||
cfg.Packer.RequiredPlugins = append(cfg.Packer.RequiredPlugins, &RequiredPlugins{
|
||||
RequiredPlugins: map[string]*RequiredPlugin{
|
||||
plugin.Type: {
|
||||
Name: plugin.Type,
|
||||
Source: plugin.String(),
|
||||
Type: plugin,
|
||||
Requirement: VersionConstraint{
|
||||
Required: nil, // means latest
|
||||
},
|
||||
PluginDependencyReason: PluginDependencyImplicit,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// RequiredPlugin represents a declaration of a dependency on a particular
|
||||
// Plugin version or source.
|
||||
type RequiredPlugin struct {
|
||||
@@ -71,8 +174,24 @@ type RequiredPlugin struct {
|
||||
Type *addrs.Plugin
|
||||
Requirement VersionConstraint
|
||||
DeclRange hcl.Range
|
||||
PluginDependencyReason
|
||||
}
|
||||
|
||||
// PluginDependencyReason is an enumeration of reasons why a dependency might be
|
||||
// present.
|
||||
type PluginDependencyReason int
|
||||
|
||||
const (
|
||||
// PluginDependencyExplicit means that there is an explicit
|
||||
// "required_plugin" block in the configuration.
|
||||
PluginDependencyExplicit PluginDependencyReason = iota
|
||||
|
||||
// PluginDependencyImplicit means that there is no explicit
|
||||
// "required_plugin" block but there is at least one resource that uses this
|
||||
// plugin.
|
||||
PluginDependencyImplicit
|
||||
)
|
||||
|
||||
type RequiredPlugins struct {
|
||||
RequiredPlugins map[string]*RequiredPlugin
|
||||
DeclRange hcl.Range
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
package hcl2template
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/packer/hcl2template/addrs"
|
||||
)
|
||||
|
||||
func TestPackerConfig_required_plugin_parse(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg PackerConfig
|
||||
requirePlugins string
|
||||
restOfTemplate string
|
||||
wantDiags bool
|
||||
wantConfig PackerConfig
|
||||
}{
|
||||
{"required_plugin", PackerConfig{parser: getBasicParser()}, `
|
||||
packer {
|
||||
required_plugins {
|
||||
amazon = {
|
||||
source = "github.com/hashicorp/amazon"
|
||||
version = "~> v1.2.3"
|
||||
}
|
||||
}
|
||||
} `, `
|
||||
source "amazon-ebs" "example" {
|
||||
}
|
||||
`, false, PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: []*RequiredPlugins{
|
||||
{RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"amazon": {
|
||||
Name: "amazon",
|
||||
Source: "github.com/hashicorp/amazon",
|
||||
Type: &addrs.Plugin{Hostname: "github.com", Namespace: "hashicorp", Type: "amazon"},
|
||||
Requirement: VersionConstraint{
|
||||
Required: mustVersionConstraints(version.NewConstraint("~> v1.2.3")),
|
||||
},
|
||||
PluginDependencyReason: PluginDependencyExplicit,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
{"required_plugin_forked_no_redirect", PackerConfig{parser: getBasicParser()}, `
|
||||
packer {
|
||||
required_plugins {
|
||||
amazon = {
|
||||
source = "github.com/azr/amazon"
|
||||
version = "~> v1.2.3"
|
||||
}
|
||||
}
|
||||
} `, `
|
||||
source "amazon-chroot" "example" {
|
||||
}
|
||||
`, false, PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: []*RequiredPlugins{
|
||||
{RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"amazon": {
|
||||
Name: "amazon",
|
||||
Source: "github.com/azr/amazon",
|
||||
Type: &addrs.Plugin{Hostname: "github.com", Namespace: "azr", Type: "amazon"},
|
||||
Requirement: VersionConstraint{
|
||||
Required: mustVersionConstraints(version.NewConstraint("~> v1.2.3")),
|
||||
},
|
||||
PluginDependencyReason: PluginDependencyExplicit,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
{"required_plugin_forked", PackerConfig{
|
||||
parser: getBasicParser(func(p *Parser) {
|
||||
p.PluginConfig.BuilderRedirects = map[string]string{
|
||||
"amazon-chroot": "github.com/hashicorp/amazon",
|
||||
}
|
||||
},
|
||||
)}, `
|
||||
packer {
|
||||
required_plugins {
|
||||
amazon = {
|
||||
source = "github.com/azr/amazon"
|
||||
version = "~> v1.2.3"
|
||||
}
|
||||
}
|
||||
} `, `
|
||||
source "amazon-chroot" "example" {
|
||||
}
|
||||
`, false, PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: []*RequiredPlugins{
|
||||
{RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"amazon": {
|
||||
Name: "amazon",
|
||||
Source: "github.com/azr/amazon",
|
||||
Type: &addrs.Plugin{Hostname: "github.com", Namespace: "azr", Type: "amazon"},
|
||||
Requirement: VersionConstraint{
|
||||
Required: mustVersionConstraints(version.NewConstraint("~> v1.2.3")),
|
||||
},
|
||||
PluginDependencyReason: PluginDependencyExplicit,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
{"missing-required-plugin-for-pre-defined-builder", PackerConfig{
|
||||
parser: getBasicParser(func(p *Parser) {
|
||||
p.PluginConfig.BuilderRedirects = map[string]string{
|
||||
"amazon-ebs": "github.com/hashicorp/amazon",
|
||||
}
|
||||
},
|
||||
)},
|
||||
`
|
||||
packer {
|
||||
}`, `
|
||||
# amazon-ebs is mocked in getBasicParser()
|
||||
source "amazon-ebs" "example" {
|
||||
}
|
||||
`,
|
||||
false,
|
||||
PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: nil,
|
||||
},
|
||||
}},
|
||||
{"missing-required-plugin-for-builder", PackerConfig{
|
||||
parser: getBasicParser(func(p *Parser) {
|
||||
p.PluginConfig.BuilderRedirects = map[string]string{
|
||||
"amazon-chroot": "github.com/hashicorp/amazon",
|
||||
}
|
||||
},
|
||||
)},
|
||||
`
|
||||
packer {
|
||||
}`, `
|
||||
source "amazon-chroot" "example" {
|
||||
}
|
||||
`,
|
||||
false,
|
||||
PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: []*RequiredPlugins{
|
||||
{RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"amazon": {
|
||||
Name: "amazon",
|
||||
Source: "github.com/hashicorp/amazon",
|
||||
Type: &addrs.Plugin{Hostname: "github.com", Namespace: "hashicorp", Type: "amazon"},
|
||||
Requirement: VersionConstraint{
|
||||
Required: nil,
|
||||
},
|
||||
PluginDependencyReason: PluginDependencyImplicit,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
{"missing-required-plugin-for-provisioner", PackerConfig{
|
||||
parser: getBasicParser(func(p *Parser) {
|
||||
p.PluginConfig.ProvisionerRedirects = map[string]string{
|
||||
"ansible-local": "github.com/ansible/ansible",
|
||||
}
|
||||
},
|
||||
)},
|
||||
`
|
||||
packer {
|
||||
}`, `
|
||||
build {
|
||||
provisioner "ansible-local" {}
|
||||
}
|
||||
`,
|
||||
false,
|
||||
PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: []*RequiredPlugins{
|
||||
{RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"ansible": {
|
||||
Name: "ansible",
|
||||
Source: "github.com/ansible/ansible",
|
||||
Type: &addrs.Plugin{Hostname: "github.com", Namespace: "ansible", Type: "ansible"},
|
||||
Requirement: VersionConstraint{
|
||||
Required: nil,
|
||||
},
|
||||
PluginDependencyReason: PluginDependencyImplicit,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
{"missing-required-plugin-for-post-processor", PackerConfig{
|
||||
parser: getBasicParser(func(p *Parser) {
|
||||
p.PluginConfig.PostProcessorRedirects = map[string]string{
|
||||
"docker-push": "github.com/hashicorp/docker",
|
||||
}
|
||||
},
|
||||
)},
|
||||
`
|
||||
packer {
|
||||
}`, `
|
||||
build {
|
||||
post-processor "docker-push" {}
|
||||
}
|
||||
`,
|
||||
false,
|
||||
PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: []*RequiredPlugins{
|
||||
{RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"docker": {
|
||||
Name: "docker",
|
||||
Source: "github.com/hashicorp/docker",
|
||||
Type: &addrs.Plugin{Hostname: "github.com", Namespace: "hashicorp", Type: "docker"},
|
||||
Requirement: VersionConstraint{
|
||||
Required: nil,
|
||||
},
|
||||
PluginDependencyReason: PluginDependencyImplicit,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
{"missing-required-plugin-for-nested-post-processor", PackerConfig{
|
||||
parser: getBasicParser(func(p *Parser) {
|
||||
p.PluginConfig.PostProcessorRedirects = map[string]string{
|
||||
"docker-push": "github.com/hashicorp/docker",
|
||||
}
|
||||
},
|
||||
)},
|
||||
`
|
||||
packer {
|
||||
}`, `
|
||||
build {
|
||||
post-processors {
|
||||
post-processor "docker-push" {
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
false,
|
||||
PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: []*RequiredPlugins{
|
||||
{RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"docker": {
|
||||
Name: "docker",
|
||||
Source: "github.com/hashicorp/docker",
|
||||
Type: &addrs.Plugin{Hostname: "github.com", Namespace: "hashicorp", Type: "docker"},
|
||||
Requirement: VersionConstraint{
|
||||
Required: nil,
|
||||
},
|
||||
PluginDependencyReason: PluginDependencyImplicit,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
|
||||
{"required-plugin-renamed", PackerConfig{
|
||||
parser: getBasicParser(func(p *Parser) {
|
||||
p.PluginConfig.BuilderRedirects = map[string]string{
|
||||
"amazon-chroot": "github.com/hashicorp/amazon",
|
||||
}
|
||||
},
|
||||
)},
|
||||
`
|
||||
packer {
|
||||
required_plugins {
|
||||
amazon-v1 = {
|
||||
source = "github.com/hashicorp/amazon"
|
||||
version = "~> v1.0"
|
||||
}
|
||||
}
|
||||
}`, `
|
||||
source "amazon-v1-chroot" "example" {
|
||||
}
|
||||
source "amazon-chroot" "example" {
|
||||
}
|
||||
`,
|
||||
false,
|
||||
PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: []*RequiredPlugins{
|
||||
{RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"amazon-v1": {
|
||||
Name: "amazon-v1",
|
||||
Source: "github.com/hashicorp/amazon",
|
||||
Type: &addrs.Plugin{Hostname: "github.com", Namespace: "hashicorp", Type: "amazon"},
|
||||
Requirement: VersionConstraint{
|
||||
Required: mustVersionConstraints(version.NewConstraint("~> v1.0")),
|
||||
},
|
||||
PluginDependencyReason: PluginDependencyExplicit,
|
||||
},
|
||||
}},
|
||||
{RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"amazon": {
|
||||
Name: "amazon",
|
||||
Source: "github.com/hashicorp/amazon",
|
||||
Type: &addrs.Plugin{Hostname: "github.com", Namespace: "hashicorp", Type: "amazon"},
|
||||
Requirement: VersionConstraint{
|
||||
Required: nil,
|
||||
},
|
||||
PluginDependencyReason: PluginDependencyImplicit,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := tt.cfg
|
||||
file, diags := cfg.parser.ParseHCL([]byte(tt.requirePlugins), "required_plugins.pkr.hcl")
|
||||
if len(diags) > 0 {
|
||||
t.Fatal(diags)
|
||||
}
|
||||
if diags := cfg.decodeRequiredPluginsBlock(file); len(diags) > 0 {
|
||||
t.Fatal(diags)
|
||||
}
|
||||
|
||||
rest, diags := cfg.parser.ParseHCL([]byte(tt.restOfTemplate), "rest.pkr.hcl")
|
||||
if len(diags) > 0 {
|
||||
t.Fatal(diags)
|
||||
}
|
||||
if gotDiags := cfg.decodeImplicitRequiredPluginsBlocks(rest); (len(gotDiags) > 0) != tt.wantDiags {
|
||||
t.Fatal(gotDiags)
|
||||
}
|
||||
if diff := cmp.Diff(tt.wantConfig, cfg, cmpOpts...); diff != "" {
|
||||
t.Errorf("PackerConfig.inferImplicitRequiredPluginFromBlocks() unexpected PackerConfig: %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package hcl2template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
@@ -170,3 +171,12 @@ var NoSource SourceRef
|
||||
func (r SourceRef) String() string {
|
||||
return fmt.Sprintf("%s.%s", r.Type, r.Name)
|
||||
}
|
||||
|
||||
func listAvailableSourceNames(srcs map[SourceRef]SourceBlock) []string {
|
||||
res := make([]string, 0, len(srcs))
|
||||
for k := range srcs {
|
||||
res = append(res, k.String())
|
||||
}
|
||||
sort.Strings(res)
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -152,9 +152,19 @@ func (v *Variable) validateValue(val VariableAssignment) (diags hcl.Diagnostics)
|
||||
}
|
||||
|
||||
// Value returns the last found value from the list of variable settings.
|
||||
func (v *Variable) Value() (cty.Value, hcl.Diagnostics) {
|
||||
func (v *Variable) Value() cty.Value {
|
||||
if len(v.Values) == 0 {
|
||||
return cty.UnknownVal(v.Type), hcl.Diagnostics{&hcl.Diagnostic{
|
||||
return cty.UnknownVal(v.Type)
|
||||
}
|
||||
val := v.Values[len(v.Values)-1]
|
||||
return val.Value
|
||||
}
|
||||
|
||||
// ValidateValue tells if the selected value for the Variable is valid according
|
||||
// to its validation settings.
|
||||
func (v *Variable) ValidateValue() hcl.Diagnostics {
|
||||
if len(v.Values) == 0 {
|
||||
return hcl.Diagnostics{&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Unset variable %q", v.Name),
|
||||
Detail: "A used variable must be set or have a default value; see " +
|
||||
@@ -163,8 +173,8 @@ func (v *Variable) Value() (cty.Value, hcl.Diagnostics) {
|
||||
Context: v.Range.Ptr(),
|
||||
}}
|
||||
}
|
||||
val := v.Values[len(v.Values)-1]
|
||||
return val.Value, v.validateValue(v.Values[len(v.Values)-1])
|
||||
|
||||
return v.validateValue(v.Values[len(v.Values)-1])
|
||||
}
|
||||
|
||||
type Variables map[string]*Variable
|
||||
@@ -177,15 +187,21 @@ func (variables Variables) Keys() []string {
|
||||
return keys
|
||||
}
|
||||
|
||||
func (variables Variables) Values() (map[string]cty.Value, hcl.Diagnostics) {
|
||||
func (variables Variables) Values() map[string]cty.Value {
|
||||
res := map[string]cty.Value{}
|
||||
var diags hcl.Diagnostics
|
||||
for k, v := range variables {
|
||||
value, moreDiags := v.Value()
|
||||
diags = append(diags, moreDiags...)
|
||||
value := v.Value()
|
||||
res[k] = value
|
||||
}
|
||||
return res, diags
|
||||
return res
|
||||
}
|
||||
|
||||
func (variables Variables) ValidateValues() hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
for _, v := range variables {
|
||||
diags = append(diags, v.ValidateValue()...)
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
// decodeVariable decodes a variable key and value into Variables
|
||||
|
||||
@@ -868,7 +868,7 @@ func TestVariables_collectVariableValues(t *testing.T) {
|
||||
}
|
||||
values := map[string]cty.Value{}
|
||||
for k, v := range tt.variables {
|
||||
value, diag := v.Value()
|
||||
value, diag := v.Value(), v.ValidateValue()
|
||||
if diag != nil {
|
||||
t.Fatalf("Value %s: %v", k, diag)
|
||||
}
|
||||
|
||||
@@ -126,6 +126,15 @@ func realMain() int {
|
||||
// wrappedMain is called only when we're wrapped by panicwrap and
|
||||
// returns the exit status to exit with.
|
||||
func wrappedMain() int {
|
||||
// WARNING: WrappedMain causes unexpected behaviors when writing to stderr
|
||||
// and stdout. Anything in this function written to stderr will be captured
|
||||
// by the logger, but will not be written to the terminal. Anything in
|
||||
// this function written to standard out must be prefixed with ErrorPrefix
|
||||
// or OutputPrefix to be sent to the right terminal stream, but adding
|
||||
// these prefixes can cause nondeterministic results for output from
|
||||
// other, asynchronous methods. Try to avoid modifying output in this
|
||||
// function if at all possible.
|
||||
|
||||
// If there is no explicit number of Go threads to use, then set it
|
||||
if os.Getenv("GOMAXPROCS") == "" {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
@@ -152,6 +161,10 @@ func wrappedMain() int {
|
||||
// passed into commands like `packer build`
|
||||
config, err := loadConfig()
|
||||
if err != nil {
|
||||
// Writing to Stdout here so that the error message bypasses panicwrap. By using the
|
||||
// ErrorPrefix this output will be redirected to Stderr by the copyOutput func.
|
||||
// TODO: nywilken need to revisit this setup to better output errors to Stderr, and output to Stdout
|
||||
// without panicwrap
|
||||
fmt.Fprintf(os.Stdout, "%s Error loading configuration: \n\n%s\n", ErrorPrefix, err)
|
||||
return 1
|
||||
}
|
||||
@@ -166,6 +179,10 @@ func wrappedMain() int {
|
||||
|
||||
cacheDir, err := packersdk.CachePath()
|
||||
if err != nil {
|
||||
// Writing to Stdout here so that the error message bypasses panicwrap. By using the
|
||||
// ErrorPrefix this output will be redirected to Stderr by the copyOutput func.
|
||||
// TODO: nywilken need to revisit this setup to better output errors to Stderr, and output to Stdout
|
||||
// without panicwrap
|
||||
fmt.Fprintf(os.Stdout, "%s Error preparing cache directory: \n\n%s\n", ErrorPrefix, err)
|
||||
return 1
|
||||
}
|
||||
@@ -187,7 +204,8 @@ func wrappedMain() int {
|
||||
// Set this so that we don't get colored output in our machine-
|
||||
// readable UI.
|
||||
if err := os.Setenv("PACKER_NO_COLOR", "1"); err != nil {
|
||||
fmt.Fprintf(os.Stdout, "%s Packer failed to initialize UI: %s\n", ErrorPrefix, err)
|
||||
// Outputting error using Ui here to conform to the machine readable format.
|
||||
ui.Error(fmt.Sprintf("Packer failed to initialize UI: %s\n", err))
|
||||
return 1
|
||||
}
|
||||
} else {
|
||||
@@ -202,13 +220,16 @@ func wrappedMain() int {
|
||||
currentPID := os.Getpid()
|
||||
backgrounded, err := checkProcess(currentPID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stdout, "%s cannot determine if process is in "+
|
||||
"background: %s\n", ErrorPrefix, err)
|
||||
// Writing to Stderr will ensure that the output gets captured by panicwrap.
|
||||
// This error message and any other message writing to Stderr after this point will only show up with PACKER_LOG=1
|
||||
// TODO: nywilken need to revisit this setup to better output errors to Stderr, and output to Stdout without panicwrap.
|
||||
fmt.Fprintf(os.Stderr, "%s cannot determine if process is in background: %s\n", ErrorPrefix, err)
|
||||
}
|
||||
|
||||
if backgrounded {
|
||||
fmt.Fprintf(os.Stdout, "%s Running in background, not using a TTY\n", ErrorPrefix)
|
||||
fmt.Fprintf(os.Stderr, "%s Running in background, not using a TTY\n", ErrorPrefix)
|
||||
} else if TTY, err := openTTY(); err != nil {
|
||||
fmt.Fprintf(os.Stdout, "%s No tty available: %s\n", ErrorPrefix, err)
|
||||
fmt.Fprintf(os.Stderr, "%s No tty available: %s\n", ErrorPrefix, err)
|
||||
} else {
|
||||
basicUi.TTY = TTY
|
||||
basicUi.PB = &packer.UiProgressBar{}
|
||||
@@ -246,6 +267,10 @@ func wrappedMain() int {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Writing to Stdout here so that the error message bypasses panicwrap. By using the
|
||||
// ErrorPrefix this output will be redirected to Stderr by the copyOutput func.
|
||||
// TODO: nywilken need to revisit this setup to better output errors to Stderr, and output to Stdout
|
||||
// without panicwrap
|
||||
fmt.Fprintf(os.Stdout, "%s Error executing CLI: %s\n", ErrorPrefix, err)
|
||||
return 1
|
||||
}
|
||||
@@ -297,6 +322,67 @@ func loadConfig() (*config, error) {
|
||||
PluginMinPort: 10000,
|
||||
PluginMaxPort: 25000,
|
||||
KnownPluginFolders: packer.PluginFolders("."),
|
||||
|
||||
// BuilderRedirects
|
||||
BuilderRedirects: map[string]string{
|
||||
|
||||
// "amazon-chroot": "github.com/hashicorp/amazon",
|
||||
// "amazon-ebs": "github.com/hashicorp/amazon",
|
||||
// "amazon-ebssurrogate": "github.com/hashicorp/amazon",
|
||||
// "amazon-ebsvolume": "github.com/hashicorp/amazon",
|
||||
// "amazon-instance": "github.com/hashicorp/amazon",
|
||||
|
||||
// "azure-arm": "github.com/hashicorp/azure",
|
||||
// "azure-chroot": "github.com/hashicorp/azure",
|
||||
// "dtl": "github.com/hashicorp/azure",
|
||||
|
||||
// "docker": "github.com/hashicorp/docker",
|
||||
|
||||
// "googlecompute": "github.com/hashicorp/googlecompute",
|
||||
|
||||
// "parallels-iso": "github.com/hashicorp/parallels",
|
||||
// "parallels-pvm": "github.com/hashicorp/parallels",
|
||||
|
||||
// "qemu": "github.com/hashicorp/qemu",
|
||||
|
||||
// "vagrant": "github.com/hashicorp/vagrant",
|
||||
|
||||
// "virtualbox-iso": "github.com/hashicorp/virtualbox",
|
||||
// "virtualbox-ovf": "github.com/hashicorp/virtualbox",
|
||||
// "virtualbox-vm": "github.com/hashicorp/virtualbox",
|
||||
|
||||
// "vmware-iso": "github.com/hashicorp/vmware",
|
||||
// "vmware-vmx": "github.com/hashicorp/vmware",
|
||||
|
||||
// "vsphere-iso": "github.com/hashicorp/vsphere",
|
||||
// "vsphere-clone": "github.com/hashicorp/vsphere",
|
||||
},
|
||||
DatasourceRedirects: map[string]string{
|
||||
// "amazon-ami": "github.com/hashicorp/amazon",
|
||||
},
|
||||
ProvisionerRedirects: map[string]string{
|
||||
// "ansible": "github.com/hashicorp/ansible",
|
||||
// "ansible-local": "github.com/hashicorp/ansible",
|
||||
|
||||
// "azure-dtlartifact": "github.com/hashicorp/azure",
|
||||
},
|
||||
PostProcessorRedirects: map[string]string{
|
||||
// "amazon-import": "github.com/hashicorp/amazon",
|
||||
|
||||
// "docker-import": "github.com/hashicorp/docker",
|
||||
// "docker-push": "github.com/hashicorp/docker",
|
||||
// "docker-save": "github.com/hashicorp/docker",
|
||||
// "docker-tag": "github.com/hashicorp/docker",
|
||||
|
||||
// "googlecompute-export": "github.com/hashicorp/googlecompute",
|
||||
// "googlecompute-import": "github.com/hashicorp/googlecompute",
|
||||
|
||||
// "vagrant": "github.com/hashicorp/vagrant",
|
||||
// "vagrant-cloud": "github.com/hashicorp/vagrant",
|
||||
|
||||
// "vsphere": "github.com/hashicorp/vsphere",
|
||||
// "vsphere-template": "github.com/hashicorp/vsphere",
|
||||
},
|
||||
}
|
||||
if err := config.Plugins.Discover(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -38,6 +38,9 @@ type Requirement struct {
|
||||
// VersionConstraints as defined by user. Empty ( to be avoided ) means
|
||||
// highest found version.
|
||||
VersionConstraints version.Constraints
|
||||
|
||||
// was this require implicitly guessed ?
|
||||
Implicit bool
|
||||
}
|
||||
|
||||
type BinaryInstallationOptions struct {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user