Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00da5f634d | |||
| c9410fa1a5 |
+3
-16
@@ -37,19 +37,15 @@ commands:
|
|||||||
parameters:
|
parameters:
|
||||||
GOOS:
|
GOOS:
|
||||||
type: string
|
type: string
|
||||||
GOARCH:
|
|
||||||
default: "amd64"
|
|
||||||
type: string
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: GOOS=<< parameters.GOOS >> GOARCH=<<parameters.GOARCH>> go build -ldflags="-s -w -X github.com/hashicorp/packer/version.GitCommit=${CIRCLE_SHA1}" -o ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >> .
|
- run: 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 >>_<< parameters.GOARCH >>.zip ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >>
|
- run: zip ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH).zip ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH)
|
||||||
- run: rm ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >>
|
- run: rm ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH)
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: .
|
root: .
|
||||||
paths:
|
paths:
|
||||||
- ./pkg/
|
- ./pkg/
|
||||||
|
|
||||||
# Golang CircleCI 2.0 configuration file
|
# Golang CircleCI 2.0 configuration file
|
||||||
#
|
#
|
||||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
||||||
@@ -127,13 +123,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- build-and-persist-packer-binary:
|
- build-and-persist-packer-binary:
|
||||||
GOOS: darwin
|
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:
|
build_freebsd:
|
||||||
executor: golang
|
executor: golang
|
||||||
working_directory: /go/src/github.com/hashicorp/packer
|
working_directory: /go/src/github.com/hashicorp/packer
|
||||||
@@ -214,7 +203,6 @@ workflows:
|
|||||||
jobs:
|
jobs:
|
||||||
- build_linux
|
- build_linux
|
||||||
- build_darwin
|
- build_darwin
|
||||||
- build_darwin_arm64
|
|
||||||
- build_windows
|
- build_windows
|
||||||
- build_freebsd
|
- build_freebsd
|
||||||
- build_openbsd
|
- build_openbsd
|
||||||
@@ -223,7 +211,6 @@ workflows:
|
|||||||
requires:
|
requires:
|
||||||
- build_linux
|
- build_linux
|
||||||
- build_darwin
|
- build_darwin
|
||||||
- build_darwin_arm64
|
|
||||||
- build_windows
|
- build_windows
|
||||||
- build_freebsd
|
- build_freebsd
|
||||||
- build_openbsd
|
- build_openbsd
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
*.mdx text eol=lf
|
*.mdx text eol=lf
|
||||||
*.ps1 text eol=lf
|
*.ps1 text eol=lf
|
||||||
*.hcl text eol=lf
|
*.hcl text eol=lf
|
||||||
*.tmpl text eol=lf
|
|
||||||
*.txt text eol=lf
|
*.txt text eol=lf
|
||||||
go.mod text eol=lf
|
go.mod text eol=lf
|
||||||
go.sum text eol=lf
|
go.sum text eol=lf
|
||||||
|
|||||||
@@ -11,12 +11,6 @@ contribute to the project, read on. This document will cover what we're looking
|
|||||||
for. By addressing all the points we're looking for, it raises the chances we
|
for. By addressing all the points we're looking for, it raises the chances we
|
||||||
can quickly merge or address your contributions.
|
can quickly merge or address your contributions.
|
||||||
|
|
||||||
When contributing in any way to the Packer project (new issue, PR, etc), please
|
|
||||||
be aware that our team identifies with many gender pronouns. Please remember to
|
|
||||||
use nonbinary pronouns (they/them) and gender neutral language ("Hello folks")
|
|
||||||
when addressing our team. For more reading on our code of conduct, please see the
|
|
||||||
[HashiCorp community guidelines](https://www.hashicorp.com/community-guidelines).
|
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
|
|
||||||
### Reporting an Issue
|
### Reporting an Issue
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
const fetchPluginDocs = require("../../website/components/remote-plugin-docs/utils/fetch-plugin-docs");
|
|
||||||
|
|
||||||
const COLOR_RESET = "\x1b[0m";
|
|
||||||
const COLOR_GREEN = "\x1b[32m";
|
|
||||||
const COLOR_BLUE = "\x1b[34m";
|
|
||||||
const COLOR_RED = "\x1b[31m";
|
|
||||||
|
|
||||||
async function checkPluginDocs() {
|
|
||||||
const failureMessages = [];
|
|
||||||
const pluginsPath = "website/data/docs-remote-plugins.json";
|
|
||||||
const pluginsFile = fs.readFileSync(path.join(process.cwd(), pluginsPath));
|
|
||||||
const pluginEntries = JSON.parse(pluginsFile);
|
|
||||||
const entriesCount = pluginEntries.length;
|
|
||||||
console.log(`\nResolving plugin docs from ${entriesCount} repositories …`);
|
|
||||||
for (var i = 0; i < entriesCount; i++) {
|
|
||||||
const pluginEntry = pluginEntries[i];
|
|
||||||
const { title, repo, version } = pluginEntry;
|
|
||||||
console.log(`\n${COLOR_BLUE}${repo}${COLOR_RESET} | ${title}`);
|
|
||||||
console.log(`Fetching docs from release "${version}" …`);
|
|
||||||
try {
|
|
||||||
const undefinedProps = ["title", "repo", "version", "path"].filter(
|
|
||||||
(key) => typeof pluginEntry[key] == "undefined"
|
|
||||||
);
|
|
||||||
if (undefinedProps.length > 0) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to validate plugin docs config. Undefined configuration properties ${JSON.stringify(
|
|
||||||
undefinedProps
|
|
||||||
)} found for "${
|
|
||||||
title || pluginEntry.path || repo
|
|
||||||
}". In "website/data/docs-remote-plugins.json", please ensure the missing properties ${JSON.stringify(
|
|
||||||
undefinedProps
|
|
||||||
)} are defined. Additional information on this configuration can be found in "website/README.md".`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const docsMdxFiles = await fetchPluginDocs({ repo, tag: version });
|
|
||||||
const mdxFilesByComponent = docsMdxFiles.reduce((acc, mdxFile) => {
|
|
||||||
const componentType = mdxFile.filePath.split("/")[1];
|
|
||||||
if (!acc[componentType]) acc[componentType] = [];
|
|
||||||
acc[componentType].push(mdxFile);
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
console.log(`${COLOR_GREEN}Found valid docs:${COLOR_RESET}`);
|
|
||||||
Object.keys(mdxFilesByComponent).forEach((component) => {
|
|
||||||
const componentFiles = mdxFilesByComponent[component];
|
|
||||||
console.log(` ${component}`);
|
|
||||||
componentFiles.forEach(({ filePath }) => {
|
|
||||||
const pathFromComponent = filePath.split("/").slice(2).join("/");
|
|
||||||
console.log(` ├── ${pathFromComponent}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.log(`${COLOR_RED}${err}${COLOR_RESET}`);
|
|
||||||
failureMessages.push(`\n${COLOR_RED}× ${repo}: ${COLOR_RESET}${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failureMessages.length === 0) {
|
|
||||||
console.log(
|
|
||||||
`\n---\n\n${COLOR_GREEN}Summary: Successfully resolved all plugin docs.`
|
|
||||||
);
|
|
||||||
pluginEntries.forEach((e) =>
|
|
||||||
console.log(`${COLOR_GREEN}✓ ${e.repo}${COLOR_RESET}`)
|
|
||||||
);
|
|
||||||
console.log("");
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`\n---\n\n${COLOR_RED}Summary: Failed to fetch docs for ${failureMessages.length} plugin(s):`
|
|
||||||
);
|
|
||||||
failureMessages.forEach((err) => console.log(err));
|
|
||||||
console.log("");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkPluginDocs();
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#
|
|
||||||
# This GitHub action checks plugin repositories for valid docs.
|
|
||||||
#
|
|
||||||
# This provides a quick assessment on PRs of whether
|
|
||||||
# there might be issues with docs in plugin repositories.
|
|
||||||
#
|
|
||||||
# This is intended to help debug Vercel build issues, which
|
|
||||||
# may or may not be related to docs in plugin repositories.
|
|
||||||
|
|
||||||
name: "website: Check plugin docs"
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "website/**"
|
|
||||||
schedule:
|
|
||||||
- cron: "45 0 * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check-plugin-docs:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: npm i isomorphic-unfetch adm-zip gray-matter
|
|
||||||
- name: Fetch and validate plugin docs
|
|
||||||
run: node .github/workflows/check-plugin-docs.js
|
|
||||||
@@ -7,7 +7,7 @@ name: Check markdown links on modified website files
|
|||||||
jobs:
|
jobs:
|
||||||
vercel-deployment-poll:
|
vercel-deployment-poll:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 5 #cancel job if no deployment is found within x minutes
|
timeout-minutes: 3 #cancel job if no deployment is found within x minutes
|
||||||
outputs:
|
outputs:
|
||||||
url: ${{ steps.waitForVercelPreviewDeployment.outputs.url }}
|
url: ${{ steps.waitForVercelPreviewDeployment.outputs.url }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -22,21 +22,3 @@ poll "closed_issue_locker" "locker" {
|
|||||||
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.
|
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
poll "label_issue_migrater" "remote_plugin_migrater" {
|
|
||||||
schedule = "0 20 * * * *"
|
|
||||||
new_owner = "hashicorp"
|
|
||||||
repo_prefix = "packer-plugin-"
|
|
||||||
label_prefix = "remote-plugin/"
|
|
||||||
excluded_label_prefixes = ["communicator/"]
|
|
||||||
excluded_labels = ["build", "core", "new-plugin-contribution", "website"]
|
|
||||||
|
|
||||||
issue_header = <<-EOF
|
|
||||||
_This issue was originally opened by @${var.user} as ${var.repository}#${var.issue_number}. It was migrated here as a result of the [Packer plugin split](https://github.com/hashicorp/packer/issues/8610#issuecomment-770034737). The original body of the issue is below._
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
EOF
|
|
||||||
migrated_comment = "This issue has been automatically migrated to ${var.repository}#${var.issue_number} because it looks like an issue with that plugin. If you believe this is _not_ an issue with the plugin, please reply to ${var.repository}#${var.issue_number}."
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
+4
-123
@@ -1,149 +1,30 @@
|
|||||||
## 1.7.2 (Upcoming)
|
## 1.7.1 (Upcoming)
|
||||||
|
|
||||||
### IMPROVEMENTS:
|
|
||||||
|
|
||||||
* builder/alicloud: Add `ramrole` configuration to ECS instance. [GH-10845]
|
|
||||||
|
|
||||||
### BUG FIXES:
|
|
||||||
|
|
||||||
* builder/proxmox: Update Proxmox Go API to ensure only the first non-loopback
|
|
||||||
IPv4 address gets returned. [GH-10858]
|
|
||||||
* builder/vsphere: Fix primary disk resize on clone. [GH-10848]
|
|
||||||
* core: Fix bug where call to "packer version" sent output to stderr instead of
|
|
||||||
stdout. [GH-10850]
|
|
||||||
|
|
||||||
## 1.7.1 (March 31, 2021)
|
|
||||||
|
|
||||||
### NOTES:
|
|
||||||
|
|
||||||
* builder/amazon: Has been vendored in this release and will no longer be
|
|
||||||
updated with Packer core. In Packer v1.8.0 the plugin will be removed
|
|
||||||
entirely. The `amazon` components will continue to work as expected until
|
|
||||||
then, but for the latest offerings of the Amazon plugin, users are
|
|
||||||
encourage to use the `packer init` command to install the latest release
|
|
||||||
version. For more details see [Installing Packer
|
|
||||||
Plugins](https://www.packer.io/docs/plugins#installing-plugins)
|
|
||||||
* builder/docker: Has been vendored in this release and will no longer be
|
|
||||||
updated with Packer core. In Packer v1.8.0 the plugin will be removed
|
|
||||||
entirely. The `docker` builder will continue to work as expected until
|
|
||||||
then, but for the latest offerings of the Docker plugin, users are
|
|
||||||
encourage to use the `packer init` command to install the latest release
|
|
||||||
version. For more details see [Installing Packer
|
|
||||||
Plugins](https://www.packer.io/docs/plugins#installing-plugins)
|
|
||||||
* darwin/arm64: Packer now includes the darwin/arm64 binary to its releases to
|
|
||||||
supports the new OSX M1. [GH-10804]
|
|
||||||
* 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)
|
|
||||||
* 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
|
|
||||||
work as expected until then, but for the latest offerings of the Exoscale
|
|
||||||
plugin, users are encourage to use the `packer init` command to install the
|
|
||||||
latest release version. For more details see [Exoscale Plugin
|
|
||||||
Repostiroy](https://github.com/exoscale/packer-plugin-exoscale). [GH-10709]
|
|
||||||
|
|
||||||
### IMPROVEMENTS
|
### IMPROVEMENTS
|
||||||
* builder/amazon: allow creation of ebs snapshots without volumes. [GH-9591]
|
* builder/amazon: allow creation of ebs snapshots wihtout volumes. [GH-9591]
|
||||||
* builder/amazon: Fix issue for multi-region AMI build that fail when
|
|
||||||
encrypting with KMS and sharing across accounts. [GH-10754]
|
|
||||||
* builder/azure: Add client_cert_token_timeout option. [GH-10528]
|
|
||||||
* builder/google: Make Windows password timeout configurable. [GH-10727]
|
|
||||||
* builder/google: Update public GCP image project as gce-uefi-images are
|
|
||||||
deprecated. [GH-10724]
|
|
||||||
* builder/oracle-oci: Update Oracle Go SDK to add support for OCI flexible
|
|
||||||
shapes. [GH-10833]
|
|
||||||
* builder/proxmox: Allow using API tokens for Proxmox authentication.
|
|
||||||
[GH-10797]
|
|
||||||
* builder/qemu: Added firmware option. [GH-10683]
|
|
||||||
* builder/scaleway: add support for timeout in shutdown step. [GH-10503]
|
* builder/scaleway: add support for timeout in shutdown step. [GH-10503]
|
||||||
* builder/vagrant: Fix logging to be clearer when Vagrant builder overrides
|
|
||||||
values retrieved from vagrant's ssh_config call. [GH-10743]
|
|
||||||
* builder/virtualbox: Added ISO builder option to create additional disks.
|
|
||||||
[GH-10674]
|
|
||||||
* builder/virtualbox: Add options for nested virtualisation and RTC time base.
|
|
||||||
[GH-10736]
|
|
||||||
* builder/virtualbox: Add template options for chipset, firmware, nic, graphics
|
* builder/virtualbox: Add template options for chipset, firmware, nic, graphics
|
||||||
controller, and audio controller. [GH-10671]
|
controller, and audio controller. [GH-10671]
|
||||||
* builder/virtualbox: Support for "virtio" storage and ISO drive. [GH-10632]
|
* builder/virtualbox: Support for "virtio" storage and ISO drive. [GH-10632]
|
||||||
* builder/vmware: Added "attach_snapshot" parameter to vmware vmx builder.
|
* builder/vmware: Added "attach_snapshot" parameter to vmware vmx builder.
|
||||||
[GH-10651]
|
[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.
|
* core: Change template parsing error to include warning about file extensions.
|
||||||
[GH-10652]
|
[GH-10652]
|
||||||
* core: Update to gopsutil v3.21.1 to allow builds to work for darwin arm64.
|
* hcl2_upgrade: hcl2_upgrade command can now upgrade json var-files [GH-10676]
|
||||||
[GH-10697]
|
|
||||||
* provisioner/inspec: Allow non-zero exit codes for inspec provisioner.
|
|
||||||
[GH-10723]
|
|
||||||
|
|
||||||
### BUG FIXES
|
### BUG FIXES
|
||||||
* buider/azure: Update builder to ensure a proper clean up Azure temporary
|
|
||||||
managed Os disks. [GH-10713]
|
|
||||||
* builder/amazon: Update amazon SDK to fix an SSO login issue. [GH-10668]
|
* builder/amazon: Update amazon SDK to fix an SSO login issue. [GH-10668]
|
||||||
* builder/azure: Don't overwrite subscription id if unset. [GH-10659]
|
* builder/azure: Don't overwrite subscription id if unset. [GH-10659]
|
||||||
* builder/azure: Set default for the parameter client_cert_token_timeout
|
|
||||||
[GH-10783]
|
|
||||||
* builder/google: Add new configuration field `windows_password_timeout` to
|
|
||||||
allow user to set configurable timeouts. [GH-10727]
|
|
||||||
* builder/hyperv: Make Packer respect winrm_host flag in winrm connect func.
|
|
||||||
[GH-10748]
|
|
||||||
* builder/openstack: Make Packer respect winrm_host flag in winrm connect func.
|
|
||||||
[GH-10748]
|
|
||||||
* builder/oracle-oci: Update Oracle Go SDK to fix issue with reading key file.
|
* builder/oracle-oci: Update Oracle Go SDK to fix issue with reading key file.
|
||||||
[GH-10560] [GH-10774]
|
[GH-10560]
|
||||||
* 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
|
|
||||||
templates. [GH-10772]
|
|
||||||
* builder/qemu: Make Packer respect winrm_host flag in winrm connect func.
|
|
||||||
[GH-10748]
|
|
||||||
* builder/virtualbox: Make Packer respect winrm_host flag in winrm connect
|
|
||||||
func. [GH-10748]
|
|
||||||
* builder/vmware: Added a fallback file check when trying to determine the
|
* builder/vmware: Added a fallback file check when trying to determine the
|
||||||
network-mapping configuration. [GH-10543]
|
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
|
* core/hcl2_upgrade: Make hcl2_upgrade command correctly translate
|
||||||
pause_before. [GH-10654]
|
pause_before. [GH-10654]
|
||||||
* core/hcl2_upgrade: Make json variables using template engines get stored as
|
|
||||||
locals so they can be properly interpolated. [GH-10685]
|
|
||||||
* core/init: Fixes issue where `packer init` was failing to install valid
|
|
||||||
plugins containing a 'v' within its name. [GH-10760]
|
|
||||||
* core: Packer will now show a proper error message when failing to load the
|
|
||||||
contents of PACKER_CONFIG. [GH-10766]
|
|
||||||
* core: Pin Packer to Golang 1.16 to fix code generation issues. [GH-10702]
|
|
||||||
* core: Templates previously could not interpolate the environment variable
|
* core: Templates previously could not interpolate the environment variable
|
||||||
PACKER_LOG_PATH. [GH-10660]
|
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.
|
* provisioner/chef-solo: HCL2 templates can support the json_string option.
|
||||||
[GH-10655]
|
[GH-10655]
|
||||||
* provisioner/inspec: Add new configuration field `valid_exit_codes` to allow
|
|
||||||
for non-zero exit codes. [GH-10723]
|
|
||||||
* provisioner/salt-masterless: Update urls for the bootstrap scripts used by
|
|
||||||
salt-masterless provide. [GH-10755]
|
|
||||||
|
|
||||||
## 1.7.0 (February 17, 2021)
|
## 1.7.0 (February 17, 2021)
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ package:
|
|||||||
@sh -c "$(CURDIR)/scripts/dist.sh $(VERSION)"
|
@sh -c "$(CURDIR)/scripts/dist.sh $(VERSION)"
|
||||||
|
|
||||||
install-build-deps: ## Install dependencies for bin build
|
install-build-deps: ## Install dependencies for bin build
|
||||||
@go install github.com/mitchellh/gox@v1.0.1
|
@go get github.com/mitchellh/gox
|
||||||
|
|
||||||
install-gen-deps: ## Install dependencies for code generation
|
install-gen-deps: ## Install dependencies for code generation
|
||||||
# to avoid having to tidy our go deps, we `go get` our binaries from a temp
|
# to avoid having to tidy our go deps, we `go get` our binaries from a temp
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
|
|||||||
InstanceType: b.config.InstanceType,
|
InstanceType: b.config.InstanceType,
|
||||||
UserData: b.config.UserData,
|
UserData: b.config.UserData,
|
||||||
UserDataFile: b.config.UserDataFile,
|
UserDataFile: b.config.UserDataFile,
|
||||||
RamRoleName: b.config.RamRoleName,
|
|
||||||
RegionId: b.config.AlicloudRegion,
|
RegionId: b.config.AlicloudRegion,
|
||||||
InternetChargeType: b.config.InternetChargeType,
|
InternetChargeType: b.config.InternetChargeType,
|
||||||
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
|
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ type FlatConfig struct {
|
|||||||
AlicloudSourceImage *string `mapstructure:"source_image" required:"true" cty:"source_image" hcl:"source_image"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
||||||
@@ -206,7 +205,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"source_image": &hcldec.AttrSpec{Name: "source_image", Type: cty.String, Required: false},
|
"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},
|
"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},
|
"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_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},
|
"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},
|
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ type RunConfig struct {
|
|||||||
// E.g., Sysprep a windows which may shutdown the instance within its command.
|
// E.g., Sysprep a windows which may shutdown the instance within its command.
|
||||||
// The default value is false.
|
// The default value is false.
|
||||||
DisableStopInstance bool `mapstructure:"disable_stop_instance" required:"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
|
// ID of the security group to which a newly
|
||||||
// created instance belongs. Mutual access is allowed between instances in one
|
// created instance belongs. Mutual access is allowed between instances in one
|
||||||
// security group. If not specified, the newly created instance will be added
|
// security group. If not specified, the newly created instance will be added
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ type stepCreateAlicloudInstance struct {
|
|||||||
UserData string
|
UserData string
|
||||||
UserDataFile string
|
UserDataFile string
|
||||||
instanceId string
|
instanceId string
|
||||||
RamRoleName string
|
|
||||||
RegionId string
|
RegionId string
|
||||||
InternetChargeType string
|
InternetChargeType string
|
||||||
InternetMaxBandwidthOut int
|
InternetMaxBandwidthOut int
|
||||||
@@ -116,7 +115,6 @@ func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep.
|
|||||||
request.RegionId = s.RegionId
|
request.RegionId = s.RegionId
|
||||||
request.InstanceType = s.InstanceType
|
request.InstanceType = s.InstanceType
|
||||||
request.InstanceName = s.InstanceName
|
request.InstanceName = s.InstanceName
|
||||||
request.RamRoleName = s.RamRoleName
|
|
||||||
request.ZoneId = s.ZoneId
|
request.ZoneId = s.ZoneId
|
||||||
|
|
||||||
sourceImage := state.Get("source_image").(*ecs.Image)
|
sourceImage := state.Get("source_image").(*ecs.Image)
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,6 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/chroot"
|
"github.com/hashicorp/packer-plugin-sdk/chroot"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
@@ -24,6 +23,7 @@ import (
|
|||||||
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
|
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||||
|
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The unique ID for this builder
|
// The unique ID for this builder
|
||||||
+1
-1
@@ -4,8 +4,8 @@ package chroot
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
"github.com/hashicorp/packer-plugin-amazon/builder/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||||
|
"github.com/hashicorp/packer/builder/amazon/common"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
package chroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testConfig() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"ami_name": "foo",
|
||||||
|
"source_ami": "foo",
|
||||||
|
"region": "us-east-1",
|
||||||
|
// region validation logic is checked in ami_config_test
|
||||||
|
"skip_region_validation": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||||
|
var raw interface{}
|
||||||
|
raw = &Builder{}
|
||||||
|
if _, ok := raw.(packersdk.Builder); !ok {
|
||||||
|
t.Fatalf("Builder should be a builder")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Test good
|
||||||
|
config["ami_name"] = "foo"
|
||||||
|
config["skip_region_validation"] = true
|
||||||
|
_, warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test bad
|
||||||
|
config["ami_name"] = "foo {{"
|
||||||
|
b = Builder{}
|
||||||
|
_, warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test bad
|
||||||
|
delete(config, "ami_name")
|
||||||
|
b = Builder{}
|
||||||
|
_, warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ChrootMounts(t *testing.T) {
|
||||||
|
b := &Builder{}
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
config["chroot_mounts"] = nil
|
||||||
|
_, warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ChrootMountsBadDefaults(t *testing.T) {
|
||||||
|
b := &Builder{}
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
config["chroot_mounts"] = [][]string{
|
||||||
|
{"bad"},
|
||||||
|
}
|
||||||
|
_, warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestBuilderPrepare_SourceAmi(t *testing.T) {
|
||||||
|
b := &Builder{}
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
config["source_ami"] = ""
|
||||||
|
_, warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
config["source_ami"] = "foo"
|
||||||
|
_, warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_CommandWrapper(t *testing.T) {
|
||||||
|
b := &Builder{}
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
config["command_wrapper"] = "echo hi; {{.Command}}"
|
||||||
|
_, warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_CopyFiles(t *testing.T) {
|
||||||
|
b := &Builder{}
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
_, warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.config.CopyFiles) != 1 && b.config.CopyFiles[0] != "/etc/resolv.conf" {
|
||||||
|
t.Errorf("Was expecting default value for copy_files.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_CopyFilesNoDefault(t *testing.T) {
|
||||||
|
b := &Builder{}
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
config["copy_files"] = []string{}
|
||||||
|
_, warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.config.CopyFiles) > 0 {
|
||||||
|
t.Errorf("Was expecting no default value for copy_files. Found %v",
|
||||||
|
b.config.CopyFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_RootDeviceNameAndAMIMappings(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
config["root_device_name"] = "/dev/sda"
|
||||||
|
config["ami_block_device_mappings"] = []interface{}{map[string]string{}}
|
||||||
|
config["root_volume_size"] = 15
|
||||||
|
_, warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) == 0 {
|
||||||
|
t.Fatal("Missing warning, stating block device mappings will be overwritten")
|
||||||
|
} else if len(warnings) > 1 {
|
||||||
|
t.Fatalf("excessive warnings: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_AMIMappingsNoRootDeviceName(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
config["ami_block_device_mappings"] = []interface{}{map[string]string{}}
|
||||||
|
_, warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should have error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_RootDeviceNameNoAMIMappings(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
config["root_device_name"] = "/dev/sda"
|
||||||
|
_, warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should have error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
generatedData, warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
if len(generatedData) == 0 {
|
||||||
|
t.Fatalf("Generated data should not be empty")
|
||||||
|
}
|
||||||
|
if generatedData[0] != "SourceAMIName" {
|
||||||
|
t.Fatalf("Generated data should contain SourceAMIName")
|
||||||
|
}
|
||||||
|
if generatedData[1] != "BuildRegion" {
|
||||||
|
t.Fatalf("Generated data should contain BuildRegion")
|
||||||
|
}
|
||||||
|
if generatedData[2] != "SourceAMI" {
|
||||||
|
t.Fatalf("Generated data should contain SourceAMI")
|
||||||
|
}
|
||||||
|
if generatedData[3] != "SourceAMICreationDate" {
|
||||||
|
t.Fatalf("Generated data should contain SourceAMICreationDate")
|
||||||
|
}
|
||||||
|
if generatedData[4] != "SourceAMIOwner" {
|
||||||
|
t.Fatalf("Generated data should contain SourceAMIOwner")
|
||||||
|
}
|
||||||
|
if generatedData[5] != "SourceAMIOwnerName" {
|
||||||
|
t.Fatalf("Generated data should contain SourceAMIOwnerName")
|
||||||
|
}
|
||||||
|
if generatedData[6] != "Device" {
|
||||||
|
t.Fatalf("Generated data should contain Device")
|
||||||
|
}
|
||||||
|
if generatedData[7] != "MountPath" {
|
||||||
|
t.Fatalf("Generated data should contain MountPath")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package chroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCopyFile(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
first, err := ioutil.TempFile("", "copy_files_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("couldn't create temp file.")
|
||||||
|
}
|
||||||
|
defer os.Remove(first.Name())
|
||||||
|
newName := first.Name() + "-new"
|
||||||
|
|
||||||
|
payload := "copy_files_test.go payload"
|
||||||
|
if _, err = first.WriteString(payload); err != nil {
|
||||||
|
t.Fatalf("Couldn't write payload to first file.")
|
||||||
|
}
|
||||||
|
first.Sync()
|
||||||
|
|
||||||
|
cmd := common.ShellCommand(fmt.Sprintf("cp %s %s", first.Name(), newName))
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
t.Fatalf("Couldn't copy file")
|
||||||
|
}
|
||||||
|
defer os.Remove(newName)
|
||||||
|
|
||||||
|
second, err := os.Open(newName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't open copied file.")
|
||||||
|
}
|
||||||
|
defer second.Close()
|
||||||
|
|
||||||
|
var copiedPayload = make([]byte, len(payload))
|
||||||
|
if _, err := second.Read(copiedPayload); err != nil {
|
||||||
|
t.Fatalf("Couldn't open copied file for reading.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(copiedPayload) != payload {
|
||||||
|
t.Fatalf("payload not copied.")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package chroot
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestDevicePrefixMatch(t *testing.T) {
|
||||||
|
/*
|
||||||
|
if devicePrefixMatch("nvme0n1") != "" {
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
+1
-1
@@ -7,9 +7,9 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StepAttachVolume attaches the previously created volume to an
|
// StepAttachVolume attaches the previously created volume to an
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package chroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/chroot"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAttachVolumeCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||||
|
var raw interface{}
|
||||||
|
raw = new(StepAttachVolume)
|
||||||
|
if _, ok := raw.(chroot.Cleanup); !ok {
|
||||||
|
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -8,11 +8,11 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||||
|
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StepCreateVolume creates a new volume from the snapshot of the root
|
// StepCreateVolume creates a new volume from the snapshot of the root
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package chroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
confighelper "github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildTestRootDevice() *ec2.BlockDeviceMapping {
|
||||||
|
return &ec2.BlockDeviceMapping{
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
VolumeSize: aws.Int64(10),
|
||||||
|
SnapshotId: aws.String("snap-1234"),
|
||||||
|
VolumeType: aws.String("gp2"),
|
||||||
|
Encrypted: aws.Bool(false),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateVolume_Default(t *testing.T) {
|
||||||
|
stepCreateVolume := new(StepCreateVolume)
|
||||||
|
_, err := stepCreateVolume.buildCreateVolumeInput("test-az", buildTestRootDevice())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateVolume_Shrink(t *testing.T) {
|
||||||
|
stepCreateVolume := StepCreateVolume{RootVolumeSize: 1}
|
||||||
|
testRootDevice := buildTestRootDevice()
|
||||||
|
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Ensure that the new value is equal to the size of the old root device
|
||||||
|
assert.Equal(t, *ret.Size, *testRootDevice.Ebs.VolumeSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateVolume_Expand(t *testing.T) {
|
||||||
|
stepCreateVolume := StepCreateVolume{RootVolumeSize: 25}
|
||||||
|
testRootDevice := buildTestRootDevice()
|
||||||
|
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Ensure that the new value is equal to the size of the value passed in
|
||||||
|
assert.Equal(t, *ret.Size, stepCreateVolume.RootVolumeSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateVolume_io1_to_io1(t *testing.T) {
|
||||||
|
stepCreateVolume := StepCreateVolume{RootVolumeType: "io1"}
|
||||||
|
testRootDevice := buildTestRootDevice()
|
||||||
|
testRootDevice.Ebs.VolumeType = aws.String("io1")
|
||||||
|
testRootDevice.Ebs.Iops = aws.Int64(1000)
|
||||||
|
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, *ret.VolumeType, stepCreateVolume.RootVolumeType)
|
||||||
|
assert.Equal(t, *ret.Iops, *testRootDevice.Ebs.Iops)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateVolume_io1_to_gp2(t *testing.T) {
|
||||||
|
stepCreateVolume := StepCreateVolume{RootVolumeType: "gp2"}
|
||||||
|
testRootDevice := buildTestRootDevice()
|
||||||
|
testRootDevice.Ebs.VolumeType = aws.String("io1")
|
||||||
|
testRootDevice.Ebs.Iops = aws.Int64(1000)
|
||||||
|
|
||||||
|
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, *ret.VolumeType, stepCreateVolume.RootVolumeType)
|
||||||
|
assert.Nil(t, ret.Iops)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateVolume_gp2_to_io1(t *testing.T) {
|
||||||
|
stepCreateVolume := StepCreateVolume{RootVolumeType: "io1"}
|
||||||
|
testRootDevice := buildTestRootDevice()
|
||||||
|
|
||||||
|
_, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateVolume_Encrypted(t *testing.T) {
|
||||||
|
stepCreateVolume := StepCreateVolume{RootVolumeEncryptBoot: confighelper.TrileanFromBool(true)}
|
||||||
|
testRootDevice := buildTestRootDevice()
|
||||||
|
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Ensure that the new value is equal to the the value passed in
|
||||||
|
assert.Equal(t, confighelper.TrileanFromBool(*ret.Encrypted), stepCreateVolume.RootVolumeEncryptBoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateVolume_Custom_KMS_Key_Encrypted(t *testing.T) {
|
||||||
|
stepCreateVolume := StepCreateVolume{
|
||||||
|
RootVolumeEncryptBoot: confighelper.TrileanFromBool(true),
|
||||||
|
RootVolumeKmsKeyId: "alias/1234",
|
||||||
|
}
|
||||||
|
testRootDevice := buildTestRootDevice()
|
||||||
|
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Ensure that the new value is equal to the value passed in
|
||||||
|
assert.Equal(t, *ret.KmsKeyId, stepCreateVolume.RootVolumeKmsKeyId)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package chroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/chroot"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFlockCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||||
|
var raw interface{}
|
||||||
|
raw = new(StepFlock)
|
||||||
|
if _, ok := raw.(chroot.Cleanup); !ok {
|
||||||
|
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package chroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/chroot"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMountDeviceCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||||
|
var raw interface{}
|
||||||
|
raw = new(StepMountDevice)
|
||||||
|
if _, ok := raw.(chroot.Cleanup); !ok {
|
||||||
|
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -6,11 +6,11 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/random"
|
"github.com/hashicorp/packer-plugin-sdk/random"
|
||||||
confighelper "github.com/hashicorp/packer-plugin-sdk/template/config"
|
confighelper "github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||||
|
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StepRegisterAMI creates the AMI.
|
// StepRegisterAMI creates the AMI.
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
package chroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||||
|
amazon "github.com/hashicorp/packer/builder/amazon/common"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testImage() ec2.Image {
|
||||||
|
return ec2.Image{
|
||||||
|
ImageId: aws.String("ami-abcd1234"),
|
||||||
|
Name: aws.String("ami_test_name"),
|
||||||
|
Architecture: aws.String("x86_64"),
|
||||||
|
KernelId: aws.String("aki-abcd1234"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepRegisterAmi_buildRegisterOpts_pv(t *testing.T) {
|
||||||
|
config := Config{}
|
||||||
|
config.AMIName = "test_ami_name"
|
||||||
|
config.AMIDescription = "test_ami_description"
|
||||||
|
config.AMIVirtType = "paravirtual"
|
||||||
|
rootDeviceName := "foo"
|
||||||
|
|
||||||
|
image := testImage()
|
||||||
|
|
||||||
|
blockDevices := []*ec2.BlockDeviceMapping{}
|
||||||
|
|
||||||
|
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
|
||||||
|
|
||||||
|
expected := config.AMIVirtType
|
||||||
|
if *opts.VirtualizationType != expected {
|
||||||
|
t.Fatalf("Unexpected VirtType value: expected %s got %s\n", expected, *opts.VirtualizationType)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = config.AMIName
|
||||||
|
if *opts.Name != expected {
|
||||||
|
t.Fatalf("Unexpected Name value: expected %s got %s\n", expected, *opts.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = *image.KernelId
|
||||||
|
if *opts.KernelId != expected {
|
||||||
|
t.Fatalf("Unexpected KernelId value: expected %s got %s\n", expected, *opts.KernelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = rootDeviceName
|
||||||
|
if *opts.RootDeviceName != expected {
|
||||||
|
t.Fatalf("Unexpected RootDeviceName value: expected %s got %s\n", expected, *opts.RootDeviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepRegisterAmi_buildRegisterOpts_hvm(t *testing.T) {
|
||||||
|
config := Config{}
|
||||||
|
config.AMIName = "test_ami_name"
|
||||||
|
config.AMIDescription = "test_ami_description"
|
||||||
|
config.AMIVirtType = "hvm"
|
||||||
|
rootDeviceName := "foo"
|
||||||
|
|
||||||
|
image := testImage()
|
||||||
|
|
||||||
|
blockDevices := []*ec2.BlockDeviceMapping{}
|
||||||
|
|
||||||
|
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
|
||||||
|
|
||||||
|
expected := config.AMIVirtType
|
||||||
|
if *opts.VirtualizationType != expected {
|
||||||
|
t.Fatalf("Unexpected VirtType value: expected %s got %s\n", expected, *opts.VirtualizationType)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = config.AMIName
|
||||||
|
if *opts.Name != expected {
|
||||||
|
t.Fatalf("Unexpected Name value: expected %s got %s\n", expected, *opts.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.KernelId != nil {
|
||||||
|
t.Fatalf("Unexpected KernelId value: expected nil got %s\n", *opts.KernelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = rootDeviceName
|
||||||
|
if *opts.RootDeviceName != expected {
|
||||||
|
t.Fatalf("Unexpected RootDeviceName value: expected %s got %s\n", expected, *opts.RootDeviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepRegisterAmi_buildRegisterOptsFromScratch(t *testing.T) {
|
||||||
|
rootDeviceName := "/dev/sda"
|
||||||
|
snapshotID := "foo"
|
||||||
|
config := Config{
|
||||||
|
FromScratch: true,
|
||||||
|
PackerConfig: common.PackerConfig{},
|
||||||
|
AMIMappings: []amazon.BlockDevice{
|
||||||
|
{
|
||||||
|
DeviceName: rootDeviceName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RootDeviceName: rootDeviceName,
|
||||||
|
}
|
||||||
|
registerOpts := buildBaseRegisterOpts(&config, nil, 10, snapshotID, config.AMIName)
|
||||||
|
|
||||||
|
if len(registerOpts.BlockDeviceMappings) != 1 {
|
||||||
|
t.Fatal("Expected block device mapping of length 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *registerOpts.BlockDeviceMappings[0].Ebs.SnapshotId != snapshotID {
|
||||||
|
t.Fatalf("Snapshot ID of root disk not set to snapshot id %s", rootDeviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepRegisterAmi_buildRegisterOptFromExistingImage(t *testing.T) {
|
||||||
|
rootDeviceName := "/dev/sda"
|
||||||
|
snapshotID := "foo"
|
||||||
|
|
||||||
|
config := Config{
|
||||||
|
FromScratch: false,
|
||||||
|
PackerConfig: common.PackerConfig{},
|
||||||
|
}
|
||||||
|
sourceImage := ec2.Image{
|
||||||
|
RootDeviceName: &rootDeviceName,
|
||||||
|
BlockDeviceMappings: []*ec2.BlockDeviceMapping{
|
||||||
|
{
|
||||||
|
DeviceName: &rootDeviceName,
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
VolumeSize: aws.Int64(10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Throw in an ephemeral device, it seems like all devices in the return struct in a source AMI have
|
||||||
|
// a size, even if it's for ephemeral
|
||||||
|
{
|
||||||
|
DeviceName: aws.String("/dev/sdb"),
|
||||||
|
VirtualName: aws.String("ephemeral0"),
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
VolumeSize: aws.Int64(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotID, config.AMIName)
|
||||||
|
|
||||||
|
if len(registerOpts.BlockDeviceMappings) != 2 {
|
||||||
|
t.Fatal("Expected block device mapping of length 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dev := range registerOpts.BlockDeviceMappings {
|
||||||
|
if dev.Ebs.SnapshotId != nil && *dev.Ebs.SnapshotId == snapshotID {
|
||||||
|
// Even though root volume size is in config, it isn't used, instead we use the root volume size
|
||||||
|
// that's derived when we build the step
|
||||||
|
if *dev.Ebs.VolumeSize != 15 {
|
||||||
|
t.Fatalf("Root volume size not 15 GB instead %d", *dev.Ebs.VolumeSize)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("Could not find device with snapshot ID %s", snapshotID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepRegisterAmi_buildRegisterOptFromExistingImageWithBlockDeviceMappings(t *testing.T) {
|
||||||
|
const (
|
||||||
|
rootDeviceName = "/dev/xvda"
|
||||||
|
oldRootDevice = "/dev/sda1"
|
||||||
|
)
|
||||||
|
snapshotId := "foo"
|
||||||
|
|
||||||
|
config := Config{
|
||||||
|
FromScratch: false,
|
||||||
|
PackerConfig: common.PackerConfig{},
|
||||||
|
AMIMappings: []amazon.BlockDevice{
|
||||||
|
{
|
||||||
|
DeviceName: rootDeviceName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RootDeviceName: rootDeviceName,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intentionally try to use a different root devicename
|
||||||
|
sourceImage := ec2.Image{
|
||||||
|
RootDeviceName: aws.String(oldRootDevice),
|
||||||
|
BlockDeviceMappings: []*ec2.BlockDeviceMapping{
|
||||||
|
{
|
||||||
|
DeviceName: aws.String(oldRootDevice),
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
VolumeSize: aws.Int64(10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Throw in an ephemeral device, it seems like all devices in the return struct in a source AMI have
|
||||||
|
// a size, even if it's for ephemeral
|
||||||
|
{
|
||||||
|
DeviceName: aws.String("/dev/sdb"),
|
||||||
|
VirtualName: aws.String("ephemeral0"),
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
VolumeSize: aws.Int64(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotId, config.AMIName)
|
||||||
|
|
||||||
|
if len(registerOpts.BlockDeviceMappings) != 1 {
|
||||||
|
t.Fatal("Expected block device mapping of length 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *registerOpts.BlockDeviceMappings[0].Ebs.SnapshotId != snapshotId {
|
||||||
|
t.Fatalf("Snapshot ID of root disk set to '%s' expected '%s'", *registerOpts.BlockDeviceMappings[0].Ebs.SnapshotId, rootDeviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *registerOpts.RootDeviceName != rootDeviceName {
|
||||||
|
t.Fatalf("Root device set to '%s' expected %s", *registerOpts.RootDeviceName, rootDeviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *registerOpts.BlockDeviceMappings[0].Ebs.VolumeSize != 15 {
|
||||||
|
t.Fatalf("Size of root disk not set to 15 GB, instead %d", *registerOpts.BlockDeviceMappings[0].Ebs.VolumeSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -6,9 +6,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StepSnapshot creates a snapshot of the created volume.
|
// StepSnapshot creates a snapshot of the created volume.
|
||||||
+2
-2
@@ -17,7 +17,7 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||||
awsbase "github.com/hashicorp/aws-sdk-go-base"
|
awsbase "github.com/hashicorp/aws-sdk-go-base"
|
||||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
||||||
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
|
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||||
vaultapi "github.com/hashicorp/vault/api"
|
vaultapi "github.com/hashicorp/vault/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ type AccessConfig struct {
|
|||||||
// environmental variable.
|
// environmental variable.
|
||||||
Token string `mapstructure:"token" required:"false"`
|
Token string `mapstructure:"token" required:"false"`
|
||||||
session *session.Session
|
session *session.Session
|
||||||
// Get credentials from HashiCorp Vault's aws secrets engine. You must
|
// Get credentials from Hashicorp Vault's aws secrets engine. You must
|
||||||
// already have created a role to use. For more information about
|
// already have created a role to use. For more information about
|
||||||
// generating credentials via the Vault engine, see the [Vault
|
// generating credentials via the Vault engine, see the [Vault
|
||||||
// docs.](https://www.vaultproject.io/api/secret/aws#generate-credentials)
|
// docs.](https://www.vaultproject.io/api/secret/aws#generate-credentials)
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccessConfigPrepare_Region(t *testing.T) {
|
||||||
|
c := FakeAccessConfig()
|
||||||
|
|
||||||
|
c.RawRegion = "us-east-12"
|
||||||
|
err := c.ValidateRegion(c.RawRegion)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should have region validation err: %s", c.RawRegion)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.RawRegion = "us-east-1"
|
||||||
|
err = c.ValidateRegion(c.RawRegion)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("shouldn't have region validation err: %s", c.RawRegion)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.RawRegion = "custom"
|
||||||
|
err = c.ValidateRegion(c.RawRegion)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should have region validation err: %s", c.RawRegion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessConfigPrepare_RegionRestricted(t *testing.T) {
|
||||||
|
c := FakeAccessConfig()
|
||||||
|
|
||||||
|
// Create a Session with a custom region
|
||||||
|
c.session = session.Must(session.NewSession(&aws.Config{
|
||||||
|
Region: aws.String("us-gov-west-1"),
|
||||||
|
}))
|
||||||
|
|
||||||
|
if err := c.Prepare(); err != nil {
|
||||||
|
t.Fatalf("shouldn't have err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.IsGovCloud() {
|
||||||
|
t.Fatal("We should be in gov region.")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testAMIConfig() *AMIConfig {
|
||||||
|
return &AMIConfig{
|
||||||
|
AMIName: "foo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFakeAccessConfig(region string) *AccessConfig {
|
||||||
|
c := FakeAccessConfig()
|
||||||
|
c.RawRegion = region
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAMIConfigPrepare_name(t *testing.T) {
|
||||||
|
c := testAMIConfig()
|
||||||
|
accessConf := FakeAccessConfig()
|
||||||
|
if err := c.Prepare(accessConf, nil); err != nil {
|
||||||
|
t.Fatalf("shouldn't have err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AMIName = ""
|
||||||
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockEC2Client) DescribeRegions(*ec2.DescribeRegionsInput) (*ec2.DescribeRegionsOutput, error) {
|
||||||
|
return &ec2.DescribeRegionsOutput{
|
||||||
|
Regions: []*ec2.Region{
|
||||||
|
{RegionName: aws.String("us-east-1")},
|
||||||
|
{RegionName: aws.String("us-east-2")},
|
||||||
|
{RegionName: aws.String("us-west-1")},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||||
|
c := testAMIConfig()
|
||||||
|
c.AMIRegions = nil
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
var err error
|
||||||
|
accessConf := FakeAccessConfig()
|
||||||
|
mockConn := &mockEC2Client{}
|
||||||
|
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||||
|
t.Fatalf("shouldn't have err: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AMIRegions, err = listEC2Regions(mockConn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("shouldn't have err: %s", err.Error())
|
||||||
|
}
|
||||||
|
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||||
|
t.Fatalf("shouldn't have err: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-1"}
|
||||||
|
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||||
|
t.Fatalf("bad: %s", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{"us-east-1", "us-west-1"}
|
||||||
|
if !reflect.DeepEqual(c.AMIRegions, expected) {
|
||||||
|
t.Fatalf("bad: %#v", c.AMIRegions)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AMIRegions = []string{"custom"}
|
||||||
|
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||||
|
t.Fatal("shouldn't have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
|
||||||
|
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||||
|
"us-east-1": "123-456-7890",
|
||||||
|
"us-west-1": "789-012-3456",
|
||||||
|
"us-east-2": "456-789-0123",
|
||||||
|
}
|
||||||
|
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||||
|
t.Fatal(fmt.Sprintf("shouldn't have error: %s", errs[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
|
||||||
|
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||||
|
"us-east-1": "123-456-7890",
|
||||||
|
"us-west-1": "789-012-3456",
|
||||||
|
"us-east-2": "",
|
||||||
|
}
|
||||||
|
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||||
|
t.Fatal("should have passed; we are able to use default KMS key if not sharing")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SnapshotUsers = []string{"user-foo", "user-bar"}
|
||||||
|
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
|
||||||
|
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||||
|
"us-east-1": "123-456-7890",
|
||||||
|
"us-west-1": "789-012-3456",
|
||||||
|
"us-east-2": "",
|
||||||
|
}
|
||||||
|
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||||
|
t.Fatal("should have an error b/c can't use default KMS key if sharing")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AMIRegions = []string{"us-east-1", "us-west-1"}
|
||||||
|
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||||
|
"us-east-1": "123-456-7890",
|
||||||
|
"us-west-1": "789-012-3456",
|
||||||
|
"us-east-2": "456-789-0123",
|
||||||
|
}
|
||||||
|
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||||
|
t.Fatal("should have error b/c theres a region in the key map that isn't in ami_regions")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
|
||||||
|
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||||
|
"us-east-1": "123-456-7890",
|
||||||
|
"us-west-1": "789-012-3456",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
|
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SnapshotUsers = []string{"foo", "bar"}
|
||||||
|
c.AMIKmsKeyId = "123-abc-456"
|
||||||
|
c.AMIEncryptBootVolume = config.TriTrue
|
||||||
|
c.AMIRegions = []string{"us-east-1", "us-west-1"}
|
||||||
|
c.AMIRegionKMSKeyIDs = map[string]string{
|
||||||
|
"us-east-1": "123-456-7890",
|
||||||
|
"us-west-1": "",
|
||||||
|
}
|
||||||
|
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||||
|
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow rawregion to exist in ami_regions list.
|
||||||
|
accessConf = getFakeAccessConfig("us-east-1")
|
||||||
|
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
|
||||||
|
c.AMIRegionKMSKeyIDs = nil
|
||||||
|
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||||
|
t.Fatal("should allow user to have the raw region in ami_regions")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
|
||||||
|
c := testAMIConfig()
|
||||||
|
c.AMIUsers = []string{"testAccountID"}
|
||||||
|
c.AMIEncryptBootVolume = config.TriTrue
|
||||||
|
|
||||||
|
accessConf := FakeAccessConfig()
|
||||||
|
|
||||||
|
c.AMIKmsKeyId = ""
|
||||||
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
|
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
|
||||||
|
}
|
||||||
|
c.AMIKmsKeyId = "89c3fb9a-de87-4f2a-aedc-fddc5138193c"
|
||||||
|
if err := c.Prepare(accessConf, nil); err != nil {
|
||||||
|
t.Fatal("should be able to share ami with encrypted boot volume")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAMIConfigPrepare_ValidateKmsKey(t *testing.T) {
|
||||||
|
c := testAMIConfig()
|
||||||
|
c.AMIEncryptBootVolume = config.TriTrue
|
||||||
|
|
||||||
|
accessConf := FakeAccessConfig()
|
||||||
|
|
||||||
|
validCases := []string{
|
||||||
|
"abcd1234-e567-890f-a12b-a123b4cd56ef",
|
||||||
|
"alias/foo/bar",
|
||||||
|
"arn:aws:kms:us-east-1:012345678910:key/abcd1234-a123-456a-a12b-a123b4cd56ef",
|
||||||
|
"arn:aws:kms:us-east-1:012345678910:alias/foo/bar",
|
||||||
|
"arn:aws-us-gov:kms:us-gov-east-1:123456789012:key/12345678-1234-abcd-0000-123456789012",
|
||||||
|
}
|
||||||
|
for _, validCase := range validCases {
|
||||||
|
c.AMIKmsKeyId = validCase
|
||||||
|
if err := c.Prepare(accessConf, nil); err != nil {
|
||||||
|
t.Fatalf("%s should not have failed KMS key validation", validCase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidCases := []string{
|
||||||
|
"ABCD1234-e567-890f-a12b-a123b4cd56ef",
|
||||||
|
"ghij1234-e567-890f-a12b-a123b4cd56ef",
|
||||||
|
"ghij1234+e567_890f-a12b-a123b4cd56ef",
|
||||||
|
"foo/bar",
|
||||||
|
"arn:aws:kms:us-east-1:012345678910:foo/bar",
|
||||||
|
"arn:foo:kms:us-east-1:012345678910:key/abcd1234-a123-456a-a12b-a123b4cd56ef",
|
||||||
|
}
|
||||||
|
for _, invalidCase := range invalidCases {
|
||||||
|
c.AMIKmsKeyId = invalidCase
|
||||||
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
|
t.Fatalf("%s should have failed KMS key validation", invalidCase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAMINameValidation(t *testing.T) {
|
||||||
|
c := testAMIConfig()
|
||||||
|
|
||||||
|
accessConf := FakeAccessConfig()
|
||||||
|
|
||||||
|
c.AMIName = "aa"
|
||||||
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
|
t.Fatal("shouldn't be able to have an ami name with less than 3 characters")
|
||||||
|
}
|
||||||
|
|
||||||
|
var longAmiName string
|
||||||
|
for i := 0; i < 129; i++ {
|
||||||
|
longAmiName += "a"
|
||||||
|
}
|
||||||
|
c.AMIName = longAmiName
|
||||||
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
|
t.Fatal("shouldn't be able to have an ami name with great than 128 characters")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AMIName = "+aaa"
|
||||||
|
if err := c.Prepare(accessConf, nil); err == nil {
|
||||||
|
t.Fatal("shouldn't be able to have an ami name with invalid characters")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AMIName = "fooBAR1()[] ./-'@_"
|
||||||
|
if err := c.Prepare(accessConf, nil); err != nil {
|
||||||
|
t.Fatal("should be able to use all of the allowed AMI characters")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AMIName = `xyz-base-2017-04-05-1934`
|
||||||
|
if err := c.Prepare(accessConf, nil); err != nil {
|
||||||
|
t.Fatalf("expected `xyz-base-2017-04-05-1934` to pass validation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArtifact_Impl(t *testing.T) {
|
||||||
|
var _ packersdk.Artifact = new(Artifact)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArtifactId(t *testing.T) {
|
||||||
|
expected := `east:foo,west:bar`
|
||||||
|
|
||||||
|
amis := make(map[string]string)
|
||||||
|
amis["east"] = "foo"
|
||||||
|
amis["west"] = "bar"
|
||||||
|
|
||||||
|
a := &Artifact{
|
||||||
|
Amis: amis,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := a.Id()
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("bad: %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArtifactState_atlasMetadata(t *testing.T) {
|
||||||
|
a := &Artifact{
|
||||||
|
Amis: map[string]string{
|
||||||
|
"east": "foo",
|
||||||
|
"west": "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := a.State("atlas.artifact.metadata")
|
||||||
|
expected := map[string]string{
|
||||||
|
"region.east": "foo",
|
||||||
|
"region.west": "bar",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArtifactString(t *testing.T) {
|
||||||
|
expected := `AMIs were created:
|
||||||
|
east: foo
|
||||||
|
west: bar
|
||||||
|
`
|
||||||
|
|
||||||
|
amis := make(map[string]string)
|
||||||
|
amis["east"] = "foo"
|
||||||
|
amis["west"] = "bar"
|
||||||
|
|
||||||
|
a := &Artifact{Amis: amis}
|
||||||
|
result := a.String()
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("bad: %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArtifactState(t *testing.T) {
|
||||||
|
expectedData := "this is the data"
|
||||||
|
artifact := &Artifact{
|
||||||
|
StateData: map[string]interface{}{"state_data": expectedData},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid state
|
||||||
|
result := artifact.State("state_data")
|
||||||
|
if result != expectedData {
|
||||||
|
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid state
|
||||||
|
result = artifact.State("invalid_key")
|
||||||
|
if result != nil {
|
||||||
|
t.Fatalf("Bad: State should be nil for invalid state data name")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nil StateData should not fail and should return nil
|
||||||
|
artifact = &Artifact{}
|
||||||
|
result = artifact.State("key")
|
||||||
|
if result != nil {
|
||||||
|
t.Fatalf("Bad: State should be nil for nil StateData")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBlockDevice(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Config *BlockDevice
|
||||||
|
Result *ec2.BlockDeviceMapping
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Config: &BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
SnapshotId: "snap-1234",
|
||||||
|
VolumeType: "standard",
|
||||||
|
VolumeSize: 8,
|
||||||
|
DeleteOnTermination: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: &ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String("/dev/sdb"),
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
SnapshotId: aws.String("snap-1234"),
|
||||||
|
VolumeType: aws.String("standard"),
|
||||||
|
VolumeSize: aws.Int64(8),
|
||||||
|
DeleteOnTermination: aws.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeSize: 8,
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: &ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String("/dev/sdb"),
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
VolumeSize: aws.Int64(8),
|
||||||
|
DeleteOnTermination: aws.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "io1",
|
||||||
|
VolumeSize: 8,
|
||||||
|
DeleteOnTermination: true,
|
||||||
|
IOPS: aws.Int64(1000),
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: &ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String("/dev/sdb"),
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
VolumeType: aws.String("io1"),
|
||||||
|
VolumeSize: aws.Int64(8),
|
||||||
|
DeleteOnTermination: aws.Bool(true),
|
||||||
|
Iops: aws.Int64(1000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "io2",
|
||||||
|
VolumeSize: 8,
|
||||||
|
DeleteOnTermination: true,
|
||||||
|
IOPS: aws.Int64(1000),
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: &ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String("/dev/sdb"),
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
VolumeType: aws.String("io2"),
|
||||||
|
VolumeSize: aws.Int64(8),
|
||||||
|
DeleteOnTermination: aws.Bool(true),
|
||||||
|
Iops: aws.Int64(1000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "gp2",
|
||||||
|
VolumeSize: 8,
|
||||||
|
DeleteOnTermination: true,
|
||||||
|
Encrypted: config.TriTrue,
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: &ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String("/dev/sdb"),
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
VolumeType: aws.String("gp2"),
|
||||||
|
VolumeSize: aws.Int64(8),
|
||||||
|
DeleteOnTermination: aws.Bool(true),
|
||||||
|
Encrypted: aws.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "gp2",
|
||||||
|
VolumeSize: 8,
|
||||||
|
DeleteOnTermination: true,
|
||||||
|
Encrypted: config.TriTrue,
|
||||||
|
KmsKeyId: "2Fa48a521f-3aff-4b34-a159-376ac5d37812",
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: &ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String("/dev/sdb"),
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
VolumeType: aws.String("gp2"),
|
||||||
|
VolumeSize: aws.Int64(8),
|
||||||
|
DeleteOnTermination: aws.Bool(true),
|
||||||
|
Encrypted: aws.Bool(true),
|
||||||
|
KmsKeyId: aws.String("2Fa48a521f-3aff-4b34-a159-376ac5d37812"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "standard",
|
||||||
|
DeleteOnTermination: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: &ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String("/dev/sdb"),
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
VolumeType: aws.String("standard"),
|
||||||
|
DeleteOnTermination: aws.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VirtualName: "ephemeral0",
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: &ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String("/dev/sdb"),
|
||||||
|
VirtualName: aws.String("ephemeral0"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
NoDevice: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: &ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String("/dev/sdb"),
|
||||||
|
NoDevice: aws.String(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: &BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "gp3",
|
||||||
|
VolumeSize: 8,
|
||||||
|
Throughput: aws.Int64(125),
|
||||||
|
IOPS: aws.Int64(3000),
|
||||||
|
DeleteOnTermination: true,
|
||||||
|
Encrypted: config.TriTrue,
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: &ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String("/dev/sdb"),
|
||||||
|
Ebs: &ec2.EbsBlockDevice{
|
||||||
|
VolumeType: aws.String("gp3"),
|
||||||
|
VolumeSize: aws.Int64(8),
|
||||||
|
Throughput: aws.Int64(125),
|
||||||
|
Iops: aws.Int64(3000),
|
||||||
|
DeleteOnTermination: aws.Bool(true),
|
||||||
|
Encrypted: aws.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
var amiBlockDevices BlockDevices = []BlockDevice{*tc.Config}
|
||||||
|
|
||||||
|
var launchBlockDevices BlockDevices = []BlockDevice{*tc.Config}
|
||||||
|
|
||||||
|
expected := []*ec2.BlockDeviceMapping{tc.Result}
|
||||||
|
|
||||||
|
amiResults := amiBlockDevices.BuildEC2BlockDeviceMappings()
|
||||||
|
if diff := cmp.Diff(expected, amiResults); diff != "" {
|
||||||
|
t.Fatalf("Bad block device: %s", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
launchResults := launchBlockDevices.BuildEC2BlockDeviceMappings()
|
||||||
|
if diff := cmp.Diff(expected, launchResults); diff != "" {
|
||||||
|
t.Fatalf("Bad block device: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIOPSValidation(t *testing.T) {
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
device BlockDevice
|
||||||
|
ok bool
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
// volume size unknown
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "io1",
|
||||||
|
IOPS: aws.Int64(1000),
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "io2",
|
||||||
|
IOPS: aws.Int64(1000),
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
// ratio requirement satisfied
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "io1",
|
||||||
|
VolumeSize: 50,
|
||||||
|
IOPS: aws.Int64(1000),
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "io2",
|
||||||
|
VolumeSize: 100,
|
||||||
|
IOPS: aws.Int64(1000),
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
// ratio requirement not satisfied
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "io1",
|
||||||
|
VolumeSize: 10,
|
||||||
|
IOPS: aws.Int64(2000),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
msg: "/dev/sdb: the maximum ratio of provisioned IOPS to requested volume size (in GiB) is 50:1 for io1 volumes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "io2",
|
||||||
|
VolumeSize: 50,
|
||||||
|
IOPS: aws.Int64(30000),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
msg: "/dev/sdb: the maximum ratio of provisioned IOPS to requested volume size (in GiB) is 500:1 for io2 volumes",
|
||||||
|
},
|
||||||
|
// exceed max iops
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "io2",
|
||||||
|
VolumeSize: 500,
|
||||||
|
IOPS: aws.Int64(99999),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
msg: "IOPS must be between 100 and 64000 for device /dev/sdb",
|
||||||
|
},
|
||||||
|
// lower than min iops
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "io2",
|
||||||
|
VolumeSize: 50,
|
||||||
|
IOPS: aws.Int64(10),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
msg: "IOPS must be between 100 and 64000 for device /dev/sdb",
|
||||||
|
},
|
||||||
|
// exceed max iops
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "gp3",
|
||||||
|
VolumeSize: 50,
|
||||||
|
Throughput: aws.Int64(125),
|
||||||
|
IOPS: aws.Int64(99999),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
msg: "IOPS must be between 3000 and 16000 for device /dev/sdb",
|
||||||
|
},
|
||||||
|
// lower than min iops
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "gp3",
|
||||||
|
VolumeSize: 50,
|
||||||
|
Throughput: aws.Int64(125),
|
||||||
|
IOPS: aws.Int64(10),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
msg: "IOPS must be between 3000 and 16000 for device /dev/sdb",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := interpolate.Context{}
|
||||||
|
for _, testCase := range cases {
|
||||||
|
err := testCase.device.Prepare(&ctx)
|
||||||
|
if testCase.ok && err != nil {
|
||||||
|
t.Fatalf("should not error, but: %v", err)
|
||||||
|
}
|
||||||
|
if !testCase.ok {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should error")
|
||||||
|
} else if err.Error() != testCase.msg {
|
||||||
|
t.Fatalf("wrong error: expected %s, found: %v", testCase.msg, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestThroughputValidation(t *testing.T) {
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
device BlockDevice
|
||||||
|
ok bool
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "gp3",
|
||||||
|
Throughput: aws.Int64(125),
|
||||||
|
IOPS: aws.Int64(3000),
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "gp3",
|
||||||
|
Throughput: aws.Int64(1000),
|
||||||
|
IOPS: aws.Int64(3000),
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
// exceed max Throughput
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "gp3",
|
||||||
|
Throughput: aws.Int64(1001),
|
||||||
|
IOPS: aws.Int64(3000),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
msg: "Throughput must be between 125 and 1000 for device /dev/sdb",
|
||||||
|
},
|
||||||
|
// lower than min Throughput
|
||||||
|
{
|
||||||
|
device: BlockDevice{
|
||||||
|
DeviceName: "/dev/sdb",
|
||||||
|
VolumeType: "gp3",
|
||||||
|
Throughput: aws.Int64(124),
|
||||||
|
IOPS: aws.Int64(3000),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
msg: "Throughput must be between 125 and 1000 for device /dev/sdb",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := interpolate.Context{}
|
||||||
|
for _, testCase := range cases {
|
||||||
|
err := testCase.device.Prepare(&ctx)
|
||||||
|
if testCase.ok && err != nil {
|
||||||
|
t.Fatalf("should not error, but: %v", err)
|
||||||
|
}
|
||||||
|
if !testCase.ok {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should error")
|
||||||
|
} else if err.Error() != testCase.msg {
|
||||||
|
t.Fatalf("wrong error: expected %s, found: %v", testCase.msg, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStepSourceAmiInfo_BuildFilter(t *testing.T) {
|
||||||
|
filter_key := "name"
|
||||||
|
filter_value := "foo"
|
||||||
|
filter_key2 := "name2"
|
||||||
|
filter_value2 := "foo2"
|
||||||
|
|
||||||
|
inputFilter := map[string]string{filter_key: filter_value, filter_key2: filter_value2}
|
||||||
|
outputFilter := buildEc2Filters(inputFilter)
|
||||||
|
|
||||||
|
// deconstruct filter back into things we can test
|
||||||
|
foundMap := map[string]bool{filter_key: false, filter_key2: false}
|
||||||
|
for _, filter := range outputFilter {
|
||||||
|
for key, value := range inputFilter {
|
||||||
|
if *filter.Name == key && *filter.Values[0] == value {
|
||||||
|
foundMap[key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range foundMap {
|
||||||
|
if !v {
|
||||||
|
t.Fatalf("Fail: should have found value for key: %s", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/sts"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockSTS struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockSTS) DecodeAuthorizationMessage(input *sts.DecodeAuthorizationMessageInput) (*sts.DecodeAuthorizationMessageOutput, error) {
|
||||||
|
return &sts.DecodeAuthorizationMessageOutput{
|
||||||
|
DecodedMessage: aws.String(`{
|
||||||
|
"allowed": false,
|
||||||
|
"explicitDeny": true,
|
||||||
|
"matchedStatements": {}
|
||||||
|
}`),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorsParsing_RequestFailure(t *testing.T) {
|
||||||
|
|
||||||
|
ae := awserr.New("UnauthorizedOperation",
|
||||||
|
`You are not authorized to perform this operation. Encoded authorization failure message: D9Q7oicjOMr9l2CC-NPP1FiZXK9Ijia1k-3l0siBFCcrK3oSuMFMkBIO5TNj0HdXE-WfwnAcdycFOohfKroNO6toPJEns8RFVfy_M_IjNGmrEFJ6E62pnmBW0OLrMsXxR9FQE4gB4gJzSM0AD6cV6S3FOfqYzWBRX-sQdOT4HryGkFNRoFBr9Xbp-tRwiadwkbdHdfnV9fbRkXmnwCdULml16NBSofC4ZPepLMKmIB5rKjwk-m179UUh2XA-J5no0si6XcRo5GbHQB5QfCIwSHL4vsro2wLZUd16-8OWKyr3tVlTbQe0ERZskqRqRQ5E28QuiBCVV6XstUyo-T4lBSr75Fgnyr3wCO-dS3b_5Ns3WzA2JD4E2AJOAStXIU8IH5YuKkAg7C-dJMuBMPpmKCBEXhNoHDwCyOo5PsV3xMlc0jSb0qYGpfst_TDDtejcZfn7NssUjxVq9qkdH-OXz2gPoQB-hX8ycmZCL5UZwKc3TCLUr7TGnudHjmnMrE9cUo-yTCWfyHPLprhiYhTCKW18EikJ0O1EKI3FJ_b4F19_jFBPARjSwQc7Ut6MNCVzrPdZGYSF6acj5gPaxdy9uSkVQwWXK7Pd5MFP7EBDE1_DgYbzodgwDO2PXeVFUbSLBHKWo_ebZS9ZX2nYPcGss_sYaly0ZVSIJXp7G58B5BoFVhvVH6jYnF9XiAOjMltuP_ycu1pQP1lki500RY3baLvfeYeAsB38XZHKEgWZzq7Fei-uh89q0cjJTmlVyrfRU3q6`,
|
||||||
|
fmt.Errorf("You can't do it!!"))
|
||||||
|
rf := awserr.NewRequestFailure(ae, 400, "abc-def-123-456")
|
||||||
|
|
||||||
|
result := decodeAWSError(&mockSTS{}, rf)
|
||||||
|
if result == nil {
|
||||||
|
t.Error("Expected resulting error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(result.Error(), "Authorization failure message:") {
|
||||||
|
t.Error("Expected authorization failure message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorsParsing_NonAuthorizationFailure(t *testing.T) {
|
||||||
|
|
||||||
|
ae := awserr.New("BadRequest",
|
||||||
|
`You did something wrong. Try again`,
|
||||||
|
fmt.Errorf("Request was no good."))
|
||||||
|
rf := awserr.NewRequestFailure(ae, 400, "abc-def-123-456")
|
||||||
|
|
||||||
|
result := decodeAWSError(&mockSTS{}, rf)
|
||||||
|
if result == nil {
|
||||||
|
t.Error("Expected resulting error")
|
||||||
|
}
|
||||||
|
if result != rf {
|
||||||
|
t.Error("Expected original error to be returned unchanged")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorsParsing_NonAWSError(t *testing.T) {
|
||||||
|
|
||||||
|
err := fmt.Errorf("Random error occurred")
|
||||||
|
|
||||||
|
result := decodeAWSError(&mockSTS{}, err)
|
||||||
|
if result == nil {
|
||||||
|
t.Error("Expected resulting error")
|
||||||
|
}
|
||||||
|
if result != err {
|
||||||
|
t.Error("Expected original error to be returned unchanged")
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/retry"
|
"github.com/hashicorp/packer-plugin-sdk/retry"
|
||||||
|
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DestroyAMIs deregisters the AWS machine images in imageids from an active AWS account
|
// DestroyAMIs deregisters the AWS machine images in imageids from an active AWS account
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testImage() *ec2.Image {
|
||||||
|
return &ec2.Image{
|
||||||
|
ImageId: aws.String("ami-abcd1234"),
|
||||||
|
CreationDate: aws.String("ami_test_creation_date"),
|
||||||
|
Name: aws.String("ami_test_name"),
|
||||||
|
OwnerId: aws.String("ami_test_owner_id"),
|
||||||
|
ImageOwnerAlias: aws.String("ami_test_owner_alias"),
|
||||||
|
RootDeviceType: aws.String("ebs"),
|
||||||
|
Tags: []*ec2.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String("key-1"),
|
||||||
|
Value: aws.String("value-1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: aws.String("key-2"),
|
||||||
|
Value: aws.String("value-2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testState() multistep.StateBag {
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedData(state multistep.StateBag) packerbuilderdata.GeneratedData {
|
||||||
|
generatedData := packerbuilderdata.GeneratedData{State: state}
|
||||||
|
return generatedData
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterpolateBuildInfo_extractBuildInfo_noSourceImage(t *testing.T) {
|
||||||
|
state := testState()
|
||||||
|
generatedData := testGeneratedData(state)
|
||||||
|
buildInfo := extractBuildInfo("foo", state, &generatedData)
|
||||||
|
|
||||||
|
expected := BuildInfoTemplate{
|
||||||
|
BuildRegion: "foo",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*buildInfo, expected) {
|
||||||
|
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterpolateBuildInfo_extractBuildInfo_withSourceImage(t *testing.T) {
|
||||||
|
state := testState()
|
||||||
|
state.Put("source_image", testImage())
|
||||||
|
generatedData := testGeneratedData(state)
|
||||||
|
buildInfo := extractBuildInfo("foo", state, &generatedData)
|
||||||
|
|
||||||
|
expected := BuildInfoTemplate{
|
||||||
|
BuildRegion: "foo",
|
||||||
|
SourceAMI: "ami-abcd1234",
|
||||||
|
SourceAMICreationDate: "ami_test_creation_date",
|
||||||
|
SourceAMIName: "ami_test_name",
|
||||||
|
SourceAMIOwner: "ami_test_owner_id",
|
||||||
|
SourceAMIOwnerName: "ami_test_owner_alias",
|
||||||
|
SourceAMITags: map[string]string{
|
||||||
|
"key-1": "value-1",
|
||||||
|
"key-2": "value-2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*buildInfo, expected) {
|
||||||
|
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterpolateBuildInfo_extractBuildInfo_GeneratedDataWithSourceImageName(t *testing.T) {
|
||||||
|
state := testState()
|
||||||
|
state.Put("source_image", testImage())
|
||||||
|
generatedData := testGeneratedData(state)
|
||||||
|
extractBuildInfo("foo", state, &generatedData)
|
||||||
|
|
||||||
|
generatedDataState := state.Get("generated_data").(map[string]interface{})
|
||||||
|
|
||||||
|
if generatedDataState["SourceAMIName"] != "ami_test_name" {
|
||||||
|
t.Fatalf("Unexpected state SourceAMIName: expected %#v got %#v\n", "ami_test_name", generatedDataState["SourceAMIName"])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Clear out the AWS access key env vars so they don't
|
||||||
|
// affect our tests.
|
||||||
|
os.Setenv("AWS_ACCESS_KEY_ID", "")
|
||||||
|
os.Setenv("AWS_ACCESS_KEY", "")
|
||||||
|
os.Setenv("AWS_SECRET_ACCESS_KEY", "")
|
||||||
|
os.Setenv("AWS_SECRET_KEY", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfig() *RunConfig {
|
||||||
|
return &RunConfig{
|
||||||
|
SourceAmi: "abcd",
|
||||||
|
InstanceType: "m1.small",
|
||||||
|
|
||||||
|
Comm: communicator.Config{
|
||||||
|
SSH: communicator.SSH{
|
||||||
|
SSHUsername: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfigFilter() *RunConfig {
|
||||||
|
config := testConfig()
|
||||||
|
config.SourceAmi = ""
|
||||||
|
config.SourceAmiFilter = AmiFilterOptions{}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare(t *testing.T) {
|
||||||
|
c := testConfig()
|
||||||
|
err := c.Prepare(nil)
|
||||||
|
if len(err) > 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_InstanceType(t *testing.T) {
|
||||||
|
c := testConfig()
|
||||||
|
c.InstanceType = ""
|
||||||
|
if err := c.Prepare(nil); len(err) != 1 {
|
||||||
|
t.Fatalf("Should error if an instance_type is not specified")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_SourceAmi(t *testing.T) {
|
||||||
|
c := testConfig()
|
||||||
|
c.SourceAmi = ""
|
||||||
|
if err := c.Prepare(nil); len(err) != 2 {
|
||||||
|
t.Fatalf("Should error if a source_ami (or source_ami_filter) is not specified")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_SourceAmiFilterBlank(t *testing.T) {
|
||||||
|
c := testConfigFilter()
|
||||||
|
if err := c.Prepare(nil); len(err) != 2 {
|
||||||
|
t.Fatalf("Should error if source_ami_filter is empty or not specified (and source_ami is not specified)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_SourceAmiFilterOwnersBlank(t *testing.T) {
|
||||||
|
c := testConfigFilter()
|
||||||
|
filter_key := "name"
|
||||||
|
filter_value := "foo"
|
||||||
|
c.SourceAmiFilter.Filters = map[string]string{filter_key: filter_value}
|
||||||
|
if err := c.Prepare(nil); len(err) != 1 {
|
||||||
|
t.Fatalf("Should error if Owners is not specified)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) {
|
||||||
|
c := testConfigFilter()
|
||||||
|
owner := "123"
|
||||||
|
filter_key := "name"
|
||||||
|
filter_value := "foo"
|
||||||
|
goodFilter := AmiFilterOptions{
|
||||||
|
Owners: []string{owner},
|
||||||
|
Filters: map[string]string{filter_key: filter_value},
|
||||||
|
}
|
||||||
|
c.SourceAmiFilter = goodFilter
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_EnableT2UnlimitedGood(t *testing.T) {
|
||||||
|
c := testConfig()
|
||||||
|
// Must have a T2 instance type if T2 Unlimited is enabled
|
||||||
|
c.InstanceType = "t2.micro"
|
||||||
|
c.EnableT2Unlimited = true
|
||||||
|
err := c.Prepare(nil)
|
||||||
|
if len(err) > 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_EnableT2UnlimitedBadInstanceType(t *testing.T) {
|
||||||
|
c := testConfig()
|
||||||
|
// T2 Unlimited cannot be used with instance types other than T2
|
||||||
|
c.InstanceType = "m5.large"
|
||||||
|
c.EnableT2Unlimited = true
|
||||||
|
err := c.Prepare(nil)
|
||||||
|
if len(err) != 1 {
|
||||||
|
t.Fatalf("Should error if T2 Unlimited is enabled with non-T2 instance_type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_EnableT2UnlimitedBadWithSpotInstanceRequest(t *testing.T) {
|
||||||
|
c := testConfig()
|
||||||
|
// T2 Unlimited cannot be used with Spot Instances
|
||||||
|
c.InstanceType = "t2.micro"
|
||||||
|
c.EnableT2Unlimited = true
|
||||||
|
c.SpotPrice = "auto"
|
||||||
|
err := c.Prepare(nil)
|
||||||
|
if len(err) != 1 {
|
||||||
|
t.Fatalf("Should error if T2 Unlimited has been used in conjuntion with a Spot Price request")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_SpotAuto(t *testing.T) {
|
||||||
|
c := testConfig()
|
||||||
|
c.SpotPrice = "auto"
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shouldn't error (YET) even though SpotPriceAutoProduct is deprecated
|
||||||
|
c.SpotPriceAutoProduct = "Linux/Unix"
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_SSHPort(t *testing.T) {
|
||||||
|
c := testConfig()
|
||||||
|
c.Comm.SSHPort = 0
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Comm.SSHPort != 22 {
|
||||||
|
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Comm.SSHPort = 44
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Comm.SSHPort != 44 {
|
||||||
|
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_UserData(t *testing.T) {
|
||||||
|
c := testConfig()
|
||||||
|
tf, err := ioutil.TempFile("", "packer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tf.Name())
|
||||||
|
defer tf.Close()
|
||||||
|
|
||||||
|
c.UserData = "foo"
|
||||||
|
c.UserDataFile = tf.Name()
|
||||||
|
if err := c.Prepare(nil); len(err) != 1 {
|
||||||
|
t.Fatalf("Should error if user_data string and user_data_file have both been specified")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_UserDataFile(t *testing.T) {
|
||||||
|
c := testConfig()
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.UserDataFile = "idontexistidontthink"
|
||||||
|
if err := c.Prepare(nil); len(err) != 1 {
|
||||||
|
t.Fatalf("Should error if the file specified by user_data_file does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
tf, err := ioutil.TempFile("", "packer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tf.Name())
|
||||||
|
defer tf.Close()
|
||||||
|
|
||||||
|
c.UserDataFile = tf.Name()
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
|
||||||
|
c := testConfig()
|
||||||
|
c.Comm.SSHTemporaryKeyPairName = ""
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Comm.SSHTemporaryKeyPairName == "" {
|
||||||
|
t.Fatal("keypair name is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match prefix and UUID, e.g. "packer_5790d491-a0b8-c84c-c9d2-2aea55086550".
|
||||||
|
r := regexp.MustCompile(`\Apacker_(?:(?i)[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}?)\z`)
|
||||||
|
if !r.MatchString(c.Comm.SSHTemporaryKeyPairName) {
|
||||||
|
t.Fatal("keypair name is not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
|
||||||
|
t.Fatal("keypair name does not match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_TenancyBad(t *testing.T) {
|
||||||
|
c := testConfig()
|
||||||
|
c.Tenancy = "not_real"
|
||||||
|
|
||||||
|
if err := c.Prepare(nil); len(err) != 1 {
|
||||||
|
t.Fatal("Should error if tenancy is set to an invalid type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_TenancyGood(t *testing.T) {
|
||||||
|
validTenancy := []string{"", "default", "dedicated", "host"}
|
||||||
|
for _, vt := range validTenancy {
|
||||||
|
c := testConfig()
|
||||||
|
c.Tenancy = vt
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("Should not error if tenancy is set to %s", vt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
privateIP = "10.0.0.1"
|
||||||
|
publicIP = "192.168.1.1"
|
||||||
|
privateDNS = "private.dns.test"
|
||||||
|
publicDNS = "public.dns.test"
|
||||||
|
localhost = "localhost"
|
||||||
|
sshHostTemplate = "custom.host.value"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSSHHost(t *testing.T) {
|
||||||
|
origSshHostSleepDuration := sshHostSleepDuration
|
||||||
|
defer func() { sshHostSleepDuration = origSshHostSleepDuration }()
|
||||||
|
sshHostSleepDuration = 0
|
||||||
|
|
||||||
|
var cases = []struct {
|
||||||
|
allowTries int
|
||||||
|
vpcId string
|
||||||
|
sshInterface string
|
||||||
|
|
||||||
|
ok bool
|
||||||
|
wantHost string
|
||||||
|
sshHostOverride string
|
||||||
|
}{
|
||||||
|
{1, "", "", true, publicDNS, ""},
|
||||||
|
{1, "", "private_ip", true, privateIP, ""},
|
||||||
|
{1, "", "session_manager", true, localhost, ""},
|
||||||
|
{1, "vpc-id", "", true, publicIP, ""},
|
||||||
|
{1, "vpc-id", "private_ip", true, privateIP, ""},
|
||||||
|
{1, "vpc-id", "private_dns", true, privateDNS, ""},
|
||||||
|
{1, "vpc-id", "public_dns", true, publicDNS, ""},
|
||||||
|
{1, "vpc-id", "public_ip", true, publicIP, ""},
|
||||||
|
{1, "vpc-id", "session_manager", true, localhost, ""},
|
||||||
|
{2, "", "", true, publicDNS, ""},
|
||||||
|
{2, "", "private_ip", true, privateIP, ""},
|
||||||
|
{2, "vpc-id", "", true, publicIP, ""},
|
||||||
|
{2, "vpc-id", "private_ip", true, privateIP, ""},
|
||||||
|
{2, "vpc-id", "private_dns", true, privateDNS, ""},
|
||||||
|
{2, "vpc-id", "public_dns", true, publicDNS, ""},
|
||||||
|
{2, "vpc-id", "public_ip", true, publicIP, ""},
|
||||||
|
{3, "", "", false, "", ""},
|
||||||
|
{3, "", "private_ip", false, "", ""},
|
||||||
|
{3, "vpc-id", "", false, "", ""},
|
||||||
|
{3, "vpc-id", "private_ip", false, "", ""},
|
||||||
|
{3, "vpc-id", "private_dns", false, "", ""},
|
||||||
|
{3, "vpc-id", "public_dns", false, "", ""},
|
||||||
|
{3, "vpc-id", "public_ip", false, "", ""},
|
||||||
|
{1, "", "", true, sshHostTemplate, sshHostTemplate},
|
||||||
|
{1, "vpc-id", "", true, sshHostTemplate, sshHostTemplate},
|
||||||
|
{2, "vpc-id", "private_dns", true, sshHostTemplate, sshHostTemplate},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
testSSHHost(t, c.allowTries, c.vpcId, c.sshInterface, c.ok, c.wantHost,
|
||||||
|
c.sshHostOverride)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSSHHost(t *testing.T, allowTries int, vpcId string, sshInterface string,
|
||||||
|
ok bool, wantHost string, sshHostOverride string) {
|
||||||
|
t.Logf("allowTries=%d vpcId=%s sshInterface=%s ok=%t wantHost=%q sshHostOverride=%s",
|
||||||
|
allowTries, vpcId, sshInterface, ok, wantHost, sshHostOverride)
|
||||||
|
|
||||||
|
e := &fakeEC2Describer{
|
||||||
|
allowTries: allowTries,
|
||||||
|
vpcId: vpcId,
|
||||||
|
privateIP: privateIP,
|
||||||
|
publicIP: publicIP,
|
||||||
|
privateDNS: privateDNS,
|
||||||
|
publicDNS: publicDNS,
|
||||||
|
}
|
||||||
|
|
||||||
|
f := SSHHost(e, sshInterface, sshHostOverride)
|
||||||
|
st := &multistep.BasicStateBag{}
|
||||||
|
st.Put("instance", &ec2.Instance{
|
||||||
|
InstanceId: aws.String("instance-id"),
|
||||||
|
})
|
||||||
|
|
||||||
|
host, err := f(st)
|
||||||
|
|
||||||
|
if e.tries > allowTries {
|
||||||
|
t.Fatalf("got %d ec2 DescribeInstances tries, want %d", e.tries, allowTries)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case ok && err != nil:
|
||||||
|
t.Fatalf("expected no error, got %+v", err)
|
||||||
|
case !ok && err == nil:
|
||||||
|
t.Fatalf("expected error, got none and host %s", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
if host != wantHost {
|
||||||
|
t.Fatalf("got host %s, want %s", host, wantHost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeEC2Describer struct {
|
||||||
|
allowTries int
|
||||||
|
tries int
|
||||||
|
|
||||||
|
vpcId string
|
||||||
|
privateIP, publicIP, privateDNS, publicDNS string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *fakeEC2Describer) DescribeInstances(in *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
|
||||||
|
d.tries++
|
||||||
|
|
||||||
|
instance := &ec2.Instance{
|
||||||
|
InstanceId: aws.String("instance-id"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.vpcId != "" {
|
||||||
|
instance.VpcId = aws.String(d.vpcId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.tries >= d.allowTries {
|
||||||
|
instance.PublicIpAddress = aws.String(d.publicIP)
|
||||||
|
instance.PrivateIpAddress = aws.String(d.privateIP)
|
||||||
|
instance.PublicDnsName = aws.String(d.publicDNS)
|
||||||
|
instance.PrivateDnsName = aws.String(d.privateDNS)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := &ec2.DescribeInstancesOutput{
|
||||||
|
Reservations: []*ec2.Reservation{
|
||||||
|
{
|
||||||
|
Instances: []*ec2.Instance{instance},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
+1
-1
@@ -12,10 +12,10 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/ssm"
|
"github.com/aws/aws-sdk-go/service/ssm"
|
||||||
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
|
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
|
||||||
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/retry"
|
"github.com/hashicorp/packer-plugin-sdk/retry"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/shell-local/localexec"
|
"github.com/hashicorp/packer-plugin-sdk/shell-local/localexec"
|
||||||
|
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testGetWaiterOptions(t *testing.T) {
|
||||||
|
// no vars are set
|
||||||
|
envValues := overridableWaitVars{
|
||||||
|
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
|
||||||
|
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
|
||||||
|
envInfo{"AWS_TIMEOUT_SECONDS", 300, false},
|
||||||
|
}
|
||||||
|
options := applyEnvOverrides(envValues)
|
||||||
|
if len(options) > 0 {
|
||||||
|
t.Fatalf("Did not expect any waiter options to be generated; actual: %#v", options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// all vars are set
|
||||||
|
envValues = overridableWaitVars{
|
||||||
|
envInfo{"AWS_POLL_DELAY_SECONDS", 1, true},
|
||||||
|
envInfo{"AWS_MAX_ATTEMPTS", 800, true},
|
||||||
|
envInfo{"AWS_TIMEOUT_SECONDS", 20, true},
|
||||||
|
}
|
||||||
|
options = applyEnvOverrides(envValues)
|
||||||
|
expected := []request.WaiterOption{
|
||||||
|
request.WithWaiterDelay(request.ConstantWaiterDelay(time.Duration(1) * time.Second)),
|
||||||
|
request.WithWaiterMaxAttempts(800),
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(options, expected) {
|
||||||
|
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// poll delay is not set
|
||||||
|
envValues = overridableWaitVars{
|
||||||
|
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
|
||||||
|
envInfo{"AWS_MAX_ATTEMPTS", 800, true},
|
||||||
|
envInfo{"AWS_TIMEOUT_SECONDS", 300, false},
|
||||||
|
}
|
||||||
|
options = applyEnvOverrides(envValues)
|
||||||
|
expected = []request.WaiterOption{
|
||||||
|
request.WithWaiterMaxAttempts(800),
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(options, expected) {
|
||||||
|
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// poll delay is not set but timeout seconds is
|
||||||
|
envValues = overridableWaitVars{
|
||||||
|
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
|
||||||
|
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
|
||||||
|
envInfo{"AWS_TIMEOUT_SECONDS", 20, true},
|
||||||
|
}
|
||||||
|
options = applyEnvOverrides(envValues)
|
||||||
|
expected = []request.WaiterOption{
|
||||||
|
request.WithWaiterDelay(request.ConstantWaiterDelay(time.Duration(2) * time.Second)),
|
||||||
|
request.WithWaiterMaxAttempts(10),
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(options, expected) {
|
||||||
|
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,404 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/request"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define a mock struct to be used in unit tests for common aws steps.
|
||||||
|
type mockEC2Conn struct {
|
||||||
|
ec2iface.EC2API
|
||||||
|
Config *aws.Config
|
||||||
|
|
||||||
|
// Counters to figure out what code path was taken
|
||||||
|
copyImageCount int
|
||||||
|
describeImagesCount int
|
||||||
|
deregisterImageCount int
|
||||||
|
deleteSnapshotCount int
|
||||||
|
waitCount int
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockEC2Conn) CopyImage(copyInput *ec2.CopyImageInput) (*ec2.CopyImageOutput, error) {
|
||||||
|
m.lock.Lock()
|
||||||
|
m.copyImageCount++
|
||||||
|
m.lock.Unlock()
|
||||||
|
copiedImage := fmt.Sprintf("%s-copied-%d", *copyInput.SourceImageId, m.copyImageCount)
|
||||||
|
output := &ec2.CopyImageOutput{
|
||||||
|
ImageId: &copiedImage,
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// functions we have to create mock responses for in order for test to run
|
||||||
|
func (m *mockEC2Conn) DescribeImages(*ec2.DescribeImagesInput) (*ec2.DescribeImagesOutput, error) {
|
||||||
|
m.lock.Lock()
|
||||||
|
m.describeImagesCount++
|
||||||
|
m.lock.Unlock()
|
||||||
|
output := &ec2.DescribeImagesOutput{
|
||||||
|
Images: []*ec2.Image{{}},
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockEC2Conn) DeregisterImage(*ec2.DeregisterImageInput) (*ec2.DeregisterImageOutput, error) {
|
||||||
|
m.lock.Lock()
|
||||||
|
m.deregisterImageCount++
|
||||||
|
m.lock.Unlock()
|
||||||
|
output := &ec2.DeregisterImageOutput{}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockEC2Conn) DeleteSnapshot(*ec2.DeleteSnapshotInput) (*ec2.DeleteSnapshotOutput, error) {
|
||||||
|
m.lock.Lock()
|
||||||
|
m.deleteSnapshotCount++
|
||||||
|
m.lock.Unlock()
|
||||||
|
output := &ec2.DeleteSnapshotOutput{}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockEC2Conn) WaitUntilImageAvailableWithContext(aws.Context, *ec2.DescribeImagesInput, ...request.WaiterOption) error {
|
||||||
|
m.lock.Lock()
|
||||||
|
m.waitCount++
|
||||||
|
m.lock.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMockConn(config *AccessConfig, target string) (ec2iface.EC2API, error) {
|
||||||
|
mockConn := &mockEC2Conn{
|
||||||
|
Config: aws.NewConfig(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return mockConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create statebag for running test
|
||||||
|
func tState() multistep.StateBag {
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("ui", &packersdk.BasicUi{
|
||||||
|
Reader: new(bytes.Buffer),
|
||||||
|
Writer: new(bytes.Buffer),
|
||||||
|
})
|
||||||
|
state.Put("amis", map[string]string{"us-east-1": "ami-12345"})
|
||||||
|
state.Put("snapshots", map[string][]string{"us-east-1": {"snap-0012345"}})
|
||||||
|
conn, _ := getMockConn(&AccessConfig{}, "us-east-2")
|
||||||
|
state.Put("ec2", conn)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepAMIRegionCopy_duplicates(t *testing.T) {
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Test that if the original region is added to both Regions and Region,
|
||||||
|
// the ami is only copied once (with encryption).
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
stepAMIRegionCopy := StepAMIRegionCopy{
|
||||||
|
AccessConfig: FakeAccessConfig(),
|
||||||
|
Regions: []string{"us-east-1"},
|
||||||
|
AMIKmsKeyId: "12345",
|
||||||
|
// Original region key in regionkeyids is different than in amikmskeyid
|
||||||
|
RegionKeyIds: map[string]string{"us-east-1": "12345"},
|
||||||
|
EncryptBootVolume: config.TriTrue,
|
||||||
|
Name: "fake-ami-name",
|
||||||
|
OriginalRegion: "us-east-1",
|
||||||
|
}
|
||||||
|
// mock out the region connection code
|
||||||
|
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||||
|
|
||||||
|
state := tState()
|
||||||
|
state.Put("intermediary_image", true)
|
||||||
|
stepAMIRegionCopy.Run(context.Background(), state)
|
||||||
|
|
||||||
|
if len(stepAMIRegionCopy.Regions) != 1 {
|
||||||
|
t.Fatalf("Should have added original ami to Regions one time only")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Both Region and Regions set, but no encryption - shouldn't copy anything
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// the ami is only copied once.
|
||||||
|
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||||
|
AccessConfig: FakeAccessConfig(),
|
||||||
|
Regions: []string{"us-east-1"},
|
||||||
|
Name: "fake-ami-name",
|
||||||
|
OriginalRegion: "us-east-1",
|
||||||
|
}
|
||||||
|
// mock out the region connection code
|
||||||
|
state.Put("intermediary_image", false)
|
||||||
|
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||||
|
stepAMIRegionCopy.Run(context.Background(), state)
|
||||||
|
|
||||||
|
if len(stepAMIRegionCopy.Regions) != 0 {
|
||||||
|
t.Fatalf("Should not have added original ami to Regions; not encrypting")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Both Region and Regions set, but no encryption - shouldn't copy anything,
|
||||||
|
// this tests false as opposed to nil value above.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// the ami is only copied once.
|
||||||
|
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||||
|
AccessConfig: FakeAccessConfig(),
|
||||||
|
Regions: []string{"us-east-1"},
|
||||||
|
EncryptBootVolume: config.TriFalse,
|
||||||
|
Name: "fake-ami-name",
|
||||||
|
OriginalRegion: "us-east-1",
|
||||||
|
}
|
||||||
|
// mock out the region connection code
|
||||||
|
state.Put("intermediary_image", false)
|
||||||
|
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||||
|
stepAMIRegionCopy.Run(context.Background(), state)
|
||||||
|
|
||||||
|
if len(stepAMIRegionCopy.Regions) != 0 {
|
||||||
|
t.Fatalf("Should not have added original ami to Regions once; not" +
|
||||||
|
"encrypting")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Multiple regions, many duplicates, and encryption (this shouldn't ever
|
||||||
|
// happen because of our template validation, but good to test it.)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||||
|
AccessConfig: FakeAccessConfig(),
|
||||||
|
// Many duplicates for only 3 actual values
|
||||||
|
Regions: []string{"us-east-1", "us-west-2", "us-west-2", "ap-east-1", "ap-east-1", "ap-east-1"},
|
||||||
|
AMIKmsKeyId: "IlikePancakes",
|
||||||
|
// Original region key in regionkeyids is different than in amikmskeyid
|
||||||
|
RegionKeyIds: map[string]string{"us-east-1": "12345", "us-west-2": "abcde", "ap-east-1": "xyz"},
|
||||||
|
EncryptBootVolume: config.TriTrue,
|
||||||
|
Name: "fake-ami-name",
|
||||||
|
OriginalRegion: "us-east-1",
|
||||||
|
}
|
||||||
|
// mock out the region connection code
|
||||||
|
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||||
|
state.Put("intermediary_image", true)
|
||||||
|
stepAMIRegionCopy.Run(context.Background(), state)
|
||||||
|
|
||||||
|
if len(stepAMIRegionCopy.Regions) != 3 {
|
||||||
|
t.Fatalf("Each AMI should have been added to Regions one time only.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also verify that we respect RegionKeyIds over AMIKmsKeyIds:
|
||||||
|
if stepAMIRegionCopy.RegionKeyIds["us-east-1"] != "12345" {
|
||||||
|
t.Fatalf("RegionKeyIds should take precedence over AmiKmsKeyIds")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Multiple regions, many duplicates, NO encryption
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||||
|
AccessConfig: FakeAccessConfig(),
|
||||||
|
// Many duplicates for only 3 actual values
|
||||||
|
Regions: []string{"us-east-1", "us-west-2", "us-west-2", "ap-east-1", "ap-east-1", "ap-east-1"},
|
||||||
|
Name: "fake-ami-name",
|
||||||
|
OriginalRegion: "us-east-1",
|
||||||
|
}
|
||||||
|
// mock out the region connection code
|
||||||
|
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||||
|
state.Put("intermediary_image", false)
|
||||||
|
stepAMIRegionCopy.Run(context.Background(), state)
|
||||||
|
|
||||||
|
if len(stepAMIRegionCopy.Regions) != 2 {
|
||||||
|
t.Fatalf("Each AMI should have been added to Regions one time only, " +
|
||||||
|
"and original region shouldn't be added at all")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepAmiRegionCopy_nil_encryption(t *testing.T) {
|
||||||
|
// create step
|
||||||
|
stepAMIRegionCopy := StepAMIRegionCopy{
|
||||||
|
AccessConfig: FakeAccessConfig(),
|
||||||
|
Regions: make([]string, 0),
|
||||||
|
AMIKmsKeyId: "",
|
||||||
|
RegionKeyIds: make(map[string]string),
|
||||||
|
EncryptBootVolume: config.TriUnset,
|
||||||
|
Name: "fake-ami-name",
|
||||||
|
OriginalRegion: "us-east-1",
|
||||||
|
}
|
||||||
|
// mock out the region connection code
|
||||||
|
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||||
|
|
||||||
|
state := tState()
|
||||||
|
state.Put("intermediary_image", false)
|
||||||
|
stepAMIRegionCopy.Run(context.Background(), state)
|
||||||
|
|
||||||
|
if stepAMIRegionCopy.toDelete != "" {
|
||||||
|
t.Fatalf("Shouldn't have an intermediary ami if encrypt is nil")
|
||||||
|
}
|
||||||
|
if len(stepAMIRegionCopy.Regions) != 0 {
|
||||||
|
t.Fatalf("Should not have added original ami to original region")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepAmiRegionCopy_true_encryption(t *testing.T) {
|
||||||
|
// create step
|
||||||
|
stepAMIRegionCopy := StepAMIRegionCopy{
|
||||||
|
AccessConfig: FakeAccessConfig(),
|
||||||
|
Regions: make([]string, 0),
|
||||||
|
AMIKmsKeyId: "",
|
||||||
|
RegionKeyIds: make(map[string]string),
|
||||||
|
EncryptBootVolume: config.TriTrue,
|
||||||
|
Name: "fake-ami-name",
|
||||||
|
OriginalRegion: "us-east-1",
|
||||||
|
}
|
||||||
|
// mock out the region connection code
|
||||||
|
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||||
|
|
||||||
|
state := tState()
|
||||||
|
state.Put("intermediary_image", true)
|
||||||
|
stepAMIRegionCopy.Run(context.Background(), state)
|
||||||
|
|
||||||
|
if stepAMIRegionCopy.toDelete == "" {
|
||||||
|
t.Fatalf("Should delete original AMI if encrypted=true")
|
||||||
|
}
|
||||||
|
if len(stepAMIRegionCopy.Regions) == 0 {
|
||||||
|
t.Fatalf("Should have added original ami to Regions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepAmiRegionCopy_nil_intermediary(t *testing.T) {
|
||||||
|
// create step
|
||||||
|
stepAMIRegionCopy := StepAMIRegionCopy{
|
||||||
|
AccessConfig: FakeAccessConfig(),
|
||||||
|
Regions: make([]string, 0),
|
||||||
|
AMIKmsKeyId: "",
|
||||||
|
RegionKeyIds: make(map[string]string),
|
||||||
|
EncryptBootVolume: config.TriFalse,
|
||||||
|
Name: "fake-ami-name",
|
||||||
|
OriginalRegion: "us-east-1",
|
||||||
|
}
|
||||||
|
// mock out the region connection code
|
||||||
|
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||||
|
|
||||||
|
state := tState()
|
||||||
|
stepAMIRegionCopy.Run(context.Background(), state)
|
||||||
|
|
||||||
|
if stepAMIRegionCopy.toDelete != "" {
|
||||||
|
t.Fatalf("Should not delete original AMI if no intermediary")
|
||||||
|
}
|
||||||
|
if len(stepAMIRegionCopy.Regions) != 0 {
|
||||||
|
t.Fatalf("Should not have added original ami to Regions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepAmiRegionCopy_AMISkipBuildRegion(t *testing.T) {
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// skip build region is true
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
stepAMIRegionCopy := StepAMIRegionCopy{
|
||||||
|
AccessConfig: FakeAccessConfig(),
|
||||||
|
Regions: []string{"us-west-1"},
|
||||||
|
AMIKmsKeyId: "",
|
||||||
|
RegionKeyIds: map[string]string{"us-west-1": "abcde"},
|
||||||
|
Name: "fake-ami-name",
|
||||||
|
OriginalRegion: "us-east-1",
|
||||||
|
AMISkipBuildRegion: true,
|
||||||
|
}
|
||||||
|
// mock out the region connection code
|
||||||
|
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||||
|
|
||||||
|
state := tState()
|
||||||
|
state.Put("intermediary_image", true)
|
||||||
|
stepAMIRegionCopy.Run(context.Background(), state)
|
||||||
|
|
||||||
|
if stepAMIRegionCopy.toDelete == "" {
|
||||||
|
t.Fatalf("Should delete original AMI if skip_save_build_region=true")
|
||||||
|
}
|
||||||
|
if len(stepAMIRegionCopy.Regions) != 1 {
|
||||||
|
t.Fatalf("Should not have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// skip build region is false.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||||
|
AccessConfig: FakeAccessConfig(),
|
||||||
|
Regions: []string{"us-west-1"},
|
||||||
|
AMIKmsKeyId: "",
|
||||||
|
RegionKeyIds: make(map[string]string),
|
||||||
|
Name: "fake-ami-name",
|
||||||
|
OriginalRegion: "us-east-1",
|
||||||
|
AMISkipBuildRegion: false,
|
||||||
|
}
|
||||||
|
// mock out the region connection code
|
||||||
|
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||||
|
|
||||||
|
state.Put("intermediary_image", false) // not encrypted
|
||||||
|
stepAMIRegionCopy.Run(context.Background(), state)
|
||||||
|
|
||||||
|
if stepAMIRegionCopy.toDelete != "" {
|
||||||
|
t.Fatalf("Shouldn't have an intermediary AMI, so dont delete original ami")
|
||||||
|
}
|
||||||
|
if len(stepAMIRegionCopy.Regions) != 1 {
|
||||||
|
t.Fatalf("Should not have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// skip build region is false, but encrypt is true
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||||
|
AccessConfig: FakeAccessConfig(),
|
||||||
|
Regions: []string{"us-west-1"},
|
||||||
|
AMIKmsKeyId: "",
|
||||||
|
RegionKeyIds: map[string]string{"us-west-1": "abcde"},
|
||||||
|
Name: "fake-ami-name",
|
||||||
|
OriginalRegion: "us-east-1",
|
||||||
|
AMISkipBuildRegion: false,
|
||||||
|
EncryptBootVolume: config.TriTrue,
|
||||||
|
}
|
||||||
|
// mock out the region connection code
|
||||||
|
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||||
|
|
||||||
|
state.Put("intermediary_image", true) //encrypted
|
||||||
|
stepAMIRegionCopy.Run(context.Background(), state)
|
||||||
|
|
||||||
|
if stepAMIRegionCopy.toDelete == "" {
|
||||||
|
t.Fatalf("Have to delete intermediary AMI")
|
||||||
|
}
|
||||||
|
if len(stepAMIRegionCopy.Regions) != 2 {
|
||||||
|
t.Fatalf("Should have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// skip build region is true, and encrypt is true
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
stepAMIRegionCopy = StepAMIRegionCopy{
|
||||||
|
AccessConfig: FakeAccessConfig(),
|
||||||
|
Regions: []string{"us-west-1"},
|
||||||
|
AMIKmsKeyId: "",
|
||||||
|
RegionKeyIds: map[string]string{"us-west-1": "abcde"},
|
||||||
|
Name: "fake-ami-name",
|
||||||
|
OriginalRegion: "us-east-1",
|
||||||
|
AMISkipBuildRegion: true,
|
||||||
|
EncryptBootVolume: config.TriTrue,
|
||||||
|
}
|
||||||
|
// mock out the region connection code
|
||||||
|
stepAMIRegionCopy.getRegionConn = getMockConn
|
||||||
|
|
||||||
|
state.Put("intermediary_image", true) //encrypted
|
||||||
|
stepAMIRegionCopy.Run(context.Background(), state)
|
||||||
|
|
||||||
|
if stepAMIRegionCopy.toDelete == "" {
|
||||||
|
t.Fatalf("Have to delete intermediary AMI")
|
||||||
|
}
|
||||||
|
if len(stepAMIRegionCopy.Regions) != 1 {
|
||||||
|
t.Fatalf("Should not have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -9,10 +9,10 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/aws/session"
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/aws/aws-sdk-go/service/ssm"
|
"github.com/aws/aws-sdk-go/service/ssm"
|
||||||
pssm "github.com/hashicorp/packer-plugin-amazon/builder/common/ssm"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/net"
|
"github.com/hashicorp/packer-plugin-sdk/net"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
pssm "github.com/hashicorp/packer/builder/amazon/common/ssm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StepCreateSSMTunnel struct {
|
type StepCreateSSMTunnel struct {
|
||||||
+1
-1
@@ -8,11 +8,11 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/retry"
|
"github.com/hashicorp/packer-plugin-sdk/retry"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||||
|
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StepCreateTags struct {
|
type StepCreateTags struct {
|
||||||
+1
-1
@@ -9,10 +9,10 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"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/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||||
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/retry"
|
"github.com/hashicorp/packer-plugin-sdk/retry"
|
||||||
|
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StepPreValidate provides an opportunity to pre-validate any configuration for
|
// StepPreValidate provides an opportunity to pre-validate any configuration for
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
)
|
||||||
|
|
||||||
|
//DescribeVpcs mocks an ec2.DescribeVpcsOutput for a given input
|
||||||
|
func (m *mockEC2Conn) DescribeVpcs(input *ec2.DescribeVpcsInput) (*ec2.DescribeVpcsOutput, error) {
|
||||||
|
|
||||||
|
if input == nil || aws.StringValue(input.VpcIds[0]) == "" {
|
||||||
|
return nil, fmt.Errorf("oops looks like we need more input")
|
||||||
|
}
|
||||||
|
|
||||||
|
var isDefault bool
|
||||||
|
vpcID := aws.StringValue(input.VpcIds[0])
|
||||||
|
|
||||||
|
//only one default VPC per region
|
||||||
|
if strings.Contains("vpc-default-id", vpcID) {
|
||||||
|
isDefault = true
|
||||||
|
}
|
||||||
|
|
||||||
|
output := &ec2.DescribeVpcsOutput{
|
||||||
|
Vpcs: []*ec2.Vpc{
|
||||||
|
{IsDefault: aws.Bool(isDefault),
|
||||||
|
VpcId: aws.String(vpcID),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepPreValidate_checkVpc(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
step StepPreValidate
|
||||||
|
errorExpected bool
|
||||||
|
}{
|
||||||
|
{"DefaultVpc", StepPreValidate{VpcId: "vpc-default-id"}, false},
|
||||||
|
{"NonDefaultVpcNoSubnet", StepPreValidate{VpcId: "vpc-1234567890"}, true},
|
||||||
|
{"NonDefaultVpcWithSubnet", StepPreValidate{VpcId: "vpc-1234567890", SubnetId: "subnet-1234567890"}, false},
|
||||||
|
{"SubnetWithNoVpc", StepPreValidate{SubnetId: "subnet-1234567890"}, false},
|
||||||
|
{"NoVpcInformation", StepPreValidate{}, false},
|
||||||
|
{"NonDefaultVpcWithSubnetFilter", StepPreValidate{VpcId: "vpc-1234567890", HasSubnetFilter: true}, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockConn, err := getMockConn(nil, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unable to get a mock connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := tc.step.checkVpc(mockConn)
|
||||||
|
|
||||||
|
if tc.errorExpected && err == nil {
|
||||||
|
t.Errorf("expected a validation error for %q but got %q", tc.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.errorExpected && err != nil {
|
||||||
|
t.Errorf("expected a validation to pass for %q but got %q", tc.name, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -11,12 +11,12 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/retry"
|
"github.com/hashicorp/packer-plugin-sdk/retry"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||||
|
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StepRunSourceInstance struct {
|
type StepRunSourceInstance struct {
|
||||||
+1
-1
@@ -12,13 +12,13 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"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/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||||
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/random"
|
"github.com/hashicorp/packer-plugin-sdk/random"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/retry"
|
"github.com/hashicorp/packer-plugin-sdk/retry"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||||
|
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EC2BlockDeviceMappingsBuilder interface {
|
type EC2BlockDeviceMappingsBuilder interface {
|
||||||
@@ -0,0 +1,366 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create statebag for running test
|
||||||
|
func tStateSpot() multistep.StateBag {
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("ui", &packersdk.BasicUi{
|
||||||
|
Reader: new(bytes.Buffer),
|
||||||
|
Writer: new(bytes.Buffer),
|
||||||
|
})
|
||||||
|
state.Put("availability_zone", "us-east-1c")
|
||||||
|
state.Put("securityGroupIds", []string{"sg-0b8984db72f213dc3"})
|
||||||
|
state.Put("iamInstanceProfile", "packer-123")
|
||||||
|
state.Put("subnet_id", "subnet-077fde4e")
|
||||||
|
state.Put("source_image", "")
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBasicStep() *StepRunSpotInstance {
|
||||||
|
stepRunSpotInstance := StepRunSpotInstance{
|
||||||
|
PollingConfig: new(AWSPollingConfig),
|
||||||
|
AssociatePublicIpAddress: false,
|
||||||
|
LaunchMappings: BlockDevices{},
|
||||||
|
BlockDurationMinutes: 0,
|
||||||
|
Debug: false,
|
||||||
|
Comm: &communicator.Config{
|
||||||
|
SSH: communicator.SSH{
|
||||||
|
SSHKeyPairName: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EbsOptimized: false,
|
||||||
|
ExpectedRootDevice: "ebs",
|
||||||
|
InstanceInitiatedShutdownBehavior: "stop",
|
||||||
|
InstanceType: "t2.micro",
|
||||||
|
Region: "us-east-1",
|
||||||
|
SourceAMI: "",
|
||||||
|
SpotPrice: "auto",
|
||||||
|
SpotTags: nil,
|
||||||
|
Tags: map[string]string{},
|
||||||
|
VolumeTags: nil,
|
||||||
|
UserData: "",
|
||||||
|
UserDataFile: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
return &stepRunSpotInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateTemplateData(t *testing.T) {
|
||||||
|
state := tStateSpot()
|
||||||
|
stepRunSpotInstance := getBasicStep()
|
||||||
|
template := stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
|
||||||
|
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
|
||||||
|
|
||||||
|
// expected := []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{
|
||||||
|
// &ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{
|
||||||
|
// DeleteOnTermination: aws.Bool(true),
|
||||||
|
// DeviceIndex: aws.Int64(0),
|
||||||
|
// Groups: aws.StringSlice([]string{"sg-0b8984db72f213dc3"}),
|
||||||
|
// SubnetId: aws.String("subnet-077fde4e"),
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// if expected != template.NetworkInterfaces {
|
||||||
|
if template.NetworkInterfaces == nil {
|
||||||
|
t.Fatalf("Template should have contained a networkInterface object: recieved %#v", template.NetworkInterfaces)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *template.IamInstanceProfile.Name != state.Get("iamInstanceProfile") {
|
||||||
|
t.Fatalf("Template should have contained a InstanceProfile name: recieved %#v", template.IamInstanceProfile.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rerun, this time testing that we set security group IDs
|
||||||
|
state.Put("subnet_id", "")
|
||||||
|
template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
|
||||||
|
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
|
||||||
|
if template.NetworkInterfaces != nil {
|
||||||
|
t.Fatalf("Template shouldn't contain network interfaces object if subnet_id is unset.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rerun, this time testing that instance doesn't have instance profile is iamInstanceProfile is unset
|
||||||
|
state.Put("iamInstanceProfile", "")
|
||||||
|
template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
|
||||||
|
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
|
||||||
|
fmt.Println(template.IamInstanceProfile)
|
||||||
|
if *template.IamInstanceProfile.Name != "" {
|
||||||
|
t.Fatalf("Template shouldn't contain instance profile if iamInstanceProfile is unset.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateTemplateData_NoEphemeral(t *testing.T) {
|
||||||
|
state := tStateSpot()
|
||||||
|
stepRunSpotInstance := getBasicStep()
|
||||||
|
stepRunSpotInstance.NoEphemeral = true
|
||||||
|
template := stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
|
||||||
|
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
|
||||||
|
if len(template.BlockDeviceMappings) != 26 {
|
||||||
|
t.Fatalf("Should have created 26 mappings to keep ephemeral drives from appearing.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check that noEphemeral doesn't mess with the mappings in real life.
|
||||||
|
// state = tStateSpot()
|
||||||
|
// stepRunSpotInstance = getBasicStep()
|
||||||
|
// stepRunSpotInstance.NoEphemeral = true
|
||||||
|
// mappings := []*ec2.InstanceBlockDeviceMapping{
|
||||||
|
// &ec2.InstanceBlockDeviceMapping{
|
||||||
|
// DeviceName: "xvda",
|
||||||
|
// Ebs: {
|
||||||
|
// DeleteOnTermination: true,
|
||||||
|
// Status: "attaching",
|
||||||
|
// VolumeId: "vol-044cd49c330f21c05",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// &ec2.InstanceBlockDeviceMapping{
|
||||||
|
// DeviceName: "/dev/xvdf",
|
||||||
|
// Ebs: {
|
||||||
|
// DeleteOnTermination: false,
|
||||||
|
// Status: "attaching",
|
||||||
|
// VolumeId: "vol-0eefaf2d6ae35827e",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
|
||||||
|
// &ec2.LaunchTemplateInstanceMarketOptionsRequest{})
|
||||||
|
// if len(*template.BlockDeviceMappings) != 26 {
|
||||||
|
// t.Fatalf("Should have created 26 mappings to keep ephemeral drives from appearing.")
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
type runSpotEC2ConnMock struct {
|
||||||
|
ec2iface.EC2API
|
||||||
|
|
||||||
|
CreateLaunchTemplateParams []*ec2.CreateLaunchTemplateInput
|
||||||
|
CreateLaunchTemplateFn func(*ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error)
|
||||||
|
|
||||||
|
CreateFleetParams []*ec2.CreateFleetInput
|
||||||
|
CreateFleetFn func(*ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error)
|
||||||
|
|
||||||
|
CreateTagsParams []*ec2.CreateTagsInput
|
||||||
|
CreateTagsFn func(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error)
|
||||||
|
|
||||||
|
DescribeInstancesParams []*ec2.DescribeInstancesInput
|
||||||
|
DescribeInstancesFn func(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *runSpotEC2ConnMock) CreateLaunchTemplate(req *ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error) {
|
||||||
|
m.CreateLaunchTemplateParams = append(m.CreateLaunchTemplateParams, req)
|
||||||
|
resp, err := m.CreateLaunchTemplateFn(req)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *runSpotEC2ConnMock) CreateFleet(req *ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error) {
|
||||||
|
m.CreateFleetParams = append(m.CreateFleetParams, req)
|
||||||
|
if m.CreateFleetFn != nil {
|
||||||
|
resp, err := m.CreateFleetFn(req)
|
||||||
|
return resp, err
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *runSpotEC2ConnMock) DescribeInstances(req *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
|
||||||
|
m.DescribeInstancesParams = append(m.DescribeInstancesParams, req)
|
||||||
|
if m.DescribeInstancesFn != nil {
|
||||||
|
resp, err := m.DescribeInstancesFn(req)
|
||||||
|
return resp, err
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *runSpotEC2ConnMock) CreateTags(req *ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) {
|
||||||
|
m.CreateTagsParams = append(m.CreateTagsParams, req)
|
||||||
|
if m.CreateTagsFn != nil {
|
||||||
|
resp, err := m.CreateTagsFn(req)
|
||||||
|
return resp, err
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultEc2Mock(instanceId, spotRequestId, volumeId, launchTemplateId *string) *runSpotEC2ConnMock {
|
||||||
|
instance := &ec2.Instance{
|
||||||
|
InstanceId: instanceId,
|
||||||
|
SpotInstanceRequestId: spotRequestId,
|
||||||
|
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
|
||||||
|
{
|
||||||
|
Ebs: &ec2.EbsInstanceBlockDevice{
|
||||||
|
VolumeId: volumeId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &runSpotEC2ConnMock{
|
||||||
|
CreateLaunchTemplateFn: func(in *ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error) {
|
||||||
|
return &ec2.CreateLaunchTemplateOutput{
|
||||||
|
LaunchTemplate: &ec2.LaunchTemplate{
|
||||||
|
LaunchTemplateId: launchTemplateId,
|
||||||
|
},
|
||||||
|
Warning: nil,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
CreateFleetFn: func(*ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error) {
|
||||||
|
return &ec2.CreateFleetOutput{
|
||||||
|
Errors: nil,
|
||||||
|
FleetId: nil,
|
||||||
|
Instances: []*ec2.CreateFleetInstance{
|
||||||
|
{
|
||||||
|
InstanceIds: []*string{instanceId},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
DescribeInstancesFn: func(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
|
||||||
|
return &ec2.DescribeInstancesOutput{
|
||||||
|
NextToken: nil,
|
||||||
|
Reservations: []*ec2.Reservation{
|
||||||
|
{
|
||||||
|
Instances: []*ec2.Instance{instance},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRun(t *testing.T) {
|
||||||
|
instanceId := aws.String("test-instance-id")
|
||||||
|
spotRequestId := aws.String("spot-id")
|
||||||
|
volumeId := aws.String("volume-id")
|
||||||
|
launchTemplateId := aws.String("launchTemplateId")
|
||||||
|
ec2Mock := defaultEc2Mock(instanceId, spotRequestId, volumeId, launchTemplateId)
|
||||||
|
|
||||||
|
uiMock := packersdk.TestUi(t)
|
||||||
|
|
||||||
|
state := tStateSpot()
|
||||||
|
state.Put("ec2", ec2Mock)
|
||||||
|
state.Put("ui", uiMock)
|
||||||
|
state.Put("source_image", testImage())
|
||||||
|
|
||||||
|
stepRunSpotInstance := getBasicStep()
|
||||||
|
stepRunSpotInstance.Tags["Name"] = "Packer Builder"
|
||||||
|
stepRunSpotInstance.Tags["test-tag"] = "test-value"
|
||||||
|
stepRunSpotInstance.SpotTags = map[string]string{
|
||||||
|
"spot-tag": "spot-tag-value",
|
||||||
|
}
|
||||||
|
stepRunSpotInstance.VolumeTags = map[string]string{
|
||||||
|
"volume-tag": "volume-tag-value",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.TODO()
|
||||||
|
action := stepRunSpotInstance.Run(ctx, state)
|
||||||
|
|
||||||
|
if err := state.Get("error"); err != nil {
|
||||||
|
t.Fatalf("should not error, but: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if action != multistep.ActionContinue {
|
||||||
|
t.Fatalf("shoul continue, but: %v", action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ec2Mock.CreateLaunchTemplateParams) != 1 {
|
||||||
|
t.Fatalf("createLaunchTemplate should be invoked once, but invoked %v", len(ec2Mock.CreateLaunchTemplateParams))
|
||||||
|
}
|
||||||
|
launchTemplateName := ec2Mock.CreateLaunchTemplateParams[0].LaunchTemplateName
|
||||||
|
|
||||||
|
if len(ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications) != 1 {
|
||||||
|
t.Fatalf("exactly one launch template tag specification expected")
|
||||||
|
}
|
||||||
|
if *ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications[0].ResourceType != "launch-template" {
|
||||||
|
t.Fatalf("resource type 'launch-template' expected")
|
||||||
|
}
|
||||||
|
if len(ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications[0].Tags) != 1 {
|
||||||
|
t.Fatalf("1 launch template tag expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
nameTag := ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications[0].Tags[0]
|
||||||
|
if *nameTag.Key != "spot-tag" || *nameTag.Value != "spot-tag-value" {
|
||||||
|
t.Fatalf("expected spot-tag: spot-tag-value")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ec2Mock.CreateFleetParams) != 1 {
|
||||||
|
t.Fatalf("createFleet should be invoked once, but invoked %v", len(ec2Mock.CreateLaunchTemplateParams))
|
||||||
|
}
|
||||||
|
if *ec2Mock.CreateFleetParams[0].TargetCapacitySpecification.DefaultTargetCapacityType != "spot" {
|
||||||
|
t.Fatalf("capacity type should be spot")
|
||||||
|
}
|
||||||
|
if *ec2Mock.CreateFleetParams[0].TargetCapacitySpecification.TotalTargetCapacity != 1 {
|
||||||
|
t.Fatalf("target capacity should be 1")
|
||||||
|
}
|
||||||
|
if len(ec2Mock.CreateFleetParams[0].LaunchTemplateConfigs) != 1 {
|
||||||
|
t.Fatalf("exactly one launch config template expected")
|
||||||
|
}
|
||||||
|
if *ec2Mock.CreateFleetParams[0].LaunchTemplateConfigs[0].LaunchTemplateSpecification.LaunchTemplateName != *launchTemplateName {
|
||||||
|
t.Fatalf("launchTemplateName should match in createLaunchTemplate and createFleet requests")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ec2Mock.DescribeInstancesParams) != 1 {
|
||||||
|
t.Fatalf("describeInstancesParams should be invoked once, but invoked %v", len(ec2Mock.DescribeInstancesParams))
|
||||||
|
}
|
||||||
|
if *ec2Mock.DescribeInstancesParams[0].InstanceIds[0] != *instanceId {
|
||||||
|
t.Fatalf("instanceId should match from createFleet response")
|
||||||
|
}
|
||||||
|
|
||||||
|
uiMock.Say(fmt.Sprintf("%v", ec2Mock.CreateTagsParams))
|
||||||
|
if len(ec2Mock.CreateTagsParams) != 3 {
|
||||||
|
t.Fatalf("createTags should be invoked 3 times")
|
||||||
|
}
|
||||||
|
if len(ec2Mock.CreateTagsParams[0].Resources) != 1 || *ec2Mock.CreateTagsParams[0].Resources[0] != *spotRequestId {
|
||||||
|
t.Fatalf("should create tags for spot request")
|
||||||
|
}
|
||||||
|
if len(ec2Mock.CreateTagsParams[1].Resources) != 1 || *ec2Mock.CreateTagsParams[1].Resources[0] != *instanceId {
|
||||||
|
t.Fatalf("should create tags for instance")
|
||||||
|
}
|
||||||
|
if len(ec2Mock.CreateTagsParams[2].Resources) != 1 || ec2Mock.CreateTagsParams[2].Resources[0] != volumeId {
|
||||||
|
t.Fatalf("should create tags for volume")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRun_NoSpotTags(t *testing.T) {
|
||||||
|
instanceId := aws.String("test-instance-id")
|
||||||
|
spotRequestId := aws.String("spot-id")
|
||||||
|
volumeId := aws.String("volume-id")
|
||||||
|
launchTemplateId := aws.String("lt-id")
|
||||||
|
ec2Mock := defaultEc2Mock(instanceId, spotRequestId, volumeId, launchTemplateId)
|
||||||
|
|
||||||
|
uiMock := packersdk.TestUi(t)
|
||||||
|
|
||||||
|
state := tStateSpot()
|
||||||
|
state.Put("ec2", ec2Mock)
|
||||||
|
state.Put("ui", uiMock)
|
||||||
|
state.Put("source_image", testImage())
|
||||||
|
|
||||||
|
stepRunSpotInstance := getBasicStep()
|
||||||
|
stepRunSpotInstance.Tags["Name"] = "Packer Builder"
|
||||||
|
stepRunSpotInstance.Tags["test-tag"] = "test-value"
|
||||||
|
stepRunSpotInstance.VolumeTags = map[string]string{
|
||||||
|
"volume-tag": "volume-tag-value",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.TODO()
|
||||||
|
action := stepRunSpotInstance.Run(ctx, state)
|
||||||
|
|
||||||
|
if err := state.Get("error"); err != nil {
|
||||||
|
t.Fatalf("should not error, but: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if action != multistep.ActionContinue {
|
||||||
|
t.Fatalf("shoul continue, but: %v", action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications) != 0 {
|
||||||
|
t.Fatalf("0 launch template tags expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStepSourceAmiInfo_PVImage(t *testing.T) {
|
||||||
|
err := new(StepSourceAMIInfo).canEnableEnhancedNetworking(&ec2.Image{
|
||||||
|
VirtualizationType: aws.String("paravirtual"),
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepSourceAmiInfo_HVMImage(t *testing.T) {
|
||||||
|
err := new(StepSourceAMIInfo).canEnableEnhancedNetworking(&ec2.Image{
|
||||||
|
VirtualizationType: aws.String("hvm"),
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepSourceAmiInfo_PVImageWithAMIVirtPV(t *testing.T) {
|
||||||
|
stepSourceAMIInfo := StepSourceAMIInfo{
|
||||||
|
AMIVirtType: "paravirtual",
|
||||||
|
}
|
||||||
|
err := stepSourceAMIInfo.canEnableEnhancedNetworking(&ec2.Image{
|
||||||
|
VirtualizationType: aws.String("paravirtual"),
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepSourceAmiInfo_PVImageWithAMIVirtHVM(t *testing.T) {
|
||||||
|
stepSourceAMIInfo := StepSourceAMIInfo{
|
||||||
|
AMIVirtType: "hvm",
|
||||||
|
}
|
||||||
|
err := stepSourceAMIInfo.canEnableEnhancedNetworking(&ec2.Image{
|
||||||
|
VirtualizationType: aws.String("paravirtual"),
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
+1
-1
@@ -6,10 +6,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/hashicorp/packer-plugin-amazon/builder/common/awserrors"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/retry"
|
"github.com/hashicorp/packer-plugin-sdk/retry"
|
||||||
|
"github.com/hashicorp/packer/builder/amazon/common/awserrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StepStopEBSBackedInstance struct {
|
type StepStopEBSBackedInstance struct {
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAMITemplatePrepare_clean(t *testing.T) {
|
||||||
|
origName := "AMZamz09()./-_:&^ $%[]#'@"
|
||||||
|
expected := "AMZamz09()./-_--- --[]-'@"
|
||||||
|
|
||||||
|
name := templateCleanAMIName(origName)
|
||||||
|
|
||||||
|
if name != expected {
|
||||||
|
t.Fatalf("template names do not match: expected %s got %s\n", expected, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package amazon_acc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AWSHelper struct {
|
||||||
|
Region string
|
||||||
|
AMIName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AWSHelper) CleanUpAmi() error {
|
||||||
|
accessConfig := &awscommon.AccessConfig{}
|
||||||
|
session, err := accessConfig.Session()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("AWSAMICleanUp: Unable to create aws session %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
regionconn := ec2.New(session.Copy(&aws.Config{
|
||||||
|
Region: aws.String(a.Region),
|
||||||
|
}))
|
||||||
|
|
||||||
|
resp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
|
||||||
|
Owners: aws.StringSlice([]string{"self"}),
|
||||||
|
Filters: []*ec2.Filter{{
|
||||||
|
Name: aws.String("name"),
|
||||||
|
Values: aws.StringSlice([]string{a.AMIName}),
|
||||||
|
}}})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("AWSAMICleanUp: Unable to find Image %s: %s", a.AMIName, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != nil && len(resp.Images) > 0 {
|
||||||
|
_, err = regionconn.DeregisterImage(&ec2.DeregisterImageInput{
|
||||||
|
ImageId: resp.Images[0].ImageId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("AWSAMICleanUp: Unable to Deregister Image %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package amazon_acc
|
||||||
|
|
||||||
|
// This is the code necessary for running the provisioner acceptance tests.
|
||||||
|
// It provides the builder config and cleans up created resource.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
amazonebsbuilder "github.com/hashicorp/packer/builder/amazon/ebs"
|
||||||
|
|
||||||
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AmazonEBSAccTest struct{}
|
||||||
|
|
||||||
|
func (s *AmazonEBSAccTest) GetConfigs() (map[string]string, error) {
|
||||||
|
fixtures := map[string]string{
|
||||||
|
"linux": "amazon-ebs.txt",
|
||||||
|
"windows": "amazon-ebs_windows.txt",
|
||||||
|
}
|
||||||
|
|
||||||
|
configs := make(map[string]string)
|
||||||
|
|
||||||
|
for distro, fixture := range fixtures {
|
||||||
|
fileName := fixture
|
||||||
|
filePath := filepath.Join("../../builder/amazon/ebs/acceptance/test-fixtures/", fileName)
|
||||||
|
config, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Expected to find %s", filePath)
|
||||||
|
}
|
||||||
|
defer config.Close()
|
||||||
|
|
||||||
|
file, err := ioutil.ReadAll(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to read %s", filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
configs[distro] = string(file)
|
||||||
|
|
||||||
|
}
|
||||||
|
return configs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AmazonEBSAccTest) CleanUp() error {
|
||||||
|
helper := AWSHelper{
|
||||||
|
Region: "us-east-1",
|
||||||
|
AMIName: "packer-acc-test",
|
||||||
|
}
|
||||||
|
return helper.CleanUpAmi()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AmazonEBSAccTest) GetBuilderStore() packersdk.MapOfBuilder {
|
||||||
|
return packersdk.MapOfBuilder{
|
||||||
|
"amazon-ebs": func() (packersdk.Builder, error) { return &amazonebsbuilder.Builder{}, nil },
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"type": "amazon-ebs",
|
||||||
|
"ami_name": "packer-acc-test",
|
||||||
|
"instance_type": "m1.small",
|
||||||
|
"region": "us-east-1",
|
||||||
|
"ssh_username": "ubuntu",
|
||||||
|
"source_ami": "ami-0568456c",
|
||||||
|
"force_deregister" : true,
|
||||||
|
"tags": {
|
||||||
|
"packer-test": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"type": "amazon-ebs",
|
||||||
|
"region": "us-east-1",
|
||||||
|
"instance_type": "t2.micro",
|
||||||
|
"source_ami_filter": {
|
||||||
|
"filters": {
|
||||||
|
"virtualization-type": "hvm",
|
||||||
|
"name": "*Windows_Server-2012-R2*English-64Bit-Base*",
|
||||||
|
"root-device-type": "ebs"
|
||||||
|
},
|
||||||
|
"most_recent": true,
|
||||||
|
"owners": "amazon"
|
||||||
|
},
|
||||||
|
"ami_name": "packer-acc-test",
|
||||||
|
"user_data_file": "../../builder/amazon/ebs/acceptance/test-fixtures/scripts/bootstrap_win.txt",
|
||||||
|
"communicator": "winrm",
|
||||||
|
"winrm_username": "Administrator",
|
||||||
|
"winrm_password": "SuperS3cr3t!!!!",
|
||||||
|
"force_deregister" : true,
|
||||||
|
"tags": {
|
||||||
|
"packer-test": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<powershell>
|
||||||
|
# Set administrator password
|
||||||
|
net user Administrator SuperS3cr3t!!!!
|
||||||
|
wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE
|
||||||
|
|
||||||
|
# First, make sure WinRM can't be connected to
|
||||||
|
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block
|
||||||
|
|
||||||
|
# Delete any existing WinRM listeners
|
||||||
|
winrm delete winrm/config/listener?Address=*+Transport=HTTP 2>$Null
|
||||||
|
winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null
|
||||||
|
|
||||||
|
# Disable group policies which block basic authentication and unencrypted login
|
||||||
|
|
||||||
|
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowBasic -Value 1
|
||||||
|
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowUnencryptedTraffic -Value 1
|
||||||
|
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowBasic -Value 1
|
||||||
|
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowUnencryptedTraffic -Value 1
|
||||||
|
|
||||||
|
|
||||||
|
# Create a new WinRM listener and configure
|
||||||
|
winrm create winrm/config/listener?Address=*+Transport=HTTP
|
||||||
|
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}'
|
||||||
|
winrm set winrm/config '@{MaxTimeoutms="7200000"}'
|
||||||
|
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
|
||||||
|
winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}'
|
||||||
|
winrm set winrm/config/service/auth '@{Basic="true"}'
|
||||||
|
winrm set winrm/config/client/auth '@{Basic="true"}'
|
||||||
|
|
||||||
|
# Configure UAC to allow privilege elevation in remote shells
|
||||||
|
$Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
|
||||||
|
$Setting = 'LocalAccountTokenFilterPolicy'
|
||||||
|
Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force
|
||||||
|
|
||||||
|
# Configure and restart the WinRM Service; Enable the required firewall exception
|
||||||
|
Stop-Service -Name WinRM
|
||||||
|
Set-Service -Name WinRM -StartupType Automatic
|
||||||
|
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
|
||||||
|
Start-Service -Name WinRM
|
||||||
|
</powershell>
|
||||||
+1
-1
@@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/aws/aws-sdk-go/service/iam"
|
"github.com/aws/aws-sdk-go/service/iam"
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
@@ -24,6 +23,7 @@ import (
|
|||||||
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
|
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||||
|
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The unique ID for this builder
|
// The unique ID for this builder
|
||||||
+1
-1
@@ -4,8 +4,8 @@ package ebs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
"github.com/hashicorp/packer-plugin-amazon/builder/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||||
|
"github.com/hashicorp/packer/builder/amazon/common"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user