Compare commits

...

88 Commits

Author SHA1 Message Date
packer-ci 030da4b6b9 Cut version 1.7.1 2021-03-31 16:43:47 +00:00
packer-ci 3a437d4891 cut version 1.7.1 2021-03-31 16:43:45 +00:00
packer-ci 8c2f26718e update changelog 2021-03-31 16:43:45 +00:00
Wilken Rivera 076596cd3b Makefile: Update install steps for gox 2021-03-31 11:53:41 -04:00
Wilken Rivera f541cd59ed Update packer-plugin-sdk to latest release
```
go get github.com/hashicorp/packer-plugin-sdk@v0.1.1
go mod tidy
go mod vendor
```
2021-03-31 10:36:41 -04:00
Wilken Rivera 4b6891d6d5 Update CHANGELOG 2021-03-30 17:37:13 -04:00
Wilken Rivera 4746fc682d Update version of packer-plugin-docker (#10847)
```
go get github.com/hashicorp/packer-plugin-docker@v0.0.7
go mod tidy
go mod vendor
```
2021-03-30 17:12:43 -04:00
Wilken Rivera f6dbc3e78a Update steps for generating the remote plugin docs.zip file (#10846)
* Update steps for generating the remote plugin docs.zip file

* Update a few typos

* Fix tabbing issue
2021-03-30 17:12:28 -04:00
Adrien Delorme 03d79a2c39 HCL2 variables: split validation from getting value (#10843)
* HCL2 variables: split validation from getting value, to only

This way we do this only once and log this only once. The errors were being ignored anyways.

* Update types.variables_test.go
2021-03-30 14:58:26 -04:00
Sylvia Moss 349a300213 Add new disk to existingDevices list (#10844)
* add new disk to existingDevices list

* add tests
2021-03-30 14:48:06 -04:00
Megan Marsh dfc5d76108 Merge pull request #10833 from harveylowndes/add-oci-flex-shape-support
Support OCI flexible shapes
2021-03-30 10:11:47 -07:00
Adrien Delorme 77a29fc2f8 Allow to have dynamic blocks in a build block + tests (#10825)
This :
* allows to have a `build.dynamic` block
* add tests
* makes sure to show a correct message when a source was not found
  * display only name of source (instead of a weird map printout) 
  * use a "Did you mean %q" feature where possible 


Because dynamic blocks need all variables to be evaluated and available, I moved parsing of everything that is not a variable to "after" variables are extrapolated. Meaning that dynamic block get expanded in the `init` phase and then only we start interpreting HCL2 content.

After #10819 fix #10657
2021-03-30 15:53:04 +02:00
Sylvia Moss a588808270 update v1.7.1 changelog (#10837) 2021-03-29 14:04:32 +02:00
Harvey Lowndes b9b1cdf75f Address review comments 2021-03-29 12:57:10 +01:00
Kerim Satirli 2ac5fe894c adds missing word (#10836) 2021-03-29 11:01:03 +02:00
Kaivalya Shah fbb9429910 HCL2 example syntax changes (#10832)
An HCL2 example contained commas to separate the lines, and the metadata block did not have the equals sign, which is not valid.
2021-03-29 11:00:42 +02:00
Harvey Lowndes cb359e8064 Update OCI docs for flex shapes
Update the docs to reflect flex shape additions. Also makes a fix to the mapstructure.
2021-03-26 12:26:52 +00:00
Adrien Delorme a9bec7945e remove .mdx extensions in links (#10823)
Some of them were added in #10800 some where already there
2021-03-25 14:02:21 +01:00
Sylvia Moss 505cbd2591 Vendor amazon plugin (#10800)
* remove amazon from core
* vendor amazon plugin
* remove website content
* Add amazon to docs-remote-plugins
* update amazon reference links in the documentation
* update amazon docs version to latest

Co-authored-by: Adrien Delorme <adrien.delorme@icloud.com>
2021-03-25 13:37:48 +01:00
GennadySpb 25fddf3199 Add release build for darwin/arm64 (#10804)
* Add release build for darwin/arm64

Co-authored-by: Adrien Delorme <azr@users.noreply.github.com>
2021-03-25 13:34:21 +01:00
Ricardo Katz 166df2ce1d correct provisioner typo (#10822)
Correct provision typo
2021-03-25 13:27:05 +01:00
Megan Marsh 755395faf8 Merge pull request #10820 from chrisroberts/force-no-direct-upload-to-vagrantcloud
Override direct upload based on box size
2021-03-24 14:50:49 -07:00
Megan Marsh b958fe7a54 Merge pull request #10821 from zchsh/zs.fix-nested-plugin-urls
website: fix issue with .mdx in plugin docs URL paths
2021-03-24 14:10:14 -07:00
Zach Shilton cc133ea250 website: fix issue with .mdx in URL paths 2021-03-24 16:39:29 -04:00
Chris Roberts 4ea4c0570f Add test coverage for direct upload file size limits 2021-03-24 10:54:11 -07:00
Chris Roberts a665e6b822 Always include all upload steps regardless of configuration 2021-03-24 10:53:38 -07:00
Chris Roberts 2de91e4862 Check configuration before running callback for upload confirmation 2021-03-24 10:52:56 -07:00
Chris Roberts 3a11820a41 Adjust upload limit value and fix error value stored in state bag 2021-03-24 10:51:10 -07:00
Adrien Delorme 0e3fcb589b Implicit required_plugin blocks (#10732)
* used components that don't have a required_plugin block will make Packer 'implicitly' require those. These components are manually selected and commented for now.
* add tests
 * docs
2021-03-24 11:31:39 +01:00
Wilken Rivera 70ceed1110 Update vendor modules 2021-03-23 17:41:43 -04:00
Wilken Rivera 82eedc8f02 Update packer docs to latest (#10814) 2021-03-23 17:02:44 -04:00
Wilken Rivera 1d53080625 Update script to exit on immediate failure (#10815) 2021-03-23 16:44:21 -04:00
Zachary Shilton 89931d0f2a website: fixes and tweaks for plugin docs (#10812)
* website: sort nested plugin docs files

* website: allow ignored .md files in plugin docs folders

* website: allow plugin docs/README.md only as extra file

* website: fix issue with latest link for plugin docs.zip
2021-03-23 16:06:50 -04:00
Wilken Rivera 5e17dbeff2 Fix up regex in test 2021-03-23 14:39:45 -04:00
Wilken Rivera d0512c6edd docs/amazon: Updated generated docs 2021-03-23 14:14:59 -04:00
Adrien Delorme 7732f7998c Add http_content func to serve variables from HTTP @ preseed (#10801)
This imports hashicorp/packer-plugin-sdk#43

* code generate things
* update docs
* update guides
* update examples

We want to add a new guide.
2021-03-23 12:31:13 +01:00
Adrien Delorme ff01e6715a HCL2: add templatefile function (#10776)
* tests
* docs
2021-03-23 12:02:05 +01:00
Megan Marsh edc19eb859 Merge pull request #10806 from onlydole/bugfix/hashicorp-typo
Update 'Hashicorp' to 'HashiCorp' in the Amazon Documentation
2021-03-22 11:20:53 -07:00
Taylor Dolezal 1bb5c455aa Update 'Hashicorp' to 'HashiCorp' 2021-03-22 11:05:42 -07:00
Bryce Kalow 8c61ca174f feat: adds should-build website script (#10779) 2021-03-22 10:21:59 -04:00
Kyle MacDonald ef6093c4c3 Merge pull request #10764 from zchsh/zs.remote-plugin-zip-approach
website: Implement RFC MKTG-033
2021-03-22 09:59:38 -04:00
Marcus Weiner 4d9fb629c6 Allow using API tokens for Proxmox authentication (#10797) 2021-03-22 11:48:31 +01:00
Megan Marsh 0993c976fa hcl2_upgrade escaped quotes fix (#10794)
* clean up extra quoting that can cause text template failures. when everyone else abandons you, regex will always be there.

* LINTING
2021-03-22 10:56:30 +01:00
outscale-mgo 1e312ebc21 Fix description that was ignored in Osc builder (#10792)
Signed-off-by: Matthias Gatto <matthias.gatto@outscale.com>
2021-03-22 09:05:10 +01:00
Zach Shilton b780da5750 website: run plugin docs check also on schedule 2021-03-20 21:59:34 -04:00
Zach Shilton 565ca6627c website: revert test of plugin docs config validation 2021-03-20 21:51:42 -04:00
Zach Shilton c100b56d44 website: clarify error message in plugin config check 2021-03-20 21:50:20 -04:00
Avi Miller e6596a0a1d [oracle-oci] Add support for E3/E4.Flex shapes
This addes an optional shape_config stanza which allows
you to specify how many ocpus and memory the Flex
instance should be allocated.

This required an upgrade of the OCI Go SDK version.

Signed-off-by: Avi Miller <avi.miller@oracle.com>
2021-03-21 12:46:21 +11:00
Zach Shilton 55012937a9 website: add comments to duo of plugin docs zip fns 2021-03-20 21:45:40 -04:00
Zach Shilton 15d467eaf1 website: fix outdated comment 2021-03-20 21:45:12 -04:00
Zach Shilton ce896351b9 website: temporary change to double-check validation 2021-03-20 21:37:21 -04:00
Zach Shilton fb0886b724 website: Implement basic validation for plugin docs config 2021-03-20 21:35:46 -04:00
Andrew Pryde a915ec8e05 Upgrade oci-go-sdk to latest 2021-03-20 02:16:17 +00:00
Megan Marsh 667f930d3d Merge pull request #10793 from hashicorp/update-changelog-plugin-extraction-notes
update CHANGELOG
2021-03-19 13:42:54 -07:00
Wilken Rivera f2f65607eb update CHANGELOG 2021-03-19 14:32:05 -04:00
Megan Marsh ecaff88af9 Merge pull request #10780 from hashicorp/fix_10728
add legacy_isotime hcl function
2021-03-19 09:51:24 -07:00
Megan Marsh a40a782408 remove escaped dir 2021-03-19 09:28:30 -07:00
Brian Farrell 80f807de4d Fix issue with test breaking default value when client_cert_token_timeout is missing (#10783) 2021-03-19 15:17:41 +01:00
Wilken Rivera ac7c0f2f04 Update link in issue migrator config (#10791) 2021-03-19 09:48:58 -04:00
Adrien Delorme e2e6bce4c4 Update hcl2_upgrade_test.go
show diffs with strings
2021-03-19 13:56:41 +01:00
jhawk28 9f647ba2bb try to retype key if an error is received (#10541) 2021-03-19 13:27:05 +01:00
Wilken Rivera 7c6c399a38 Add hashibot configuration for transferring issues (#10785)
The added configuration will allow us to transfer open remote-plugin/* issues from
hashicorp/packer to their new respective repos. HashiBot issue transfer
only works with orgs it has write access to. Which is similar to how
GitHub's issue transfer feature works.
2021-03-19 12:47:32 +01:00
Adrien Delorme d5ccf73e91 oci builder: Show key_file errors (#10774) 2021-03-19 11:59:10 +01:00
Megan Marsh 502708b86a Refactor hcl2_upgrade (#10787) 2021-03-19 10:24:49 +01:00
sophia 9b641c9bfd Force NoDirectUpload for vagrantcloud if asset size > 5 GB 2021-03-18 17:22:27 -05:00
Zach Shilton 1d485988ea website: bump timeout for vercel build polling 2021-03-18 15:31:04 -04:00
Zach Shilton de12cd318d website: remove outdated comment on plugin config entries 2021-03-18 15:10:02 -04:00
Zachary Shilton 597dcce2ab website: fix tag vs version reference in README
Co-authored-by: Wilken Rivera <dev@wilkenrivera.com>
2021-03-18 15:06:42 -04:00
Zachary Shilton 0f8a658a23 website: clarify tag vs version in error msg
Co-authored-by: Wilken Rivera <dev@wilkenrivera.com>
2021-03-18 15:05:52 -04:00
Megan Marsh 4242cf3151 fix tests 2021-03-18 10:02:27 -07:00
Zach Shilton 618a3d42c6 website: clarify purpose of sourceUrl 2021-03-18 12:31:24 -04:00
Zach Shilton 203900e403 website: delete unused plugin-docs utilities 2021-03-18 12:26:27 -04:00
Zach Shilton bece5c3c69 website: fix minor style issue in PluginTier component 2021-03-18 12:26:16 -04:00
Zach Shilton 9e03647ad7 website: update readme to reflect revised nav-data and plugin docs 2021-03-18 12:26:03 -04:00
Zach Shilton e68e736b6c website: add indexing for plugin docs content 2021-03-18 12:25:49 -04:00
Zach Shilton 26a572270d website: add github action to flag plugin-docs issues 2021-03-18 12:25:36 -04:00
Zach Shilton 8b3e7e6f2f website: use revised remote-plugin-docs server implementation
- also bumps to stable docs-page, and makes related api changes for intro and guides routes
2021-03-18 12:24:36 -04:00
Zach Shilton 341308c582 website: add refactored remote-plugin-docs utilities 2021-03-18 12:21:53 -04:00
Ace Eldeib 3227d3da43 clean up azure temporary managed os disk (#10713)
* clean up temporary managed os disk

* improve message for skipping disk deletion

Co-authored-by: Wilken Rivera <dev@wilkenrivera.com>

* re-arrange osdisk/additional disk cleanup

Co-authored-by: Wilken Rivera <dev@wilkenrivera.com>

* remove additional disk cleanup

* add some validation on scenarios

* alway clean up resources inside template cleanup

* tidy to master

* clarify naming and comments

Co-authored-by: Wilken Rivera <dev@wilkenrivera.com>

* test: add acceptance testing for azure cleanup

* revert template changes

* fix err check

* delete resources in parallel with retry to avoid serial deletion

* nit: improve log message for transient deletion errors

* fix: typo

* remove unused func to make lint happy

Co-authored-by: Wilken Rivera <dev@wilkenrivera.com>
2021-03-18 12:09:57 -04:00
Sylvia Moss 0f541aaf5e Update CONTRIBUTING.md (#10782) 2021-03-18 16:24:34 +01:00
Megan Marsh 0ecc4b5e52 add annotation warning to isotime func usage 2021-03-17 16:06:19 -07:00
Megan Marsh 6986fb0e81 make upgrade set isotime func properly 2021-03-17 15:52:22 -07:00
Megan Marsh a6f907c688 tests 2021-03-17 10:48:55 -07:00
Adrien Delorme 3e8641e30e Delete formatted.pkr.hcl (#10775) 2021-03-17 14:41:46 +01:00
Sylvia Moss d0737dcd17 skip DownloadPathKey and ShouldUploadISO on mapstructure-to-hcl2 (#10772) 2021-03-17 14:26:38 +01:00
Megan Marsh f12c89bd84 add legacy_isotime function to hcl funcs 2021-03-16 13:33:43 -07:00
Megan Marsh 0b5f8901cc changelog 2021-03-16 09:59:49 -07:00
Megan Marsh 64a3219f69 fix error messaging in wrappedmain. Stderr gets eaten by panicwrap, so we need to write to stdout, which then gets unpacked into error and output messages using the ErrorPrefix and OutputPrefix (#10766) 2021-03-16 11:21:00 +01:00
1497 changed files with 38595 additions and 20621 deletions
+16 -3
View File
@@ -37,15 +37,19 @@ commands:
parameters:
GOOS:
type: string
GOARCH:
default: "amd64"
type: string
steps:
- checkout
- run: GOOS=<< parameters.GOOS >> go build -ldflags="-s -w -X github.com/hashicorp/packer/version.GitCommit=${CIRCLE_SHA1}" -o ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH) .
- run: zip ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH).zip ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH)
- run: rm ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH)
- run: GOOS=<< parameters.GOOS >> GOARCH=<<parameters.GOARCH>> go build -ldflags="-s -w -X github.com/hashicorp/packer/version.GitCommit=${CIRCLE_SHA1}" -o ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >> .
- run: zip ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >>.zip ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >>
- run: rm ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >>
- persist_to_workspace:
root: .
paths:
- ./pkg/
# Golang CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-go/ for more details
@@ -123,6 +127,13 @@ jobs:
steps:
- build-and-persist-packer-binary:
GOOS: darwin
build_darwin_arm64:
executor: golang
working_directory: /go/src/github.com/hashicorp/packer
steps:
- build-and-persist-packer-binary:
GOOS: darwin
GOARCH: arm64
build_freebsd:
executor: golang
working_directory: /go/src/github.com/hashicorp/packer
@@ -203,6 +214,7 @@ workflows:
jobs:
- build_linux
- build_darwin
- build_darwin_arm64
- build_windows
- build_freebsd
- build_openbsd
@@ -211,6 +223,7 @@ workflows:
requires:
- build_linux
- build_darwin
- build_darwin_arm64
- build_windows
- build_freebsd
- build_openbsd
+1
View File
@@ -6,6 +6,7 @@
*.mdx text eol=lf
*.ps1 text eol=lf
*.hcl text eol=lf
*.tmpl text eol=lf
*.txt text eol=lf
go.mod text eol=lf
go.sum text eol=lf
+6
View File
@@ -11,6 +11,12 @@ 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
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
### Reporting an Issue
+77
View File
@@ -0,0 +1,77 @@
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();
+29
View File
@@ -0,0 +1,29 @@
#
# 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
+1 -1
View File
@@ -7,7 +7,7 @@ name: Check markdown links on modified website files
jobs:
vercel-deployment-poll:
runs-on: ubuntu-latest
timeout-minutes: 3 #cancel job if no deployment is found within x minutes
timeout-minutes: 5 #cancel job if no deployment is found within x minutes
outputs:
url: ${{ steps.waitForVercelPreviewDeployment.outputs.url }}
steps:
+18
View File
@@ -22,3 +22,21 @@ 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.
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}."
}
+109 -4
View File
@@ -1,30 +1,135 @@
## 1.7.1 (Upcoming)
## 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
* builder/amazon: allow creation of ebs snapshots wihtout volumes. [GH-9591]
* builder/amazon: allow creation of ebs snapshots without volumes. [GH-9591]
* builder/amazon: Fix issue for multi-region AMI build that fail when
encrypting with KMS and sharing across accounts. [GH-10754]
* builder/azure: Add client_cert_token_timeout option. [GH-10528]
* builder/google: Make Windows password timeout configurable. [GH-10727]
* builder/google: Update public GCP image project as gce-uefi-images are
deprecated. [GH-10724]
* builder/oracle-oci: Update Oracle Go SDK to add support for OCI flexible
shapes. [GH-10833]
* builder/proxmox: Allow using API tokens for Proxmox authentication.
[GH-10797]
* builder/qemu: Added firmware option. [GH-10683]
* builder/scaleway: add support for timeout in shutdown step. [GH-10503]
* builder/vagrant: Fix logging to be clearer when Vagrant builder overrides
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
controller, and audio controller. [GH-10671]
* builder/virtualbox: Support for "virtio" storage and ISO drive. [GH-10632]
* builder/vmware: Added "attach_snapshot" parameter to vmware vmx builder.
[GH-10651]
* command/fmt: Adding recursive flag to formatter to format subdirectories.
[GH-10457]
* core/hcl2: Add legacy_isotime function. [GH-10780]
* core/hcl2: Add support for generating `dynamic` blocks within a `build`
block. [GH-10825]
* core/hcl2: Add templatefile function. [GH-10776]
* core/hcl2_upgrade: hcl2_upgrade command can now upgrade json var-files.
[GH-10676]
* core/init: Add implicit required_plugin blocks feature. [GH-10732]
* core: Add http_content option to serve variables from HTTP at preseed.
[GH-10801]
* core: Change template parsing error to include warning about file extensions.
[GH-10652]
* hcl2_upgrade: hcl2_upgrade command can now upgrade json var-files [GH-10676]
* core: Update to gopsutil v3.21.1 to allow builds to work for darwin arm64.
[GH-10697]
* provisioner/inspec: Allow non-zero exit codes for inspec provisioner.
[GH-10723]
### 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/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.
[GH-10560]
[GH-10560] [GH-10774]
* builder/outscale: Fix omi_description that was ignored in Osc builder
[GH-10792]
* builder/parallels: Make Packer respect winrm_host flag in winrm connect func.
[GH-10748]
* builder/proxmox: Fixes issue when using `additional_iso_files` in HCL enabled
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
network-mapping configuration. [GH-10543]
* builder/vsphere: Fix invalid device configuration issue when creating a
vm with multiple disk on the same controller. [GH-10844]
* builder/vsphere: Fix issue where boot command would fail the build do to a
key typing error. This change will now retry to type the key on error
before giving up. [GH-10541]
* core/hcl2_upgrade: Check for nil config map when provisioner/post-processor
doesn't have config. [GH-10730]
* core/hcl2_upgrade: Fix escaped quotes in template functions [GH-10794]
* core/hcl2_upgrade: Make hcl2_upgrade command correctly translate
pause_before. [GH-10654]
* core/hcl2_upgrade: Make json variables using template engines get stored as
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
PACKER_LOG_PATH. [GH-10660]
* post-processor/vagrant-cloud: Override direct upload based on box size
[GH-10820]
* provisioner/chef-solo: HCL2 templates can support the json_string option.
[GH-10655]
* provisioner/inspec: Add new configuration field `valid_exit_codes` to allow
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 -1
View File
@@ -49,7 +49,7 @@ package:
@sh -c "$(CURDIR)/scripts/dist.sh $(VERSION)"
install-build-deps: ## Install dependencies for bin build
@go get github.com/mitchellh/gox
@go install github.com/mitchellh/gox@v1.0.1
install-gen-deps: ## Install dependencies for code generation
# to avoid having to tidy our go deps, we `go get` our binaries from a temp
-251
View File
@@ -1,251 +0,0 @@
package chroot
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"ami_name": "foo",
"source_ami": "foo",
"region": "us-east-1",
// region validation logic is checked in ami_config_test
"skip_region_validation": true,
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packersdk.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilderPrepare_AMIName(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["ami_name"] = "foo"
config["skip_region_validation"] = true
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["ami_name"] = "foo {{"
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
// Test bad
delete(config, "ami_name")
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_ChrootMounts(t *testing.T) {
b := &Builder{}
config := testConfig()
config["chroot_mounts"] = nil
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_ChrootMountsBadDefaults(t *testing.T) {
b := &Builder{}
config := testConfig()
config["chroot_mounts"] = [][]string{
{"bad"},
}
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_SourceAmi(t *testing.T) {
b := &Builder{}
config := testConfig()
config["source_ami"] = ""
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
config["source_ami"] = "foo"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_CommandWrapper(t *testing.T) {
b := &Builder{}
config := testConfig()
config["command_wrapper"] = "echo hi; {{.Command}}"
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_CopyFiles(t *testing.T) {
b := &Builder{}
config := testConfig()
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
if len(b.config.CopyFiles) != 1 && b.config.CopyFiles[0] != "/etc/resolv.conf" {
t.Errorf("Was expecting default value for copy_files.")
}
}
func TestBuilderPrepare_CopyFilesNoDefault(t *testing.T) {
b := &Builder{}
config := testConfig()
config["copy_files"] = []string{}
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
if len(b.config.CopyFiles) > 0 {
t.Errorf("Was expecting no default value for copy_files. Found %v",
b.config.CopyFiles)
}
}
func TestBuilderPrepare_RootDeviceNameAndAMIMappings(t *testing.T) {
var b Builder
config := testConfig()
config["root_device_name"] = "/dev/sda"
config["ami_block_device_mappings"] = []interface{}{map[string]string{}}
config["root_volume_size"] = 15
_, warnings, err := b.Prepare(config)
if len(warnings) == 0 {
t.Fatal("Missing warning, stating block device mappings will be overwritten")
} else if len(warnings) > 1 {
t.Fatalf("excessive warnings: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_AMIMappingsNoRootDeviceName(t *testing.T) {
var b Builder
config := testConfig()
config["ami_block_device_mappings"] = []interface{}{map[string]string{}}
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("should have error")
}
}
func TestBuilderPrepare_RootDeviceNameNoAMIMappings(t *testing.T) {
var b Builder
config := testConfig()
config["root_device_name"] = "/dev/sda"
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("should have error")
}
}
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
var b Builder
config := testConfig()
generatedData, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if generatedData[0] != "SourceAMIName" {
t.Fatalf("Generated data should contain SourceAMIName")
}
if generatedData[1] != "BuildRegion" {
t.Fatalf("Generated data should contain BuildRegion")
}
if generatedData[2] != "SourceAMI" {
t.Fatalf("Generated data should contain SourceAMI")
}
if generatedData[3] != "SourceAMICreationDate" {
t.Fatalf("Generated data should contain SourceAMICreationDate")
}
if generatedData[4] != "SourceAMIOwner" {
t.Fatalf("Generated data should contain SourceAMIOwner")
}
if generatedData[5] != "SourceAMIOwnerName" {
t.Fatalf("Generated data should contain SourceAMIOwnerName")
}
if generatedData[6] != "Device" {
t.Fatalf("Generated data should contain Device")
}
if generatedData[7] != "MountPath" {
t.Fatalf("Generated data should contain MountPath")
}
}
-51
View File
@@ -1,51 +0,0 @@
package chroot
import (
"fmt"
"io/ioutil"
"os"
"runtime"
"testing"
"github.com/hashicorp/packer-plugin-sdk/common"
)
func TestCopyFile(t *testing.T) {
if runtime.GOOS == "windows" {
return
}
first, err := ioutil.TempFile("", "copy_files_test")
if err != nil {
t.Fatalf("couldn't create temp file.")
}
defer os.Remove(first.Name())
newName := first.Name() + "-new"
payload := "copy_files_test.go payload"
if _, err = first.WriteString(payload); err != nil {
t.Fatalf("Couldn't write payload to first file.")
}
first.Sync()
cmd := common.ShellCommand(fmt.Sprintf("cp %s %s", first.Name(), newName))
if err := cmd.Run(); err != nil {
t.Fatalf("Couldn't copy file")
}
defer os.Remove(newName)
second, err := os.Open(newName)
if err != nil {
t.Fatalf("Couldn't open copied file.")
}
defer second.Close()
var copiedPayload = make([]byte, len(payload))
if _, err := second.Read(copiedPayload); err != nil {
t.Fatalf("Couldn't open copied file for reading.")
}
if string(copiedPayload) != payload {
t.Fatalf("payload not copied.")
}
}
-10
View File
@@ -1,10 +0,0 @@
package chroot
import "testing"
func TestDevicePrefixMatch(t *testing.T) {
/*
if devicePrefixMatch("nvme0n1") != "" {
}
*/
}
@@ -1,15 +0,0 @@
package chroot
import (
"testing"
"github.com/hashicorp/packer-plugin-sdk/chroot"
)
func TestAttachVolumeCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
var raw interface{}
raw = new(StepAttachVolume)
if _, ok := raw.(chroot.Cleanup); !ok {
t.Fatalf("cleanup func should be a CleanupFunc")
}
}
@@ -1,97 +0,0 @@
package chroot
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
confighelper "github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/stretchr/testify/assert"
)
func buildTestRootDevice() *ec2.BlockDeviceMapping {
return &ec2.BlockDeviceMapping{
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(10),
SnapshotId: aws.String("snap-1234"),
VolumeType: aws.String("gp2"),
Encrypted: aws.Bool(false),
},
}
}
func TestCreateVolume_Default(t *testing.T) {
stepCreateVolume := new(StepCreateVolume)
_, err := stepCreateVolume.buildCreateVolumeInput("test-az", buildTestRootDevice())
assert.NoError(t, err)
}
func TestCreateVolume_Shrink(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeSize: 1}
testRootDevice := buildTestRootDevice()
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
// Ensure that the new value is equal to the size of the old root device
assert.Equal(t, *ret.Size, *testRootDevice.Ebs.VolumeSize)
}
func TestCreateVolume_Expand(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeSize: 25}
testRootDevice := buildTestRootDevice()
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
// Ensure that the new value is equal to the size of the value passed in
assert.Equal(t, *ret.Size, stepCreateVolume.RootVolumeSize)
}
func TestCreateVolume_io1_to_io1(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeType: "io1"}
testRootDevice := buildTestRootDevice()
testRootDevice.Ebs.VolumeType = aws.String("io1")
testRootDevice.Ebs.Iops = aws.Int64(1000)
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
assert.Equal(t, *ret.VolumeType, stepCreateVolume.RootVolumeType)
assert.Equal(t, *ret.Iops, *testRootDevice.Ebs.Iops)
}
func TestCreateVolume_io1_to_gp2(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeType: "gp2"}
testRootDevice := buildTestRootDevice()
testRootDevice.Ebs.VolumeType = aws.String("io1")
testRootDevice.Ebs.Iops = aws.Int64(1000)
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
assert.Equal(t, *ret.VolumeType, stepCreateVolume.RootVolumeType)
assert.Nil(t, ret.Iops)
}
func TestCreateVolume_gp2_to_io1(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeType: "io1"}
testRootDevice := buildTestRootDevice()
_, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.Error(t, err)
}
func TestCreateVolume_Encrypted(t *testing.T) {
stepCreateVolume := StepCreateVolume{RootVolumeEncryptBoot: confighelper.TrileanFromBool(true)}
testRootDevice := buildTestRootDevice()
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
// Ensure that the new value is equal to the the value passed in
assert.Equal(t, confighelper.TrileanFromBool(*ret.Encrypted), stepCreateVolume.RootVolumeEncryptBoot)
}
func TestCreateVolume_Custom_KMS_Key_Encrypted(t *testing.T) {
stepCreateVolume := StepCreateVolume{
RootVolumeEncryptBoot: confighelper.TrileanFromBool(true),
RootVolumeKmsKeyId: "alias/1234",
}
testRootDevice := buildTestRootDevice()
ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice)
assert.NoError(t, err)
// Ensure that the new value is equal to the value passed in
assert.Equal(t, *ret.KmsKeyId, stepCreateVolume.RootVolumeKmsKeyId)
}
-15
View File
@@ -1,15 +0,0 @@
package chroot
import (
"testing"
"github.com/hashicorp/packer-plugin-sdk/chroot"
)
func TestFlockCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
var raw interface{}
raw = new(StepFlock)
if _, ok := raw.(chroot.Cleanup); !ok {
t.Fatalf("cleanup func should be a CleanupFunc")
}
}
@@ -1,15 +0,0 @@
package chroot
import (
"testing"
"github.com/hashicorp/packer-plugin-sdk/chroot"
)
func TestMountDeviceCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
var raw interface{}
raw = new(StepMountDevice)
if _, ok := raw.(chroot.Cleanup); !ok {
t.Fatalf("cleanup func should be a CleanupFunc")
}
}
@@ -1,216 +0,0 @@
package chroot
import (
"testing"
"github.com/hashicorp/packer-plugin-sdk/common"
amazon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
)
func testImage() ec2.Image {
return ec2.Image{
ImageId: aws.String("ami-abcd1234"),
Name: aws.String("ami_test_name"),
Architecture: aws.String("x86_64"),
KernelId: aws.String("aki-abcd1234"),
}
}
func TestStepRegisterAmi_buildRegisterOpts_pv(t *testing.T) {
config := Config{}
config.AMIName = "test_ami_name"
config.AMIDescription = "test_ami_description"
config.AMIVirtType = "paravirtual"
rootDeviceName := "foo"
image := testImage()
blockDevices := []*ec2.BlockDeviceMapping{}
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
expected := config.AMIVirtType
if *opts.VirtualizationType != expected {
t.Fatalf("Unexpected VirtType value: expected %s got %s\n", expected, *opts.VirtualizationType)
}
expected = config.AMIName
if *opts.Name != expected {
t.Fatalf("Unexpected Name value: expected %s got %s\n", expected, *opts.Name)
}
expected = *image.KernelId
if *opts.KernelId != expected {
t.Fatalf("Unexpected KernelId value: expected %s got %s\n", expected, *opts.KernelId)
}
expected = rootDeviceName
if *opts.RootDeviceName != expected {
t.Fatalf("Unexpected RootDeviceName value: expected %s got %s\n", expected, *opts.RootDeviceName)
}
}
func TestStepRegisterAmi_buildRegisterOpts_hvm(t *testing.T) {
config := Config{}
config.AMIName = "test_ami_name"
config.AMIDescription = "test_ami_description"
config.AMIVirtType = "hvm"
rootDeviceName := "foo"
image := testImage()
blockDevices := []*ec2.BlockDeviceMapping{}
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
expected := config.AMIVirtType
if *opts.VirtualizationType != expected {
t.Fatalf("Unexpected VirtType value: expected %s got %s\n", expected, *opts.VirtualizationType)
}
expected = config.AMIName
if *opts.Name != expected {
t.Fatalf("Unexpected Name value: expected %s got %s\n", expected, *opts.Name)
}
if opts.KernelId != nil {
t.Fatalf("Unexpected KernelId value: expected nil got %s\n", *opts.KernelId)
}
expected = rootDeviceName
if *opts.RootDeviceName != expected {
t.Fatalf("Unexpected RootDeviceName value: expected %s got %s\n", expected, *opts.RootDeviceName)
}
}
func TestStepRegisterAmi_buildRegisterOptsFromScratch(t *testing.T) {
rootDeviceName := "/dev/sda"
snapshotID := "foo"
config := Config{
FromScratch: true,
PackerConfig: common.PackerConfig{},
AMIMappings: []amazon.BlockDevice{
{
DeviceName: rootDeviceName,
},
},
RootDeviceName: rootDeviceName,
}
registerOpts := buildBaseRegisterOpts(&config, nil, 10, snapshotID, config.AMIName)
if len(registerOpts.BlockDeviceMappings) != 1 {
t.Fatal("Expected block device mapping of length 1")
}
if *registerOpts.BlockDeviceMappings[0].Ebs.SnapshotId != snapshotID {
t.Fatalf("Snapshot ID of root disk not set to snapshot id %s", rootDeviceName)
}
}
func TestStepRegisterAmi_buildRegisterOptFromExistingImage(t *testing.T) {
rootDeviceName := "/dev/sda"
snapshotID := "foo"
config := Config{
FromScratch: false,
PackerConfig: common.PackerConfig{},
}
sourceImage := ec2.Image{
RootDeviceName: &rootDeviceName,
BlockDeviceMappings: []*ec2.BlockDeviceMapping{
{
DeviceName: &rootDeviceName,
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(10),
},
},
// Throw in an ephemeral device, it seems like all devices in the return struct in a source AMI have
// a size, even if it's for ephemeral
{
DeviceName: aws.String("/dev/sdb"),
VirtualName: aws.String("ephemeral0"),
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(0),
},
},
},
}
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotID, config.AMIName)
if len(registerOpts.BlockDeviceMappings) != 2 {
t.Fatal("Expected block device mapping of length 2")
}
for _, dev := range registerOpts.BlockDeviceMappings {
if dev.Ebs.SnapshotId != nil && *dev.Ebs.SnapshotId == snapshotID {
// Even though root volume size is in config, it isn't used, instead we use the root volume size
// that's derived when we build the step
if *dev.Ebs.VolumeSize != 15 {
t.Fatalf("Root volume size not 15 GB instead %d", *dev.Ebs.VolumeSize)
}
return
}
}
t.Fatalf("Could not find device with snapshot ID %s", snapshotID)
}
func TestStepRegisterAmi_buildRegisterOptFromExistingImageWithBlockDeviceMappings(t *testing.T) {
const (
rootDeviceName = "/dev/xvda"
oldRootDevice = "/dev/sda1"
)
snapshotId := "foo"
config := Config{
FromScratch: false,
PackerConfig: common.PackerConfig{},
AMIMappings: []amazon.BlockDevice{
{
DeviceName: rootDeviceName,
},
},
RootDeviceName: rootDeviceName,
}
// Intentionally try to use a different root devicename
sourceImage := ec2.Image{
RootDeviceName: aws.String(oldRootDevice),
BlockDeviceMappings: []*ec2.BlockDeviceMapping{
{
DeviceName: aws.String(oldRootDevice),
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(10),
},
},
// Throw in an ephemeral device, it seems like all devices in the return struct in a source AMI have
// a size, even if it's for ephemeral
{
DeviceName: aws.String("/dev/sdb"),
VirtualName: aws.String("ephemeral0"),
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(0),
},
},
},
}
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotId, config.AMIName)
if len(registerOpts.BlockDeviceMappings) != 1 {
t.Fatal("Expected block device mapping of length 1")
}
if *registerOpts.BlockDeviceMappings[0].Ebs.SnapshotId != snapshotId {
t.Fatalf("Snapshot ID of root disk set to '%s' expected '%s'", *registerOpts.BlockDeviceMappings[0].Ebs.SnapshotId, rootDeviceName)
}
if *registerOpts.RootDeviceName != rootDeviceName {
t.Fatalf("Root device set to '%s' expected %s", *registerOpts.RootDeviceName, rootDeviceName)
}
if *registerOpts.BlockDeviceMappings[0].Ebs.VolumeSize != 15 {
t.Fatalf("Size of root disk not set to 15 GB, instead %d", *registerOpts.BlockDeviceMappings[0].Ebs.VolumeSize)
}
}
@@ -1,47 +0,0 @@
package common
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
)
func TestAccessConfigPrepare_Region(t *testing.T) {
c := FakeAccessConfig()
c.RawRegion = "us-east-12"
err := c.ValidateRegion(c.RawRegion)
if err == nil {
t.Fatalf("should have region validation err: %s", c.RawRegion)
}
c.RawRegion = "us-east-1"
err = c.ValidateRegion(c.RawRegion)
if err != nil {
t.Fatalf("shouldn't have region validation err: %s", c.RawRegion)
}
c.RawRegion = "custom"
err = c.ValidateRegion(c.RawRegion)
if err == nil {
t.Fatalf("should have region validation err: %s", c.RawRegion)
}
}
func TestAccessConfigPrepare_RegionRestricted(t *testing.T) {
c := FakeAccessConfig()
// Create a Session with a custom region
c.session = session.Must(session.NewSession(&aws.Config{
Region: aws.String("us-gov-west-1"),
}))
if err := c.Prepare(); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
if !c.IsGovCloud() {
t.Fatal("We should be in gov region.")
}
}
-244
View File
@@ -1,244 +0,0 @@
package common
import (
"fmt"
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer-plugin-sdk/template/config"
)
func testAMIConfig() *AMIConfig {
return &AMIConfig{
AMIName: "foo",
}
}
func getFakeAccessConfig(region string) *AccessConfig {
c := FakeAccessConfig()
c.RawRegion = region
return c
}
func TestAMIConfigPrepare_name(t *testing.T) {
c := testAMIConfig()
accessConf := FakeAccessConfig()
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AMIName = ""
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("should have error")
}
}
func (m *mockEC2Client) DescribeRegions(*ec2.DescribeRegionsInput) (*ec2.DescribeRegionsOutput, error) {
return &ec2.DescribeRegionsOutput{
Regions: []*ec2.Region{
{RegionName: aws.String("us-east-1")},
{RegionName: aws.String("us-east-2")},
{RegionName: aws.String("us-west-1")},
},
}, nil
}
func TestAMIConfigPrepare_regions(t *testing.T) {
c := testAMIConfig()
c.AMIRegions = nil
var errs []error
var err error
accessConf := FakeAccessConfig()
mockConn := &mockEC2Client{}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatalf("shouldn't have err: %#v", errs)
}
c.AMIRegions, err = listEC2Regions(mockConn)
if err != nil {
t.Fatalf("shouldn't have err: %s", err.Error())
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatalf("shouldn't have err: %#v", errs)
}
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-1"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatalf("bad: %s", errs[0])
}
expected := []string{"us-east-1", "us-west-1"}
if !reflect.DeepEqual(c.AMIRegions, expected) {
t.Fatalf("bad: %#v", c.AMIRegions)
}
c.AMIRegions = []string{"custom"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("shouldn't have error")
}
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "456-789-0123",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal(fmt.Sprintf("shouldn't have error: %s", errs[0]))
}
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have passed; we are able to use default KMS key if not sharing")
}
c.SnapshotUsers = []string{"user-foo", "user-bar"}
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have an error b/c can't use default KMS key if sharing")
}
c.AMIRegions = []string{"us-east-1", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "456-789-0123",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have error b/c theres a region in the key map that isn't in ami_regions")
}
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
}
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
}
c.SnapshotUsers = []string{"foo", "bar"}
c.AMIKmsKeyId = "123-abc-456"
c.AMIEncryptBootVolume = config.TriTrue
c.AMIRegions = []string{"us-east-1", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "",
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
}
// allow rawregion to exist in ami_regions list.
accessConf = getFakeAccessConfig("us-east-1")
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
c.AMIRegionKMSKeyIDs = nil
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should allow user to have the raw region in ami_regions")
}
}
func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
c := testAMIConfig()
c.AMIUsers = []string{"testAccountID"}
c.AMIEncryptBootVolume = config.TriTrue
accessConf := FakeAccessConfig()
c.AMIKmsKeyId = ""
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
}
c.AMIKmsKeyId = "89c3fb9a-de87-4f2a-aedc-fddc5138193c"
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatal("should be able to share ami with encrypted boot volume")
}
}
func TestAMIConfigPrepare_ValidateKmsKey(t *testing.T) {
c := testAMIConfig()
c.AMIEncryptBootVolume = config.TriTrue
accessConf := FakeAccessConfig()
validCases := []string{
"abcd1234-e567-890f-a12b-a123b4cd56ef",
"alias/foo/bar",
"arn:aws:kms:us-east-1:012345678910:key/abcd1234-a123-456a-a12b-a123b4cd56ef",
"arn:aws:kms:us-east-1:012345678910:alias/foo/bar",
"arn:aws-us-gov:kms:us-gov-east-1:123456789012:key/12345678-1234-abcd-0000-123456789012",
}
for _, validCase := range validCases {
c.AMIKmsKeyId = validCase
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatalf("%s should not have failed KMS key validation", validCase)
}
}
invalidCases := []string{
"ABCD1234-e567-890f-a12b-a123b4cd56ef",
"ghij1234-e567-890f-a12b-a123b4cd56ef",
"ghij1234+e567_890f-a12b-a123b4cd56ef",
"foo/bar",
"arn:aws:kms:us-east-1:012345678910:foo/bar",
"arn:foo:kms:us-east-1:012345678910:key/abcd1234-a123-456a-a12b-a123b4cd56ef",
}
for _, invalidCase := range invalidCases {
c.AMIKmsKeyId = invalidCase
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatalf("%s should have failed KMS key validation", invalidCase)
}
}
}
func TestAMINameValidation(t *testing.T) {
c := testAMIConfig()
accessConf := FakeAccessConfig()
c.AMIName = "aa"
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("shouldn't be able to have an ami name with less than 3 characters")
}
var longAmiName string
for i := 0; i < 129; i++ {
longAmiName += "a"
}
c.AMIName = longAmiName
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("shouldn't be able to have an ami name with great than 128 characters")
}
c.AMIName = "+aaa"
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("shouldn't be able to have an ami name with invalid characters")
}
c.AMIName = "fooBAR1()[] ./-'@_"
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatal("should be able to use all of the allowed AMI characters")
}
c.AMIName = `xyz-base-2017-04-05-1934`
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatalf("expected `xyz-base-2017-04-05-1934` to pass validation.")
}
}
-90
View File
@@ -1,90 +0,0 @@
package common
import (
"reflect"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestArtifact_Impl(t *testing.T) {
var _ packersdk.Artifact = new(Artifact)
}
func TestArtifactId(t *testing.T) {
expected := `east:foo,west:bar`
amis := make(map[string]string)
amis["east"] = "foo"
amis["west"] = "bar"
a := &Artifact{
Amis: amis,
}
result := a.Id()
if result != expected {
t.Fatalf("bad: %s", result)
}
}
func TestArtifactState_atlasMetadata(t *testing.T) {
a := &Artifact{
Amis: map[string]string{
"east": "foo",
"west": "bar",
},
}
actual := a.State("atlas.artifact.metadata")
expected := map[string]string{
"region.east": "foo",
"region.west": "bar",
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestArtifactString(t *testing.T) {
expected := `AMIs were created:
east: foo
west: bar
`
amis := make(map[string]string)
amis["east"] = "foo"
amis["west"] = "bar"
a := &Artifact{Amis: amis}
result := a.String()
if result != expected {
t.Fatalf("bad: %s", result)
}
}
func TestArtifactState(t *testing.T) {
expectedData := "this is the data"
artifact := &Artifact{
StateData: map[string]interface{}{"state_data": expectedData},
}
// Valid state
result := artifact.State("state_data")
if result != expectedData {
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
}
// Invalid state
result = artifact.State("invalid_key")
if result != nil {
t.Fatalf("Bad: State should be nil for invalid state data name")
}
// Nil StateData should not fail and should return nil
artifact = &Artifact{}
result = artifact.State("key")
if result != nil {
t.Fatalf("Bad: State should be nil for nil StateData")
}
}
-401
View File
@@ -1,401 +0,0 @@
package common
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
func TestBlockDevice(t *testing.T) {
cases := []struct {
Config *BlockDevice
Result *ec2.BlockDeviceMapping
}{
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
SnapshotId: "snap-1234",
VolumeType: "standard",
VolumeSize: 8,
DeleteOnTermination: true,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
SnapshotId: aws.String("snap-1234"),
VolumeType: aws.String("standard"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeSize: 8,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(false),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io1",
VolumeSize: 8,
DeleteOnTermination: true,
IOPS: aws.Int64(1000),
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("io1"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
Iops: aws.Int64(1000),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 8,
DeleteOnTermination: true,
IOPS: aws.Int64(1000),
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("io2"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
Iops: aws.Int64(1000),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnTermination: true,
Encrypted: config.TriTrue,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("gp2"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
Encrypted: aws.Bool(true),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnTermination: true,
Encrypted: config.TriTrue,
KmsKeyId: "2Fa48a521f-3aff-4b34-a159-376ac5d37812",
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("gp2"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
Encrypted: aws.Bool(true),
KmsKeyId: aws.String("2Fa48a521f-3aff-4b34-a159-376ac5d37812"),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "standard",
DeleteOnTermination: true,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("standard"),
DeleteOnTermination: aws.Bool(true),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VirtualName: "ephemeral0",
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
VirtualName: aws.String("ephemeral0"),
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
NoDevice: true,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
NoDevice: aws.String(""),
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
VolumeSize: 8,
Throughput: aws.Int64(125),
IOPS: aws.Int64(3000),
DeleteOnTermination: true,
Encrypted: config.TriTrue,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("gp3"),
VolumeSize: aws.Int64(8),
Throughput: aws.Int64(125),
Iops: aws.Int64(3000),
DeleteOnTermination: aws.Bool(true),
Encrypted: aws.Bool(true),
},
},
},
}
for _, tc := range cases {
var amiBlockDevices BlockDevices = []BlockDevice{*tc.Config}
var launchBlockDevices BlockDevices = []BlockDevice{*tc.Config}
expected := []*ec2.BlockDeviceMapping{tc.Result}
amiResults := amiBlockDevices.BuildEC2BlockDeviceMappings()
if diff := cmp.Diff(expected, amiResults); diff != "" {
t.Fatalf("Bad block device: %s", diff)
}
launchResults := launchBlockDevices.BuildEC2BlockDeviceMappings()
if diff := cmp.Diff(expected, launchResults); diff != "" {
t.Fatalf("Bad block device: %s", diff)
}
}
}
func TestIOPSValidation(t *testing.T) {
cases := []struct {
device BlockDevice
ok bool
msg string
}{
// volume size unknown
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io1",
IOPS: aws.Int64(1000),
},
ok: true,
},
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
IOPS: aws.Int64(1000),
},
ok: true,
},
// ratio requirement satisfied
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io1",
VolumeSize: 50,
IOPS: aws.Int64(1000),
},
ok: true,
},
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 100,
IOPS: aws.Int64(1000),
},
ok: true,
},
// ratio requirement not satisfied
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io1",
VolumeSize: 10,
IOPS: aws.Int64(2000),
},
ok: false,
msg: "/dev/sdb: the maximum ratio of provisioned IOPS to requested volume size (in GiB) is 50:1 for io1 volumes",
},
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 50,
IOPS: aws.Int64(30000),
},
ok: false,
msg: "/dev/sdb: the maximum ratio of provisioned IOPS to requested volume size (in GiB) is 500:1 for io2 volumes",
},
// exceed max iops
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 500,
IOPS: aws.Int64(99999),
},
ok: false,
msg: "IOPS must be between 100 and 64000 for device /dev/sdb",
},
// lower than min iops
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 50,
IOPS: aws.Int64(10),
},
ok: false,
msg: "IOPS must be between 100 and 64000 for device /dev/sdb",
},
// exceed max iops
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
VolumeSize: 50,
Throughput: aws.Int64(125),
IOPS: aws.Int64(99999),
},
ok: false,
msg: "IOPS must be between 3000 and 16000 for device /dev/sdb",
},
// lower than min iops
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
VolumeSize: 50,
Throughput: aws.Int64(125),
IOPS: aws.Int64(10),
},
ok: false,
msg: "IOPS must be between 3000 and 16000 for device /dev/sdb",
},
}
ctx := interpolate.Context{}
for _, testCase := range cases {
err := testCase.device.Prepare(&ctx)
if testCase.ok && err != nil {
t.Fatalf("should not error, but: %v", err)
}
if !testCase.ok {
if err == nil {
t.Fatalf("should error")
} else if err.Error() != testCase.msg {
t.Fatalf("wrong error: expected %s, found: %v", testCase.msg, err)
}
}
}
}
func TestThroughputValidation(t *testing.T) {
cases := []struct {
device BlockDevice
ok bool
msg string
}{
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
Throughput: aws.Int64(125),
IOPS: aws.Int64(3000),
},
ok: true,
},
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
Throughput: aws.Int64(1000),
IOPS: aws.Int64(3000),
},
ok: true,
},
// exceed max Throughput
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
Throughput: aws.Int64(1001),
IOPS: aws.Int64(3000),
},
ok: false,
msg: "Throughput must be between 125 and 1000 for device /dev/sdb",
},
// lower than min Throughput
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
Throughput: aws.Int64(124),
IOPS: aws.Int64(3000),
},
ok: false,
msg: "Throughput must be between 125 and 1000 for device /dev/sdb",
},
}
ctx := interpolate.Context{}
for _, testCase := range cases {
err := testCase.device.Prepare(&ctx)
if testCase.ok && err != nil {
t.Fatalf("should not error, but: %v", err)
}
if !testCase.ok {
if err == nil {
t.Fatalf("should error")
} else if err.Error() != testCase.msg {
t.Fatalf("wrong error: expected %s, found: %v", testCase.msg, err)
}
}
}
}
@@ -1,31 +0,0 @@
package common
import (
"testing"
)
func TestStepSourceAmiInfo_BuildFilter(t *testing.T) {
filter_key := "name"
filter_value := "foo"
filter_key2 := "name2"
filter_value2 := "foo2"
inputFilter := map[string]string{filter_key: filter_value, filter_key2: filter_value2}
outputFilter := buildEc2Filters(inputFilter)
// deconstruct filter back into things we can test
foundMap := map[string]bool{filter_key: false, filter_key2: false}
for _, filter := range outputFilter {
for key, value := range inputFilter {
if *filter.Name == key && *filter.Values[0] == value {
foundMap[key] = true
}
}
}
for k, v := range foundMap {
if !v {
t.Fatalf("Fail: should have found value for key: %s", k)
}
}
}
-70
View File
@@ -1,70 +0,0 @@
package common
import (
"fmt"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/aws/aws-sdk-go/aws/awserr"
)
type mockSTS struct {
}
func (m *mockSTS) DecodeAuthorizationMessage(input *sts.DecodeAuthorizationMessageInput) (*sts.DecodeAuthorizationMessageOutput, error) {
return &sts.DecodeAuthorizationMessageOutput{
DecodedMessage: aws.String(`{
"allowed": false,
"explicitDeny": true,
"matchedStatements": {}
}`),
}, nil
}
func TestErrorsParsing_RequestFailure(t *testing.T) {
ae := awserr.New("UnauthorizedOperation",
`You are not authorized to perform this operation. Encoded authorization failure message: D9Q7oicjOMr9l2CC-NPP1FiZXK9Ijia1k-3l0siBFCcrK3oSuMFMkBIO5TNj0HdXE-WfwnAcdycFOohfKroNO6toPJEns8RFVfy_M_IjNGmrEFJ6E62pnmBW0OLrMsXxR9FQE4gB4gJzSM0AD6cV6S3FOfqYzWBRX-sQdOT4HryGkFNRoFBr9Xbp-tRwiadwkbdHdfnV9fbRkXmnwCdULml16NBSofC4ZPepLMKmIB5rKjwk-m179UUh2XA-J5no0si6XcRo5GbHQB5QfCIwSHL4vsro2wLZUd16-8OWKyr3tVlTbQe0ERZskqRqRQ5E28QuiBCVV6XstUyo-T4lBSr75Fgnyr3wCO-dS3b_5Ns3WzA2JD4E2AJOAStXIU8IH5YuKkAg7C-dJMuBMPpmKCBEXhNoHDwCyOo5PsV3xMlc0jSb0qYGpfst_TDDtejcZfn7NssUjxVq9qkdH-OXz2gPoQB-hX8ycmZCL5UZwKc3TCLUr7TGnudHjmnMrE9cUo-yTCWfyHPLprhiYhTCKW18EikJ0O1EKI3FJ_b4F19_jFBPARjSwQc7Ut6MNCVzrPdZGYSF6acj5gPaxdy9uSkVQwWXK7Pd5MFP7EBDE1_DgYbzodgwDO2PXeVFUbSLBHKWo_ebZS9ZX2nYPcGss_sYaly0ZVSIJXp7G58B5BoFVhvVH6jYnF9XiAOjMltuP_ycu1pQP1lki500RY3baLvfeYeAsB38XZHKEgWZzq7Fei-uh89q0cjJTmlVyrfRU3q6`,
fmt.Errorf("You can't do it!!"))
rf := awserr.NewRequestFailure(ae, 400, "abc-def-123-456")
result := decodeAWSError(&mockSTS{}, rf)
if result == nil {
t.Error("Expected resulting error")
}
if !strings.Contains(result.Error(), "Authorization failure message:") {
t.Error("Expected authorization failure message")
}
}
func TestErrorsParsing_NonAuthorizationFailure(t *testing.T) {
ae := awserr.New("BadRequest",
`You did something wrong. Try again`,
fmt.Errorf("Request was no good."))
rf := awserr.NewRequestFailure(ae, 400, "abc-def-123-456")
result := decodeAWSError(&mockSTS{}, rf)
if result == nil {
t.Error("Expected resulting error")
}
if result != rf {
t.Error("Expected original error to be returned unchanged")
}
}
func TestErrorsParsing_NonAWSError(t *testing.T) {
err := fmt.Errorf("Random error occurred")
result := decodeAWSError(&mockSTS{}, err)
if result == nil {
t.Error("Expected resulting error")
}
if result != err {
t.Error("Expected original error to be returned unchanged")
}
}
@@ -1,91 +0,0 @@
package common
import (
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
)
func testImage() *ec2.Image {
return &ec2.Image{
ImageId: aws.String("ami-abcd1234"),
CreationDate: aws.String("ami_test_creation_date"),
Name: aws.String("ami_test_name"),
OwnerId: aws.String("ami_test_owner_id"),
ImageOwnerAlias: aws.String("ami_test_owner_alias"),
RootDeviceType: aws.String("ebs"),
Tags: []*ec2.Tag{
{
Key: aws.String("key-1"),
Value: aws.String("value-1"),
},
{
Key: aws.String("key-2"),
Value: aws.String("value-2"),
},
},
}
}
func testState() multistep.StateBag {
state := new(multistep.BasicStateBag)
return state
}
func testGeneratedData(state multistep.StateBag) packerbuilderdata.GeneratedData {
generatedData := packerbuilderdata.GeneratedData{State: state}
return generatedData
}
func TestInterpolateBuildInfo_extractBuildInfo_noSourceImage(t *testing.T) {
state := testState()
generatedData := testGeneratedData(state)
buildInfo := extractBuildInfo("foo", state, &generatedData)
expected := BuildInfoTemplate{
BuildRegion: "foo",
}
if !reflect.DeepEqual(*buildInfo, expected) {
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
}
}
func TestInterpolateBuildInfo_extractBuildInfo_withSourceImage(t *testing.T) {
state := testState()
state.Put("source_image", testImage())
generatedData := testGeneratedData(state)
buildInfo := extractBuildInfo("foo", state, &generatedData)
expected := BuildInfoTemplate{
BuildRegion: "foo",
SourceAMI: "ami-abcd1234",
SourceAMICreationDate: "ami_test_creation_date",
SourceAMIName: "ami_test_name",
SourceAMIOwner: "ami_test_owner_id",
SourceAMIOwnerName: "ami_test_owner_alias",
SourceAMITags: map[string]string{
"key-1": "value-1",
"key-2": "value-2",
},
}
if !reflect.DeepEqual(*buildInfo, expected) {
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
}
}
func TestInterpolateBuildInfo_extractBuildInfo_GeneratedDataWithSourceImageName(t *testing.T) {
state := testState()
state.Put("source_image", testImage())
generatedData := testGeneratedData(state)
extractBuildInfo("foo", state, &generatedData)
generatedDataState := state.Get("generated_data").(map[string]interface{})
if generatedDataState["SourceAMIName"] != "ami_test_name" {
t.Fatalf("Unexpected state SourceAMIName: expected %#v got %#v\n", "ami_test_name", generatedDataState["SourceAMIName"])
}
}
-251
View File
@@ -1,251 +0,0 @@
package common
import (
"io/ioutil"
"os"
"regexp"
"testing"
"github.com/hashicorp/packer-plugin-sdk/communicator"
)
func init() {
// Clear out the AWS access key env vars so they don't
// affect our tests.
os.Setenv("AWS_ACCESS_KEY_ID", "")
os.Setenv("AWS_ACCESS_KEY", "")
os.Setenv("AWS_SECRET_ACCESS_KEY", "")
os.Setenv("AWS_SECRET_KEY", "")
}
func testConfig() *RunConfig {
return &RunConfig{
SourceAmi: "abcd",
InstanceType: "m1.small",
Comm: communicator.Config{
SSH: communicator.SSH{
SSHUsername: "foo",
},
},
}
}
func testConfigFilter() *RunConfig {
config := testConfig()
config.SourceAmi = ""
config.SourceAmiFilter = AmiFilterOptions{}
return config
}
func TestRunConfigPrepare(t *testing.T) {
c := testConfig()
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_InstanceType(t *testing.T) {
c := testConfig()
c.InstanceType = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if an instance_type is not specified")
}
}
func TestRunConfigPrepare_SourceAmi(t *testing.T) {
c := testConfig()
c.SourceAmi = ""
if err := c.Prepare(nil); len(err) != 2 {
t.Fatalf("Should error if a source_ami (or source_ami_filter) is not specified")
}
}
func TestRunConfigPrepare_SourceAmiFilterBlank(t *testing.T) {
c := testConfigFilter()
if err := c.Prepare(nil); len(err) != 2 {
t.Fatalf("Should error if source_ami_filter is empty or not specified (and source_ami is not specified)")
}
}
func TestRunConfigPrepare_SourceAmiFilterOwnersBlank(t *testing.T) {
c := testConfigFilter()
filter_key := "name"
filter_value := "foo"
c.SourceAmiFilter.Filters = map[string]string{filter_key: filter_value}
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if Owners is not specified)")
}
}
func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) {
c := testConfigFilter()
owner := "123"
filter_key := "name"
filter_value := "foo"
goodFilter := AmiFilterOptions{
Owners: []string{owner},
Filters: map[string]string{filter_key: filter_value},
}
c.SourceAmiFilter = goodFilter
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_EnableT2UnlimitedGood(t *testing.T) {
c := testConfig()
// Must have a T2 instance type if T2 Unlimited is enabled
c.InstanceType = "t2.micro"
c.EnableT2Unlimited = true
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_EnableT2UnlimitedBadInstanceType(t *testing.T) {
c := testConfig()
// T2 Unlimited cannot be used with instance types other than T2
c.InstanceType = "m5.large"
c.EnableT2Unlimited = true
err := c.Prepare(nil)
if len(err) != 1 {
t.Fatalf("Should error if T2 Unlimited is enabled with non-T2 instance_type")
}
}
func TestRunConfigPrepare_EnableT2UnlimitedBadWithSpotInstanceRequest(t *testing.T) {
c := testConfig()
// T2 Unlimited cannot be used with Spot Instances
c.InstanceType = "t2.micro"
c.EnableT2Unlimited = true
c.SpotPrice = "auto"
err := c.Prepare(nil)
if len(err) != 1 {
t.Fatalf("Should error if T2 Unlimited has been used in conjuntion with a Spot Price request")
}
}
func TestRunConfigPrepare_SpotAuto(t *testing.T) {
c := testConfig()
c.SpotPrice = "auto"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
// Shouldn't error (YET) even though SpotPriceAutoProduct is deprecated
c.SpotPriceAutoProduct = "Linux/Unix"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SSHPort(t *testing.T) {
c := testConfig()
c.Comm.SSHPort = 0
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHPort != 22 {
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
}
c.Comm.SSHPort = 44
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHPort != 44 {
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
}
}
func TestRunConfigPrepare_UserData(t *testing.T) {
c := testConfig()
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
c.UserData = "foo"
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if user_data string and user_data_file have both been specified")
}
}
func TestRunConfigPrepare_UserDataFile(t *testing.T) {
c := testConfig()
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
c.UserDataFile = "idontexistidontthink"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if the file specified by user_data_file does not exist")
}
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
c := testConfig()
c.Comm.SSHTemporaryKeyPairName = ""
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHTemporaryKeyPairName == "" {
t.Fatal("keypair name is empty")
}
// Match prefix and UUID, e.g. "packer_5790d491-a0b8-c84c-c9d2-2aea55086550".
r := regexp.MustCompile(`\Apacker_(?:(?i)[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}?)\z`)
if !r.MatchString(c.Comm.SSHTemporaryKeyPairName) {
t.Fatal("keypair name is not valid")
}
c.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
t.Fatal("keypair name does not match")
}
}
func TestRunConfigPrepare_TenancyBad(t *testing.T) {
c := testConfig()
c.Tenancy = "not_real"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatal("Should error if tenancy is set to an invalid type")
}
}
func TestRunConfigPrepare_TenancyGood(t *testing.T) {
validTenancy := []string{"", "default", "dedicated", "host"}
for _, vt := range validTenancy {
c := testConfig()
c.Tenancy = vt
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("Should not error if tenancy is set to %s", vt)
}
}
}
-141
View File
@@ -1,141 +0,0 @@
package common
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
const (
privateIP = "10.0.0.1"
publicIP = "192.168.1.1"
privateDNS = "private.dns.test"
publicDNS = "public.dns.test"
localhost = "localhost"
sshHostTemplate = "custom.host.value"
)
func TestSSHHost(t *testing.T) {
origSshHostSleepDuration := sshHostSleepDuration
defer func() { sshHostSleepDuration = origSshHostSleepDuration }()
sshHostSleepDuration = 0
var cases = []struct {
allowTries int
vpcId string
sshInterface string
ok bool
wantHost string
sshHostOverride string
}{
{1, "", "", true, publicDNS, ""},
{1, "", "private_ip", true, privateIP, ""},
{1, "", "session_manager", true, localhost, ""},
{1, "vpc-id", "", true, publicIP, ""},
{1, "vpc-id", "private_ip", true, privateIP, ""},
{1, "vpc-id", "private_dns", true, privateDNS, ""},
{1, "vpc-id", "public_dns", true, publicDNS, ""},
{1, "vpc-id", "public_ip", true, publicIP, ""},
{1, "vpc-id", "session_manager", true, localhost, ""},
{2, "", "", true, publicDNS, ""},
{2, "", "private_ip", true, privateIP, ""},
{2, "vpc-id", "", true, publicIP, ""},
{2, "vpc-id", "private_ip", true, privateIP, ""},
{2, "vpc-id", "private_dns", true, privateDNS, ""},
{2, "vpc-id", "public_dns", true, publicDNS, ""},
{2, "vpc-id", "public_ip", true, publicIP, ""},
{3, "", "", false, "", ""},
{3, "", "private_ip", false, "", ""},
{3, "vpc-id", "", false, "", ""},
{3, "vpc-id", "private_ip", false, "", ""},
{3, "vpc-id", "private_dns", false, "", ""},
{3, "vpc-id", "public_dns", false, "", ""},
{3, "vpc-id", "public_ip", false, "", ""},
{1, "", "", true, sshHostTemplate, sshHostTemplate},
{1, "vpc-id", "", true, sshHostTemplate, sshHostTemplate},
{2, "vpc-id", "private_dns", true, sshHostTemplate, sshHostTemplate},
}
for _, c := range cases {
testSSHHost(t, c.allowTries, c.vpcId, c.sshInterface, c.ok, c.wantHost,
c.sshHostOverride)
}
}
func testSSHHost(t *testing.T, allowTries int, vpcId string, sshInterface string,
ok bool, wantHost string, sshHostOverride string) {
t.Logf("allowTries=%d vpcId=%s sshInterface=%s ok=%t wantHost=%q sshHostOverride=%s",
allowTries, vpcId, sshInterface, ok, wantHost, sshHostOverride)
e := &fakeEC2Describer{
allowTries: allowTries,
vpcId: vpcId,
privateIP: privateIP,
publicIP: publicIP,
privateDNS: privateDNS,
publicDNS: publicDNS,
}
f := SSHHost(e, sshInterface, sshHostOverride)
st := &multistep.BasicStateBag{}
st.Put("instance", &ec2.Instance{
InstanceId: aws.String("instance-id"),
})
host, err := f(st)
if e.tries > allowTries {
t.Fatalf("got %d ec2 DescribeInstances tries, want %d", e.tries, allowTries)
}
switch {
case ok && err != nil:
t.Fatalf("expected no error, got %+v", err)
case !ok && err == nil:
t.Fatalf("expected error, got none and host %s", host)
}
if host != wantHost {
t.Fatalf("got host %s, want %s", host, wantHost)
}
}
type fakeEC2Describer struct {
allowTries int
tries int
vpcId string
privateIP, publicIP, privateDNS, publicDNS string
}
func (d *fakeEC2Describer) DescribeInstances(in *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
d.tries++
instance := &ec2.Instance{
InstanceId: aws.String("instance-id"),
}
if d.vpcId != "" {
instance.VpcId = aws.String(d.vpcId)
}
if d.tries >= d.allowTries {
instance.PublicIpAddress = aws.String(d.publicIP)
instance.PrivateIpAddress = aws.String(d.privateIP)
instance.PublicDnsName = aws.String(d.publicDNS)
instance.PrivateDnsName = aws.String(d.privateDNS)
}
out := &ec2.DescribeInstancesOutput{
Reservations: []*ec2.Reservation{
{
Instances: []*ec2.Instance{instance},
},
},
}
return out, nil
}
-66
View File
@@ -1,66 +0,0 @@
package common
import (
"reflect"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws/request"
)
func testGetWaiterOptions(t *testing.T) {
// no vars are set
envValues := overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
envInfo{"AWS_TIMEOUT_SECONDS", 300, false},
}
options := applyEnvOverrides(envValues)
if len(options) > 0 {
t.Fatalf("Did not expect any waiter options to be generated; actual: %#v", options)
}
// all vars are set
envValues = overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 1, true},
envInfo{"AWS_MAX_ATTEMPTS", 800, true},
envInfo{"AWS_TIMEOUT_SECONDS", 20, true},
}
options = applyEnvOverrides(envValues)
expected := []request.WaiterOption{
request.WithWaiterDelay(request.ConstantWaiterDelay(time.Duration(1) * time.Second)),
request.WithWaiterMaxAttempts(800),
}
if !reflect.DeepEqual(options, expected) {
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
}
// poll delay is not set
envValues = overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
envInfo{"AWS_MAX_ATTEMPTS", 800, true},
envInfo{"AWS_TIMEOUT_SECONDS", 300, false},
}
options = applyEnvOverrides(envValues)
expected = []request.WaiterOption{
request.WithWaiterMaxAttempts(800),
}
if !reflect.DeepEqual(options, expected) {
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
}
// poll delay is not set but timeout seconds is
envValues = overridableWaitVars{
envInfo{"AWS_POLL_DELAY_SECONDS", 2, false},
envInfo{"AWS_MAX_ATTEMPTS", 0, false},
envInfo{"AWS_TIMEOUT_SECONDS", 20, true},
}
options = applyEnvOverrides(envValues)
expected = []request.WaiterOption{
request.WithWaiterDelay(request.ConstantWaiterDelay(time.Duration(2) * time.Second)),
request.WithWaiterMaxAttempts(10),
}
if !reflect.DeepEqual(options, expected) {
t.Fatalf("expected != actual!! Expected: %#v; Actual: %#v.", expected, options)
}
}
@@ -1,404 +0,0 @@
package common
import (
"bytes"
"context"
"fmt"
"sync"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
)
// Define a mock struct to be used in unit tests for common aws steps.
type mockEC2Conn struct {
ec2iface.EC2API
Config *aws.Config
// Counters to figure out what code path was taken
copyImageCount int
describeImagesCount int
deregisterImageCount int
deleteSnapshotCount int
waitCount int
lock sync.Mutex
}
func (m *mockEC2Conn) CopyImage(copyInput *ec2.CopyImageInput) (*ec2.CopyImageOutput, error) {
m.lock.Lock()
m.copyImageCount++
m.lock.Unlock()
copiedImage := fmt.Sprintf("%s-copied-%d", *copyInput.SourceImageId, m.copyImageCount)
output := &ec2.CopyImageOutput{
ImageId: &copiedImage,
}
return output, nil
}
// functions we have to create mock responses for in order for test to run
func (m *mockEC2Conn) DescribeImages(*ec2.DescribeImagesInput) (*ec2.DescribeImagesOutput, error) {
m.lock.Lock()
m.describeImagesCount++
m.lock.Unlock()
output := &ec2.DescribeImagesOutput{
Images: []*ec2.Image{{}},
}
return output, nil
}
func (m *mockEC2Conn) DeregisterImage(*ec2.DeregisterImageInput) (*ec2.DeregisterImageOutput, error) {
m.lock.Lock()
m.deregisterImageCount++
m.lock.Unlock()
output := &ec2.DeregisterImageOutput{}
return output, nil
}
func (m *mockEC2Conn) DeleteSnapshot(*ec2.DeleteSnapshotInput) (*ec2.DeleteSnapshotOutput, error) {
m.lock.Lock()
m.deleteSnapshotCount++
m.lock.Unlock()
output := &ec2.DeleteSnapshotOutput{}
return output, nil
}
func (m *mockEC2Conn) WaitUntilImageAvailableWithContext(aws.Context, *ec2.DescribeImagesInput, ...request.WaiterOption) error {
m.lock.Lock()
m.waitCount++
m.lock.Unlock()
return nil
}
func getMockConn(config *AccessConfig, target string) (ec2iface.EC2API, error) {
mockConn := &mockEC2Conn{
Config: aws.NewConfig(),
}
return mockConn, nil
}
// Create statebag for running test
func tState() multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
state.Put("amis", map[string]string{"us-east-1": "ami-12345"})
state.Put("snapshots", map[string][]string{"us-east-1": {"snap-0012345"}})
conn, _ := getMockConn(&AccessConfig{}, "us-east-2")
state.Put("ec2", conn)
return state
}
func TestStepAMIRegionCopy_duplicates(t *testing.T) {
// ------------------------------------------------------------------------
// Test that if the original region is added to both Regions and Region,
// the ami is only copied once (with encryption).
// ------------------------------------------------------------------------
stepAMIRegionCopy := StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-east-1"},
AMIKmsKeyId: "12345",
// Original region key in regionkeyids is different than in amikmskeyid
RegionKeyIds: map[string]string{"us-east-1": "12345"},
EncryptBootVolume: config.TriTrue,
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state := tState()
state.Put("intermediary_image", true)
stepAMIRegionCopy.Run(context.Background(), state)
if len(stepAMIRegionCopy.Regions) != 1 {
t.Fatalf("Should have added original ami to Regions one time only")
}
// ------------------------------------------------------------------------
// Both Region and Regions set, but no encryption - shouldn't copy anything
// ------------------------------------------------------------------------
// the ami is only copied once.
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-east-1"},
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
state.Put("intermediary_image", false)
stepAMIRegionCopy.getRegionConn = getMockConn
stepAMIRegionCopy.Run(context.Background(), state)
if len(stepAMIRegionCopy.Regions) != 0 {
t.Fatalf("Should not have added original ami to Regions; not encrypting")
}
// ------------------------------------------------------------------------
// Both Region and Regions set, but no encryption - shouldn't copy anything,
// this tests false as opposed to nil value above.
// ------------------------------------------------------------------------
// the ami is only copied once.
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-east-1"},
EncryptBootVolume: config.TriFalse,
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
state.Put("intermediary_image", false)
stepAMIRegionCopy.getRegionConn = getMockConn
stepAMIRegionCopy.Run(context.Background(), state)
if len(stepAMIRegionCopy.Regions) != 0 {
t.Fatalf("Should not have added original ami to Regions once; not" +
"encrypting")
}
// ------------------------------------------------------------------------
// Multiple regions, many duplicates, and encryption (this shouldn't ever
// happen because of our template validation, but good to test it.)
// ------------------------------------------------------------------------
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
// Many duplicates for only 3 actual values
Regions: []string{"us-east-1", "us-west-2", "us-west-2", "ap-east-1", "ap-east-1", "ap-east-1"},
AMIKmsKeyId: "IlikePancakes",
// Original region key in regionkeyids is different than in amikmskeyid
RegionKeyIds: map[string]string{"us-east-1": "12345", "us-west-2": "abcde", "ap-east-1": "xyz"},
EncryptBootVolume: config.TriTrue,
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state.Put("intermediary_image", true)
stepAMIRegionCopy.Run(context.Background(), state)
if len(stepAMIRegionCopy.Regions) != 3 {
t.Fatalf("Each AMI should have been added to Regions one time only.")
}
// Also verify that we respect RegionKeyIds over AMIKmsKeyIds:
if stepAMIRegionCopy.RegionKeyIds["us-east-1"] != "12345" {
t.Fatalf("RegionKeyIds should take precedence over AmiKmsKeyIds")
}
// ------------------------------------------------------------------------
// Multiple regions, many duplicates, NO encryption
// ------------------------------------------------------------------------
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
// Many duplicates for only 3 actual values
Regions: []string{"us-east-1", "us-west-2", "us-west-2", "ap-east-1", "ap-east-1", "ap-east-1"},
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state.Put("intermediary_image", false)
stepAMIRegionCopy.Run(context.Background(), state)
if len(stepAMIRegionCopy.Regions) != 2 {
t.Fatalf("Each AMI should have been added to Regions one time only, " +
"and original region shouldn't be added at all")
}
}
func TestStepAmiRegionCopy_nil_encryption(t *testing.T) {
// create step
stepAMIRegionCopy := StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: make([]string, 0),
AMIKmsKeyId: "",
RegionKeyIds: make(map[string]string),
EncryptBootVolume: config.TriUnset,
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state := tState()
state.Put("intermediary_image", false)
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete != "" {
t.Fatalf("Shouldn't have an intermediary ami if encrypt is nil")
}
if len(stepAMIRegionCopy.Regions) != 0 {
t.Fatalf("Should not have added original ami to original region")
}
}
func TestStepAmiRegionCopy_true_encryption(t *testing.T) {
// create step
stepAMIRegionCopy := StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: make([]string, 0),
AMIKmsKeyId: "",
RegionKeyIds: make(map[string]string),
EncryptBootVolume: config.TriTrue,
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state := tState()
state.Put("intermediary_image", true)
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete == "" {
t.Fatalf("Should delete original AMI if encrypted=true")
}
if len(stepAMIRegionCopy.Regions) == 0 {
t.Fatalf("Should have added original ami to Regions")
}
}
func TestStepAmiRegionCopy_nil_intermediary(t *testing.T) {
// create step
stepAMIRegionCopy := StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: make([]string, 0),
AMIKmsKeyId: "",
RegionKeyIds: make(map[string]string),
EncryptBootVolume: config.TriFalse,
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state := tState()
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete != "" {
t.Fatalf("Should not delete original AMI if no intermediary")
}
if len(stepAMIRegionCopy.Regions) != 0 {
t.Fatalf("Should not have added original ami to Regions")
}
}
func TestStepAmiRegionCopy_AMISkipBuildRegion(t *testing.T) {
// ------------------------------------------------------------------------
// skip build region is true
// ------------------------------------------------------------------------
stepAMIRegionCopy := StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-west-1"},
AMIKmsKeyId: "",
RegionKeyIds: map[string]string{"us-west-1": "abcde"},
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
AMISkipBuildRegion: true,
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state := tState()
state.Put("intermediary_image", true)
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete == "" {
t.Fatalf("Should delete original AMI if skip_save_build_region=true")
}
if len(stepAMIRegionCopy.Regions) != 1 {
t.Fatalf("Should not have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
}
// ------------------------------------------------------------------------
// skip build region is false.
// ------------------------------------------------------------------------
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-west-1"},
AMIKmsKeyId: "",
RegionKeyIds: make(map[string]string),
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
AMISkipBuildRegion: false,
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state.Put("intermediary_image", false) // not encrypted
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete != "" {
t.Fatalf("Shouldn't have an intermediary AMI, so dont delete original ami")
}
if len(stepAMIRegionCopy.Regions) != 1 {
t.Fatalf("Should not have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
}
// ------------------------------------------------------------------------
// skip build region is false, but encrypt is true
// ------------------------------------------------------------------------
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-west-1"},
AMIKmsKeyId: "",
RegionKeyIds: map[string]string{"us-west-1": "abcde"},
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
AMISkipBuildRegion: false,
EncryptBootVolume: config.TriTrue,
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state.Put("intermediary_image", true) //encrypted
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete == "" {
t.Fatalf("Have to delete intermediary AMI")
}
if len(stepAMIRegionCopy.Regions) != 2 {
t.Fatalf("Should have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
}
// ------------------------------------------------------------------------
// skip build region is true, and encrypt is true
// ------------------------------------------------------------------------
stepAMIRegionCopy = StepAMIRegionCopy{
AccessConfig: FakeAccessConfig(),
Regions: []string{"us-west-1"},
AMIKmsKeyId: "",
RegionKeyIds: map[string]string{"us-west-1": "abcde"},
Name: "fake-ami-name",
OriginalRegion: "us-east-1",
AMISkipBuildRegion: true,
EncryptBootVolume: config.TriTrue,
}
// mock out the region connection code
stepAMIRegionCopy.getRegionConn = getMockConn
state.Put("intermediary_image", true) //encrypted
stepAMIRegionCopy.Run(context.Background(), state)
if stepAMIRegionCopy.toDelete == "" {
t.Fatalf("Have to delete intermediary AMI")
}
if len(stepAMIRegionCopy.Regions) != 1 {
t.Fatalf("Should not have added original ami to Regions; Regions: %#v", stepAMIRegionCopy.Regions)
}
}
@@ -1,70 +0,0 @@
package common
import (
"fmt"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
)
//DescribeVpcs mocks an ec2.DescribeVpcsOutput for a given input
func (m *mockEC2Conn) DescribeVpcs(input *ec2.DescribeVpcsInput) (*ec2.DescribeVpcsOutput, error) {
if input == nil || aws.StringValue(input.VpcIds[0]) == "" {
return nil, fmt.Errorf("oops looks like we need more input")
}
var isDefault bool
vpcID := aws.StringValue(input.VpcIds[0])
//only one default VPC per region
if strings.Contains("vpc-default-id", vpcID) {
isDefault = true
}
output := &ec2.DescribeVpcsOutput{
Vpcs: []*ec2.Vpc{
{IsDefault: aws.Bool(isDefault),
VpcId: aws.String(vpcID),
},
},
}
return output, nil
}
func TestStepPreValidate_checkVpc(t *testing.T) {
tt := []struct {
name string
step StepPreValidate
errorExpected bool
}{
{"DefaultVpc", StepPreValidate{VpcId: "vpc-default-id"}, false},
{"NonDefaultVpcNoSubnet", StepPreValidate{VpcId: "vpc-1234567890"}, true},
{"NonDefaultVpcWithSubnet", StepPreValidate{VpcId: "vpc-1234567890", SubnetId: "subnet-1234567890"}, false},
{"SubnetWithNoVpc", StepPreValidate{SubnetId: "subnet-1234567890"}, false},
{"NoVpcInformation", StepPreValidate{}, false},
{"NonDefaultVpcWithSubnetFilter", StepPreValidate{VpcId: "vpc-1234567890", HasSubnetFilter: true}, false},
}
mockConn, err := getMockConn(nil, "")
if err != nil {
t.Fatal("unable to get a mock connection")
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
err := tc.step.checkVpc(mockConn)
if tc.errorExpected && err == nil {
t.Errorf("expected a validation error for %q but got %q", tc.name, err)
}
if !tc.errorExpected && err != nil {
t.Errorf("expected a validation to pass for %q but got %q", tc.name, err)
}
})
}
}
@@ -1,366 +0,0 @@
package common
import (
"bytes"
"context"
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// Create statebag for running test
func tStateSpot() multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
state.Put("availability_zone", "us-east-1c")
state.Put("securityGroupIds", []string{"sg-0b8984db72f213dc3"})
state.Put("iamInstanceProfile", "packer-123")
state.Put("subnet_id", "subnet-077fde4e")
state.Put("source_image", "")
return state
}
func getBasicStep() *StepRunSpotInstance {
stepRunSpotInstance := StepRunSpotInstance{
PollingConfig: new(AWSPollingConfig),
AssociatePublicIpAddress: false,
LaunchMappings: BlockDevices{},
BlockDurationMinutes: 0,
Debug: false,
Comm: &communicator.Config{
SSH: communicator.SSH{
SSHKeyPairName: "foo",
},
},
EbsOptimized: false,
ExpectedRootDevice: "ebs",
InstanceInitiatedShutdownBehavior: "stop",
InstanceType: "t2.micro",
Region: "us-east-1",
SourceAMI: "",
SpotPrice: "auto",
SpotTags: nil,
Tags: map[string]string{},
VolumeTags: nil,
UserData: "",
UserDataFile: "",
}
return &stepRunSpotInstance
}
func TestCreateTemplateData(t *testing.T) {
state := tStateSpot()
stepRunSpotInstance := getBasicStep()
template := stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
// expected := []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{
// &ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{
// DeleteOnTermination: aws.Bool(true),
// DeviceIndex: aws.Int64(0),
// Groups: aws.StringSlice([]string{"sg-0b8984db72f213dc3"}),
// SubnetId: aws.String("subnet-077fde4e"),
// },
// }
// if expected != template.NetworkInterfaces {
if template.NetworkInterfaces == nil {
t.Fatalf("Template should have contained a networkInterface object: recieved %#v", template.NetworkInterfaces)
}
if *template.IamInstanceProfile.Name != state.Get("iamInstanceProfile") {
t.Fatalf("Template should have contained a InstanceProfile name: recieved %#v", template.IamInstanceProfile.Name)
}
// Rerun, this time testing that we set security group IDs
state.Put("subnet_id", "")
template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
if template.NetworkInterfaces != nil {
t.Fatalf("Template shouldn't contain network interfaces object if subnet_id is unset.")
}
// Rerun, this time testing that instance doesn't have instance profile is iamInstanceProfile is unset
state.Put("iamInstanceProfile", "")
template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
fmt.Println(template.IamInstanceProfile)
if *template.IamInstanceProfile.Name != "" {
t.Fatalf("Template shouldn't contain instance profile if iamInstanceProfile is unset.")
}
}
func TestCreateTemplateData_NoEphemeral(t *testing.T) {
state := tStateSpot()
stepRunSpotInstance := getBasicStep()
stepRunSpotInstance.NoEphemeral = true
template := stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
if len(template.BlockDeviceMappings) != 26 {
t.Fatalf("Should have created 26 mappings to keep ephemeral drives from appearing.")
}
// Now check that noEphemeral doesn't mess with the mappings in real life.
// state = tStateSpot()
// stepRunSpotInstance = getBasicStep()
// stepRunSpotInstance.NoEphemeral = true
// mappings := []*ec2.InstanceBlockDeviceMapping{
// &ec2.InstanceBlockDeviceMapping{
// DeviceName: "xvda",
// Ebs: {
// DeleteOnTermination: true,
// Status: "attaching",
// VolumeId: "vol-044cd49c330f21c05",
// },
// },
// &ec2.InstanceBlockDeviceMapping{
// DeviceName: "/dev/xvdf",
// Ebs: {
// DeleteOnTermination: false,
// Status: "attaching",
// VolumeId: "vol-0eefaf2d6ae35827e",
// },
// },
// }
// template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
// &ec2.LaunchTemplateInstanceMarketOptionsRequest{})
// if len(*template.BlockDeviceMappings) != 26 {
// t.Fatalf("Should have created 26 mappings to keep ephemeral drives from appearing.")
// }
}
type runSpotEC2ConnMock struct {
ec2iface.EC2API
CreateLaunchTemplateParams []*ec2.CreateLaunchTemplateInput
CreateLaunchTemplateFn func(*ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error)
CreateFleetParams []*ec2.CreateFleetInput
CreateFleetFn func(*ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error)
CreateTagsParams []*ec2.CreateTagsInput
CreateTagsFn func(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error)
DescribeInstancesParams []*ec2.DescribeInstancesInput
DescribeInstancesFn func(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
}
func (m *runSpotEC2ConnMock) CreateLaunchTemplate(req *ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error) {
m.CreateLaunchTemplateParams = append(m.CreateLaunchTemplateParams, req)
resp, err := m.CreateLaunchTemplateFn(req)
return resp, err
}
func (m *runSpotEC2ConnMock) CreateFleet(req *ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error) {
m.CreateFleetParams = append(m.CreateFleetParams, req)
if m.CreateFleetFn != nil {
resp, err := m.CreateFleetFn(req)
return resp, err
} else {
return nil, nil
}
}
func (m *runSpotEC2ConnMock) DescribeInstances(req *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
m.DescribeInstancesParams = append(m.DescribeInstancesParams, req)
if m.DescribeInstancesFn != nil {
resp, err := m.DescribeInstancesFn(req)
return resp, err
} else {
return nil, nil
}
}
func (m *runSpotEC2ConnMock) CreateTags(req *ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) {
m.CreateTagsParams = append(m.CreateTagsParams, req)
if m.CreateTagsFn != nil {
resp, err := m.CreateTagsFn(req)
return resp, err
} else {
return nil, nil
}
}
func defaultEc2Mock(instanceId, spotRequestId, volumeId, launchTemplateId *string) *runSpotEC2ConnMock {
instance := &ec2.Instance{
InstanceId: instanceId,
SpotInstanceRequestId: spotRequestId,
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
{
Ebs: &ec2.EbsInstanceBlockDevice{
VolumeId: volumeId,
},
},
},
}
return &runSpotEC2ConnMock{
CreateLaunchTemplateFn: func(in *ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error) {
return &ec2.CreateLaunchTemplateOutput{
LaunchTemplate: &ec2.LaunchTemplate{
LaunchTemplateId: launchTemplateId,
},
Warning: nil,
}, nil
},
CreateFleetFn: func(*ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error) {
return &ec2.CreateFleetOutput{
Errors: nil,
FleetId: nil,
Instances: []*ec2.CreateFleetInstance{
{
InstanceIds: []*string{instanceId},
},
},
}, nil
},
DescribeInstancesFn: func(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
return &ec2.DescribeInstancesOutput{
NextToken: nil,
Reservations: []*ec2.Reservation{
{
Instances: []*ec2.Instance{instance},
},
},
}, nil
},
}
}
func TestRun(t *testing.T) {
instanceId := aws.String("test-instance-id")
spotRequestId := aws.String("spot-id")
volumeId := aws.String("volume-id")
launchTemplateId := aws.String("launchTemplateId")
ec2Mock := defaultEc2Mock(instanceId, spotRequestId, volumeId, launchTemplateId)
uiMock := packersdk.TestUi(t)
state := tStateSpot()
state.Put("ec2", ec2Mock)
state.Put("ui", uiMock)
state.Put("source_image", testImage())
stepRunSpotInstance := getBasicStep()
stepRunSpotInstance.Tags["Name"] = "Packer Builder"
stepRunSpotInstance.Tags["test-tag"] = "test-value"
stepRunSpotInstance.SpotTags = map[string]string{
"spot-tag": "spot-tag-value",
}
stepRunSpotInstance.VolumeTags = map[string]string{
"volume-tag": "volume-tag-value",
}
ctx := context.TODO()
action := stepRunSpotInstance.Run(ctx, state)
if err := state.Get("error"); err != nil {
t.Fatalf("should not error, but: %v", err)
}
if action != multistep.ActionContinue {
t.Fatalf("shoul continue, but: %v", action)
}
if len(ec2Mock.CreateLaunchTemplateParams) != 1 {
t.Fatalf("createLaunchTemplate should be invoked once, but invoked %v", len(ec2Mock.CreateLaunchTemplateParams))
}
launchTemplateName := ec2Mock.CreateLaunchTemplateParams[0].LaunchTemplateName
if len(ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications) != 1 {
t.Fatalf("exactly one launch template tag specification expected")
}
if *ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications[0].ResourceType != "launch-template" {
t.Fatalf("resource type 'launch-template' expected")
}
if len(ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications[0].Tags) != 1 {
t.Fatalf("1 launch template tag expected")
}
nameTag := ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications[0].Tags[0]
if *nameTag.Key != "spot-tag" || *nameTag.Value != "spot-tag-value" {
t.Fatalf("expected spot-tag: spot-tag-value")
}
if len(ec2Mock.CreateFleetParams) != 1 {
t.Fatalf("createFleet should be invoked once, but invoked %v", len(ec2Mock.CreateLaunchTemplateParams))
}
if *ec2Mock.CreateFleetParams[0].TargetCapacitySpecification.DefaultTargetCapacityType != "spot" {
t.Fatalf("capacity type should be spot")
}
if *ec2Mock.CreateFleetParams[0].TargetCapacitySpecification.TotalTargetCapacity != 1 {
t.Fatalf("target capacity should be 1")
}
if len(ec2Mock.CreateFleetParams[0].LaunchTemplateConfigs) != 1 {
t.Fatalf("exactly one launch config template expected")
}
if *ec2Mock.CreateFleetParams[0].LaunchTemplateConfigs[0].LaunchTemplateSpecification.LaunchTemplateName != *launchTemplateName {
t.Fatalf("launchTemplateName should match in createLaunchTemplate and createFleet requests")
}
if len(ec2Mock.DescribeInstancesParams) != 1 {
t.Fatalf("describeInstancesParams should be invoked once, but invoked %v", len(ec2Mock.DescribeInstancesParams))
}
if *ec2Mock.DescribeInstancesParams[0].InstanceIds[0] != *instanceId {
t.Fatalf("instanceId should match from createFleet response")
}
uiMock.Say(fmt.Sprintf("%v", ec2Mock.CreateTagsParams))
if len(ec2Mock.CreateTagsParams) != 3 {
t.Fatalf("createTags should be invoked 3 times")
}
if len(ec2Mock.CreateTagsParams[0].Resources) != 1 || *ec2Mock.CreateTagsParams[0].Resources[0] != *spotRequestId {
t.Fatalf("should create tags for spot request")
}
if len(ec2Mock.CreateTagsParams[1].Resources) != 1 || *ec2Mock.CreateTagsParams[1].Resources[0] != *instanceId {
t.Fatalf("should create tags for instance")
}
if len(ec2Mock.CreateTagsParams[2].Resources) != 1 || ec2Mock.CreateTagsParams[2].Resources[0] != volumeId {
t.Fatalf("should create tags for volume")
}
}
func TestRun_NoSpotTags(t *testing.T) {
instanceId := aws.String("test-instance-id")
spotRequestId := aws.String("spot-id")
volumeId := aws.String("volume-id")
launchTemplateId := aws.String("lt-id")
ec2Mock := defaultEc2Mock(instanceId, spotRequestId, volumeId, launchTemplateId)
uiMock := packersdk.TestUi(t)
state := tStateSpot()
state.Put("ec2", ec2Mock)
state.Put("ui", uiMock)
state.Put("source_image", testImage())
stepRunSpotInstance := getBasicStep()
stepRunSpotInstance.Tags["Name"] = "Packer Builder"
stepRunSpotInstance.Tags["test-tag"] = "test-value"
stepRunSpotInstance.VolumeTags = map[string]string{
"volume-tag": "volume-tag-value",
}
ctx := context.TODO()
action := stepRunSpotInstance.Run(ctx, state)
if err := state.Get("error"); err != nil {
t.Fatalf("should not error, but: %v", err)
}
if action != multistep.ActionContinue {
t.Fatalf("shoul continue, but: %v", action)
}
if len(ec2Mock.CreateLaunchTemplateParams[0].TagSpecifications) != 0 {
t.Fatalf("0 launch template tags expected")
}
}
@@ -1,43 +0,0 @@
package common
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/stretchr/testify/assert"
)
func TestStepSourceAmiInfo_PVImage(t *testing.T) {
err := new(StepSourceAMIInfo).canEnableEnhancedNetworking(&ec2.Image{
VirtualizationType: aws.String("paravirtual"),
})
assert.Error(t, err)
}
func TestStepSourceAmiInfo_HVMImage(t *testing.T) {
err := new(StepSourceAMIInfo).canEnableEnhancedNetworking(&ec2.Image{
VirtualizationType: aws.String("hvm"),
})
assert.NoError(t, err)
}
func TestStepSourceAmiInfo_PVImageWithAMIVirtPV(t *testing.T) {
stepSourceAMIInfo := StepSourceAMIInfo{
AMIVirtType: "paravirtual",
}
err := stepSourceAMIInfo.canEnableEnhancedNetworking(&ec2.Image{
VirtualizationType: aws.String("paravirtual"),
})
assert.Error(t, err)
}
func TestStepSourceAmiInfo_PVImageWithAMIVirtHVM(t *testing.T) {
stepSourceAMIInfo := StepSourceAMIInfo{
AMIVirtType: "hvm",
}
err := stepSourceAMIInfo.canEnableEnhancedNetworking(&ec2.Image{
VirtualizationType: aws.String("paravirtual"),
})
assert.NoError(t, err)
}
@@ -1,16 +0,0 @@
package common
import (
"testing"
)
func TestAMITemplatePrepare_clean(t *testing.T) {
origName := "AMZamz09()./-_:&^ $%[]#'@"
expected := "AMZamz09()./-_--- --[]-'@"
name := templateCleanAMIName(origName)
if name != expected {
t.Fatalf("template names do not match: expected %s got %s\n", expected, name)
}
}
-46
View File
@@ -1,46 +0,0 @@
package amazon_acc
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
)
type AWSHelper struct {
Region string
AMIName string
}
func (a *AWSHelper) CleanUpAmi() error {
accessConfig := &awscommon.AccessConfig{}
session, err := accessConfig.Session()
if err != nil {
return fmt.Errorf("AWSAMICleanUp: Unable to create aws session %s", err.Error())
}
regionconn := ec2.New(session.Copy(&aws.Config{
Region: aws.String(a.Region),
}))
resp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
Owners: aws.StringSlice([]string{"self"}),
Filters: []*ec2.Filter{{
Name: aws.String("name"),
Values: aws.StringSlice([]string{a.AMIName}),
}}})
if err != nil {
return fmt.Errorf("AWSAMICleanUp: Unable to find Image %s: %s", a.AMIName, err.Error())
}
if resp != nil && len(resp.Images) > 0 {
_, err = regionconn.DeregisterImage(&ec2.DeregisterImageInput{
ImageId: resp.Images[0].ImageId,
})
if err != nil {
return fmt.Errorf("AWSAMICleanUp: Unable to Deregister Image %s", err.Error())
}
}
return nil
}
@@ -1,59 +0,0 @@
package amazon_acc
// This is the code necessary for running the provisioner acceptance tests.
// It provides the builder config and cleans up created resource.
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
amazonebsbuilder "github.com/hashicorp/packer/builder/amazon/ebs"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type AmazonEBSAccTest struct{}
func (s *AmazonEBSAccTest) GetConfigs() (map[string]string, error) {
fixtures := map[string]string{
"linux": "amazon-ebs.txt",
"windows": "amazon-ebs_windows.txt",
}
configs := make(map[string]string)
for distro, fixture := range fixtures {
fileName := fixture
filePath := filepath.Join("../../builder/amazon/ebs/acceptance/test-fixtures/", fileName)
config, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("Expected to find %s", filePath)
}
defer config.Close()
file, err := ioutil.ReadAll(config)
if err != nil {
return nil, fmt.Errorf("Unable to read %s", filePath)
}
configs[distro] = string(file)
}
return configs, nil
}
func (s *AmazonEBSAccTest) CleanUp() error {
helper := AWSHelper{
Region: "us-east-1",
AMIName: "packer-acc-test",
}
return helper.CleanUpAmi()
}
func (s *AmazonEBSAccTest) GetBuilderStore() packersdk.MapOfBuilder {
return packersdk.MapOfBuilder{
"amazon-ebs": func() (packersdk.Builder, error) { return &amazonebsbuilder.Builder{}, nil },
}
}
@@ -1,12 +0,0 @@
{
"type": "amazon-ebs",
"ami_name": "packer-acc-test",
"instance_type": "m1.small",
"region": "us-east-1",
"ssh_username": "ubuntu",
"source_ami": "ami-0568456c",
"force_deregister" : true,
"tags": {
"packer-test": "true"
}
}
@@ -1,23 +0,0 @@
{
"type": "amazon-ebs",
"region": "us-east-1",
"instance_type": "t2.micro",
"source_ami_filter": {
"filters": {
"virtualization-type": "hvm",
"name": "*Windows_Server-2012-R2*English-64Bit-Base*",
"root-device-type": "ebs"
},
"most_recent": true,
"owners": "amazon"
},
"ami_name": "packer-acc-test",
"user_data_file": "../../builder/amazon/ebs/acceptance/test-fixtures/scripts/bootstrap_win.txt",
"communicator": "winrm",
"winrm_username": "Administrator",
"winrm_password": "SuperS3cr3t!!!!",
"force_deregister" : true,
"tags": {
"packer-test": "true"
}
}
@@ -1,40 +0,0 @@
<powershell>
# Set administrator password
net user Administrator SuperS3cr3t!!!!
wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE
# First, make sure WinRM can't be connected to
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block
# Delete any existing WinRM listeners
winrm delete winrm/config/listener?Address=*+Transport=HTTP 2>$Null
winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null
# Disable group policies which block basic authentication and unencrypted login
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowBasic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowUnencryptedTraffic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowBasic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowUnencryptedTraffic -Value 1
# Create a new WinRM listener and configure
winrm create winrm/config/listener?Address=*+Transport=HTTP
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}'
winrm set winrm/config '@{MaxTimeoutms="7200000"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
winrm set winrm/config/client/auth '@{Basic="true"}'
# Configure UAC to allow privilege elevation in remote shells
$Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
$Setting = 'LocalAccountTokenFilterPolicy'
Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force
# Configure and restart the WinRM Service; Enable the required firewall exception
Stop-Service -Name WinRM
Set-Service -Name WinRM -StartupType Automatic
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
Start-Service -Name WinRM
</powershell>
-396
View File
@@ -1,396 +0,0 @@
/*
Deregister the test image with
aws ec2 deregister-image --image-id $(aws ec2 describe-images --output text --filters "Name=name,Values=packer-test-packer-test-dereg" --query 'Images[*].{ID:ImageId}')
*/
package ebs
import (
"fmt"
"os"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/amazon/common"
)
func TestBuilderAcc_basic(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccBasic,
})
}
func TestBuilderAcc_regionCopy(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccRegionCopy,
Check: checkRegionCopy([]string{"us-east-1", "us-west-2"}),
})
}
func TestBuilderAcc_forceDeregister(t *testing.T) {
// Build the same AMI name twice, with force_deregister on the second run
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: buildForceDeregisterConfig("false", "dereg"),
SkipArtifactTeardown: true,
})
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: buildForceDeregisterConfig("true", "dereg"),
})
}
func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) {
amiName := "packer-test-dereg"
// Build the same AMI name twice, with force_delete_snapshot on the second run
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: buildForceDeleteSnapshotConfig("false", amiName),
SkipArtifactTeardown: true,
})
// Get image data by AMI name
ec2conn, _ := testEC2Conn()
describeInput := &ec2.DescribeImagesInput{Filters: []*ec2.Filter{
{
Name: aws.String("name"),
Values: []*string{aws.String(amiName)},
},
}}
ec2conn.WaitUntilImageExists(describeInput)
imageResp, _ := ec2conn.DescribeImages(describeInput)
image := imageResp.Images[0]
// Get snapshot ids for image
snapshotIds := []*string{}
for _, device := range image.BlockDeviceMappings {
if device.Ebs != nil && device.Ebs.SnapshotId != nil {
snapshotIds = append(snapshotIds, device.Ebs.SnapshotId)
}
}
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: buildForceDeleteSnapshotConfig("true", amiName),
Check: checkSnapshotsDeleted(snapshotIds),
})
}
func checkSnapshotsDeleted(snapshotIds []*string) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
// Verify the snapshots are gone
ec2conn, _ := testEC2Conn()
snapshotResp, _ := ec2conn.DescribeSnapshots(
&ec2.DescribeSnapshotsInput{SnapshotIds: snapshotIds},
)
if len(snapshotResp.Snapshots) > 0 {
return fmt.Errorf("Snapshots weren't successfully deleted by `force_delete_snapshot`")
}
return nil
}
}
func TestBuilderAcc_amiSharing(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccSharingPreCheck(t) },
Builder: &Builder{},
Template: buildSharingConfig(os.Getenv("TESTACC_AWS_ACCOUNT_ID")),
Check: checkAMISharing(2, os.Getenv("TESTACC_AWS_ACCOUNT_ID"), "all"),
})
}
func TestBuilderAcc_encryptedBoot(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccEncrypted,
Check: checkBootEncrypted(),
})
}
func checkAMISharing(count int, uid, group string) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
if len(artifacts) > 1 {
return fmt.Errorf("more than 1 artifact")
}
// Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0]
artifact, ok := artifactRaw.(*common.Artifact)
if !ok {
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
}
// describe the image, get block devices with a snapshot
ec2conn, _ := testEC2Conn()
imageResp, err := ec2conn.DescribeImageAttribute(&ec2.DescribeImageAttributeInput{
Attribute: aws.String("launchPermission"),
ImageId: aws.String(artifact.Amis["us-east-1"]),
})
if err != nil {
return fmt.Errorf("Error retrieving Image Attributes for AMI Artifact (%#v) in AMI Sharing Test: %s", artifact, err)
}
// Launch Permissions are in addition to the userid that created it, so if
// you add 3 additional ami_users, you expect 2 Launch Permissions here
if len(imageResp.LaunchPermissions) != count {
return fmt.Errorf("Error in Image Attributes, expected (%d) Launch Permissions, got (%d)", count, len(imageResp.LaunchPermissions))
}
userFound := false
for _, lp := range imageResp.LaunchPermissions {
if lp.UserId != nil && uid == *lp.UserId {
userFound = true
}
}
if !userFound {
return fmt.Errorf("Error in Image Attributes, expected User ID (%s) to have Launch Permissions, but was not found", uid)
}
groupFound := false
for _, lp := range imageResp.LaunchPermissions {
if lp.Group != nil && group == *lp.Group {
groupFound = true
}
}
if !groupFound {
return fmt.Errorf("Error in Image Attributes, expected Group ID (%s) to have Launch Permissions, but was not found", group)
}
return nil
}
}
func checkRegionCopy(regions []string) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
if len(artifacts) > 1 {
return fmt.Errorf("more than 1 artifact")
}
// Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0]
artifact, ok := artifactRaw.(*common.Artifact)
if !ok {
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
}
// Verify that we copied to only the regions given
regionSet := make(map[string]struct{})
for _, r := range regions {
regionSet[r] = struct{}{}
}
for r := range artifact.Amis {
if _, ok := regionSet[r]; !ok {
return fmt.Errorf("unknown region: %s", r)
}
delete(regionSet, r)
}
if len(regionSet) > 0 {
return fmt.Errorf("didn't copy to: %#v", regionSet)
}
return nil
}
}
func checkBootEncrypted() builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
// Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0]
artifact, ok := artifactRaw.(*common.Artifact)
if !ok {
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
}
// describe the image, get block devices with a snapshot
ec2conn, _ := testEC2Conn()
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{aws.String(artifact.Amis["us-east-1"])},
})
if err != nil {
return fmt.Errorf("Error retrieving Image Attributes for AMI (%s) in AMI Encrypted Boot Test: %s", artifact, err)
}
image := imageResp.Images[0] // Only requested a single AMI ID
rootDeviceName := image.RootDeviceName
for _, bd := range image.BlockDeviceMappings {
if *bd.DeviceName == *rootDeviceName {
if *bd.Ebs.Encrypted != true {
return fmt.Errorf("volume not encrypted: %s", *bd.Ebs.SnapshotId)
}
}
}
return nil
}
}
func TestBuilderAcc_SessionManagerInterface(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccSessionManagerInterface,
})
}
func testAccPreCheck(t *testing.T) {
}
func testAccSharingPreCheck(t *testing.T) {
if v := os.Getenv("TESTACC_AWS_ACCOUNT_ID"); v == "" {
t.Fatal(fmt.Sprintf("TESTACC_AWS_ACCOUNT_ID must be set for acceptance tests"))
}
}
func testEC2Conn() (*ec2.EC2, error) {
access := &common.AccessConfig{RawRegion: "us-east-1"}
session, err := access.Session()
if err != nil {
return nil, err
}
return ec2.New(session), nil
}
const testBuilderAccBasic = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"ami_name": "packer-test {{timestamp}}"
}]
}
`
const testBuilderAccRegionCopy = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"ami_name": "packer-test {{timestamp}}",
"ami_regions": ["us-east-1", "us-west-2"]
}]
}
`
const testBuilderAccForceDeregister = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"force_deregister": "%s",
"ami_name": "%s"
}]
}
`
const testBuilderAccForceDeleteSnapshot = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"force_deregister": "%s",
"force_delete_snapshot": "%s",
"ami_name": "%s"
}]
}
`
const testBuilderAccSharing = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"ami_name": "packer-test {{timestamp}}",
"ami_users":["%s"],
"ami_groups":["all"]
}]
}
`
const testBuilderAccEncrypted = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami":"ami-c15bebaa",
"ssh_username": "ubuntu",
"ami_name": "packer-enc-test {{timestamp}}",
"encrypt_boot": true
}]
}
`
const testBuilderAccSessionManagerInterface = `
{
"builders": [{
"type": "test",
"region": "us-east-1",
"instance_type": "m3.medium",
"source_ami_filter": {
"filters": {
"virtualization-type": "hvm",
"name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*",
"root-device-type": "ebs"
},
"owners": [
"099720109477"
],
"most_recent": true
},
"ssh_username": "ubuntu",
"ssh_interface": "session_manager",
"iam_instance_profile": "SSMInstanceProfile",
"ami_name": "packer-ssm-test-{{timestamp}}"
}]
}
`
func buildForceDeregisterConfig(val, name string) string {
return fmt.Sprintf(testBuilderAccForceDeregister, val, name)
}
func buildForceDeleteSnapshotConfig(val, name string) string {
return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name)
}
func buildSharingConfig(val string) string {
return fmt.Sprintf(testBuilderAccSharing, val)
}
-166
View File
@@ -1,166 +0,0 @@
package ebs
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"access_key": "foo",
"secret_key": "bar",
"source_ami": "foo",
"instance_type": "foo",
"region": "us-east-1",
"ssh_username": "root",
"ami_name": "foo",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packersdk.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
"access_key": []string{},
}
_, warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("prepare should fail")
}
}
func TestBuilderPrepare_AMIName(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["ami_name"] = "foo"
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["ami_name"] = "foo {{"
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
// Test bad
delete(config, "ami_name")
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["shutdown_behavior"] = "terminate"
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test good
config["shutdown_behavior"] = "stop"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["shutdown_behavior"] = "foobar"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
var b Builder
config := testConfig()
generatedData, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if generatedData[0] != "SourceAMIName" {
t.Fatalf("Generated data should contain SourceAMIName")
}
if generatedData[1] != "BuildRegion" {
t.Fatalf("Generated data should contain BuildRegion")
}
if generatedData[2] != "SourceAMI" {
t.Fatalf("Generated data should contain SourceAMI")
}
if generatedData[3] != "SourceAMICreationDate" {
t.Fatalf("Generated data should contain SourceAMICreationDate")
}
if generatedData[4] != "SourceAMIOwner" {
t.Fatalf("Generated data should contain SourceAMIOwner")
}
if generatedData[5] != "SourceAMIOwnerName" {
t.Fatalf("Generated data should contain SourceAMIOwnerName")
}
}
-137
View File
@@ -1,137 +0,0 @@
package ebs
import (
"encoding/json"
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/amazon/common"
)
type TFBuilder struct {
Type string `json:"type"`
Region string `json:"region"`
SourceAmi string `json:"source_ami"`
InstanceType string `json:"instance_type"`
SshUsername string `json:"ssh_username"`
AmiName string `json:"ami_name"`
Tags map[string]string `json:"tags"`
SnapshotTags map[string]string `json:"snapshot_tags"`
}
type TFConfig struct {
Builders []TFBuilder `json:"builders"`
}
func TestBuilderTagsAcc_basic(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderTagsAccBasic,
Check: checkTags(),
})
}
func checkTags() builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
if len(artifacts) > 1 {
return fmt.Errorf("more than 1 artifact")
}
config := TFConfig{}
json.Unmarshal([]byte(testBuilderTagsAccBasic), &config)
tags := config.Builders[0].Tags
snapshotTags := config.Builders[0].SnapshotTags
// Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0]
artifact, ok := artifactRaw.(*common.Artifact)
if !ok {
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
}
// Describe the image, get block devices with a snapshot
ec2conn, _ := testEC2Conn()
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{aws.String(artifact.Amis["us-east-1"])},
})
if err != nil {
return fmt.Errorf("Error retrieving details for AMI Artifact (%#v) in Tags Test: %s", artifact, err)
}
if len(imageResp.Images) == 0 {
return fmt.Errorf("No images found for AMI Artifact (%#v) in Tags Test: %s", artifact, err)
}
image := imageResp.Images[0]
// Check only those with a Snapshot ID, i.e. not Ephemeral
var snapshots []*string
for _, device := range image.BlockDeviceMappings {
if device.Ebs != nil && device.Ebs.SnapshotId != nil {
snapshots = append(snapshots, device.Ebs.SnapshotId)
}
}
// Grab matching snapshot info
resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{
SnapshotIds: snapshots,
})
if err != nil {
return fmt.Errorf("Error retrieving Snapshots for AMI Artifact (%#v) in Tags Test: %s", artifact, err)
}
if len(resp.Snapshots) == 0 {
return fmt.Errorf("No Snapshots found for AMI Artifact (%#v) in Tags Test", artifact)
}
// Grab the snapshots, check the tags
for _, s := range resp.Snapshots {
expected := len(tags)
for _, t := range s.Tags {
for key, value := range tags {
if val, ok := snapshotTags[key]; ok && val == *t.Value {
expected--
} else if key == *t.Key && value == *t.Value {
expected--
}
}
}
if expected > 0 {
return fmt.Errorf("Not all tags found")
}
}
return nil
}
}
const testBuilderTagsAccBasic = `
{
"builders": [
{
"type": "test",
"region": "us-east-1",
"source_ami": "ami-9eaa1cf6",
"instance_type": "t2.micro",
"ssh_username": "ubuntu",
"ami_name": "packer-tags-testing-{{timestamp}}",
"tags": {
"OS_Version": "Ubuntu",
"Release": "Latest",
"Name": "Bleep"
},
"snapshot_tags": {
"Name": "Foobar"
}
}
]
}
`
-110
View File
@@ -1,110 +0,0 @@
package ebssurrogate
import (
"testing"
"github.com/hashicorp/packer/builder/amazon/common"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"access_key": "foo",
"secret_key": "bar",
"source_ami": "foo",
"instance_type": "foo",
"region": "us-east-1",
"ssh_username": "root",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packersdk.Builder); !ok {
t.Fatal("Builder should be a builder")
}
}
func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
"access_key": []string{},
}
_, warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("prepare should fail")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
var b Builder
// Basic configuration
b.config.RootDevice = RootBlockDevice{
SourceDeviceName: "device name",
DeviceName: "device name",
}
b.config.LaunchMappings = BlockDevices{
BlockDevice{
BlockDevice: common.BlockDevice{
DeviceName: "device name",
},
OmitFromArtifact: false,
},
}
b.config.AMIVirtType = "type"
config := testConfig()
config["ami_name"] = "name"
generatedData, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if generatedData[0] != "SourceAMIName" {
t.Fatalf("Generated data should contain SourceAMIName")
}
if generatedData[1] != "BuildRegion" {
t.Fatalf("Generated data should contain BuildRegion")
}
if generatedData[2] != "SourceAMI" {
t.Fatalf("Generated data should contain SourceAMI")
}
if generatedData[3] != "SourceAMICreationDate" {
t.Fatalf("Generated data should contain SourceAMICreationDate")
}
if generatedData[4] != "SourceAMIOwner" {
t.Fatalf("Generated data should contain SourceAMIOwner")
}
if generatedData[5] != "SourceAMIOwnerName" {
t.Fatalf("Generated data should contain SourceAMIOwnerName")
}
}
@@ -1,249 +0,0 @@
package ebssurrogate
import (
"reflect"
"sort"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/builder/amazon/common"
)
const sourceDeviceName = "/dev/xvdf"
const rootDeviceName = "/dev/xvda"
func newStepRegisterAMI(amiDevices, launchDevices []*ec2.BlockDeviceMapping) *StepRegisterAMI {
return &StepRegisterAMI{
RootDevice: RootBlockDevice{
SourceDeviceName: sourceDeviceName,
DeviceName: rootDeviceName,
DeleteOnTermination: true,
VolumeType: "ebs",
VolumeSize: 10,
},
AMIDevices: amiDevices,
LaunchDevices: launchDevices,
PollingConfig: new(common.AWSPollingConfig),
}
}
func sorted(devices []*ec2.BlockDeviceMapping) []*ec2.BlockDeviceMapping {
sort.SliceStable(devices, func(i, j int) bool {
return *devices[i].DeviceName < *devices[j].DeviceName
})
return devices
}
func TestStepRegisterAmi_combineDevices(t *testing.T) {
cases := []struct {
snapshotIds map[string]string
amiDevices []*ec2.BlockDeviceMapping
launchDevices []*ec2.BlockDeviceMapping
allDevices []*ec2.BlockDeviceMapping
}{
{
snapshotIds: map[string]string{},
amiDevices: []*ec2.BlockDeviceMapping{},
launchDevices: []*ec2.BlockDeviceMapping{},
allDevices: []*ec2.BlockDeviceMapping{},
},
{
snapshotIds: map[string]string{},
amiDevices: []*ec2.BlockDeviceMapping{},
launchDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{},
DeviceName: aws.String(sourceDeviceName),
},
},
allDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{},
DeviceName: aws.String(rootDeviceName),
},
},
},
{
// Minimal single device
snapshotIds: map[string]string{
sourceDeviceName: "snap-0123456789abcdef1",
},
amiDevices: []*ec2.BlockDeviceMapping{},
launchDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{},
DeviceName: aws.String(sourceDeviceName),
},
},
allDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{
SnapshotId: aws.String("snap-0123456789abcdef1"),
},
DeviceName: aws.String(rootDeviceName),
},
},
},
{
// Single launch device with AMI device
snapshotIds: map[string]string{
sourceDeviceName: "snap-0123456789abcdef1",
},
amiDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{},
DeviceName: aws.String("/dev/xvdg"),
},
},
launchDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{},
DeviceName: aws.String(sourceDeviceName),
},
},
allDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{
SnapshotId: aws.String("snap-0123456789abcdef1"),
},
DeviceName: aws.String(rootDeviceName),
},
{
Ebs: &ec2.EbsBlockDevice{},
DeviceName: aws.String("/dev/xvdg"),
},
},
},
{
// Multiple launch devices
snapshotIds: map[string]string{
sourceDeviceName: "snap-0123456789abcdef1",
"/dev/xvdg": "snap-0123456789abcdef2",
},
amiDevices: []*ec2.BlockDeviceMapping{},
launchDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{},
DeviceName: aws.String(sourceDeviceName),
},
{
Ebs: &ec2.EbsBlockDevice{},
DeviceName: aws.String("/dev/xvdg"),
},
},
allDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{
SnapshotId: aws.String("snap-0123456789abcdef1"),
},
DeviceName: aws.String(rootDeviceName),
},
{
Ebs: &ec2.EbsBlockDevice{
SnapshotId: aws.String("snap-0123456789abcdef2"),
},
DeviceName: aws.String("/dev/xvdg"),
},
},
},
{
// Multiple launch devices with encryption
snapshotIds: map[string]string{
sourceDeviceName: "snap-0123456789abcdef1",
"/dev/xvdg": "snap-0123456789abcdef2",
},
amiDevices: []*ec2.BlockDeviceMapping{},
launchDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{
Encrypted: aws.Bool(true),
},
DeviceName: aws.String(sourceDeviceName),
},
{
Ebs: &ec2.EbsBlockDevice{
Encrypted: aws.Bool(true),
},
DeviceName: aws.String("/dev/xvdg"),
},
},
allDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{
SnapshotId: aws.String("snap-0123456789abcdef1"),
// Encrypted: true stripped from snapshotted devices
},
DeviceName: aws.String(rootDeviceName),
},
{
Ebs: &ec2.EbsBlockDevice{
SnapshotId: aws.String("snap-0123456789abcdef2"),
},
DeviceName: aws.String("/dev/xvdg"),
},
},
},
{
// Multiple launch devices and AMI devices with encryption
snapshotIds: map[string]string{
sourceDeviceName: "snap-0123456789abcdef1",
"/dev/xvdg": "snap-0123456789abcdef2",
},
amiDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{
Encrypted: aws.Bool(true),
KmsKeyId: aws.String("keyId"),
},
// Source device name can be used in AMI devices
// since launch device of same name gets renamed
// to root device name
DeviceName: aws.String(sourceDeviceName),
},
},
launchDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{
Encrypted: aws.Bool(true),
},
DeviceName: aws.String(sourceDeviceName),
},
{
Ebs: &ec2.EbsBlockDevice{
Encrypted: aws.Bool(true),
},
DeviceName: aws.String("/dev/xvdg"),
},
},
allDevices: []*ec2.BlockDeviceMapping{
{
Ebs: &ec2.EbsBlockDevice{
Encrypted: aws.Bool(true),
KmsKeyId: aws.String("keyId"),
},
DeviceName: aws.String(sourceDeviceName),
},
{
Ebs: &ec2.EbsBlockDevice{
SnapshotId: aws.String("snap-0123456789abcdef1"),
},
DeviceName: aws.String(rootDeviceName),
},
{
Ebs: &ec2.EbsBlockDevice{
SnapshotId: aws.String("snap-0123456789abcdef2"),
},
DeviceName: aws.String("/dev/xvdg"),
},
},
},
}
for _, tc := range cases {
stepRegisterAmi := newStepRegisterAMI(tc.amiDevices, tc.launchDevices)
allDevices := stepRegisterAmi.combineDevices(tc.snapshotIds)
if !reflect.DeepEqual(sorted(allDevices), sorted(tc.allDevices)) {
t.Fatalf("Unexpected output from combineDevices")
}
}
}
-29
View File
@@ -1,29 +0,0 @@
package ebsvolume
import "testing"
func TestArtifactState(t *testing.T) {
expectedData := "this is the data"
artifact := &Artifact{
StateData: map[string]interface{}{"state_data": expectedData},
}
// Valid state
result := artifact.State("state_data")
if result != expectedData {
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
}
// Invalid state
result = artifact.State("invalid_key")
if result != nil {
t.Fatalf("Bad: State should be nil for invalid state data name")
}
// Nil StateData should not fail and should return nil
artifact = &Artifact{}
result = artifact.State("key")
if result != nil {
t.Fatalf("Bad: State should be nil for nil StateData")
}
}
-166
View File
@@ -1,166 +0,0 @@
package ebsvolume
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"access_key": "foo",
"secret_key": "bar",
"source_ami": "foo",
"instance_type": "foo",
"region": "us-east-1",
"ssh_username": "root",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packersdk.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
"access_key": []string{},
}
_, warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("prepare should fail")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["shutdown_behavior"] = "terminate"
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test good
config["shutdown_behavior"] = "stop"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["shutdown_behavior"] = "foobar"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
var b Builder
config := testConfig()
generatedData, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if generatedData[0] != "SourceAMIName" {
t.Fatalf("Generated data should contain SourceAMIName")
}
if generatedData[1] != "BuildRegion" {
t.Fatalf("Generated data should contain BuildRegion")
}
if generatedData[2] != "SourceAMI" {
t.Fatalf("Generated data should contain SourceAMI")
}
if generatedData[3] != "SourceAMICreationDate" {
t.Fatalf("Generated data should contain SourceAMICreationDate")
}
if generatedData[4] != "SourceAMIOwner" {
t.Fatalf("Generated data should contain SourceAMIOwner")
}
if generatedData[5] != "SourceAMIOwnerName" {
t.Fatalf("Generated data should contain SourceAMIOwnerName")
}
}
func TestBuidler_ConfigBlockdevicemapping(t *testing.T) {
var b Builder
config := testConfig()
//Set some snapshot settings
config["ebs_volumes"] = []map[string]interface{}{
{
"device_name": "/dev/xvdb",
"volume_size": "32",
"delete_on_termination": true,
},
{
"device_name": "/dev/xvdc",
"volume_size": "32",
"delete_on_termination": true,
"snapshot_tags": map[string]string{
"Test_Tag": "tag_value",
"another tag": "another value",
},
"snapshot_users": []string{
"123", "456",
},
},
}
generatedData, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
t.Logf("Test gen %+v", b.config.VolumeMappings)
}
@@ -1,170 +0,0 @@
package ebsvolume
import (
"bytes"
"context"
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
//"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/amazon/common"
)
// Define a mock struct to be used in unit tests for common aws steps.
type mockEC2Conn struct {
ec2iface.EC2API
Config *aws.Config
}
func (m *mockEC2Conn) CreateSnapshot(input *ec2.CreateSnapshotInput) (*ec2.Snapshot, error) {
snap := &ec2.Snapshot{
// This isn't typical amazon format, but injecting the volume id into
// this field lets us verify that the right volume was snapshotted with
// a simple string comparison
SnapshotId: aws.String(fmt.Sprintf("snap-of-%s", *input.VolumeId)),
}
return snap, nil
}
func (m *mockEC2Conn) WaitUntilSnapshotCompletedWithContext(aws.Context, *ec2.DescribeSnapshotsInput, ...request.WaiterOption) error {
return nil
}
func getMockConn(config *common.AccessConfig, target string) (ec2iface.EC2API, error) {
mockConn := &mockEC2Conn{
Config: aws.NewConfig(),
}
return mockConn, nil
}
// Create statebag for running test
func tState(t *testing.T) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
// state.Put("amis", map[string]string{"us-east-1": "ami-12345"})
// state.Put("snapshots", map[string][]string{"us-east-1": {"snap-0012345"}})
conn, _ := getMockConn(&common.AccessConfig{}, "us-east-2")
state.Put("ec2", conn)
// Store a fake instance that contains a block device that matches the
// volumes defined in the config above
state.Put("instance", &ec2.Instance{
InstanceId: aws.String("instance-id"),
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
{
DeviceName: aws.String("/dev/xvda"),
Ebs: &ec2.EbsInstanceBlockDevice{
VolumeId: aws.String("vol-1234"),
},
},
{
DeviceName: aws.String("/dev/xvdb"),
Ebs: &ec2.EbsInstanceBlockDevice{
VolumeId: aws.String("vol-5678"),
},
},
},
})
return state
}
func TestStepSnapshot_run_simple(t *testing.T) {
var b Builder
config := testConfig() //from builder_test
//Set some snapshot settings
config["ebs_volumes"] = []map[string]interface{}{
{
"device_name": "/dev/xvdb",
"volume_size": "32",
"delete_on_termination": true,
"snapshot_volume": true,
},
}
generatedData, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
state := tState(t)
accessConfig := common.FakeAccessConfig()
step := stepSnapshotEBSVolumes{
PollingConfig: new(common.AWSPollingConfig),
AccessConfig: accessConfig,
VolumeMapping: b.config.VolumeMappings,
Ctx: b.config.ctx,
}
step.Run(context.Background(), state)
if len(step.snapshotMap) != 1 {
t.Fatalf("Missing Snapshot from step")
}
if volmapping := step.snapshotMap["snap-of-vol-5678"]; volmapping == nil {
t.Fatalf("Didn't snapshot correct volume: Map is %#v", step.snapshotMap)
}
}
func TestStepSnapshot_run_no_snaps(t *testing.T) {
var b Builder
config := testConfig() //from builder_test
//Set some snapshot settings
config["ebs_volumes"] = []map[string]interface{}{
{
"device_name": "/dev/xvdb",
"volume_size": "32",
"delete_on_termination": true,
"snapshot_volume": false,
},
}
generatedData, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
state := tState(t)
accessConfig := common.FakeAccessConfig()
step := stepSnapshotEBSVolumes{
PollingConfig: new(common.AWSPollingConfig),
AccessConfig: accessConfig,
VolumeMapping: b.config.VolumeMappings,
Ctx: b.config.ctx,
}
step.Run(context.Background(), state)
if len(step.snapshotMap) != 0 {
t.Fatalf("Shouldn't have snapshotted any volumes")
}
}
@@ -1 +0,0 @@
command: packer build -var "accesskey=*" -var "secretkey=" -var "shellpath=packages.sh" .\apache.json
@@ -1,31 +0,0 @@
{
"variables":
{
"accesskey": "",
"secretkey": "",
"shellpath": "packages.sh"
},
"builders":[
{
"type": "amazon-ebs",
"access_key": "{{user `accesskey`}}",
"secret_key": "{{user `secretkey`}}",
"region": "ap-south-1",
"source_ami": "ami-sa7608343426b",
"instance_type": "t2.micro",
"ssh_username": "ubuntu",
"ami_name": "apache",
"tags": {
"OS_Version": "Ubuntu",
"Release": "Latest"
}
}
],
"provisioners":[
{
"type": "shell",
"script": "{{user `shellpath`}}"
}
]
}
@@ -1,6 +0,0 @@
echo "installing apache "
sudo apt-get update
sudo apt-get install apache2 -y
sudo apt-get update
sudo service apache2 restart
sudo apache2 --version
@@ -1 +0,0 @@
command: packer build -var "accesskey=*" -var "secretkey=" -var "shellpath=packages.sh" .\nginx.json
@@ -1,31 +0,0 @@
{
"variables":
{
"accesskey": "",
"secretkey": "",
"shellpath": "packages.sh"
},
"builders":[
{
"type": "amazon-ebs",
"access_key": "{{user `accesskey`}}",
"secret_key": "{{user `secretkey`}}",
"region": "ap-south-1",
"source_ami": "ami-sa7608343426b",
"instance_type": "t2.micro",
"ssh_username": "ubuntu",
"ami_name": "nginx",
"tags": {
"OS_Version": "Ubuntu",
"Release": "Latest"
}
}
],
"provisioners":[
{
"type": "shell",
"script": "{{user `shellpath`}}"
}
]
}
@@ -1,4 +0,0 @@
echo "installing nginx "
sudo apt-get update
sudo apt-get install nginx -y
sudo service nginx restart
-343
View File
@@ -1,343 +0,0 @@
package instance
import (
"io/ioutil"
"os"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func testConfig() (config map[string]interface{}, tf *os.File) {
tf, err := ioutil.TempFile("", "packer")
if err != nil {
panic(err)
}
config = map[string]interface{}{
"account_id": "foo",
"ami_name": "foo",
"instance_type": "m1.small",
"region": "us-east-1",
"s3_bucket": "foo",
"source_ami": "foo",
"ssh_username": "bob",
"x509_cert_path": tf.Name(),
"x509_key_path": tf.Name(),
"x509_upload_path": "/foo",
}
return config, tf
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packersdk.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilderPrepare_AccountId(t *testing.T) {
b := &Builder{}
config, tempfile := testConfig()
config["skip_region_validation"] = true
defer os.Remove(tempfile.Name())
defer tempfile.Close()
config["account_id"] = ""
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
config["account_id"] = "foo"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
config["account_id"] = "0123-0456-7890"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("err: %s", err)
}
if b.config.AccountId != "012304567890" {
t.Errorf("should strip hyphens: %s", b.config.AccountId)
}
}
func TestBuilderPrepare_AMIName(t *testing.T) {
var b Builder
config, tempfile := testConfig()
defer os.Remove(tempfile.Name())
defer tempfile.Close()
// Test good
config["ami_name"] = "foo"
config["skip_region_validation"] = true
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["ami_name"] = "foo {{"
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
// Test bad
delete(config, "ami_name")
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_BundleDestination(t *testing.T) {
b := &Builder{}
config, tempfile := testConfig()
config["skip_region_validation"] = true
defer os.Remove(tempfile.Name())
defer tempfile.Close()
config["bundle_destination"] = ""
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("err: %s", err)
}
if b.config.BundleDestination != "/tmp" {
t.Fatalf("bad: %s", b.config.BundleDestination)
}
}
func TestBuilderPrepare_BundlePrefix(t *testing.T) {
b := &Builder{}
config, tempfile := testConfig()
config["skip_region_validation"] = true
defer os.Remove(tempfile.Name())
defer tempfile.Close()
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("err: %s", err)
}
if b.config.BundlePrefix == "" {
t.Fatalf("bad: %s", b.config.BundlePrefix)
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config, tempfile := testConfig()
defer os.Remove(tempfile.Name())
defer tempfile.Close()
// Add a random key
config["i_should_not_be_valid"] = true
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_S3Bucket(t *testing.T) {
b := &Builder{}
config, tempfile := testConfig()
config["skip_region_validation"] = true
defer os.Remove(tempfile.Name())
defer tempfile.Close()
config["s3_bucket"] = ""
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
config["s3_bucket"] = "foo"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_X509CertPath(t *testing.T) {
b := &Builder{}
config, tempfile := testConfig()
config["skip_region_validation"] = true
defer os.Remove(tempfile.Name())
defer tempfile.Close()
config["x509_cert_path"] = ""
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
config["x509_cert_path"] = "i/am/a/file/that/doesnt/exist"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Error("should have error")
}
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("error tempfile: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
config["x509_cert_path"] = tf.Name()
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_X509KeyPath(t *testing.T) {
b := &Builder{}
config, tempfile := testConfig()
config["skip_region_validation"] = true
defer os.Remove(tempfile.Name())
defer tempfile.Close()
config["x509_key_path"] = ""
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
config["x509_key_path"] = "i/am/a/file/that/doesnt/exist"
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Error("should have error")
}
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("error tempfile: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
config["x509_key_path"] = tf.Name()
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_X509UploadPath(t *testing.T) {
b := &Builder{}
config, tempfile := testConfig()
config["skip_region_validation"] = true
defer os.Remove(tempfile.Name())
defer tempfile.Close()
config["x509_upload_path"] = ""
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
var b Builder
config, tempfile := testConfig()
defer os.Remove(tempfile.Name())
defer tempfile.Close()
generatedData, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if len(generatedData) == 0 {
t.Fatalf("Generated data should not be empty")
}
if generatedData[0] != "SourceAMIName" {
t.Fatalf("Generated data should contain SourceAMIName")
}
if generatedData[1] != "BuildRegion" {
t.Fatalf("Generated data should contain BuildRegion")
}
if generatedData[2] != "SourceAMI" {
t.Fatalf("Generated data should contain SourceAMI")
}
if generatedData[3] != "SourceAMICreationDate" {
t.Fatalf("Generated data should contain SourceAMICreationDate")
}
if generatedData[4] != "SourceAMIOwner" {
t.Fatalf("Generated data should contain SourceAMIOwner")
}
if generatedData[5] != "SourceAMIOwnerName" {
t.Fatalf("Generated data should contain SourceAMIOwnerName")
}
}
-13
View File
@@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var AWSPluginVersion *version.PluginVersion
func init() {
AWSPluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}
+115 -7
View File
@@ -20,12 +20,18 @@ package arm
// go test -v -timeout 90m -run TestBuilderAcc_.*
import (
"testing"
"bytes"
"context"
"errors"
"fmt"
"os"
"strings"
"testing"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure/auth"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
@@ -96,10 +102,15 @@ func TestBuilderAcc_ManagedDisk_Linux_AzureCLI(t *testing.T) {
return
}
var b Builder
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
PreCheck: func() { testAuthPreCheck(t) },
Builder: &b,
Template: testBuilderAccManagedDiskLinuxAzureCLI,
Check: func([]packersdk.Artifact) error {
checkTemporaryGroupDeleted(t, &b)
return nil
},
})
}
@@ -112,15 +123,108 @@ func TestBuilderAcc_Blob_Windows(t *testing.T) {
}
func TestBuilderAcc_Blob_Linux(t *testing.T) {
var b Builder
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
PreCheck: func() { testAuthPreCheck(t) },
Builder: &b,
Template: testBuilderAccBlobLinux,
Check: func([]packersdk.Artifact) error {
checkUnmanagedVHDDeleted(t, &b)
return nil
},
})
}
func testAccPreCheck(*testing.T) {}
func testAuthPreCheck(t *testing.T) {
_, err := auth.NewAuthorizerFromEnvironment()
if err != nil {
t.Fatalf("failed to auth to azure: %s", err)
}
}
func checkTemporaryGroupDeleted(t *testing.T, b *Builder) {
ui := testUi()
spnCloud, spnKeyVault, err := b.getServicePrincipalTokens(ui.Say)
if err != nil {
t.Fatalf("failed getting azure tokens: %s", err)
}
ui.Message("Creating test Azure Resource Manager (ARM) client ...")
azureClient, err := NewAzureClient(
b.config.ClientConfig.SubscriptionID,
b.config.SharedGalleryDestination.SigDestinationSubscription,
b.config.ResourceGroupName,
b.config.StorageAccount,
b.config.ClientConfig.CloudEnvironment(),
b.config.SharedGalleryTimeout,
b.config.PollingDurationTimeout,
spnCloud,
spnKeyVault)
if err != nil {
t.Fatalf("failed to create azure client: %s", err)
}
// Validate resource group has been deleted
_, err = azureClient.GroupsClient.Get(context.Background(), b.config.tmpResourceGroupName)
if err == nil || !resourceNotFound(err) {
t.Fatalf("failed validating resource group deletion: %s", err)
}
}
func checkUnmanagedVHDDeleted(t *testing.T, b *Builder) {
ui := testUi()
spnCloud, spnKeyVault, err := b.getServicePrincipalTokens(ui.Say)
if err != nil {
t.Fatalf("failed getting azure tokens: %s", err)
}
azureClient, err := NewAzureClient(
b.config.ClientConfig.SubscriptionID,
b.config.SharedGalleryDestination.SigDestinationSubscription,
b.config.ResourceGroupName,
b.config.StorageAccount,
b.config.ClientConfig.CloudEnvironment(),
b.config.SharedGalleryTimeout,
b.config.PollingDurationTimeout,
spnCloud,
spnKeyVault)
if err != nil {
t.Fatalf("failed to create azure client: %s", err)
}
// validate temporary os blob was deleted
blob := azureClient.BlobStorageClient.GetContainerReference("images").GetBlobReference(b.config.tmpOSDiskName)
_, err = blob.BreakLease(nil)
if err != nil && !strings.Contains(err.Error(), "BlobNotFound") {
t.Fatalf("failed validating deletion of unmanaged vhd: %s", err)
}
// Validate resource group has been deleted
_, err = azureClient.GroupsClient.Get(context.Background(), b.config.tmpResourceGroupName)
if err == nil || !resourceNotFound(err) {
t.Fatalf("failed validating resource group deletion: %s", err)
}
}
func resourceNotFound(err error) bool {
derr := autorest.DetailedError{}
return errors.As(err, &derr) && derr.StatusCode == 404
}
func testUi() *packersdk.BasicUi {
return &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
ErrorWriter: new(bytes.Buffer),
}
}
const testBuilderAccManagedDiskWindows = `
{
"variables": {
@@ -155,6 +259,7 @@ const testBuilderAccManagedDiskWindows = `
}]
}
`
const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
{
"variables": {
@@ -287,6 +392,7 @@ const testBuilderAccManagedDiskLinux = `
}]
}
`
const testBuilderAccManagedDiskLinuxDeviceLogin = `
{
"variables": {
@@ -379,6 +485,7 @@ const testBuilderAccBlobLinux = `
}]
}
`
const testBuilderAccManagedDiskLinuxAzureCLI = `
{
"builders": [{
@@ -388,7 +495,8 @@ const testBuilderAccManagedDiskLinuxAzureCLI = `
"managed_image_resource_group_name": "packer-acceptance-test",
"managed_image_name": "testBuilderAccManagedDiskLinuxAzureCLI-{{timestamp}}",
"temp_resource_group_name": "packer-acceptance-test-managed-cli",
"os_type": "Linux",
"image_publisher": "Canonical",
"image_offer": "UbuntuServer",
+78 -43
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"net/url"
"strings"
"sync"
"time"
"github.com/hashicorp/packer-plugin-sdk/multistep"
@@ -15,16 +16,17 @@ import (
)
type StepDeployTemplate struct {
client *AzureClient
deploy func(ctx context.Context, resourceGroupName string, deploymentName string) error
delete func(ctx context.Context, deploymentName, resourceGroupName string) error
disk func(ctx context.Context, resourceGroupName string, computeName string) (string, string, error)
deleteDisk func(ctx context.Context, imageType string, imageName string, resourceGroupName string) error
say func(message string)
error func(e error)
config *Config
factory templateFactoryFunc
name string
client *AzureClient
deploy func(ctx context.Context, resourceGroupName string, deploymentName string) error
delete func(ctx context.Context, deploymentName, resourceGroupName string) error
disk func(ctx context.Context, resourceGroupName string, computeName string) (string, string, error)
deleteDisk func(ctx context.Context, imageType string, imageName string, resourceGroupName string) error
deleteDeployment func(ctx context.Context, state multistep.StateBag) error
say func(message string)
error func(e error)
config *Config
factory templateFactoryFunc
name string
}
func NewStepDeployTemplate(client *AzureClient, ui packersdk.Ui, config *Config, deploymentName string, factory templateFactoryFunc) *StepDeployTemplate {
@@ -41,6 +43,7 @@ func NewStepDeployTemplate(client *AzureClient, ui packersdk.Ui, config *Config,
step.delete = step.deleteDeploymentResources
step.disk = step.getImageDetails
step.deleteDisk = step.deleteImage
step.deleteDeployment = step.deleteDeploymentObject
return step
}
@@ -58,32 +61,45 @@ func (s *StepDeployTemplate) Run(ctx context.Context, state multistep.StateBag)
func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
defer func() {
err := s.deleteTemplate(context.Background(), state)
err := s.deleteDeployment(context.Background(), state)
if err != nil {
s.say(s.client.LastError.Error())
s.say(err.Error())
}
}()
// Only clean up if this is an existing resource group that has been verified to exist.
// ArmIsResourceGroupCreated is set in step_create_resource_group to true, when Packer has verified that the resource group exists.
// ArmIsExistingResourceGroup is set to true when build_resource_group is set in the Packer configuration.
existingResourceGroup := state.Get(constants.ArmIsExistingResourceGroup).(bool)
resourceGroupCreated := state.Get(constants.ArmIsResourceGroupCreated).(bool)
if !existingResourceGroup || !resourceGroupCreated {
return
}
ui := state.Get("ui").(packersdk.Ui)
ui.Say("\nThe resource group was not created by Packer, deleting individual resources ...")
ui.Say("\nDeleting individual resources ...")
deploymentName := s.name
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
err := s.deleteDeploymentResources(context.TODO(), deploymentName, resourceGroupName)
// Get image disk details before deleting the image; otherwise we won't be able to
// delete the disk as the image request will return a 404
computeName := state.Get(constants.ArmComputeName).(string)
imageType, imageName, err := s.disk(context.TODO(), resourceGroupName, computeName)
if err != nil && !strings.Contains(err.Error(), "ResourceNotFound") {
ui.Error(fmt.Sprintf("Could not retrieve OS Image details: %s", err))
}
err = s.delete(context.TODO(), deploymentName, resourceGroupName)
if err != nil {
s.reportIfError(err, resourceGroupName)
}
NewStepDeleteAdditionalDisks(s.client, ui).Run(context.TODO(), state)
// The disk was not found on the VM, this is an error.
if imageType == "" && imageName == "" {
ui.Error(fmt.Sprintf("Failed to find temporary OS disk on VM. Please delete manually.\n\n"+
"VM Name: %s\n"+
"Error: %s", computeName, err))
return
}
ui.Say(fmt.Sprintf(" Deleting -> %s : '%s'", imageType, imageName))
err = s.deleteDisk(context.TODO(), imageType, imageName, resourceGroupName)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
"Name: %s\n"+
"Error: %s", imageName, err))
}
}
func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupName string, deploymentName string) error {
@@ -106,7 +122,7 @@ func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupNa
return err
}
func (s *StepDeployTemplate) deleteTemplate(ctx context.Context, state multistep.StateBag) error {
func (s *StepDeployTemplate) deleteDeploymentObject(ctx context.Context, state multistep.StateBag) error {
deploymentName := s.name
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
ui := state.Get("ui").(packersdk.Ui)
@@ -222,6 +238,8 @@ func (s *StepDeployTemplate) deleteDeploymentResources(ctx context.Context, depl
return err
}
resources := map[string]string{}
for deploymentOperations.NotDone() {
deploymentOperation := deploymentOperations.Value()
// Sometimes an empty operation is added to the list by Azure
@@ -233,30 +251,47 @@ func (s *StepDeployTemplate) deleteDeploymentResources(ctx context.Context, depl
resourceName := *deploymentOperation.Properties.TargetResource.ResourceName
resourceType := *deploymentOperation.Properties.TargetResource.ResourceType
s.say(fmt.Sprintf(" -> %s : '%s'", resourceType, resourceName))
err = retry.Config{
Tries: 10,
RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 600 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
err := deleteResource(ctx, s.client,
resourceType,
resourceName,
resourceGroupName)
if err != nil {
s.reportIfError(err, resourceName)
}
return nil
})
if err != nil {
s.reportIfError(err, resourceName)
}
s.say(fmt.Sprintf("Adding to deletion queue -> %s : '%s'", resourceType, resourceName))
resources[resourceType] = resourceName
if err = deploymentOperations.Next(); err != nil {
return err
}
}
var wg sync.WaitGroup
wg.Add(len(resources))
for resourceType, resourceName := range resources {
go func(resourceType, resourceName string) {
defer wg.Done()
retryConfig := retry.Config{
Tries: 10,
RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 600 * time.Second, Multiplier: 2}).Linear,
}
err = retryConfig.Run(ctx, func(ctx context.Context) error {
s.say(fmt.Sprintf("Attempting deletion -> %s : '%s'", resourceType, resourceName))
err := deleteResource(ctx, s.client,
resourceType,
resourceName,
resourceGroupName)
if err != nil {
s.say(fmt.Sprintf("Error deleting resource. Will retry.\n"+
"Name: %s\n"+
"Error: %s\n", resourceName, err.Error()))
}
return err
})
if err != nil {
s.reportIfError(err, resourceName)
}
}(resourceType, resourceName)
}
s.say("Waiting for deletion of all resources...")
wg.Wait()
return nil
}
@@ -6,6 +6,7 @@ import (
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/azure/common/constants"
)
@@ -108,11 +109,97 @@ func TestStepDeployTemplateDeleteImageShouldFailWithInvalidImage(t *testing.T) {
}
}
func TestStepDeployTemplateCleanupShouldDeleteManagedOSImageInExistingResourceGroup(t *testing.T) {
var deleteDiskCounter = 0
var testSubject = createTestStepDeployTemplateDeleteOSImage(&deleteDiskCounter)
stateBag := createTestStateBagStepDeployTemplate()
stateBag.Put(constants.ArmIsManagedImage, true)
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
stateBag.Put(constants.ArmIsResourceGroupCreated, true)
stateBag.Put("ui", packersdk.TestUi(t))
testSubject.Cleanup(stateBag)
if deleteDiskCounter != 1 {
t.Fatalf("Expected DeployTemplate Cleanup to invoke deleteDisk 1 time, but invoked %d times", deleteDiskCounter)
}
}
func TestStepDeployTemplateCleanupShouldDeleteManagedOSImageInTemporaryResourceGroup(t *testing.T) {
var deleteDiskCounter = 0
var testSubject = createTestStepDeployTemplateDeleteOSImage(&deleteDiskCounter)
stateBag := createTestStateBagStepDeployTemplate()
stateBag.Put(constants.ArmIsManagedImage, true)
stateBag.Put(constants.ArmIsExistingResourceGroup, false)
stateBag.Put(constants.ArmIsResourceGroupCreated, true)
stateBag.Put("ui", packersdk.TestUi(t))
testSubject.Cleanup(stateBag)
if deleteDiskCounter != 1 {
t.Fatalf("Expected DeployTemplate Cleanup to invoke deleteDisk 1 times, but invoked %d times", deleteDiskCounter)
}
}
func TestStepDeployTemplateCleanupShouldDeleteVHDOSImageInExistingResourceGroup(t *testing.T) {
var deleteDiskCounter = 0
var testSubject = createTestStepDeployTemplateDeleteOSImage(&deleteDiskCounter)
stateBag := createTestStateBagStepDeployTemplate()
stateBag.Put(constants.ArmIsManagedImage, false)
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
stateBag.Put(constants.ArmIsResourceGroupCreated, true)
stateBag.Put("ui", packersdk.TestUi(t))
testSubject.Cleanup(stateBag)
if deleteDiskCounter != 1 {
t.Fatalf("Expected DeployTemplate Cleanup to invoke deleteDisk 1 time, but invoked %d times", deleteDiskCounter)
}
}
func TestStepDeployTemplateCleanupShouldVHDOSImageInTemporaryResourceGroup(t *testing.T) {
var deleteDiskCounter = 0
var testSubject = createTestStepDeployTemplateDeleteOSImage(&deleteDiskCounter)
stateBag := createTestStateBagStepDeployTemplate()
stateBag.Put(constants.ArmIsManagedImage, false)
stateBag.Put(constants.ArmIsExistingResourceGroup, false)
stateBag.Put(constants.ArmIsResourceGroupCreated, true)
stateBag.Put("ui", packersdk.TestUi(t))
testSubject.Cleanup(stateBag)
if deleteDiskCounter != 1 {
t.Fatalf("Expected DeployTemplate Cleanup to invoke deleteDisk 1 times, but invoked %d times", deleteDiskCounter)
}
}
func createTestStateBagStepDeployTemplate() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmDeploymentName, "Unit Test: DeploymentName")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
stateBag.Put(constants.ArmComputeName, "Unit Test: ComputeName")
return stateBag
}
func createTestStepDeployTemplateDeleteOSImage(deleteDiskCounter *int) *StepDeployTemplate {
return &StepDeployTemplate{
deploy: func(context.Context, string, string) error { return nil },
say: func(message string) {},
error: func(e error) {},
deleteDisk: func(ctx context.Context, imageType string, imageName string, resourceGroupName string) error {
*deleteDiskCounter++
return nil
},
disk: func(ctx context.Context, resourceGroupName, computeName string) (string, string, error) {
return "Microsoft.Compute/disks", "", nil
},
delete: func(ctx context.Context, deploymentName, resourceGroupName string) error {
return nil
},
deleteDeployment: func(ctx context.Context, state multistep.StateBag) error {
return nil
},
}
}
+1 -1
View File
@@ -165,7 +165,7 @@ func (c Config) Validate(errs *packersdk.MultiError) {
if _, err := os.Stat(c.ClientCertPath); err != nil {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("client_cert_path is not an accessible file: %v", err))
}
if c.ClientCertExpireTimeout < 5*time.Minute {
if c.ClientCertExpireTimeout != 0 && c.ClientCertExpireTimeout < 5*time.Minute {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("client_cert_token_timeout will expire within 5 minutes, please set a value greater than 5 minutes"))
}
return
+1 -6
View File
@@ -60,12 +60,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
// Build the steps.
steps := []multistep.Step{
&stepPrepareConfig{},
&commonsteps.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&stepKeypair{
Debug: b.config.PackerDebug,
Comm: &b.config.Comm,
+2
View File
@@ -19,6 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -135,6 +136,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
+1 -6
View File
@@ -211,12 +211,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Directories: b.config.FloppyConfig.FloppyDirectories,
Label: b.config.FloppyConfig.FloppyLabel,
},
&commonsteps.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&hypervcommon.StepCreateSwitch{
SwitchName: b.config.SwitchName,
},
+2
View File
@@ -19,6 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -141,6 +142,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
+1 -6
View File
@@ -251,12 +251,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Directories: b.config.FloppyConfig.FloppyDirectories,
Label: b.config.FloppyConfig.FloppyLabel,
},
&commonsteps.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&hypervcommon.StepCreateSwitch{
SwitchName: b.config.SwitchName,
},
+2
View File
@@ -19,6 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -143,6 +144,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context"
"fmt"
"github.com/oracle/oci-go-sdk/core"
"github.com/oracle/oci-go-sdk/v36/core"
)
// Artifact is an artifact implementation that contains a built Custom Image.
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
ocommon "github.com/hashicorp/packer/builder/oracle/common"
"github.com/oracle/oci-go-sdk/core"
"github.com/oracle/oci-go-sdk/v36/core"
)
// BuilderId uniquely identifies the builder
+23 -5
View File
@@ -1,4 +1,4 @@
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest,FlexShapeConfig
package oci
@@ -19,8 +19,8 @@ import (
"github.com/hashicorp/packer-plugin-sdk/pathing"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
ocicommon "github.com/oracle/oci-go-sdk/common"
ociauth "github.com/oracle/oci-go-sdk/common/auth"
ocicommon "github.com/oracle/oci-go-sdk/v36/common"
ociauth "github.com/oracle/oci-go-sdk/v36/common/auth"
)
type CreateVNICDetails struct {
@@ -46,6 +46,11 @@ type ListImagesRequest struct {
Shape *string `mapstructure:"shape"`
}
type FlexShapeConfig struct {
Ocpus *float32 `mapstructure:"ocpus" required:"false"`
MemoryInGBs *float32 `mapstructure:"memory_in_gbs" required:"false"`
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
@@ -91,6 +96,7 @@ type Config struct {
InstanceTags map[string]string `mapstructure:"instance_tags"`
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
Shape string `mapstructure:"shape"`
ShapeConfig FlexShapeConfig `mapstructure:"shape_config"`
BootVolumeSizeInGBs int64 `mapstructure:"disk_size"`
// Metadata optionally contains custom metadata key/value pairs provided in the
@@ -222,7 +228,7 @@ func (c *Config) Prepare(raws ...interface{}) error {
}
providers := []ocicommon.ConfigurationProvider{
NewRawConfigurationProvider(c.TenancyID, c.UserID, c.Region, c.Fingerprint, string(keyContent), &c.PassPhrase),
ocicommon.NewRawConfigurationProvider(c.TenancyID, c.UserID, c.Region, c.Fingerprint, string(keyContent), &c.PassPhrase),
}
if fileProvider != nil {
@@ -253,7 +259,7 @@ func (c *Config) Prepare(raws ...interface{}) error {
if _, err := configProvider.PrivateRSAKey(); err != nil {
errs = packersdk.MultiErrorAppend(
errs, errors.New("'key_file' must be specified"))
errs, fmt.Errorf("'key_file' must be correctly specified. %w", err))
}
c.configProvider = configProvider
@@ -277,6 +283,18 @@ func (c *Config) Prepare(raws ...interface{}) error {
errs, errors.New("'shape' must be specified"))
}
if strings.HasSuffix(c.Shape, "Flex") {
if c.ShapeConfig.Ocpus == nil {
errs = packersdk.MultiErrorAppend(
errs, errors.New("'Ocpus' must be specified when using flexible shapes"))
}
}
if c.ShapeConfig.MemoryInGBs != nil && c.ShapeConfig.Ocpus == nil {
errs = packersdk.MultiErrorAppend(
errs, errors.New("'Ocpus' must be specified if memory_in_gbs is specified"))
}
if (c.SubnetID == "") && (c.CreateVnicDetails.SubnetId == nil) {
errs = packersdk.MultiErrorAppend(
errs, errors.New("'subnet_ocid' must be specified"))
+28 -1
View File
@@ -1,4 +1,4 @@
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest,FlexShapeConfig"; DO NOT EDIT.
package oci
@@ -88,6 +88,7 @@ type FlatConfig struct {
InstanceTags map[string]string `mapstructure:"instance_tags" cty:"instance_tags" hcl:"instance_tags"`
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags" cty:"instance_defined_tags" hcl:"instance_defined_tags"`
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
ShapeConfig *FlatFlexShapeConfig `mapstructure:"shape_config" cty:"shape_config" hcl:"shape_config"`
BootVolumeSizeInGBs *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
Metadata map[string]string `mapstructure:"metadata" cty:"metadata" hcl:"metadata"`
UserData *string `mapstructure:"user_data" cty:"user_data" hcl:"user_data"`
@@ -188,6 +189,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"instance_tags": &hcldec.AttrSpec{Name: "instance_tags", Type: cty.Map(cty.String), Required: false},
"instance_defined_tags": &hcldec.AttrSpec{Name: "instance_defined_tags", Type: cty.Map(cty.String), Required: false},
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
"shape_config": &hcldec.BlockSpec{TypeName: "shape_config", Nested: hcldec.ObjectSpec((*FlatFlexShapeConfig)(nil).HCL2Spec())},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
@@ -239,6 +241,31 @@ func (*FlatCreateVNICDetails) HCL2Spec() map[string]hcldec.Spec {
return s
}
// FlatFlexShapeConfig is an auto-generated flat version of FlexShapeConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatFlexShapeConfig struct {
Ocpus *float32 `mapstructure:"ocpus" required:"false" cty:"ocpus" hcl:"ocpus"`
MemoryInGBs *float32 `mapstructure:"memory_in_gbs" required:"false" cty:"memory_in_gbs" hcl:"memory_in_gbs"`
}
// FlatMapstructure returns a new FlatFlexShapeConfig.
// FlatFlexShapeConfig is an auto-generated flat version of FlexShapeConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*FlexShapeConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatFlexShapeConfig)
}
// HCL2Spec returns the hcl spec of a FlexShapeConfig.
// This spec is used by HCL to read the fields of FlexShapeConfig.
// The decoded values from this spec will then be applied to a FlatFlexShapeConfig.
func (*FlatFlexShapeConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"ocpus": &hcldec.AttrSpec{Name: "ocpus", Type: cty.Number, Required: false},
"memory_in_gbs": &hcldec.AttrSpec{Name: "memory_in_gbs", Type: cty.Number, Required: false},
}
return s
}
// FlatListImagesRequest is an auto-generated flat version of ListImagesRequest.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatListImagesRequest struct {
-76
View File
@@ -1,76 +0,0 @@
package oci
import (
"crypto/rsa"
"errors"
"fmt"
"github.com/oracle/oci-go-sdk/common"
)
// rawConfigurationProvider allows a user to simply construct a configuration
// provider from raw values. It errors on access when those values are empty.
type rawConfigurationProvider struct {
tenancy string
user string
region string
fingerprint string
privateKey string
privateKeyPassphrase *string
}
// NewRawConfigurationProvider will create a rawConfigurationProvider.
func NewRawConfigurationProvider(tenancy, user, region, fingerprint, privateKey string, privateKeyPassphrase *string) common.ConfigurationProvider {
return rawConfigurationProvider{tenancy, user, region, fingerprint, privateKey, privateKeyPassphrase}
}
func (p rawConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
return common.PrivateKeyFromBytes([]byte(p.privateKey), p.privateKeyPassphrase)
}
func (p rawConfigurationProvider) KeyID() (keyID string, err error) {
tenancy, err := p.TenancyOCID()
if err != nil {
return
}
user, err := p.UserOCID()
if err != nil {
return
}
fingerprint, err := p.KeyFingerprint()
if err != nil {
return
}
return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil
}
func (p rawConfigurationProvider) TenancyOCID() (string, error) {
if p.tenancy == "" {
return "", errors.New("no tenancy provided")
}
return p.tenancy, nil
}
func (p rawConfigurationProvider) UserOCID() (string, error) {
if p.user == "" {
return "", errors.New("no user provided")
}
return p.user, nil
}
func (p rawConfigurationProvider) KeyFingerprint() (string, error) {
if p.fingerprint == "" {
return "", errors.New("no fingerprint provided")
}
return p.fingerprint, nil
}
func (p rawConfigurationProvider) Region() (string, error) {
if p.region == "" {
return "", errors.New("no region provided")
}
return p.region, nil
}
+5 -1
View File
@@ -131,7 +131,7 @@ func TestConfig(t *testing.T) {
t.Run("NoAccessConfig", func(t *testing.T) {
raw := testConfig(cfgFile)
delete(raw, "access_cfg_file")
raw["access_cfg_file"] = "/tmp/random/access/config/file/should/not/exist"
var c Config
errs := c.Prepare(raw)
@@ -140,6 +140,10 @@ func TestConfig(t *testing.T) {
"'user_ocid'", "'tenancy_ocid'", "'fingerprint'", "'key_file'",
}
if errs == nil {
t.Fatalf("Expected errors %q but got none", expectedErrors)
}
s := errs.Error()
for _, expected := range expectedErrors {
if !strings.Contains(s, expected) {
+1 -1
View File
@@ -3,7 +3,7 @@ package oci
import (
"context"
"github.com/oracle/oci-go-sdk/core"
"github.com/oracle/oci-go-sdk/v36/core"
)
// Driver interfaces between the builder steps and the OCI SDK.
+1 -1
View File
@@ -3,7 +3,7 @@ package oci
import (
"context"
"github.com/oracle/oci-go-sdk/core"
"github.com/oracle/oci-go-sdk/v36/core"
)
// driverMock implements the Driver interface and communicates with Oracle
+10 -2
View File
@@ -11,8 +11,8 @@ import (
"sync/atomic"
"time"
"github.com/oracle/oci-go-sdk/common"
core "github.com/oracle/oci-go-sdk/core"
"github.com/oracle/oci-go-sdk/v36/common"
core "github.com/oracle/oci-go-sdk/v36/core"
)
// driverOCI implements the Driver interface and communicates with Oracle
@@ -158,6 +158,14 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
Metadata: metadata,
}
if d.cfg.ShapeConfig.Ocpus != nil {
LaunchInstanceShapeConfigDetails := core.LaunchInstanceShapeConfigDetails{
Ocpus: d.cfg.ShapeConfig.Ocpus,
MemoryInGBs: d.cfg.ShapeConfig.MemoryInGBs,
}
instanceDetails.ShapeConfig = &LaunchInstanceShapeConfigDetails
}
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{
LaunchInstanceDetails: instanceDetails,
RequestMetadata: requestMetadata,
@@ -3,6 +3,8 @@ package oci
import (
"crypto/rand"
"crypto/rsa"
"github.com/oracle/oci-go-sdk/v36/common"
)
// Mock struct to be used during testing to obtain Instance Principals.
@@ -32,3 +34,10 @@ func (p instancePrincipalConfigurationProviderMock) KeyFingerprint() (string, er
func (p instancePrincipalConfigurationProviderMock) Region() (string, error) {
return "some_random_region", nil
}
func (p instancePrincipalConfigurationProviderMock) AuthType() (common.AuthConfig, error) {
return common.AuthConfig{
AuthType: common.InstancePrincipal,
IsFromConfigFile: false,
OboToken: nil}, nil
}
+3
View File
@@ -32,6 +32,9 @@ func (s *stepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
ImageName: omiName,
BlockDeviceMappings: config.BlockDevices.BuildOscOMIDevices(),
}
if config.OMIDescription != "" {
createOpts.Description = config.OMIDescription
}
resp, _, err := oscconn.ImageApi.CreateImage(context.Background(), &osc.CreateImageOpts{
CreateImageRequest: optional.NewInterface(createOpts),
@@ -37,6 +37,9 @@ func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) mul
BlockDeviceMappings: blockDevices,
}
if config.OMIDescription != "" {
registerOpts.Description = config.OMIDescription
}
registerResp, _, err := oscconn.ImageApi.CreateImage(context.Background(), &osc.CreateImageOpts{
CreateImageRequest: optional.NewInterface(registerOpts),
})
+4
View File
@@ -75,6 +75,10 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
registerOpts = buildRegisterOpts(config, image, newMappings)
}
if config.OMIDescription != "" {
registerOpts.Description = config.OMIDescription
}
registerResp, _, err := osconn.ImageApi.CreateImage(context.Background(), &osc.CreateImageOpts{
CreateImageRequest: optional.NewInterface(registerOpts),
})
+1 -6
View File
@@ -209,12 +209,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Directories: b.config.FloppyConfig.FloppyDirectories,
Label: b.config.FloppyConfig.FloppyLabel,
},
&commonsteps.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
new(stepCreateVM),
new(stepCreateDisk),
new(stepSetBootOrder),
@@ -19,6 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -126,6 +127,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
+4
View File
@@ -20,6 +20,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -81,6 +82,7 @@ type FlatConfig struct {
SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
Token *string `mapstructure:"token" cty:"token" hcl:"token"`
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
@@ -129,6 +131,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
@@ -190,6 +193,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"node": &hcldec.AttrSpec{Name: "node", Type: cty.String, Required: false},
"pool": &hcldec.AttrSpec{Name: "pool", Type: cty.String, Required: false},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
+2 -16
View File
@@ -2,7 +2,6 @@ package proxmox
import (
"context"
"crypto/tls"
"errors"
"fmt"
@@ -35,15 +34,7 @@ type Builder struct {
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook, state multistep.StateBag) (packersdk.Artifact, error) {
var err error
tlsConfig := &tls.Config{
InsecureSkipVerify: b.config.SkipCertValidation,
}
b.proxmoxClient, err = proxmox.NewClient(b.config.proxmoxURL.String(), nil, tlsConfig)
if err != nil {
return nil, err
}
err = b.proxmoxClient.Login(b.config.Username, b.config.Password, "")
b.proxmoxClient, err = newProxmoxClient(b.config)
if err != nil {
return nil, err
}
@@ -61,12 +52,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook,
&stepStartVM{
vmCreator: b.vmCreator,
},
&commonsteps.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&stepTypeBootCommand{
BootConfig: b.config.BootConfig,
Ctx: b.config.Ctx,
+37
View File
@@ -0,0 +1,37 @@
package proxmox
import (
"crypto/tls"
"log"
"time"
"github.com/Telmate/proxmox-api-go/proxmox"
)
const defaultTaskTimeout = 30 * time.Second
func newProxmoxClient(config Config) (*proxmox.Client, error) {
tlsConfig := &tls.Config{
InsecureSkipVerify: config.SkipCertValidation,
}
client, err := proxmox.NewClient(config.proxmoxURL.String(), nil, tlsConfig, int(defaultTaskTimeout.Seconds()))
if err != nil {
return nil, err
}
if config.Token != "" {
// configure token auth
log.Print("using token auth")
client.SetAPIToken(config.Username, config.Token)
} else {
// fallback to login if not using tokens
log.Print("using password auth")
err = client.Login(config.Username, config.Password, "")
if err != nil {
return nil, err
}
}
return client, nil
}
+91
View File
@@ -0,0 +1,91 @@
package proxmox
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/stretchr/testify/require"
)
func TestTokenAuth(t *testing.T) {
mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get("Authorization") != "PVEAPIToken=dummy@vmhost!test-token=ac5293bf-15e2-477f-b04c-a6dfa7a46b80" {
rw.WriteHeader(http.StatusUnauthorized)
return
}
}))
defer mockAPI.Close()
pmURL, _ := url.Parse(mockAPI.URL)
config := Config{
proxmoxURL: pmURL,
SkipCertValidation: false,
Username: "dummy@vmhost!test-token",
Password: "not-used",
Token: "ac5293bf-15e2-477f-b04c-a6dfa7a46b80",
}
client, err := newProxmoxClient(config)
require.NoError(t, err)
ref := proxmox.NewVmRef(110)
ref.SetNode("node1")
ref.SetVmType("qemu")
err = client.Sendkey(ref, "ping")
require.NoError(t, err)
}
func TestLogin(t *testing.T) {
mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
// mock ticketing api
if req.Method == http.MethodPost && req.URL.Path == "/access/ticket" {
body, _ := ioutil.ReadAll(req.Body)
values, _ := url.ParseQuery(string(body))
user := values.Get("username")
pass := values.Get("password")
if user != "dummy@vmhost" || pass != "correct-horse-battery-staple" {
rw.WriteHeader(http.StatusUnauthorized)
return
}
_ = json.NewEncoder(rw).Encode(map[string]interface{}{
"data": map[string]string{
"username": user,
"ticket": "dummy-ticket",
"CSRFPreventionToken": "random-token",
},
})
return
}
// validate ticket
if val, err := req.Cookie("PVEAuthCookie"); err != nil || val.Value != "dummy-ticket" {
rw.WriteHeader(http.StatusUnauthorized)
return
}
}))
defer mockAPI.Close()
pmURL, _ := url.Parse(mockAPI.URL)
config := Config{
proxmoxURL: pmURL,
SkipCertValidation: false,
Username: "dummy@vmhost",
Password: "correct-horse-battery-staple",
Token: "",
}
client, err := newProxmoxClient(config)
require.NoError(t, err)
ref := proxmox.NewVmRef(110)
ref.SetNode("node1")
ref.SetVmType("qemu")
err = client.Sendkey(ref, "ping")
require.NoError(t, err)
}
+8 -4
View File
@@ -35,6 +35,7 @@ type Config struct {
SkipCertValidation bool `mapstructure:"insecure_skip_tls_verify"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Token string `mapstructure:"token"`
Node string `mapstructure:"node"`
Pool string `mapstructure:"pool"`
@@ -73,8 +74,8 @@ type additionalISOsConfig struct {
ISOFile string `mapstructure:"iso_file"`
ISOStoragePool string `mapstructure:"iso_storage_pool"`
Unmount bool `mapstructure:"unmount"`
ShouldUploadISO bool
DownloadPathKey string
ShouldUploadISO bool `mapstructure-to-hcl2:",skip"`
DownloadPathKey string `mapstructure-to-hcl2:",skip"`
}
type nicConfig struct {
@@ -135,6 +136,9 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st
if c.Password == "" {
c.Password = os.Getenv("PROXMOX_PASSWORD")
}
if c.Token == "" {
c.Token = os.Getenv("PROXMOX_TOKEN")
}
if c.BootKeyInterval == 0 && os.Getenv(bootcommand.PackerKeyEnv) != "" {
var err error
c.BootKeyInterval, err = time.ParseDuration(os.Getenv(bootcommand.PackerKeyEnv))
@@ -220,8 +224,8 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st
if c.Username == "" {
errs = packersdk.MultiErrorAppend(errs, errors.New("username must be specified"))
}
if c.Password == "" {
errs = packersdk.MultiErrorAppend(errs, errors.New("password must be specified"))
if c.Password == "" && c.Token == "" {
errs = packersdk.MultiErrorAppend(errs, errors.New("password or token must be specified"))
}
if c.ProxmoxURLRaw == "" {
errs = packersdk.MultiErrorAppend(errs, errors.New("proxmox_url must be specified"))
+4 -4
View File
@@ -19,6 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -80,6 +81,7 @@ type FlatConfig struct {
SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
Token *string `mapstructure:"token" cty:"token" hcl:"token"`
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
@@ -126,6 +128,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
@@ -187,6 +190,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"node": &hcldec.AttrSpec{Name: "node", Type: cty.String, Required: false},
"pool": &hcldec.AttrSpec{Name: "pool", Type: cty.String, Required: false},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
@@ -226,8 +230,6 @@ type FlatadditionalISOsConfig struct {
ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"`
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"`
Unmount *bool `mapstructure:"unmount" cty:"unmount" hcl:"unmount"`
ShouldUploadISO *bool `cty:"should_upload_iso" hcl:"should_upload_iso"`
DownloadPathKey *string `cty:"download_path_key" hcl:"download_path_key"`
}
// FlatMapstructure returns a new FlatadditionalISOsConfig.
@@ -251,8 +253,6 @@ func (*FlatadditionalISOsConfig) HCL2Spec() map[string]hcldec.Spec {
"iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false},
"iso_storage_pool": &hcldec.AttrSpec{Name: "iso_storage_pool", Type: cty.String, Required: false},
"unmount": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false},
"should_upload_iso": &hcldec.AttrSpec{Name: "should_upload_iso", Type: cty.Bool, Required: false},
"download_path_key": &hcldec.AttrSpec{Name: "download_path_key", Type: cty.String, Required: false},
}
return s
}
+4
View File
@@ -20,6 +20,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -81,6 +82,7 @@ type FlatConfig struct {
SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
Token *string `mapstructure:"token" cty:"token" hcl:"token"`
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
@@ -135,6 +137,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
@@ -196,6 +199,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"node": &hcldec.AttrSpec{Name: "node", Type: cty.String, Required: false},
"pool": &hcldec.AttrSpec{Name: "pool", Type: cty.String, Required: false},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
+1 -6
View File
@@ -96,12 +96,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
QemuImgArgs: b.config.QemuImgArgs,
},
new(stepHTTPIPDiscover),
&commonsteps.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&stepPortForward{
CommunicatorType: b.config.CommConfig.Comm.Type,
NetBridge: b.config.NetBridge,
+2
View File
@@ -19,6 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -153,6 +154,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
+2
View File
@@ -298,6 +298,7 @@ func (s *stepRun) applyUserOverrides(defaultArgs map[string]interface{}, config
HTTPIP string
HTTPPort int
HTTPDir string
HTTPContent map[string]string
OutputDir string
Name string
SSHHostPort int
@@ -308,6 +309,7 @@ func (s *stepRun) applyUserOverrides(defaultArgs map[string]interface{}, config
HTTPIP: httpIp,
HTTPPort: httpPort,
HTTPDir: config.HTTPDir,
HTTPContent: config.HTTPContent,
OutputDir: config.OutputDir,
Name: config.VMName,
SSHHostPort: commHostPort,
+2
View File
@@ -19,6 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -128,6 +129,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
+1 -6
View File
@@ -406,12 +406,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Label: b.config.CDConfig.CDLabel,
},
new(vboxcommon.StepHTTPIPDiscover),
&commonsteps.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&vboxcommon.StepSshKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName),
@@ -19,6 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -162,6 +163,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
+1 -6
View File
@@ -65,12 +65,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Label: b.config.CDConfig.CDLabel,
},
new(vboxcommon.StepHTTPIPDiscover),
&commonsteps.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&vboxcommon.StepSshKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName),
@@ -19,6 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -138,6 +139,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
+1 -6
View File
@@ -64,12 +64,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
KeepRegistered: b.config.KeepRegistered,
},
new(vboxcommon.StepHTTPIPDiscover),
&commonsteps.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&vboxcommon.StepDownloadGuestAdditions{
GuestAdditionsMode: b.config.GuestAdditionsMode,
GuestAdditionsURL: b.config.GuestAdditionsURL,
+2
View File
@@ -19,6 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -136,6 +137,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
+1 -6
View File
@@ -117,12 +117,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
},
&vmwcommon.StepSuppressMessages{},
&vmwcommon.StepHTTPIPDiscover{},
&commonsteps.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&vmwcommon.StepConfigureVNC{
Enabled: !b.config.DisableVNC && !b.config.VNCOverWebsocket,
VNCBindAddress: b.config.VNCBindAddress,
+2
View File
@@ -19,6 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -167,6 +168,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
+1 -6
View File
@@ -108,12 +108,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
},
&vmwcommon.StepSuppressMessages{},
&vmwcommon.StepHTTPIPDiscover{},
&commonsteps.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&vmwcommon.StepUploadVMX{
RemoteType: b.config.RemoteType,
},
+2
View File
@@ -19,6 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
@@ -149,6 +150,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
+1 -6
View File
@@ -89,12 +89,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
HTTPIP: b.config.BootConfig.HTTPIP,
Network: b.config.WaitIpConfig.GetIPNet(),
},
&commonsteps.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&common.StepSshKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName),

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