Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 429e5149d3 | |||
| 0c00ef62a5 | |||
| 38254bca28 | |||
| fa034ed424 | |||
| 56b4a512ed | |||
| 3f7550cad1 | |||
| 559c5c040b | |||
| 26b952e66d | |||
| 453e56e554 |
+9
-20
@@ -7,7 +7,7 @@ version: 2.1
|
||||
executors:
|
||||
golang:
|
||||
docker:
|
||||
- image: docker.mirror.hashicorp.services/circleci/golang:1.16
|
||||
- image: docker.mirror.hashicorp.services/circleci/golang:1.15
|
||||
resource_class: medium+
|
||||
darwin:
|
||||
macos:
|
||||
@@ -37,19 +37,15 @@ commands:
|
||||
parameters:
|
||||
GOOS:
|
||||
type: string
|
||||
GOARCH:
|
||||
default: "amd64"
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- run: GOOS=<< parameters.GOOS >> GOARCH=<<parameters.GOARCH>> go build -ldflags="-s -w -X github.com/hashicorp/packer/version.GitCommit=${CIRCLE_SHA1}" -o ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >> .
|
||||
- run: zip ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >>.zip ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >>
|
||||
- run: rm ./pkg/packer_<< parameters.GOOS >>_<< parameters.GOARCH >>
|
||||
- run: GOOS=<< parameters.GOOS >> go build -ldflags="-s -w -X github.com/hashicorp/packer/version.GitCommit=${CIRCLE_SHA1}" -o ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH) .
|
||||
- run: zip ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH).zip ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH)
|
||||
- run: rm ./pkg/packer_<< parameters.GOOS >>_$(go env GOARCH)
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- ./pkg/
|
||||
|
||||
# Golang CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
||||
@@ -65,11 +61,13 @@ jobs:
|
||||
file: coverage.txt
|
||||
test-darwin:
|
||||
executor: darwin
|
||||
working_directory: ~/go/github.com/hashicorp/packer
|
||||
working_directory: ~/go/src/github.com/hashicorp/packer
|
||||
environment:
|
||||
GO111MODULE: "off"
|
||||
steps:
|
||||
- install-go-run-tests-unix:
|
||||
GOOS: darwin
|
||||
GOVERSION: "1.16"
|
||||
GOVERSION: "1.15"
|
||||
- codecov/upload:
|
||||
file: coverage.txt
|
||||
test-windows:
|
||||
@@ -78,7 +76,7 @@ jobs:
|
||||
shell: bash.exe
|
||||
steps:
|
||||
- install-go-run-tests-windows:
|
||||
GOVERSION: "1.16"
|
||||
GOVERSION: "1.15"
|
||||
- codecov/upload:
|
||||
file: coverage.txt
|
||||
check-lint:
|
||||
@@ -127,13 +125,6 @@ jobs:
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: darwin
|
||||
build_darwin_arm64:
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
steps:
|
||||
- build-and-persist-packer-binary:
|
||||
GOOS: darwin
|
||||
GOARCH: arm64
|
||||
build_freebsd:
|
||||
executor: golang
|
||||
working_directory: /go/src/github.com/hashicorp/packer
|
||||
@@ -214,7 +205,6 @@ workflows:
|
||||
jobs:
|
||||
- build_linux
|
||||
- build_darwin
|
||||
- build_darwin_arm64
|
||||
- build_windows
|
||||
- build_freebsd
|
||||
- build_openbsd
|
||||
@@ -223,7 +213,6 @@ workflows:
|
||||
requires:
|
||||
- build_linux
|
||||
- build_darwin
|
||||
- build_darwin_arm64
|
||||
- build_windows
|
||||
- build_freebsd
|
||||
- build_openbsd
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
*.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
|
||||
|
||||
+4
-11
@@ -11,12 +11,6 @@ contribute to the project, read on. This document will cover what we're looking
|
||||
for. By addressing all the points we're looking for, it raises the chances we
|
||||
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
|
||||
@@ -25,7 +19,7 @@ when addressing our team. For more reading on our code of conduct, please see th
|
||||
already fixed the bug you're experiencing.
|
||||
|
||||
- Run the command with debug output with the environment variable `PACKER_LOG`.
|
||||
For example: `PACKER_LOG=1 packer build template.pkr.hcl`. Take the _entire_
|
||||
For example: `PACKER_LOG=1 packer build template.json`. Take the _entire_
|
||||
output and create a [gist](https://gist.github.com) for linking to in your
|
||||
issue. Packer should strip sensitive keys from the output, but take a look
|
||||
through just in case.
|
||||
@@ -70,9 +64,7 @@ when addressing our team. For more reading on our code of conduct, please see th
|
||||
If you have never worked with Go before, you will have to install its
|
||||
runtime in order to build packer.
|
||||
|
||||
1. This project always releases from the latest version of golang.
|
||||
[Install go](https://golang.org/doc/install#install) To properly build from
|
||||
source, you need to have golang >= v1.16
|
||||
1. This project always releases from the latest version of golang. [Install go](https://golang.org/doc/install#install)
|
||||
|
||||
## Setting up Packer for dev
|
||||
|
||||
@@ -80,6 +72,7 @@ If/when you have go installed you can already `go get` packer and `make` in
|
||||
order to compile and test Packer. These instructions target
|
||||
POSIX-like environments (macOS, Linux, Cygwin, etc.) so you may need to
|
||||
adjust them for Windows or other shells.
|
||||
The instructions below are for go 1.7. or later.
|
||||
|
||||
1. Download the Packer source (and its dependencies) by running
|
||||
`go get github.com/hashicorp/packer`. This will download the Packer source to
|
||||
@@ -98,7 +91,7 @@ adjust them for Windows or other shells.
|
||||
4. After running building Packer successfully, use
|
||||
`$GOPATH/src/github.com/hashicorp/packer/bin/packer` to build a machine and
|
||||
verify your changes work. For instance:
|
||||
`$GOPATH/src/github.com/hashicorp/packer/bin/packer build template.pkr.hcl`.
|
||||
`$GOPATH/src/github.com/hashicorp/packer/bin/packer build template.json`.
|
||||
|
||||
5. If everything works well and the tests pass, run `go fmt` on your code before
|
||||
submitting a pull-request.
|
||||
|
||||
@@ -10,7 +10,7 @@ Describe the change you are making here!
|
||||
|
||||
Please include tests. Check out these examples:
|
||||
|
||||
- https://github.com/hashicorp/packer/blob/master/builder/parallels/common/ssh_config_test.go#L34
|
||||
- https://github.com/hashicorp/packer/blob/master/builder/virtualbox/common/ssh_config_test.go#L19-L37
|
||||
- https://github.com/hashicorp/packer/blob/master/post-processor/compress/post-processor_test.go#L153-L182
|
||||
|
||||
If your PR resolves any open issue(s), please indicate them like this so they will be closed when your PR is merged:
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const fetchPluginDocs = require("../../website/components/remote-plugin-docs/utils/fetch-plugin-docs");
|
||||
|
||||
const COLOR_RESET = "\x1b[0m";
|
||||
const COLOR_GREEN = "\x1b[32m";
|
||||
const COLOR_BLUE = "\x1b[34m";
|
||||
const COLOR_RED = "\x1b[31m";
|
||||
|
||||
async function checkPluginDocs() {
|
||||
const failureMessages = [];
|
||||
const pluginsPath = "website/data/docs-remote-plugins.json";
|
||||
const pluginsFile = fs.readFileSync(path.join(process.cwd(), pluginsPath));
|
||||
const pluginEntries = JSON.parse(pluginsFile);
|
||||
const entriesCount = pluginEntries.length;
|
||||
console.log(`\nResolving plugin docs from ${entriesCount} repositories …`);
|
||||
for (var i = 0; i < entriesCount; i++) {
|
||||
const pluginEntry = pluginEntries[i];
|
||||
const { title, repo, version } = pluginEntry;
|
||||
console.log(`\n${COLOR_BLUE}${repo}${COLOR_RESET} | ${title}`);
|
||||
console.log(`Fetching docs from release "${version}" …`);
|
||||
try {
|
||||
// Validate that all required properties are present
|
||||
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".`
|
||||
);
|
||||
}
|
||||
// Validate pluginTier property
|
||||
const { pluginTier } = pluginEntry;
|
||||
if (typeof pluginTier !== "undefined") {
|
||||
const validPluginTiers = ["official", "community"];
|
||||
const isValid = validPluginTiers.indexOf(pluginTier) !== -1;
|
||||
if (!isValid) {
|
||||
throw new Error(
|
||||
`Failed to validate plugin docs config. Invalid pluginTier "${pluginTier}" found for "${
|
||||
title || pluginEntry.path || repo
|
||||
}". In "website/data/docs-remote-plugins.json", the optional pluginTier property must be one of ${JSON.stringify(
|
||||
validPluginTiers
|
||||
)}. The pluginTier property can also be omitted, in which case it will be determined from the plugin repository owner.`
|
||||
);
|
||||
}
|
||||
}
|
||||
// Attempt to fetch plugin docs files
|
||||
const docsMdxFiles = await fetchPluginDocs({ repo, tag: version });
|
||||
const mdxFilesByComponent = docsMdxFiles.reduce((acc, mdxFile) => {
|
||||
const componentType = mdxFile.filePath.split("/")[1];
|
||||
if (!acc[componentType]) acc[componentType] = [];
|
||||
acc[componentType].push(mdxFile);
|
||||
return acc;
|
||||
}, {});
|
||||
console.log(`${COLOR_GREEN}Found valid docs:${COLOR_RESET}`);
|
||||
Object.keys(mdxFilesByComponent).forEach((component) => {
|
||||
const componentFiles = mdxFilesByComponent[component];
|
||||
console.log(` ${component}`);
|
||||
componentFiles.forEach(({ filePath }) => {
|
||||
const pathFromComponent = filePath.split("/").slice(2).join("/");
|
||||
console.log(` ├── ${pathFromComponent}`);
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(`${COLOR_RED}${err}${COLOR_RESET}`);
|
||||
failureMessages.push(`\n${COLOR_RED}× ${repo}: ${COLOR_RESET}${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (failureMessages.length === 0) {
|
||||
console.log(
|
||||
`\n---\n\n${COLOR_GREEN}Summary: Successfully resolved all plugin docs.`
|
||||
);
|
||||
pluginEntries.forEach((e) =>
|
||||
console.log(`${COLOR_GREEN}✓ ${e.repo}${COLOR_RESET}`)
|
||||
);
|
||||
console.log("");
|
||||
} else {
|
||||
console.log(
|
||||
`\n---\n\n${COLOR_RED}Summary: Failed to fetch docs for ${failureMessages.length} plugin(s):`
|
||||
);
|
||||
failureMessages.forEach((err) => console.log(err));
|
||||
console.log("");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
checkPluginDocs();
|
||||
@@ -1,29 +0,0 @@
|
||||
#
|
||||
# This GitHub action checks plugin repositories for valid docs.
|
||||
#
|
||||
# This provides a quick assessment on PRs of whether
|
||||
# there might be issues with docs in plugin repositories.
|
||||
#
|
||||
# This is intended to help debug Vercel build issues, which
|
||||
# may or may not be related to docs in plugin repositories.
|
||||
|
||||
name: "website: Check plugin docs"
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "website/**"
|
||||
schedule:
|
||||
- cron: "45 0 * * *"
|
||||
|
||||
jobs:
|
||||
check-plugin-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v1
|
||||
- name: Install Dependencies
|
||||
run: npm i isomorphic-unfetch adm-zip gray-matter
|
||||
- name: Fetch and validate plugin docs
|
||||
run: node .github/workflows/check-plugin-docs.js
|
||||
@@ -7,7 +7,7 @@ name: Check markdown links on modified website files
|
||||
jobs:
|
||||
vercel-deployment-poll:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5 #cancel job if no deployment is found within x minutes
|
||||
timeout-minutes: 3 #cancel job if no deployment is found within x minutes
|
||||
outputs:
|
||||
url: ${{ steps.waitForVercelPreviewDeployment.outputs.url }}
|
||||
steps:
|
||||
|
||||
@@ -22,21 +22,3 @@ poll "closed_issue_locker" "locker" {
|
||||
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.
|
||||
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}."
|
||||
}
|
||||
|
||||
|
||||
+3
-220
@@ -1,232 +1,15 @@
|
||||
## 1.7.3 (Upcoming)
|
||||
|
||||
## 1.7.2 (April 05, 2021)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
* builder/alicloud: Add `ramrole` configuration to ECS instance. [GH-10845]
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
* builder/proxmox: Update Proxmox Go API to ensure only the first non-loopback
|
||||
IPv4 address gets returned. [GH-10858]
|
||||
* builder/vsphere: Fix primary disk resize on clone. [GH-10848]
|
||||
* core: Fix bug where call to "packer version" sent output to stderr instead of
|
||||
stdout. [GH-10850]
|
||||
|
||||
## 1.7.1 (March 31, 2021)
|
||||
|
||||
### NOTES:
|
||||
|
||||
* builder/amazon: Has been vendored in this release and will no longer be
|
||||
updated with Packer core. In Packer v1.8.0 the plugin will be removed
|
||||
entirely. The `amazon` components will continue to work as expected until
|
||||
then, but for the latest offerings of the Amazon plugin, users are
|
||||
encourage to use the `packer init` command to install the latest release
|
||||
version. For more details see [Installing Packer
|
||||
Plugins](https://www.packer.io/docs/plugins#installing-plugins)
|
||||
* builder/docker: Has been vendored in this release and will no longer be
|
||||
updated with Packer core. In Packer v1.8.0 the plugin will be removed
|
||||
entirely. The `docker` builder will continue to work as expected until
|
||||
then, but for the latest offerings of the Docker plugin, users are
|
||||
encourage to use the `packer init` command to install the latest release
|
||||
version. For more details see [Installing Packer
|
||||
Plugins](https://www.packer.io/docs/plugins#installing-plugins)
|
||||
* 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 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]
|
||||
* 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-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)
|
||||
|
||||
### FEATURES
|
||||
* **New Command** (HCL only) `packer init` command will download plugins defined
|
||||
in a new `required_plugins` block [GH-10304] [GH-10633].
|
||||
* **New Plugin Type** Data sources can be implemented (blog post forthcoming).
|
||||
[GH-10440]
|
||||
* **New Plugin** Aws Secrets Manager data source [GH-10505] [GH-10467]
|
||||
|
||||
### BACKWARDS INCOMPATIBILITIES
|
||||
* core: The API that the Packer core uses to communicate with community plugins
|
||||
has changed; maintainers of community plugins will need to upgrade their
|
||||
plugins in order to make them compatible with v1.7.0. An upgrade guide will
|
||||
be available on our guides page https://www.packer.io/guides.
|
||||
|
||||
### IMPROVEMENTS
|
||||
* builder/amazon: Add `skip_create_ami` option for testing and situations where
|
||||
artifact is not the ami. [GH-10531]
|
||||
* builder/amazon: Add IMDSv2 support for AWS EBS builder. [GH-10546]
|
||||
* builder/amazon: Add resource tags in the launch template used to request spot
|
||||
instances. [GH-10456]
|
||||
* builder/openstack: Add `skip_create_image` option for testing and situations
|
||||
where artifact is not the image. [GH-10496]
|
||||
* builder/oracle-oci: Add retry strategies to oci calls [GH-10591]
|
||||
* core/fmt: The `packer fmt` can now read from stdin. [GH-10500]
|
||||
* core/hcl: Add regex and regexall hcl2 template functions. [GH-10601]
|
||||
* core/hcl: Templates now support "sensitive" locals. [GH-10509]
|
||||
* core/hcl: Templates now support error-cleanup-provisioner. [GH-10604]
|
||||
* hcl2_upgrade: Command now comes with a flag so you can control whether output
|
||||
templates are annotated with helpful comments. [GH-10619]
|
||||
* hcl2_upgrade: Command now gracefully handles options with template engine
|
||||
interpolations. [GH-10625]
|
||||
* hcl2_upgrade: Command will convert amazon filters to use the ami data source.
|
||||
[GH-10491]
|
||||
|
||||
### BUG FIXES
|
||||
* amazon/ebssurrogate: Apply snapshot tags at same time as when taking
|
||||
snapshot. [GH-10150]
|
||||
* builder/amazon: Fix bug where validation fails if optional iops value is
|
||||
unset. [GH-10518]
|
||||
* builder/amazon: Wrap API call to get filtered image in a retry. [GH-10610]
|
||||
* builder/bsusurrogate: override bsu when omi root device is set. [GH-10490]
|
||||
* builder/google: Fix bug where Packer would fail when run by users who do not
|
||||
have permission to access the metadata, even though the metadata is not
|
||||
necessary to the run. [GH-10458]
|
||||
* builder/profitbricks: Profitbricks builder could not connect using SSH
|
||||
communicator. [GH-10549]
|
||||
* builder/proxmox: Ensure ISOs in additional_iso_files are mounted during VM
|
||||
creation. [GH-10586]
|
||||
* builder/proxmox: Improve cloud init error logging for proxmox builder.
|
||||
[GH-10499]
|
||||
* builder/qemu: Fix bug where vnc_min_port set to value greater then 5900 could
|
||||
prevent Packer from connecting to QEMU. [GH-10450] [GH-10451]
|
||||
* builder/qemu: Fix regression with cd indexing when disk_interface is `ide`.
|
||||
[GH-10519]
|
||||
* builder/vmware-esx: Skip credential validation, which requires ovftool to be
|
||||
installed, if we are not exporting an image. [GH-10520]
|
||||
* builder/yandex: Fix cloud-init config for ubuntu 20.04. [GH-10522]
|
||||
* builder/yandex: Fix incorrect access to `instance_id`. [GH-10522]
|
||||
* core/hcl: Fix bug where []uint8 types could not be passed to plugins.
|
||||
* core/hcl: fix bug where HCL core could not handle passing []uint8 to plugins.
|
||||
[GH-10516]
|
||||
* core/hcl: Fix force flag for hcl2 provisioners and post-processors.
|
||||
[GH-10571]
|
||||
* post-processor/vsphere: Fix regression where Packer would not check the exit
|
||||
status after streaming UI from the ovftool command. [GH-10468]
|
||||
* post-processor/yandex-export: Changed dhclient command and supported
|
||||
configuring disk for exportupdate-dump-method. Also added support for
|
||||
`file` builder. [GH-10488]
|
||||
## 1.7.0 (Upcoming)
|
||||
|
||||
## 1.6.6 (December 16, 2020)
|
||||
|
||||
### FEATURES
|
||||
### FEATURES:
|
||||
* **New command** `fmt` allows users to format existing HCL2 configuration
|
||||
files into a canonical style. Please see [fmt command
|
||||
docs](https://packer.io/docs/commands/fmt) for more details. [GH-10225]
|
||||
[GH-10377]
|
||||
* **New function** `env` allows users to set the default value of a variable to
|
||||
the value of an environment variable. Please see [env function
|
||||
docs](https://www.packer.io/docs/templates/hcl_templates/functions/contextual/env) for
|
||||
docs](https://www.packer.io/docs/templates/hcl_templates/functions/contextual/env") for
|
||||
more details. [GH-10240]
|
||||
* **Future Scaffolding** This release contains a large number of no-op
|
||||
refactoring changes. The Packer team at HashiCorp is preparing to split the
|
||||
|
||||
+11
-2
@@ -37,12 +37,15 @@
|
||||
/builder/oracle/ @prydie @owainlewis
|
||||
/website/pages/docs/builders/oracle* @prydie @owainlewis
|
||||
|
||||
/builder/profitbricks/ @LiviusP @mflorin
|
||||
/website/pages/docs/builders/profitbricks* @LiviusP @mflorin
|
||||
/builder/profitbricks/ @jasmingacic
|
||||
/website/pages/docs/builders/profitbricks* @jasmingacic
|
||||
|
||||
/builder/triton/ @sean-
|
||||
/website/pages/docs/builders/triton* @sean-
|
||||
|
||||
/builder/ncloud/ @YuSungDuk
|
||||
/website/pages/docs/builders/ncloud* @YuSungDuk
|
||||
|
||||
/builder/proxmox/ @carlpett
|
||||
/website/pages/docs/builders/proxmox* @carlpett
|
||||
|
||||
@@ -65,10 +68,15 @@
|
||||
/builder/yandex/ @GennadySpb @alexanderKhaustov @seukyaso
|
||||
/website/pages/docs/builders/yandex* @GennadySpb @alexanderKhaustov @seukyaso
|
||||
|
||||
/builder/osc/ @marinsalinas @Hakujou
|
||||
/website/pages/docs/builders/osc* @marinsalinas @Hakujou
|
||||
|
||||
/examples/tencentcloud/ @likexian
|
||||
/builder/tencentcloud/ @likexian
|
||||
/website/pages/docs/builders/tencentcloud* @likexian
|
||||
|
||||
|
||||
|
||||
# provisioners
|
||||
|
||||
/examples/ansible/ @bhcleek
|
||||
@@ -79,6 +87,7 @@
|
||||
|
||||
/post-processor/alicloud-import/ dongxiao.zzh@alibaba-inc.com
|
||||
/post-processor/checksum/ v.tolstov@selfip.ru
|
||||
/post-processor/exoscale-import/ @falzm @mcorbin
|
||||
/post-processor/googlecompute-export/ crunkleton@google.com
|
||||
/post-processor/yandex-export/ @GennadySpb
|
||||
/post-processor/yandex-import/ @GennadySpb
|
||||
|
||||
@@ -49,7 +49,7 @@ package:
|
||||
@sh -c "$(CURDIR)/scripts/dist.sh $(VERSION)"
|
||||
|
||||
install-build-deps: ## Install dependencies for bin build
|
||||
@go install github.com/mitchellh/gox@v1.0.1
|
||||
@go get github.com/mitchellh/gox
|
||||
|
||||
install-gen-deps: ## Install dependencies for code generation
|
||||
# to avoid having to tidy our go deps, we `go get` our binaries from a temp
|
||||
@@ -58,7 +58,8 @@ install-gen-deps: ## Install dependencies for code generation
|
||||
# install` seems to install the last tagged version and we want to install
|
||||
# master.
|
||||
@(cd $(TEMPDIR) && GO111MODULE=on go get github.com/alvaroloes/enumer@master)
|
||||
@go install github.com/hashicorp/packer-plugin-sdk/cmd/packer-sdc@latest
|
||||
@go install ./cmd/struct-markdown
|
||||
@go install ./cmd/mapstructure-to-hcl2
|
||||
|
||||
install-lint-deps: ## Install linter dependencies
|
||||
# Pinning golangci-lint at v1.23.8 as --new-from-rev seems to work properly; the latest 1.24.0 has caused issues with memory consumption
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Packer
|
||||
|
||||
[![Build Status][circleci-badge]][circleci]
|
||||
[](https://discuss.hashicorp.com/c/packer)
|
||||
[![Windows Build Status][appveyor-badge]][appveyor]
|
||||
[](https://pkg.go.dev/github.com/hashicorp/packer)
|
||||
[![GoReportCard][report-badge]][report]
|
||||
[](https://codecov.io/gh/hashicorp/packer)
|
||||
@@ -9,16 +9,15 @@
|
||||
[circleci-badge]: https://circleci.com/gh/hashicorp/packer.svg?style=svg
|
||||
[circleci]: https://app.circleci.com/pipelines/github/hashicorp/packer
|
||||
[appveyor-badge]: https://ci.appveyor.com/api/projects/status/miavlgnp989e5obc/branch/master?svg=true
|
||||
[appveyor]: https://ci.appveyor.com/project/hashicorp/packer
|
||||
[godoc-badge]: https://godoc.org/github.com/hashicorp/packer?status.svg
|
||||
[godoc]: https://godoc.org/github.com/hashicorp/packer
|
||||
[report-badge]: https://goreportcard.com/badge/github.com/hashicorp/packer
|
||||
[godoc]: https://godoc.org/github.com/hashicorp/packer
|
||||
[report-badge]: https://goreportcard.com/badge/github.com/hashicorp/packer
|
||||
[report]: https://goreportcard.com/report/github.com/hashicorp/packer
|
||||
|
||||
<p align="center" style="text-align:center;">
|
||||
<a href="https://www.packer.io">
|
||||
<img alt="HashiCorp Packer logo" src="website/public/img/logo-packer-padded.svg" width="500" />
|
||||
</a>
|
||||
</p>
|
||||
* Website: https://www.packer.io
|
||||
* IRC: `#packer-tool` on Freenode
|
||||
* Mailing list: [Google Groups](https://groups.google.com/forum/#!forum/packer-tool)
|
||||
|
||||
Packer is a tool for building identical machine images for multiple platforms
|
||||
from a single source configuration.
|
||||
@@ -48,43 +47,33 @@ yourself](https://github.com/hashicorp/packer/blob/master/.github/CONTRIBUTING.m
|
||||
|
||||
After Packer is installed, create your first template, which tells Packer
|
||||
what platforms to build images for and how you want to build them. In our
|
||||
case, we'll create a simple AMI that has Redis pre-installed.
|
||||
|
||||
Save this file as `quick-start.pkr.hcl`. Export your AWS credentials as the
|
||||
case, we'll create a simple AMI that has Redis pre-installed. Save this
|
||||
file as `quick-start.json`. Export your AWS credentials as the
|
||||
`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables.
|
||||
|
||||
```hcl
|
||||
variable "access_key" {
|
||||
type = string
|
||||
default = "${env("AWS_ACCESS_KEY_ID")}"
|
||||
}
|
||||
|
||||
variable "secret_key" {
|
||||
type = string
|
||||
default = "${env("AWS_SECRET_ACCESS_KEY")}"
|
||||
}
|
||||
|
||||
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
|
||||
|
||||
source "amazon-ebs" "quick-start" {
|
||||
access_key = "${var.access_key}"
|
||||
ami_name = "packer-example ${local.timestamp}"
|
||||
instance_type = "t2.micro"
|
||||
region = "us-east-1"
|
||||
secret_key = "${var.secret_key}"
|
||||
source_ami = "ami-af22d9b9"
|
||||
ssh_username = "ubuntu"
|
||||
}
|
||||
|
||||
build {
|
||||
sources = ["source.amazon-ebs.quick-start"]
|
||||
```json
|
||||
{
|
||||
"variables": {
|
||||
"access_key": "{{env `AWS_ACCESS_KEY_ID`}}",
|
||||
"secret_key": "{{env `AWS_SECRET_ACCESS_KEY`}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "amazon-ebs",
|
||||
"access_key": "{{user `access_key`}}",
|
||||
"secret_key": "{{user `secret_key`}}",
|
||||
"region": "us-east-1",
|
||||
"source_ami": "ami-af22d9b9",
|
||||
"instance_type": "t2.micro",
|
||||
"ssh_username": "ubuntu",
|
||||
"ami_name": "packer-example {{timestamp}}"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Next, tell Packer to build the image:
|
||||
|
||||
```
|
||||
$ packer build quick-start.pkr.hcl
|
||||
$ packer build quick-start.json
|
||||
...
|
||||
```
|
||||
|
||||
@@ -96,9 +85,11 @@ they're run, etc., is up to you.
|
||||
|
||||
## Documentation
|
||||
|
||||
Comprehensive documentation is viewable on the Packer website at https://www.packer.io/docs.
|
||||
Comprehensive documentation is viewable on the Packer website:
|
||||
|
||||
## Contributing to Packer
|
||||
https://www.packer.io/docs
|
||||
|
||||
## Developing Packer
|
||||
|
||||
See
|
||||
[CONTRIBUTING.md](https://github.com/hashicorp/packer/blob/master/.github/CONTRIBUTING.md)
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
// component_acc_test.go should contain acceptance tests for plugin components
|
||||
// to make sure all component types can be discovered and started.
|
||||
package plugin
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
amazonacc "github.com/hashicorp/packer-plugin-amazon/builder/ebs/acceptance"
|
||||
"github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
"github.com/hashicorp/packer/hcl2template/addrs"
|
||||
)
|
||||
|
||||
//go:embed test-fixtures/basic-amazon-ami-datasource.pkr.hcl
|
||||
var basicAmazonAmiDatasourceHCL2Template string
|
||||
|
||||
func TestAccInitAndBuildBasicAmazonAmiDatasource(t *testing.T) {
|
||||
plugin := addrs.Plugin{
|
||||
Hostname: "github.com",
|
||||
Namespace: "hashicorp",
|
||||
Type: "amazon",
|
||||
}
|
||||
testCase := &acctest.PluginTestCase{
|
||||
Name: "amazon-ami_basic_datasource_test",
|
||||
Setup: func() error {
|
||||
return cleanupPluginInstallation(plugin)
|
||||
},
|
||||
Teardown: func() error {
|
||||
helper := amazonacc.AWSHelper{
|
||||
Region: "us-west-2",
|
||||
AMIName: "packer-amazon-ami-test",
|
||||
}
|
||||
return helper.CleanUpAmi()
|
||||
},
|
||||
Template: basicAmazonAmiDatasourceHCL2Template,
|
||||
Type: "amazon-ami",
|
||||
Init: true,
|
||||
CheckInit: func(initCommand *exec.Cmd, logfile string) error {
|
||||
if initCommand.ProcessState != nil {
|
||||
if initCommand.ProcessState.ExitCode() != 0 {
|
||||
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
|
||||
}
|
||||
}
|
||||
logs, err := os.Open(logfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable find %s", logfile)
|
||||
}
|
||||
defer logs.Close()
|
||||
|
||||
logsBytes, err := ioutil.ReadAll(logs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read %s", logfile)
|
||||
}
|
||||
initOutput := string(logsBytes)
|
||||
return checkPluginInstallation(initOutput, plugin)
|
||||
},
|
||||
Check: func(buildCommand *exec.Cmd, logfile string) error {
|
||||
if buildCommand.ProcessState != nil {
|
||||
if buildCommand.ProcessState.ExitCode() != 0 {
|
||||
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
acctest.TestPlugin(t, testCase)
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
// plugin_acc_test.go should contain acceptance tests for features related to
|
||||
// installing, discovering and running plugins.
|
||||
package plugin
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
amazonacc "github.com/hashicorp/packer-plugin-amazon/builder/ebs/acceptance"
|
||||
"github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
"github.com/hashicorp/packer-plugin-sdk/acctest/testutils"
|
||||
"github.com/hashicorp/packer/hcl2template/addrs"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
//go:embed test-fixtures/basic-amazon-ebs.pkr.hcl
|
||||
var basicAmazonEbsHCL2Template string
|
||||
|
||||
func TestAccInitAndBuildBasicAmazonEbs(t *testing.T) {
|
||||
plugin := addrs.Plugin{
|
||||
Hostname: "github.com",
|
||||
Namespace: "hashicorp",
|
||||
Type: "amazon",
|
||||
}
|
||||
testCase := &acctest.PluginTestCase{
|
||||
Name: "amazon-ebs_basic_plugin_init_and_build_test",
|
||||
Setup: func() error {
|
||||
return cleanupPluginInstallation(plugin)
|
||||
},
|
||||
Teardown: func() error {
|
||||
helper := amazonacc.AWSHelper{
|
||||
Region: "us-east-1",
|
||||
AMIName: "packer-plugin-amazon-ebs-test",
|
||||
}
|
||||
return helper.CleanUpAmi()
|
||||
},
|
||||
Template: basicAmazonEbsHCL2Template,
|
||||
Type: "amazon-ebs",
|
||||
Init: true,
|
||||
CheckInit: func(initCommand *exec.Cmd, logfile string) error {
|
||||
if initCommand.ProcessState != nil {
|
||||
if initCommand.ProcessState.ExitCode() != 0 {
|
||||
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
|
||||
}
|
||||
}
|
||||
logs, err := os.Open(logfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable find %s", logfile)
|
||||
}
|
||||
defer logs.Close()
|
||||
|
||||
logsBytes, err := ioutil.ReadAll(logs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read %s", logfile)
|
||||
}
|
||||
initOutput := string(logsBytes)
|
||||
return checkPluginInstallation(initOutput, plugin)
|
||||
},
|
||||
Check: func(buildCommand *exec.Cmd, logfile string) error {
|
||||
if buildCommand.ProcessState != nil {
|
||||
if buildCommand.ProcessState.ExitCode() != 0 {
|
||||
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
acctest.TestPlugin(t, testCase)
|
||||
}
|
||||
|
||||
func cleanupPluginInstallation(plugin addrs.Plugin) error {
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pluginPath := filepath.Join(home,
|
||||
".packer.d",
|
||||
"plugins",
|
||||
plugin.Hostname,
|
||||
plugin.Namespace,
|
||||
plugin.Type)
|
||||
testutils.CleanupFiles(pluginPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPluginInstallation(initOutput string, plugin addrs.Plugin) error {
|
||||
expectedInitLog := "Installed plugin " + plugin.String()
|
||||
if matched, _ := regexp.MatchString(expectedInitLog+".*", initOutput); !matched {
|
||||
return fmt.Errorf("logs doesn't contain expected foo value %q", initOutput)
|
||||
}
|
||||
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pluginPath := filepath.Join(home,
|
||||
".packer.d",
|
||||
"plugins",
|
||||
plugin.Hostname,
|
||||
plugin.Namespace,
|
||||
plugin.Type)
|
||||
if !testutils.FileExists(pluginPath) {
|
||||
return fmt.Errorf("%s plugin installation not found", plugin.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
packer {
|
||||
required_plugins {
|
||||
amazon = {
|
||||
version = ">= 0.0.1"
|
||||
source = "github.com/hashicorp/amazon"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "amazon-ami" "test" {
|
||||
filters = {
|
||||
name = "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*"
|
||||
root-device-type = "ebs"
|
||||
virtualization-type = "hvm"
|
||||
}
|
||||
most_recent = true
|
||||
owners = ["099720109477"]
|
||||
}
|
||||
|
||||
source "amazon-ebs" "basic-example" {
|
||||
region = "us-west-2"
|
||||
source_ami = data.amazon-ami.test.id
|
||||
ami_name = "packer-amazon-ami-test"
|
||||
communicator = "ssh"
|
||||
instance_type = "t2.micro"
|
||||
ssh_username = "ubuntu"
|
||||
}
|
||||
|
||||
build {
|
||||
sources = [
|
||||
"source.amazon-ebs.basic-example"
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
packer {
|
||||
required_plugins {
|
||||
amazon = {
|
||||
version = ">= 0.0.1"
|
||||
source = "github.com/hashicorp/amazon"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
source "amazon-ebs" "basic-test" {
|
||||
region = "us-east-1"
|
||||
instance_type = "m3.medium"
|
||||
source_ami = "ami-76b2a71e"
|
||||
ssh_username = "ubuntu"
|
||||
ami_name = "packer-plugin-amazon-ebs-test"
|
||||
}
|
||||
|
||||
build {
|
||||
sources = ["source.amazon-ebs.basic-test"]
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package acctest
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
testTesting = true
|
||||
|
||||
if err := os.Setenv(TestEnvVar, "1"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTest_noEnv(t *testing.T) {
|
||||
// Unset the variable
|
||||
if err := os.Setenv(TestEnvVar, ""); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Setenv(TestEnvVar, "1")
|
||||
|
||||
mt := new(mockT)
|
||||
Test(mt, TestCase{})
|
||||
|
||||
if !mt.SkipCalled {
|
||||
t.Fatal("skip not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTest_preCheck(t *testing.T) {
|
||||
called := false
|
||||
|
||||
mt := new(mockT)
|
||||
Test(mt, TestCase{
|
||||
PreCheck: func() { called = true },
|
||||
})
|
||||
|
||||
if !called {
|
||||
t.Fatal("precheck should be called")
|
||||
}
|
||||
}
|
||||
|
||||
// mockT implements TestT for testing
|
||||
type mockT struct {
|
||||
ErrorCalled bool
|
||||
ErrorArgs []interface{}
|
||||
FatalCalled bool
|
||||
FatalArgs []interface{}
|
||||
SkipCalled bool
|
||||
SkipArgs []interface{}
|
||||
|
||||
f bool
|
||||
}
|
||||
|
||||
func (t *mockT) Error(args ...interface{}) {
|
||||
t.ErrorCalled = true
|
||||
t.ErrorArgs = args
|
||||
t.f = true
|
||||
}
|
||||
|
||||
func (t *mockT) Fatal(args ...interface{}) {
|
||||
t.FatalCalled = true
|
||||
t.FatalArgs = args
|
||||
t.f = true
|
||||
}
|
||||
|
||||
func (t *mockT) Skip(args ...interface{}) {
|
||||
t.SkipCalled = true
|
||||
t.SkipArgs = args
|
||||
t.f = true
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ecs
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,AlicloudDiskDevice
|
||||
//go:generate mapstructure-to-hcl2 -type Config,AlicloudDiskDevice
|
||||
|
||||
// The alicloud contains a packersdk.Builder implementation that
|
||||
// builds ecs images for alicloud.
|
||||
@@ -135,7 +135,6 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
|
||||
InstanceType: b.config.InstanceType,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
RamRoleName: b.config.RamRoleName,
|
||||
RegionId: b.config.AlicloudRegion,
|
||||
InternetChargeType: b.config.InternetChargeType,
|
||||
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,AlicloudDiskDevice"; DO NOT EDIT.
|
||||
|
||||
package ecs
|
||||
|
||||
@@ -88,7 +88,6 @@ type FlatConfig struct {
|
||||
AlicloudSourceImage *string `mapstructure:"source_image" required:"true" cty:"source_image" hcl:"source_image"`
|
||||
ForceStopInstance *bool `mapstructure:"force_stop_instance" required:"false" cty:"force_stop_instance" hcl:"force_stop_instance"`
|
||||
DisableStopInstance *bool `mapstructure:"disable_stop_instance" required:"false" cty:"disable_stop_instance" hcl:"disable_stop_instance"`
|
||||
RamRoleName *string `mapstructure:"ram_role_name" required:"false" cty:"ram_role_name" hcl:"ram_role_name"`
|
||||
SecurityGroupId *string `mapstructure:"security_group_id" required:"false" cty:"security_group_id" hcl:"security_group_id"`
|
||||
SecurityGroupName *string `mapstructure:"security_group_name" required:"false" cty:"security_group_name" hcl:"security_group_name"`
|
||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
||||
@@ -206,7 +205,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"source_image": &hcldec.AttrSpec{Name: "source_image", Type: cty.String, Required: false},
|
||||
"force_stop_instance": &hcldec.AttrSpec{Name: "force_stop_instance", Type: cty.Bool, Required: false},
|
||||
"disable_stop_instance": &hcldec.AttrSpec{Name: "disable_stop_instance", Type: cty.Bool, Required: false},
|
||||
"ram_role_name": &hcldec.AttrSpec{Name: "ram_role_name", Type: cty.String, Required: false},
|
||||
"security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false},
|
||||
"security_group_name": &hcldec.AttrSpec{Name: "security_group_name", Type: cty.String, Required: false},
|
||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
const defaultTestRegion = "cn-beijing"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ecs
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ecs
|
||||
|
||||
@@ -47,8 +47,6 @@ type RunConfig struct {
|
||||
// E.g., Sysprep a windows which may shutdown the instance within its command.
|
||||
// The default value is false.
|
||||
DisableStopInstance bool `mapstructure:"disable_stop_instance" required:"false"`
|
||||
// Ram Role to apply when launching the instance.
|
||||
RamRoleName string `mapstructure:"ram_role_name" required:"false"`
|
||||
// ID of the security group to which a newly
|
||||
// created instance belongs. Mutual access is allowed between instances in one
|
||||
// security group. If not specified, the newly created instance will be added
|
||||
|
||||
@@ -23,7 +23,6 @@ type stepCreateAlicloudInstance struct {
|
||||
UserData string
|
||||
UserDataFile string
|
||||
instanceId string
|
||||
RamRoleName string
|
||||
RegionId string
|
||||
InternetChargeType string
|
||||
InternetMaxBandwidthOut int
|
||||
@@ -116,7 +115,6 @@ func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep.
|
||||
request.RegionId = s.RegionId
|
||||
request.InstanceType = s.InstanceType
|
||||
request.InstanceName = s.InstanceName
|
||||
request.RamRoleName = s.RamRoleName
|
||||
request.ZoneId = s.ZoneId
|
||||
|
||||
sourceImage := state.Get("source_image").(*ecs.Image)
|
||||
|
||||
@@ -233,9 +233,7 @@ func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storag
|
||||
azureClient.GalleryImageVersionsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.GalleryImageVersionsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.GalleryImageVersionsClient.UserAgent)
|
||||
azureClient.GalleryImageVersionsClient.Client.PollingDuration = sharedGalleryTimeout
|
||||
if sigSubscriptionID != "" {
|
||||
azureClient.GalleryImageVersionsClient.SubscriptionID = sigSubscriptionID
|
||||
}
|
||||
azureClient.GalleryImageVersionsClient.SubscriptionID = sigSubscriptionID
|
||||
|
||||
azureClient.GalleryImagesClient = newCompute.NewGalleryImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.GalleryImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
@@ -243,9 +241,7 @@ func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storag
|
||||
azureClient.GalleryImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.GalleryImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.GalleryImagesClient.UserAgent)
|
||||
azureClient.GalleryImagesClient.Client.PollingDuration = pollingDuration
|
||||
if sigSubscriptionID != "" {
|
||||
azureClient.GalleryImagesClient.SubscriptionID = sigSubscriptionID
|
||||
}
|
||||
azureClient.GalleryImagesClient.SubscriptionID = sigSubscriptionID
|
||||
|
||||
keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint)
|
||||
if err != nil {
|
||||
|
||||
@@ -20,18 +20,12 @@ package arm
|
||||
// go test -v -timeout 90m -run TestBuilderAcc_.*
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
)
|
||||
|
||||
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
||||
@@ -102,15 +96,10 @@ func TestBuilderAcc_ManagedDisk_Linux_AzureCLI(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
var b Builder
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAuthPreCheck(t) },
|
||||
Builder: &b,
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
Template: testBuilderAccManagedDiskLinuxAzureCLI,
|
||||
Check: func([]packersdk.Artifact) error {
|
||||
checkTemporaryGroupDeleted(t, &b)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -123,108 +112,15 @@ func TestBuilderAcc_Blob_Windows(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuilderAcc_Blob_Linux(t *testing.T) {
|
||||
var b Builder
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAuthPreCheck(t) },
|
||||
Builder: &b,
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Builder: &Builder{},
|
||||
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": {
|
||||
@@ -259,7 +155,6 @@ const testBuilderAccManagedDiskWindows = `
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
|
||||
{
|
||||
"variables": {
|
||||
@@ -392,7 +287,6 @@ const testBuilderAccManagedDiskLinux = `
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskLinuxDeviceLogin = `
|
||||
{
|
||||
"variables": {
|
||||
@@ -485,7 +379,6 @@ const testBuilderAccBlobLinux = `
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
const testBuilderAccManagedDiskLinuxAzureCLI = `
|
||||
{
|
||||
"builders": [{
|
||||
@@ -495,8 +388,7 @@ 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",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation
|
||||
|
||||
package arm
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation"; DO NOT EDIT.
|
||||
|
||||
package arm
|
||||
|
||||
@@ -23,7 +23,6 @@ type FlatConfig struct {
|
||||
ClientID *string `mapstructure:"client_id" cty:"client_id" hcl:"client_id"`
|
||||
ClientSecret *string `mapstructure:"client_secret" cty:"client_secret" hcl:"client_secret"`
|
||||
ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path" hcl:"client_cert_path"`
|
||||
ClientCertExpireTimeout *string `mapstructure:"client_cert_token_timeout" required:"false" cty:"client_cert_token_timeout" hcl:"client_cert_token_timeout"`
|
||||
ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt" hcl:"client_jwt"`
|
||||
ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"`
|
||||
TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"`
|
||||
@@ -152,7 +151,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false},
|
||||
"client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false},
|
||||
"client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false},
|
||||
"client_cert_token_timeout": &hcldec.AttrSpec{Name: "client_cert_token_timeout", Type: cty.String, Required: false},
|
||||
"client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false},
|
||||
"object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false},
|
||||
"tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false},
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
@@ -16,17 +15,16 @@ 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
|
||||
deleteDeployment func(ctx context.Context, state multistep.StateBag) 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
|
||||
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 {
|
||||
@@ -43,7 +41,6 @@ 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
|
||||
}
|
||||
|
||||
@@ -61,45 +58,32 @@ func (s *StepDeployTemplate) Run(ctx context.Context, state multistep.StateBag)
|
||||
|
||||
func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
|
||||
defer func() {
|
||||
err := s.deleteDeployment(context.Background(), state)
|
||||
err := s.deleteTemplate(context.Background(), state)
|
||||
if err != nil {
|
||||
s.say(err.Error())
|
||||
s.say(s.client.LastError.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("\nDeleting individual resources ...")
|
||||
ui.Say("\nThe resource group was not created by Packer, deleting individual resources ...")
|
||||
|
||||
deploymentName := s.name
|
||||
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
|
||||
// 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)
|
||||
err := s.deleteDeploymentResources(context.TODO(), deploymentName, resourceGroupName)
|
||||
if err != nil {
|
||||
s.reportIfError(err, resourceGroupName)
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
NewStepDeleteAdditionalDisks(s.client, ui).Run(context.TODO(), state)
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupName string, deploymentName string) error {
|
||||
@@ -122,7 +106,7 @@ func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupNa
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StepDeployTemplate) deleteDeploymentObject(ctx context.Context, state multistep.StateBag) error {
|
||||
func (s *StepDeployTemplate) deleteTemplate(ctx context.Context, state multistep.StateBag) error {
|
||||
deploymentName := s.name
|
||||
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
@@ -238,8 +222,6 @@ 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
|
||||
@@ -251,47 +233,30 @@ func (s *StepDeployTemplate) deleteDeploymentResources(ctx context.Context, depl
|
||||
resourceName := *deploymentOperation.Properties.TargetResource.ResourceName
|
||||
resourceType := *deploymentOperation.Properties.TargetResource.ResourceType
|
||||
|
||||
s.say(fmt.Sprintf("Adding to deletion queue -> %s : '%s'", resourceType, resourceName))
|
||||
resources[resourceType] = resourceName
|
||||
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)
|
||||
}
|
||||
|
||||
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,7 +6,6 @@ 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"
|
||||
)
|
||||
|
||||
@@ -109,97 +108,11 @@ 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,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
// Package chroot is able to create an Azure managed image without requiring the
|
||||
// launch of a new virtual machine for every build. It does this by attaching and
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package chroot
|
||||
|
||||
@@ -22,7 +22,6 @@ type FlatConfig struct {
|
||||
ClientID *string `mapstructure:"client_id" cty:"client_id" hcl:"client_id"`
|
||||
ClientSecret *string `mapstructure:"client_secret" cty:"client_secret" hcl:"client_secret"`
|
||||
ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path" hcl:"client_cert_path"`
|
||||
ClientCertExpireTimeout *string `mapstructure:"client_cert_token_timeout" required:"false" cty:"client_cert_token_timeout" hcl:"client_cert_token_timeout"`
|
||||
ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt" hcl:"client_jwt"`
|
||||
ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"`
|
||||
TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"`
|
||||
@@ -77,7 +76,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false},
|
||||
"client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false},
|
||||
"client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false},
|
||||
"client_cert_token_timeout": &hcldec.AttrSpec{Name: "client_cert_token_timeout", Type: cty.String, Required: false},
|
||||
"client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false},
|
||||
"object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false},
|
||||
"tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type SharedImageGalleryDestination,TargetRegion
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type SharedImageGalleryDestination,TargetRegion
|
||||
|
||||
package chroot
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type SharedImageGalleryDestination,TargetRegion"; DO NOT EDIT.
|
||||
|
||||
package chroot
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate struct-markdown
|
||||
|
||||
package client
|
||||
|
||||
@@ -39,8 +39,6 @@ type Config struct {
|
||||
// The path to a pem-encoded certificate that will be used to authenticate
|
||||
// as the specified AAD SP.
|
||||
ClientCertPath string `mapstructure:"client_cert_path"`
|
||||
// The timeout for the JWT Token when using a [client certificate](#client_cert_path). Defaults to 1 hour.
|
||||
ClientCertExpireTimeout time.Duration `mapstructure:"client_cert_token_timeout" required:"false"`
|
||||
// A JWT bearer token for client auth (RFC 7523, Sec. 2.2) that will be used
|
||||
// to authenticate the AAD SP. Provides more control over token the expiration
|
||||
// when using certificate authentication than when using `client_cert_path`.
|
||||
@@ -165,9 +163,6 @@ 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 != 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
|
||||
}
|
||||
|
||||
@@ -264,7 +259,7 @@ func (c Config) GetServicePrincipalToken(
|
||||
auth = NewSecretOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientSecret, c.TenantID)
|
||||
case authTypeClientCert:
|
||||
say("Getting tokens using client certificate")
|
||||
auth, err = NewCertOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientCertPath, c.TenantID, c.ClientCertExpireTimeout)
|
||||
auth, err = NewCertOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientCertPath, c.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -341,10 +336,6 @@ func (c *Config) FillParameters() error {
|
||||
c.TenantID = tenantID
|
||||
}
|
||||
|
||||
if c.ClientCertExpireTimeout == 0 {
|
||||
c.ClientCertExpireTimeout = time.Hour
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -95,16 +95,6 @@ func Test_ClientConfig_RequiredParametersSet(t *testing.T) {
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "client_cert_token_timeout should be 5 minutes or more",
|
||||
config: Config{
|
||||
SubscriptionID: "ok",
|
||||
ClientID: "ok",
|
||||
ClientCertPath: "/dev/null",
|
||||
ClientCertExpireTimeout: 1 * time.Minute,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "too many client_* values",
|
||||
config: Config{
|
||||
|
||||
@@ -18,14 +18,14 @@ import (
|
||||
"github.com/hashicorp/packer/builder/azure/pkcs12"
|
||||
)
|
||||
|
||||
func NewCertOAuthTokenProvider(env azure.Environment, clientID, clientCertPath, tenantID string, certExpireTimeout time.Duration) (oAuthTokenProvider, error) {
|
||||
func NewCertOAuthTokenProvider(env azure.Environment, clientID, clientCertPath, tenantID string) (oAuthTokenProvider, error) {
|
||||
cert, key, err := readCert(clientCertPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading certificate: %v", err)
|
||||
}
|
||||
|
||||
audience := fmt.Sprintf("%s%s/oauth2/token", env.ActiveDirectoryEndpoint, tenantID)
|
||||
jwt, err := makeJWT(clientID, audience, cert, key, certExpireTimeout, true)
|
||||
jwt, err := makeJWT(clientID, audience, cert, key, time.Hour, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error generating JWT: %v", err)
|
||||
}
|
||||
|
||||
@@ -181,6 +181,7 @@ func NewAzureClient(subscriptionID, resourceGroupName string,
|
||||
azureClient.GalleryImageVersionsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.GalleryImageVersionsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.GalleryImageVersionsClient.UserAgent)
|
||||
azureClient.GalleryImageVersionsClient.Client.PollingDuration = SharedGalleryTimeout
|
||||
azureClient.GalleryImageVersionsClient.Client.PollingDuration = PollingDuration
|
||||
|
||||
azureClient.GalleryImagesClient = newCompute.NewGalleryImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.GalleryImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
|
||||
@@ -28,7 +28,7 @@ package dtl
|
||||
import (
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
)
|
||||
|
||||
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,DtlArtifact,ArtifactParameter
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,DtlArtifact,ArtifactParameter
|
||||
|
||||
package dtl
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,DtlArtifact,ArtifactParameter"; DO NOT EDIT.
|
||||
|
||||
package dtl
|
||||
|
||||
@@ -49,7 +49,6 @@ type FlatConfig struct {
|
||||
ClientID *string `mapstructure:"client_id" cty:"client_id" hcl:"client_id"`
|
||||
ClientSecret *string `mapstructure:"client_secret" cty:"client_secret" hcl:"client_secret"`
|
||||
ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path" hcl:"client_cert_path"`
|
||||
ClientCertExpireTimeout *string `mapstructure:"client_cert_token_timeout" required:"false" cty:"client_cert_token_timeout" hcl:"client_cert_token_timeout"`
|
||||
ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt" hcl:"client_jwt"`
|
||||
ObjectID *string `mapstructure:"object_id" cty:"object_id" hcl:"object_id"`
|
||||
TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id" hcl:"tenant_id"`
|
||||
@@ -164,7 +163,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false},
|
||||
"client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false},
|
||||
"client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false},
|
||||
"client_cert_token_timeout": &hcldec.AttrSpec{Name: "client_cert_token_timeout", Type: cty.String, Required: false},
|
||||
"client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false},
|
||||
"object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false},
|
||||
"tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false},
|
||||
|
||||
@@ -60,7 +60,12 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
|
||||
// Build the steps.
|
||||
steps := []multistep.Step{
|
||||
&stepPrepareConfig{},
|
||||
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
|
||||
&commonsteps.StepHTTPServer{
|
||||
HTTPDir: b.config.HTTPDir,
|
||||
HTTPPortMin: b.config.HTTPPortMin,
|
||||
HTTPPortMax: b.config.HTTPPortMax,
|
||||
HTTPAddress: b.config.HTTPAddress,
|
||||
},
|
||||
&stepKeypair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.Comm,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package cloudstack
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package cloudstack
|
||||
|
||||
@@ -19,7 +19,6 @@ 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,7 +135,6 @@ 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},
|
||||
|
||||
@@ -80,17 +80,10 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&communicator.StepSSHKeyGen{
|
||||
CommConf: &b.config.Comm,
|
||||
SSHTemporaryKeyPair: b.config.Comm.SSH.SSHTemporaryKeyPair,
|
||||
&stepCreateSSHKey{
|
||||
Debug: b.config.PackerDebug,
|
||||
DebugKeyPath: fmt.Sprintf("do_%s.pem", b.config.PackerBuildName),
|
||||
},
|
||||
multistep.If(b.config.PackerDebug && b.config.Comm.SSHPrivateKeyFile == "",
|
||||
&communicator.StepDumpSSHKey{
|
||||
Path: fmt.Sprintf("do_%s.pem", b.config.PackerBuildName),
|
||||
SSH: &b.config.Comm.SSH,
|
||||
},
|
||||
),
|
||||
&stepCreateSSHKey{},
|
||||
new(stepCreateDroplet),
|
||||
new(stepDropletInfo),
|
||||
&communicator.StepConnect{
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package digitalocean
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package digitalocean
|
||||
|
||||
|
||||
@@ -2,16 +2,26 @@ package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type stepCreateSSHKey struct {
|
||||
Debug bool
|
||||
DebugKeyPath string
|
||||
|
||||
keyId int
|
||||
}
|
||||
|
||||
@@ -20,12 +30,31 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
|
||||
if c.Comm.SSHPublicKey == nil {
|
||||
ui.Say("No public SSH key found; skipping SSH public key import...")
|
||||
return multistep.ActionContinue
|
||||
ui.Say("Creating temporary ssh key for droplet...")
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("error generating RSA key: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Importing SSH public key...")
|
||||
// ASN.1 DER encoded form
|
||||
priv_der := x509.MarshalPKCS1PrivateKey(priv)
|
||||
priv_blk := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: priv_der,
|
||||
}
|
||||
|
||||
// Set the private key in the config for later
|
||||
c.Comm.SSHPrivateKey = pem.EncodeToMemory(&priv_blk)
|
||||
|
||||
// Marshal the public key into SSH compatible format
|
||||
// TODO properly handle the public key error
|
||||
pub, _ := ssh.NewPublicKey(&priv.PublicKey)
|
||||
pub_sshformat := string(ssh.MarshalAuthorizedKey(pub))
|
||||
|
||||
// The name of the public key on DO
|
||||
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||
@@ -33,7 +62,7 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
// Create the key!
|
||||
key, _, err := client.Keys.Create(context.TODO(), &godo.KeyCreateRequest{
|
||||
Name: name,
|
||||
PublicKey: string(c.Comm.SSHPublicKey),
|
||||
PublicKey: pub_sshformat,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
|
||||
@@ -50,6 +79,31 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
// Remember some state for the future
|
||||
state.Put("ssh_key_id", key.ID)
|
||||
|
||||
// If we're in debug mode, output the private key to the working directory.
|
||||
if s.Debug {
|
||||
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
||||
f, err := os.Create(s.DebugKeyPath)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Write the key out
|
||||
if _, err := f.Write(pem.EncodeToMemory(&priv_blk)); err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Chmod it so that it is SSH ready
|
||||
if runtime.GOOS != "windows" {
|
||||
if err := f.Chmod(0600); err != nil {
|
||||
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestExportArtifact_impl(t *testing.T) {
|
||||
var _ packersdk.Artifact = new(ExportArtifact)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestImportArtifact_impl(t *testing.T) {
|
||||
var _ packersdk.Artifact = new(ImportArtifact)
|
||||
}
|
||||
|
||||
func TestImportArtifactBuilderId(t *testing.T) {
|
||||
a := &ImportArtifact{BuilderIdValue: "foo"}
|
||||
if a.BuilderId() != "foo" {
|
||||
t.Fatalf("bad: %#v", a.BuilderId())
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportArtifactFiles(t *testing.T) {
|
||||
a := &ImportArtifact{}
|
||||
if a.Files() != nil {
|
||||
t.Fatalf("bad: %#v", a.Files())
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportArtifactId(t *testing.T) {
|
||||
a := &ImportArtifact{IdValue: "foo"}
|
||||
if a.Id() != "foo" {
|
||||
t.Fatalf("bad: %#v", a.Id())
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportArtifactDestroy(t *testing.T) {
|
||||
d := new(MockDriver)
|
||||
a := &ImportArtifact{
|
||||
Driver: d,
|
||||
IdValue: "foo",
|
||||
}
|
||||
|
||||
// No error
|
||||
if err := a.Destroy(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !d.DeleteImageCalled {
|
||||
t.Fatal("delete image should be called")
|
||||
}
|
||||
if d.DeleteImageId != "foo" {
|
||||
t.Fatalf("bad: %#v", d.DeleteImageId)
|
||||
}
|
||||
|
||||
// With an error
|
||||
d.DeleteImageErr = errors.New("foo")
|
||||
if err := a.Destroy(); err != d.DeleteImageErr {
|
||||
t.Fatalf("err: %#v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestBuilder_implBuilder(t *testing.T) {
|
||||
var _ packersdk.Builder = new(Builder)
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
// RenderConfig helps create dynamic packer template configs for parsing by
|
||||
// builderT without having to write the config to a file.
|
||||
func RenderConfig(builderConfig map[string]interface{}, provisionerConfig []map[string]interface{}) string {
|
||||
// set up basic build template
|
||||
t := map[string][]map[string]interface{}{
|
||||
"builders": {
|
||||
// Setup basic docker config
|
||||
map[string]interface{}{
|
||||
"type": "test",
|
||||
"image": "ubuntu",
|
||||
"discard": true,
|
||||
},
|
||||
},
|
||||
"provisioners": []map[string]interface{}{},
|
||||
}
|
||||
// apply special builder overrides
|
||||
for k, v := range builderConfig {
|
||||
t["builders"][0][k] = v
|
||||
}
|
||||
// Apply special provisioner overrides
|
||||
t["provisioners"] = append(t["provisioners"], provisionerConfig...)
|
||||
|
||||
j, _ := json.Marshal(t)
|
||||
return string(j)
|
||||
}
|
||||
|
||||
// TestUploadDownload verifies that basic upload / download functionality works
|
||||
func TestUploadDownload(t *testing.T) {
|
||||
if os.Getenv("PACKER_ACC") == "" {
|
||||
t.Skip("This test is only run with PACKER_ACC=1")
|
||||
}
|
||||
|
||||
dockerBuilderExtraConfig := map[string]interface{}{
|
||||
"run_command": []string{"-d", "-i", "-t", "{{.Image}}", "/bin/sh"},
|
||||
}
|
||||
|
||||
dockerProvisionerConfig := []map[string]interface{}{
|
||||
{
|
||||
"type": "file",
|
||||
"source": "test-fixtures/onecakes/strawberry",
|
||||
"destination": "/strawberry-cake",
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "/strawberry-cake",
|
||||
"destination": "my-strawberry-cake",
|
||||
"direction": "download",
|
||||
},
|
||||
}
|
||||
|
||||
configString := RenderConfig(dockerBuilderExtraConfig, dockerProvisionerConfig)
|
||||
|
||||
// this should be a precheck
|
||||
cmd := exec.Command("docker", "-v")
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
t.Error("docker command not found; please make sure docker is installed")
|
||||
}
|
||||
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
Builder: &Builder{},
|
||||
Template: configString,
|
||||
Check: func(a []packersdk.Artifact) error {
|
||||
// Verify that the thing we downloaded is the same thing we sent up.
|
||||
// Complain loudly if it isn't.
|
||||
inputFile, err := ioutil.ReadFile("test-fixtures/onecakes/strawberry")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read input file: %s", err)
|
||||
}
|
||||
outputFile, err := ioutil.ReadFile("my-strawberry-cake")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read output file: %s", err)
|
||||
}
|
||||
if sha256.Sum256(inputFile) != sha256.Sum256(outputFile) {
|
||||
return fmt.Errorf("Input and output files do not match\n"+
|
||||
"Input:\n%s\nOutput:\n%s\n", inputFile, outputFile)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Teardown: func() error {
|
||||
// Cleanup. Honestly I don't know why you would want to get rid
|
||||
// of my strawberry cake. It's so tasty! Do you not like cake? Are you a
|
||||
// cake-hater? Or are you keeping all the cake all for yourself? So selfish!
|
||||
os.Remove("my-strawberry-cake")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// TestLargeDownload verifies that files are the appropriate size after being
|
||||
// downloaded. This is to identify and fix the race condition in #2793. You may
|
||||
// need to use github.com/cbednarski/rerun to verify since this problem occurs
|
||||
// only intermittently.
|
||||
func TestLargeDownload(t *testing.T) {
|
||||
if os.Getenv("PACKER_ACC") == "" {
|
||||
t.Skip("This test is only run with PACKER_ACC=1")
|
||||
}
|
||||
|
||||
dockerProvisionerConfig := []map[string]interface{}{
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": []string{
|
||||
"dd if=/dev/urandom of=/tmp/cupcake bs=1M count=2",
|
||||
"dd if=/dev/urandom of=/tmp/bigcake bs=1M count=100",
|
||||
"sync",
|
||||
"md5sum /tmp/cupcake /tmp/bigcake",
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "/tmp/cupcake",
|
||||
"destination": "cupcake",
|
||||
"direction": "download",
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "/tmp/bigcake",
|
||||
"destination": "bigcake",
|
||||
"direction": "download",
|
||||
},
|
||||
}
|
||||
|
||||
configString := RenderConfig(map[string]interface{}{}, dockerProvisionerConfig)
|
||||
|
||||
// this should be a precheck
|
||||
cmd := exec.Command("docker", "-v")
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
t.Error("docker command not found; please make sure docker is installed")
|
||||
}
|
||||
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
Builder: &Builder{},
|
||||
Template: configString,
|
||||
Check: func(a []packersdk.Artifact) error {
|
||||
// Verify that the things we downloaded are the right size. Complain loudly
|
||||
// if they are not.
|
||||
//
|
||||
// cupcake should be 2097152 bytes
|
||||
// bigcake should be 104857600 bytes
|
||||
cupcake, err := os.Stat("cupcake")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to stat cupcake file: %s", err)
|
||||
}
|
||||
cupcakeExpected := int64(2097152)
|
||||
if cupcake.Size() != cupcakeExpected {
|
||||
t.Errorf("Expected cupcake to be %d bytes; found %d", cupcakeExpected, cupcake.Size())
|
||||
}
|
||||
|
||||
bigcake, err := os.Stat("bigcake")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to stat bigcake file: %s", err)
|
||||
}
|
||||
bigcakeExpected := int64(104857600)
|
||||
if bigcake.Size() != bigcakeExpected {
|
||||
t.Errorf("Expected bigcake to be %d bytes; found %d", bigcakeExpected, bigcake.Size())
|
||||
}
|
||||
|
||||
// TODO if we can, calculate a sha inside the container and compare to the
|
||||
// one we get after we pull it down. We will probably have to parse the log
|
||||
// or ui output to do this because we use /dev/urandom to create the file.
|
||||
|
||||
// if sha256.Sum256(inputFile) != sha256.Sum256(outputFile) {
|
||||
// t.Fatalf("Input and output files do not match\n"+
|
||||
// "Input:\n%s\nOutput:\n%s\n", inputFile, outputFile)
|
||||
// }
|
||||
return nil
|
||||
},
|
||||
Teardown: func() error {
|
||||
os.Remove("cupcake")
|
||||
os.Remove("bigcake")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// TestFixUploadOwner verifies that owner of uploaded files is the user the container is running as.
|
||||
func TestFixUploadOwner(t *testing.T) {
|
||||
if os.Getenv("PACKER_ACC") == "" {
|
||||
t.Skip("This test is only run with PACKER_ACC=1")
|
||||
}
|
||||
|
||||
cmd := exec.Command("docker", "-v")
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
t.Error("docker command not found; please make sure docker is installed")
|
||||
}
|
||||
|
||||
dockerBuilderExtraConfig := map[string]interface{}{
|
||||
"run_command": []string{"-d", "-i", "-t", "-u", "42", "{{.Image}}", "/bin/sh"},
|
||||
}
|
||||
|
||||
testFixUploadOwnerProvisionersTemplate := []map[string]interface{}{
|
||||
{
|
||||
"type": "file",
|
||||
"source": "test-fixtures/onecakes/strawberry",
|
||||
"destination": "/tmp/strawberry-cake",
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "test-fixtures/manycakes",
|
||||
"destination": "/tmp/",
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": "touch /tmp/testUploadOwner",
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": []string{
|
||||
"[ $(stat -c %u /tmp/strawberry-cake) -eq 42 ] || (echo 'Invalid owner of /tmp/strawberry-cake' && exit 1)",
|
||||
"[ $(stat -c %u /tmp/testUploadOwner) -eq 42 ] || (echo 'Invalid owner of /tmp/testUploadOwner' && exit 1)",
|
||||
"find /tmp/manycakes | xargs -n1 -IFILE /bin/sh -c '[ $(stat -c %u FILE) -eq 42 ] || (echo \"Invalid owner of FILE\" && exit 1)'",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
configString := RenderConfig(dockerBuilderExtraConfig, testFixUploadOwnerProvisionersTemplate)
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
Builder: &Builder{},
|
||||
Template: configString,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
var (
|
||||
errArtifactNotUsed = fmt.Errorf("No instructions given for handling the artifact; expected commit, discard, or export_path")
|
||||
errArtifactUseConflict = fmt.Errorf("Cannot specify more than one of commit, discard, and export_path")
|
||||
errExportPathNotFile = fmt.Errorf("export_path must be a file, not a directory")
|
||||
errImageNotSpecified = fmt.Errorf("Image must be specified")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
// Set the author (e-mail) of a commit.
|
||||
Author string `mapstructure:"author"`
|
||||
// Dockerfile instructions to add to the commit. Example of instructions
|
||||
// are CMD, ENTRYPOINT, ENV, and EXPOSE. Example: [ "USER ubuntu", "WORKDIR
|
||||
// /app", "EXPOSE 8080" ]
|
||||
Changes []string `mapstructure:"changes"`
|
||||
// If true, the container will be committed to an image rather than exported.
|
||||
Commit bool `mapstructure:"commit" required:"true"`
|
||||
|
||||
// The directory inside container to mount temp directory from host server
|
||||
// for work [file provisioner](/docs/provisioners/file). This defaults
|
||||
// to c:/packer-files on windows and /packer-files on other systems.
|
||||
ContainerDir string `mapstructure:"container_dir" required:"false"`
|
||||
// An array of devices which will be accessible in container when it's run
|
||||
// without `--privileged` flag.
|
||||
Device []string `mapstructure:"device" required:"false"`
|
||||
// Throw away the container when the build is complete. This is useful for
|
||||
// the [artifice
|
||||
// post-processor](/docs/post-processors/artifice).
|
||||
Discard bool `mapstructure:"discard" required:"true"`
|
||||
// An array of additional [Linux
|
||||
// capabilities](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)
|
||||
// to grant to the container.
|
||||
CapAdd []string `mapstructure:"cap_add" required:"false"`
|
||||
// An array of [Linux
|
||||
// capabilities](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)
|
||||
// to drop from the container.
|
||||
CapDrop []string `mapstructure:"cap_drop" required:"false"`
|
||||
// Username (UID) to run remote commands with. You can also set the group
|
||||
// name/ID if you want: (UID or UID:GID). You may need this if you get
|
||||
// permission errors trying to run the shell or other provisioners.
|
||||
ExecUser string `mapstructure:"exec_user" required:"false"`
|
||||
// The path where the final container will be exported as a tar file.
|
||||
ExportPath string `mapstructure:"export_path" required:"true"`
|
||||
// The base image for the Docker container that will be started. This image
|
||||
// will be pulled from the Docker registry if it doesn't already exist.
|
||||
Image string `mapstructure:"image" required:"true"`
|
||||
// Set a message for the commit.
|
||||
Message string `mapstructure:"message" required:"true"`
|
||||
// If true, run the docker container with the `--privileged` flag. This
|
||||
// defaults to false if not set.
|
||||
Privileged bool `mapstructure:"privileged" required:"false"`
|
||||
Pty bool
|
||||
// If true, the configured image will be pulled using `docker pull` prior
|
||||
// to use. Otherwise, it is assumed the image already exists and can be
|
||||
// used. This defaults to true if not set.
|
||||
Pull bool `mapstructure:"pull" required:"false"`
|
||||
// An array of arguments to pass to docker run in order to run the
|
||||
// container. By default this is set to `["-d", "-i", "-t",
|
||||
// "--entrypoint=/bin/sh", "--", "{{.Image}}"]` if you are using a linux
|
||||
// container, and `["-d", "-i", "-t", "--entrypoint=powershell", "--",
|
||||
// "{{.Image}}"]` if you are running a windows container. `{{.Image}}` is a
|
||||
// template variable that corresponds to the image template option. Passing
|
||||
// the entrypoint option this way will make it the default entrypoint of
|
||||
// the resulting image, so running docker run -it --rm will start the
|
||||
// docker image from the /bin/sh shell interpreter; you could run a script
|
||||
// or another shell by running docker run -it --rm -c /bin/bash. If your
|
||||
// docker image embeds a binary intended to be run often, you should
|
||||
// consider changing the default entrypoint to point to it.
|
||||
RunCommand []string `mapstructure:"run_command" required:"false"`
|
||||
// An array of additional tmpfs volumes to mount into this container.
|
||||
TmpFs []string `mapstructure:"tmpfs" required:"false"`
|
||||
// A mapping of additional volumes to mount into this container. The key of
|
||||
// the object is the host path, the value is the container path.
|
||||
Volumes map[string]string `mapstructure:"volumes" required:"false"`
|
||||
// If true, files uploaded to the container will be owned by the user the
|
||||
// container is running as. If false, the owner will depend on the version
|
||||
// of docker installed in the system. Defaults to true.
|
||||
FixUploadOwner bool `mapstructure:"fix_upload_owner" required:"false"`
|
||||
// If "true", tells Packer that you are building a Windows container
|
||||
// running on a windows host. This is necessary for building Windows
|
||||
// containers, because our normal docker bindings do not work for them.
|
||||
WindowsContainer bool `mapstructure:"windows_container" required:"false"`
|
||||
|
||||
// This is used to login to dockerhub to pull a private base container. For
|
||||
// pushing to dockerhub, see the docker post-processors
|
||||
Login bool `mapstructure:"login" required:"false"`
|
||||
// The password to use to authenticate to login.
|
||||
LoginPassword string `mapstructure:"login_password" required:"false"`
|
||||
// The server address to login to.
|
||||
LoginServer string `mapstructure:"login_server" required:"false"`
|
||||
// The username to use to authenticate to login.
|
||||
LoginUsername string `mapstructure:"login_username" required:"false"`
|
||||
// Defaults to false. If true, the builder will login in order to pull the
|
||||
// image from Amazon EC2 Container Registry (ECR). The builder only logs in
|
||||
// for the duration of the pull. If true login_server is required and
|
||||
// login, login_username, and login_password will be ignored. For more
|
||||
// information see the section on ECR.
|
||||
EcrLogin bool `mapstructure:"ecr_login" required:"false"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
|
||||
c.FixUploadOwner = true
|
||||
|
||||
var md mapstructure.Metadata
|
||||
err := config.Decode(c, &config.DecodeOpts{
|
||||
Metadata: &md,
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &c.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"run_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if len(c.RunCommand) == 0 {
|
||||
c.RunCommand = []string{"-d", "-i", "-t", "--entrypoint=/bin/sh", "--", "{{.Image}}"}
|
||||
if c.WindowsContainer {
|
||||
c.RunCommand = []string{"-d", "-i", "-t", "--entrypoint=powershell", "--", "{{.Image}}"}
|
||||
}
|
||||
}
|
||||
|
||||
// Default Pull if it wasn't set
|
||||
hasPull := false
|
||||
for _, k := range md.Keys {
|
||||
if k == "pull" {
|
||||
hasPull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasPull {
|
||||
c.Pull = true
|
||||
}
|
||||
|
||||
// Default to the normal Docker type
|
||||
if c.Comm.Type == "" {
|
||||
c.Comm.Type = "docker"
|
||||
if c.WindowsContainer {
|
||||
c.Comm.Type = "dockerWindowsContainer"
|
||||
}
|
||||
}
|
||||
|
||||
var errs *packersdk.MultiError
|
||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||
errs = packersdk.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
if c.Image == "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errImageNotSpecified)
|
||||
}
|
||||
|
||||
if (c.ExportPath != "" && c.Commit) || (c.ExportPath != "" && c.Discard) || (c.Commit && c.Discard) {
|
||||
errs = packersdk.MultiErrorAppend(errs, errArtifactUseConflict)
|
||||
}
|
||||
|
||||
if c.ExportPath == "" && !c.Commit && !c.Discard {
|
||||
errs = packersdk.MultiErrorAppend(errs, errArtifactNotUsed)
|
||||
}
|
||||
|
||||
if c.ExportPath != "" {
|
||||
if fi, err := os.Stat(c.ExportPath); err == nil && fi.IsDir() {
|
||||
errs = packersdk.MultiErrorAppend(errs, errExportPathNotFile)
|
||||
}
|
||||
}
|
||||
|
||||
if c.ContainerDir == "" {
|
||||
if c.WindowsContainer {
|
||||
c.ContainerDir = "c:/packer-files"
|
||||
} else {
|
||||
c.ContainerDir = "/packer-files"
|
||||
}
|
||||
}
|
||||
|
||||
if c.EcrLogin && c.LoginServer == "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("ECR login requires login server to be provided."))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
||||
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"`
|
||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
||||
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
||||
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
||||
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
||||
Author *string `mapstructure:"author" cty:"author" hcl:"author"`
|
||||
Changes []string `mapstructure:"changes" cty:"changes" hcl:"changes"`
|
||||
Commit *bool `mapstructure:"commit" required:"true" cty:"commit" hcl:"commit"`
|
||||
ContainerDir *string `mapstructure:"container_dir" required:"false" cty:"container_dir" hcl:"container_dir"`
|
||||
Device []string `mapstructure:"device" required:"false" cty:"device" hcl:"device"`
|
||||
Discard *bool `mapstructure:"discard" required:"true" cty:"discard" hcl:"discard"`
|
||||
CapAdd []string `mapstructure:"cap_add" required:"false" cty:"cap_add" hcl:"cap_add"`
|
||||
CapDrop []string `mapstructure:"cap_drop" required:"false" cty:"cap_drop" hcl:"cap_drop"`
|
||||
ExecUser *string `mapstructure:"exec_user" required:"false" cty:"exec_user" hcl:"exec_user"`
|
||||
ExportPath *string `mapstructure:"export_path" required:"true" cty:"export_path" hcl:"export_path"`
|
||||
Image *string `mapstructure:"image" required:"true" cty:"image" hcl:"image"`
|
||||
Message *string `mapstructure:"message" required:"true" cty:"message" hcl:"message"`
|
||||
Privileged *bool `mapstructure:"privileged" required:"false" cty:"privileged" hcl:"privileged"`
|
||||
Pty *bool `cty:"pty" hcl:"pty"`
|
||||
Pull *bool `mapstructure:"pull" required:"false" cty:"pull" hcl:"pull"`
|
||||
RunCommand []string `mapstructure:"run_command" required:"false" cty:"run_command" hcl:"run_command"`
|
||||
TmpFs []string `mapstructure:"tmpfs" required:"false" cty:"tmpfs" hcl:"tmpfs"`
|
||||
Volumes map[string]string `mapstructure:"volumes" required:"false" cty:"volumes" hcl:"volumes"`
|
||||
FixUploadOwner *bool `mapstructure:"fix_upload_owner" required:"false" cty:"fix_upload_owner" hcl:"fix_upload_owner"`
|
||||
WindowsContainer *bool `mapstructure:"windows_container" required:"false" cty:"windows_container" hcl:"windows_container"`
|
||||
Login *bool `mapstructure:"login" required:"false" cty:"login" hcl:"login"`
|
||||
LoginPassword *string `mapstructure:"login_password" required:"false" cty:"login_password" hcl:"login_password"`
|
||||
LoginServer *string `mapstructure:"login_server" required:"false" cty:"login_server" hcl:"login_server"`
|
||||
LoginUsername *string `mapstructure:"login_username" required:"false" cty:"login_username" hcl:"login_username"`
|
||||
EcrLogin *bool `mapstructure:"ecr_login" required:"false" cty:"ecr_login" hcl:"ecr_login"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||
"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},
|
||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
||||
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
||||
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
||||
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
||||
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
||||
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
||||
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
||||
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
||||
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
||||
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
||||
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
||||
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
||||
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
||||
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
||||
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
||||
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
||||
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
|
||||
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
||||
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
||||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
||||
"author": &hcldec.AttrSpec{Name: "author", Type: cty.String, Required: false},
|
||||
"changes": &hcldec.AttrSpec{Name: "changes", Type: cty.List(cty.String), Required: false},
|
||||
"commit": &hcldec.AttrSpec{Name: "commit", Type: cty.Bool, Required: false},
|
||||
"container_dir": &hcldec.AttrSpec{Name: "container_dir", Type: cty.String, Required: false},
|
||||
"device": &hcldec.AttrSpec{Name: "device", Type: cty.List(cty.String), Required: false},
|
||||
"discard": &hcldec.AttrSpec{Name: "discard", Type: cty.Bool, Required: false},
|
||||
"cap_add": &hcldec.AttrSpec{Name: "cap_add", Type: cty.List(cty.String), Required: false},
|
||||
"cap_drop": &hcldec.AttrSpec{Name: "cap_drop", Type: cty.List(cty.String), Required: false},
|
||||
"exec_user": &hcldec.AttrSpec{Name: "exec_user", Type: cty.String, Required: false},
|
||||
"export_path": &hcldec.AttrSpec{Name: "export_path", Type: cty.String, Required: false},
|
||||
"image": &hcldec.AttrSpec{Name: "image", Type: cty.String, Required: false},
|
||||
"message": &hcldec.AttrSpec{Name: "message", Type: cty.String, Required: false},
|
||||
"privileged": &hcldec.AttrSpec{Name: "privileged", Type: cty.Bool, Required: false},
|
||||
"pty": &hcldec.AttrSpec{Name: "pty", Type: cty.Bool, Required: false},
|
||||
"pull": &hcldec.AttrSpec{Name: "pull", Type: cty.Bool, Required: false},
|
||||
"run_command": &hcldec.AttrSpec{Name: "run_command", Type: cty.List(cty.String), Required: false},
|
||||
"tmpfs": &hcldec.AttrSpec{Name: "tmpfs", Type: cty.List(cty.String), Required: false},
|
||||
"volumes": &hcldec.AttrSpec{Name: "volumes", Type: cty.Map(cty.String), Required: false},
|
||||
"fix_upload_owner": &hcldec.AttrSpec{Name: "fix_upload_owner", Type: cty.Bool, Required: false},
|
||||
"windows_container": &hcldec.AttrSpec{Name: "windows_container", Type: cty.Bool, Required: false},
|
||||
"login": &hcldec.AttrSpec{Name: "login", Type: cty.Bool, Required: false},
|
||||
"login_password": &hcldec.AttrSpec{Name: "login_password", Type: cty.String, Required: false},
|
||||
"login_server": &hcldec.AttrSpec{Name: "login_server", Type: cty.String, Required: false},
|
||||
"login_username": &hcldec.AttrSpec{Name: "login_username", Type: cty.String, Required: false},
|
||||
"ecr_login": &hcldec.AttrSpec{Name: "ecr_login", Type: cty.Bool, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"export_path": "foo",
|
||||
"image": "bar",
|
||||
}
|
||||
}
|
||||
|
||||
func testConfigStruct(t *testing.T) *Config {
|
||||
var c Config
|
||||
warns, errs := c.Prepare(testConfig())
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", len(warns))
|
||||
}
|
||||
if errs != nil {
|
||||
t.Fatalf("bad: %#v", errs)
|
||||
}
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
func testConfigErr(t *testing.T, warns []string, err error) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func testConfigOk(t *testing.T, warns []string, err error) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepare_exportPath(t *testing.T) {
|
||||
td, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
raw := testConfig()
|
||||
|
||||
// No export path. This is invalid. Previously this would not error during
|
||||
// validation and as a result the failure would happen at build time.
|
||||
delete(raw, "export_path")
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
testConfigErr(t, warns, errs)
|
||||
|
||||
// Good export path
|
||||
raw["export_path"] = "good"
|
||||
warns, errs = c.Prepare(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
|
||||
// Bad export path (directory)
|
||||
raw["export_path"] = td
|
||||
warns, errs = c.Prepare(raw)
|
||||
testConfigErr(t, warns, errs)
|
||||
}
|
||||
|
||||
func TestConfigPrepare_exportPathAndCommit(t *testing.T) {
|
||||
raw := testConfig()
|
||||
|
||||
// Export but no commit (explicit default)
|
||||
raw["commit"] = false
|
||||
warns, errs := (&Config{}).Prepare(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
|
||||
// Commit AND export specified (invalid)
|
||||
raw["commit"] = true
|
||||
warns, errs = (&Config{}).Prepare(raw)
|
||||
testConfigErr(t, warns, errs)
|
||||
|
||||
// Commit but no export
|
||||
delete(raw, "export_path")
|
||||
warns, errs = (&Config{}).Prepare(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
|
||||
func TestConfigPrepare_exportDiscard(t *testing.T) {
|
||||
raw := testConfig()
|
||||
|
||||
// Export but no discard (explicit default)
|
||||
raw["discard"] = false
|
||||
warns, errs := (&Config{}).Prepare(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
|
||||
// Discard AND export (invalid)
|
||||
raw["discard"] = true
|
||||
warns, errs = (&Config{}).Prepare(raw)
|
||||
testConfigErr(t, warns, errs)
|
||||
|
||||
// Discard but no export
|
||||
raw["discard"] = true
|
||||
delete(raw, "export_path")
|
||||
warns, errs = (&Config{}).Prepare(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
|
||||
func TestConfigPrepare_image(t *testing.T) {
|
||||
raw := testConfig()
|
||||
|
||||
// No image
|
||||
delete(raw, "image")
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
testConfigErr(t, warns, errs)
|
||||
|
||||
// Good image
|
||||
raw["image"] = "path"
|
||||
warns, errs = c.Prepare(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
|
||||
func TestConfigPrepare_pull(t *testing.T) {
|
||||
raw := testConfig()
|
||||
|
||||
// No pull set
|
||||
delete(raw, "pull")
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
if !c.Pull {
|
||||
t.Fatal("should pull by default")
|
||||
}
|
||||
|
||||
// Pull set
|
||||
raw["pull"] = false
|
||||
warns, errs = c.Prepare(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
if c.Pull {
|
||||
t.Fatal("should not pull")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package docker
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDockerDriver_impl(t *testing.T) {
|
||||
var _ Driver = new(DockerDriver)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package docker
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMockDriver_impl(t *testing.T) {
|
||||
var _ Driver = new(MockDriver)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func testStepCommitState(t *testing.T) multistep.StateBag {
|
||||
state := testState(t)
|
||||
state.Put("container_id", "foo")
|
||||
return state
|
||||
}
|
||||
|
||||
func TestStepCommit_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepCommit)
|
||||
}
|
||||
|
||||
func TestStepCommit(t *testing.T) {
|
||||
state := testStepCommitState(t)
|
||||
step := new(StepCommit)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.CommitImageId = "bar"
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we did the right thing
|
||||
if !driver.CommitCalled {
|
||||
t.Fatal("should've called")
|
||||
}
|
||||
|
||||
// verify the ID is saved
|
||||
idRaw, ok := state.GetOk("image_id")
|
||||
if !ok {
|
||||
t.Fatal("should've saved ID")
|
||||
}
|
||||
|
||||
id := idRaw.(string)
|
||||
if id != driver.CommitImageId {
|
||||
t.Fatalf("bad: %#v", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCommit_error(t *testing.T) {
|
||||
state := testStepCommitState(t)
|
||||
step := new(StepCommit)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.CommitErr = errors.New("foo")
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify the ID is not saved
|
||||
if _, ok := state.GetOk("image_id"); ok {
|
||||
t.Fatal("shouldn't save image ID")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func testStepExportState(t *testing.T) multistep.StateBag {
|
||||
state := testState(t)
|
||||
state.Put("container_id", "foo")
|
||||
return state
|
||||
}
|
||||
|
||||
func TestStepExport_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepExport)
|
||||
}
|
||||
|
||||
func TestStepExport(t *testing.T) {
|
||||
state := testStepExportState(t)
|
||||
step := new(StepExport)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// Create a tempfile for our output path
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.ExportPath = tf.Name()
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.ExportReader = bytes.NewReader([]byte("data!"))
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we did the right thing
|
||||
if !driver.ExportCalled {
|
||||
t.Fatal("should've exported")
|
||||
}
|
||||
if driver.ExportID != "foo" {
|
||||
t.Fatalf("bad: %#v", driver.ExportID)
|
||||
}
|
||||
|
||||
// verify the data exported to the file
|
||||
contents, err := ioutil.ReadFile(tf.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if string(contents) != "data!" {
|
||||
t.Fatalf("bad: %#v", string(contents))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepExport_error(t *testing.T) {
|
||||
state := testStepExportState(t)
|
||||
step := new(StepExport)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// Create a tempfile for our output path
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
|
||||
if err := os.Remove(tf.Name()); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.ExportPath = tf.Name()
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.ExportError = errors.New("foo")
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we have an error
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// verify we didn't make that file
|
||||
if _, err := os.Stat(tf.Name()); err == nil {
|
||||
t.Fatal("export path shouldn't exist")
|
||||
}
|
||||
}
|
||||
+1
-12
@@ -29,18 +29,7 @@ func (s *StepPull) Run(ctx context.Context, state multistep.StateBag) multistep.
|
||||
ui.Say(fmt.Sprintf("Pulling Docker image: %s", config.Image))
|
||||
|
||||
if config.EcrLogin {
|
||||
ui.Message("Fetching ECR credentials...")
|
||||
|
||||
username, password, err := config.EcrGetLogin(config.LoginServer)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error fetching ECR credentials: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
config.LoginUsername = username
|
||||
config.LoginPassword = password
|
||||
panic("to reimplement")
|
||||
}
|
||||
|
||||
driver := state.Get("driver").(Driver)
|
||||
@@ -0,0 +1,104 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepPull_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepPull)
|
||||
}
|
||||
|
||||
func TestStepPull(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepPull)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we did the right thing
|
||||
if !driver.PullCalled {
|
||||
t.Fatal("should've pulled")
|
||||
}
|
||||
if driver.PullImage != config.Image {
|
||||
t.Fatalf("bad: %#v", driver.PullImage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepPull_error(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepPull)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.PullError = errors.New("foo")
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we have an error
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepPull_login(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepPull)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
|
||||
config.Login = true
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we pulled
|
||||
if !driver.PullCalled {
|
||||
t.Fatal("should've pulled")
|
||||
}
|
||||
|
||||
// verify we logged in
|
||||
if !driver.LoginCalled {
|
||||
t.Fatal("should've logged in")
|
||||
}
|
||||
if !driver.LogoutCalled {
|
||||
t.Fatal("should've logged out")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepPull_noPull(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepPull)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.Pull = false
|
||||
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we did the right thing
|
||||
if driver.PullCalled {
|
||||
t.Fatal("shouldn't have pulled")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func testStepRunState(t *testing.T) multistep.StateBag {
|
||||
state := testState(t)
|
||||
state.Put("temp_dir", "/foo")
|
||||
return state
|
||||
}
|
||||
|
||||
func TestStepRun_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepRun)
|
||||
}
|
||||
|
||||
func TestStepRun(t *testing.T) {
|
||||
state := testStepRunState(t)
|
||||
step := new(StepRun)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.StartID = "foo"
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we did the right thing
|
||||
if !driver.StartCalled {
|
||||
t.Fatal("should've called")
|
||||
}
|
||||
if driver.StartConfig.Image != config.Image {
|
||||
t.Fatalf("bad: %#v", driver.StartConfig.Image)
|
||||
}
|
||||
|
||||
// verify the ID is saved
|
||||
idRaw, ok := state.GetOk("container_id")
|
||||
if !ok {
|
||||
t.Fatal("should've saved ID")
|
||||
}
|
||||
|
||||
id := idRaw.(string)
|
||||
if id != "foo" {
|
||||
t.Fatalf("bad: %#v", id)
|
||||
}
|
||||
|
||||
// Verify we haven't called stop yet
|
||||
if driver.KillCalled {
|
||||
t.Fatal("should not have stopped")
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
step.Cleanup(state)
|
||||
if !driver.KillCalled {
|
||||
t.Fatal("should've stopped")
|
||||
}
|
||||
if driver.KillID != id {
|
||||
t.Fatalf("bad: %#v", driver.StopID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepRun_error(t *testing.T) {
|
||||
state := testStepRunState(t)
|
||||
step := new(StepRun)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.StartError = errors.New("foo")
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify the ID is not saved
|
||||
if _, ok := state.GetOk("container_id"); ok {
|
||||
t.Fatal("shouldn't save container ID")
|
||||
}
|
||||
|
||||
// Verify we haven't called stop yet
|
||||
if driver.KillCalled {
|
||||
t.Fatal("should not have stopped")
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
step.Cleanup(state)
|
||||
if driver.KillCalled {
|
||||
t.Fatal("should not have stopped")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
|
||||
)
|
||||
|
||||
func TestStepSetGeneratedData_Run(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepSetGeneratedData)
|
||||
step.GeneratedData = &packerbuilderdata.GeneratedData{State: state}
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.Sha256Result = "80B3BB1B1696E73A9B19DEEF92F664F8979F948DF348088B61F9A3477655AF64"
|
||||
state.Put("image_id", "12345")
|
||||
|
||||
if action := step.Run(context.TODO(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("Should not halt")
|
||||
}
|
||||
if !driver.Sha256Called {
|
||||
t.Fatalf("driver.SHA256 should be called")
|
||||
}
|
||||
if driver.Sha256Id != "12345" {
|
||||
t.Fatalf("driver.SHA256 got wrong image it: %s", driver.Sha256Id)
|
||||
}
|
||||
genData := state.Get("generated_data").(map[string]interface{})
|
||||
imgSha256 := genData["ImageSha256"].(string)
|
||||
if imgSha256 != driver.Sha256Result {
|
||||
t.Fatalf("Expected ImageSha256 to be %s but was %s", driver.Sha256Result, imgSha256)
|
||||
}
|
||||
|
||||
// Image ID not implement
|
||||
state = testState(t)
|
||||
step.GeneratedData = &packerbuilderdata.GeneratedData{State: state}
|
||||
driver = state.Get("driver").(*MockDriver)
|
||||
notImplementedMsg := "ERR_IMAGE_SHA256_NOT_FOUND"
|
||||
|
||||
if action := step.Run(context.TODO(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("Should not halt")
|
||||
}
|
||||
if driver.Sha256Called {
|
||||
t.Fatalf("driver.SHA256 should not be called")
|
||||
}
|
||||
genData = state.Get("generated_data").(map[string]interface{})
|
||||
imgSha256 = genData["ImageSha256"].(string)
|
||||
if imgSha256 != notImplementedMsg {
|
||||
t.Fatalf("Expected ImageSha256 to be %s but was %s", notImplementedMsg, imgSha256)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepTempDir_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepTempDir)
|
||||
}
|
||||
|
||||
func testStepTempDir_impl(t *testing.T) string {
|
||||
state := testState(t)
|
||||
step := new(StepTempDir)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// sanity test
|
||||
if _, ok := state.GetOk("temp_dir"); ok {
|
||||
t.Fatalf("temp_dir should not be in state yet")
|
||||
}
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify that we got the temp dir
|
||||
dirRaw, ok := state.GetOk("temp_dir")
|
||||
if !ok {
|
||||
t.Fatalf("should've made temp_dir")
|
||||
}
|
||||
dir := dirRaw.(string)
|
||||
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
t.Fatalf("Stat for %s failed: err: %s", err, dir)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
step.Cleanup(state)
|
||||
if _, err := os.Stat(dir); err == nil {
|
||||
t.Fatalf("dir should be gone")
|
||||
}
|
||||
|
||||
return dir
|
||||
}
|
||||
|
||||
func TestStepTempDir(t *testing.T) {
|
||||
testStepTempDir_impl(t)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func testState(t *testing.T) multistep.StateBag {
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", testConfigStruct(t))
|
||||
state.Put("driver", &MockDriver{})
|
||||
state.Put("hook", &packersdk.MockHook{})
|
||||
state.Put("ui", &packersdk.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
})
|
||||
return state
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
chocolate!
|
||||
@@ -0,0 +1 @@
|
||||
vanilla!
|
||||
@@ -0,0 +1 @@
|
||||
strawberry!
|
||||
@@ -0,0 +1,13 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
||||
packerVersion "github.com/hashicorp/packer/version"
|
||||
)
|
||||
|
||||
var DigitalOceanPluginVersion *version.PluginVersion
|
||||
|
||||
func init() {
|
||||
DigitalOceanPluginVersion = version.InitializePluginVersion(
|
||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
func TestBuilder_implBuilder(t *testing.T) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package file
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package file
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_impl(t *testing.T) {
|
||||
var _ packersdk.Artifact = new(Artifact)
|
||||
}
|
||||
|
||||
func TestArtifactState_StateData(t *testing.T) {
|
||||
expectedData := "this is the data"
|
||||
artifact := &Artifact{
|
||||
StateData: map[string]interface{}{"state_data": expectedData},
|
||||
}
|
||||
|
||||
// Valid state
|
||||
result := artifact.State("state_data")
|
||||
if result != expectedData {
|
||||
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
|
||||
}
|
||||
|
||||
// Invalid state
|
||||
result = artifact.State("invalid_key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for invalid state data name")
|
||||
}
|
||||
|
||||
// Nil StateData should not fail and should return nil
|
||||
artifact = &Artifact{}
|
||||
result = artifact.State("key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for nil StateData")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config,CustomerEncryptionKey
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
// used for ImageName and ImageFamily
|
||||
var validImageName = regexp.MustCompile(`^[a-z]([-a-z0-9]{0,61}[a-z0-9])?$`)
|
||||
|
||||
// Config is the configuration structure for the GCE builder. It stores
|
||||
// both the publicly settable state as well as the privately generated
|
||||
// state of the config object.
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
// The JSON file containing your account credentials. Not required if you
|
||||
// run Packer on a GCE instance with a service account. Instructions for
|
||||
// creating the file or using service accounts are above.
|
||||
AccountFile string `mapstructure:"account_file" required:"false"`
|
||||
// This allows service account impersonation as per the [docs](https://cloud.google.com/iam/docs/impersonating-service-accounts).
|
||||
ImpersonateServiceAccount string `mapstructure:"impersonate_service_account" required:"false"`
|
||||
// The project ID that will be used to launch instances and store images.
|
||||
ProjectId string `mapstructure:"project_id" required:"true"`
|
||||
// Full or partial URL of the guest accelerator type. GPU accelerators can
|
||||
// only be used with `"on_host_maintenance": "TERMINATE"` option set.
|
||||
// Example:
|
||||
// `"projects/project_id/zones/europe-west1-b/acceleratorTypes/nvidia-tesla-k80"`
|
||||
AcceleratorType string `mapstructure:"accelerator_type" required:"false"`
|
||||
// Number of guest accelerator cards to add to the launched instance.
|
||||
AcceleratorCount int64 `mapstructure:"accelerator_count" required:"false"`
|
||||
// The name of a pre-allocated static external IP address. Note, must be
|
||||
// the name and not the actual IP address.
|
||||
Address string `mapstructure:"address" required:"false"`
|
||||
// If true, the default service account will not be used if
|
||||
// service_account_email is not specified. Set this value to true and omit
|
||||
// service_account_email to provision a VM with no service account.
|
||||
DisableDefaultServiceAccount bool `mapstructure:"disable_default_service_account" required:"false"`
|
||||
// The name of the disk, if unset the instance name will be used.
|
||||
DiskName string `mapstructure:"disk_name" required:"false"`
|
||||
// The size of the disk in GB. This defaults to 10, which is 10GB.
|
||||
DiskSizeGb int64 `mapstructure:"disk_size" required:"false"`
|
||||
// Type of disk used to back your instance, like pd-ssd or pd-standard.
|
||||
// Defaults to pd-standard.
|
||||
DiskType string `mapstructure:"disk_type" required:"false"`
|
||||
// Create a Shielded VM image with Secure Boot enabled. It helps ensure that
|
||||
// the system only runs authentic software by verifying the digital signature
|
||||
// of all boot components, and halting the boot process if signature verification
|
||||
// fails. [Details](https://cloud.google.com/security/shielded-cloud/shielded-vm)
|
||||
EnableSecureBoot bool `mapstructure:"enable_secure_boot" required:"false"`
|
||||
// Create a Shielded VM image with virtual trusted platform module
|
||||
// Measured Boot enabled. A vTPM is a virtualized trusted platform module,
|
||||
// which is a specialized computer chip you can use to protect objects,
|
||||
// like keys and certificates, that you use to authenticate access to your
|
||||
// system. [Details](https://cloud.google.com/security/shielded-cloud/shielded-vm)
|
||||
EnableVtpm bool `mapstructure:"enable_vtpm" required:"false"`
|
||||
// Integrity monitoring helps you understand and make decisions about the
|
||||
// state of your VM instances. Note: integrity monitoring relies on having
|
||||
// vTPM enabled. [Details](https://cloud.google.com/security/shielded-cloud/shielded-vm)
|
||||
EnableIntegrityMonitoring bool `mapstructure:"enable_integrity_monitoring" required:"false"`
|
||||
// Whether to use an IAP proxy.
|
||||
IAPConfig `mapstructure:",squash"`
|
||||
// Skip creating the image. Useful for setting to `true` during a build test stage. Defaults to `false`.
|
||||
SkipCreateImage bool `mapstructure:"skip_create_image" required:"false"`
|
||||
// The unique name of the resulting image. Defaults to
|
||||
// `packer-{{timestamp}}`.
|
||||
ImageName string `mapstructure:"image_name" required:"false"`
|
||||
// The description of the resulting image.
|
||||
ImageDescription string `mapstructure:"image_description" required:"false"`
|
||||
// Image encryption key to apply to the created image. Possible values:
|
||||
// * kmsKeyName - The name of the encryption key that is stored in Google Cloud KMS.
|
||||
// * RawKey: - A 256-bit customer-supplied encryption key, encodes in RFC 4648 base64.
|
||||
//
|
||||
// examples:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "kmsKeyName": "projects/${project}/locations/${region}/keyRings/computeEngine/cryptoKeys/computeEngine/cryptoKeyVersions/4"
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// ```hcl
|
||||
// image_encryption_key {
|
||||
// kmsKeyName = "projects/${var.project}/locations/${var.region}/keyRings/computeEngine/cryptoKeys/computeEngine/cryptoKeyVersions/4"
|
||||
// }
|
||||
// ```
|
||||
ImageEncryptionKey *CustomerEncryptionKey `mapstructure:"image_encryption_key" required:"false"`
|
||||
// The name of the image family to which the resulting image belongs. You
|
||||
// can create disks by specifying an image family instead of a specific
|
||||
// image name. The image family always returns its latest image that is not
|
||||
// deprecated.
|
||||
ImageFamily string `mapstructure:"image_family" required:"false"`
|
||||
// Key/value pair labels to apply to the created image.
|
||||
ImageLabels map[string]string `mapstructure:"image_labels" required:"false"`
|
||||
// Licenses to apply to the created image.
|
||||
ImageLicenses []string `mapstructure:"image_licenses" required:"false"`
|
||||
// Storage location, either regional or multi-regional, where snapshot
|
||||
// content is to be stored and only accepts 1 value. Always defaults to a nearby regional or multi-regional
|
||||
// location.
|
||||
//
|
||||
// multi-regional example:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "image_storage_locations": ["us"]
|
||||
// }
|
||||
// ```
|
||||
// regional example:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "image_storage_locations": ["us-east1"]
|
||||
// }
|
||||
// ```
|
||||
ImageStorageLocations []string `mapstructure:"image_storage_locations" required:"false"`
|
||||
// A name to give the launched instance. Beware that this must be unique.
|
||||
// Defaults to `packer-{{uuid}}`.
|
||||
InstanceName string `mapstructure:"instance_name" required:"false"`
|
||||
// Key/value pair labels to apply to the launched instance.
|
||||
Labels map[string]string `mapstructure:"labels" required:"false"`
|
||||
// The machine type. Defaults to "n1-standard-1".
|
||||
MachineType string `mapstructure:"machine_type" required:"false"`
|
||||
// Metadata applied to the launched instance.
|
||||
// All metadata configuration values are expected to be of type string.
|
||||
// Google metadata options that take a value of `TRUE` or `FALSE` should be
|
||||
// set as a string (i.e `"TRUE"` `"FALSE"` or `"true"` `"false"`).
|
||||
Metadata map[string]string `mapstructure:"metadata" required:"false"`
|
||||
// Metadata applied to the launched instance. Values are files.
|
||||
MetadataFiles map[string]string `mapstructure:"metadata_files"`
|
||||
// A Minimum CPU Platform for VM Instance. Availability and default CPU
|
||||
// platforms vary across zones, based on the hardware available in each GCP
|
||||
// zone.
|
||||
// [Details](https://cloud.google.com/compute/docs/instances/specify-min-cpu-platform)
|
||||
MinCpuPlatform string `mapstructure:"min_cpu_platform" required:"false"`
|
||||
// The Google Compute network id or URL to use for the launched instance.
|
||||
// Defaults to "default". If the value is not a URL, it will be
|
||||
// interpolated to
|
||||
// `projects/((network_project_id))/global/networks/((network))`. This value
|
||||
// is not required if a subnet is specified.
|
||||
Network string `mapstructure:"network" required:"false"`
|
||||
// The project ID for the network and subnetwork to use for launched
|
||||
// instance. Defaults to project_id.
|
||||
NetworkProjectId string `mapstructure:"network_project_id" required:"false"`
|
||||
// If true, the instance will not have an external IP. use_internal_ip must
|
||||
// be true if this property is true.
|
||||
OmitExternalIP bool `mapstructure:"omit_external_ip" required:"false"`
|
||||
// Sets Host Maintenance Option. Valid choices are `MIGRATE` and
|
||||
// `TERMINATE`. Please see [GCE Instance Scheduling
|
||||
// Options](https://cloud.google.com/compute/docs/instances/setting-instance-scheduling-options),
|
||||
// as not all machine\_types support `MIGRATE` (i.e. machines with GPUs).
|
||||
// If preemptible is true this can only be `TERMINATE`. If preemptible is
|
||||
// false, it defaults to `MIGRATE`
|
||||
OnHostMaintenance string `mapstructure:"on_host_maintenance" required:"false"`
|
||||
// If true, launch a preemptible instance.
|
||||
Preemptible bool `mapstructure:"preemptible" required:"false"`
|
||||
// The time to wait for instance state changes. Defaults to "5m".
|
||||
StateTimeout time.Duration `mapstructure:"state_timeout" required:"false"`
|
||||
// The region in which to launch the instance. Defaults to the region
|
||||
// hosting the specified zone.
|
||||
Region string `mapstructure:"region" required:"false"`
|
||||
// The service account scopes for launched
|
||||
// instance. Defaults to:
|
||||
//
|
||||
// ```json
|
||||
// [
|
||||
// "https://www.googleapis.com/auth/userinfo.email",
|
||||
// "https://www.googleapis.com/auth/compute",
|
||||
// "https://www.googleapis.com/auth/devstorage.full_control"
|
||||
// ]
|
||||
// ```
|
||||
Scopes []string `mapstructure:"scopes" required:"false"`
|
||||
// The service account to be used for launched instance. Defaults to the
|
||||
// project's default service account unless disable_default_service_account
|
||||
// is true.
|
||||
ServiceAccountEmail string `mapstructure:"service_account_email" required:"false"`
|
||||
// The source image to use to create the new image from. You can also
|
||||
// specify source_image_family instead. If both source_image and
|
||||
// source_image_family are specified, source_image takes precedence.
|
||||
// Example: "debian-8-jessie-v20161027"
|
||||
SourceImage string `mapstructure:"source_image" required:"true"`
|
||||
// The source image family to use to create the new image from. The image
|
||||
// family always returns its latest image that is not deprecated. Example:
|
||||
// "debian-8".
|
||||
SourceImageFamily string `mapstructure:"source_image_family" required:"true"`
|
||||
// A list of project IDs to search for the source image. Packer will search the first
|
||||
// project ID in the list first, and fall back to the next in the list, until it finds the source image.
|
||||
SourceImageProjectId []string `mapstructure:"source_image_project_id" required:"false"`
|
||||
// The path to a startup script to run on the launched instance from which the image will
|
||||
// be made. When set, the contents of the startup script file will be added to the instance metadata
|
||||
// under the `"startup_script"` metadata property. See [Providing startup script contents directly](https://cloud.google.com/compute/docs/startupscript#providing_startup_script_contents_directly) for more details.
|
||||
//
|
||||
// When using `startup_script_file` the following rules apply:
|
||||
// - The contents of the script file will overwrite the value of the `"startup_script"` metadata property at runtime.
|
||||
// - The contents of the script file will be wrapped in Packer's startup script wrapper, unless `wrap_startup_script` is disabled. See `wrap_startup_script` for more details.
|
||||
// - Not supported by Windows instances. See [Startup Scripts for Windows](https://cloud.google.com/compute/docs/startupscript#providing_a_startup_script_for_windows_instances) for more details.
|
||||
StartupScriptFile string `mapstructure:"startup_script_file" required:"false"`
|
||||
// For backwards compatibility this option defaults to `"true"` in the future it will default to `"false"`.
|
||||
// If "true", the contents of `startup_script_file` or `"startup_script"` in the instance metadata
|
||||
// is wrapped in a Packer specific script that tracks the execution and completion of the provided
|
||||
// startup script. The wrapper ensures that the builder will not continue until the startup script has been executed.
|
||||
// - The use of the wrapped script file requires that the user or service account
|
||||
// running the build has the compute.instance.Metadata role.
|
||||
WrapStartupScriptFile config.Trilean `mapstructure:"wrap_startup_script" required:"false"`
|
||||
// The Google Compute subnetwork id or URL to use for the launched
|
||||
// instance. Only required if the network has been created with custom
|
||||
// subnetting. Note, the region of the subnetwork must match the region or
|
||||
// zone in which the VM is launched. If the value is not a URL, it will be
|
||||
// interpolated to
|
||||
// `projects/((network_project_id))/regions/((region))/subnetworks/((subnetwork))`
|
||||
Subnetwork string `mapstructure:"subnetwork" required:"false"`
|
||||
// Assign network tags to apply firewall rules to VM instance.
|
||||
Tags []string `mapstructure:"tags" required:"false"`
|
||||
// If true, use the instance's internal IP instead of its external IP
|
||||
// during building.
|
||||
UseInternalIP bool `mapstructure:"use_internal_ip" required:"false"`
|
||||
// If true, OSLogin will be used to manage SSH access to the compute instance by
|
||||
// dynamically importing a temporary SSH key to the Google account's login profile,
|
||||
// and setting the `enable-oslogin` to `TRUE` in the instance metadata.
|
||||
// Optionally, `use_os_login` can be used with an existing `ssh_username` and `ssh_private_key_file`
|
||||
// if a SSH key has already been added to the Google account's login profile - See [Adding SSH Keys](https://cloud.google.com/compute/docs/instances/managing-instance-access#add_oslogin_keys).
|
||||
//
|
||||
// SSH keys can be added to an individual user account
|
||||
//
|
||||
//```shell-session
|
||||
// $ gcloud compute os-login ssh-keys add --key-file=/home/user/.ssh/my-key.pub
|
||||
//
|
||||
// $ gcloud compute os-login describe-profile
|
||||
//PosixAccounts:
|
||||
//- accountId: <project-id>
|
||||
// gid: '34567890754'
|
||||
// homeDirectory: /home/user_example_com
|
||||
// ...
|
||||
// primary: true
|
||||
// uid: '2504818925'
|
||||
// username: /home/user_example_com
|
||||
//sshPublicKeys:
|
||||
// 000000000000000000000000000000000000000000000000000000000000000a:
|
||||
// fingerprint: 000000000000000000000000000000000000000000000000000000000000000a
|
||||
//```
|
||||
//
|
||||
// Or SSH keys can be added to an associated service account
|
||||
//```shell-session
|
||||
// $ gcloud auth activate-service-account --key-file=<path to service account credentials file (e.g account.json)>
|
||||
// $ gcloud compute os-login ssh-keys add --key-file=/home/user/.ssh/my-key.pub
|
||||
//
|
||||
// $ gcloud compute os-login describe-profile
|
||||
//PosixAccounts:
|
||||
//- accountId: <project-id>
|
||||
// gid: '34567890754'
|
||||
// homeDirectory: /home/sa_000000000000000000000
|
||||
// ...
|
||||
// primary: true
|
||||
// uid: '2504818925'
|
||||
// username: sa_000000000000000000000
|
||||
//sshPublicKeys:
|
||||
// 000000000000000000000000000000000000000000000000000000000000000a:
|
||||
// fingerprint: 000000000000000000000000000000000000000000000000000000000000000a
|
||||
//```
|
||||
UseOSLogin bool `mapstructure:"use_os_login" required:"false"`
|
||||
// Can be set instead of account_file. If set, this builder will use
|
||||
// HashiCorp Vault to generate an Oauth token for authenticating against
|
||||
// Google Cloud. The value should be the path of the token generator
|
||||
// within vault.
|
||||
// For information on how to configure your Vault + GCP engine to produce
|
||||
// Oauth tokens, see https://www.vaultproject.io/docs/auth/gcp
|
||||
// You must have the environment variables VAULT_ADDR and VAULT_TOKEN set,
|
||||
// along with any other relevant variables for accessing your vault
|
||||
// instance. For more information, see the Vault docs:
|
||||
// https://www.vaultproject.io/docs/commands/#environment-variables
|
||||
// Example:`"vault_gcp_oauth_engine": "gcp/token/my-project-editor",`
|
||||
VaultGCPOauthEngine string `mapstructure:"vault_gcp_oauth_engine"`
|
||||
// The time to wait between the creation of the instance used to create the image,
|
||||
// and the addition of SSH configuration, including SSH keys, to that instance.
|
||||
// The delay is intended to protect packer from anything in the instance boot
|
||||
// sequence that has potential to disrupt the creation of SSH configuration
|
||||
// (e.g. SSH user creation, SSH key creation) on the instance.
|
||||
// Note: All other instance metadata, including startup scripts, are still added to the instance
|
||||
// during it's creation.
|
||||
// Example value: `5m`.
|
||||
WaitToAddSSHKeys time.Duration `mapstructure:"wait_to_add_ssh_keys"`
|
||||
// The zone in which to launch the instance used to create the image.
|
||||
// Example: "us-central1-a"
|
||||
Zone string `mapstructure:"zone" required:"true"`
|
||||
|
||||
account *ServiceAccount
|
||||
imageAlreadyExists bool
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
c.ctx.Funcs = TemplateFuncs
|
||||
err := config.Decode(c, &config.DecodeOpts{
|
||||
PluginType: BuilderId,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &c.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"run_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var errs *packersdk.MultiError
|
||||
|
||||
// Set defaults.
|
||||
if c.Network == "" && c.Subnetwork == "" {
|
||||
c.Network = "default"
|
||||
}
|
||||
|
||||
if c.NetworkProjectId == "" {
|
||||
c.NetworkProjectId = c.ProjectId
|
||||
}
|
||||
|
||||
if c.DiskSizeGb == 0 {
|
||||
c.DiskSizeGb = 20
|
||||
}
|
||||
|
||||
if c.DiskType == "" {
|
||||
c.DiskType = "pd-standard"
|
||||
}
|
||||
|
||||
// Disabling the vTPM also disables integrity monitoring, because integrity
|
||||
// monitoring relies on data gathered by Measured Boot.
|
||||
if !c.EnableVtpm {
|
||||
if c.EnableIntegrityMonitoring {
|
||||
errs = packersdk.MultiErrorAppend(errs,
|
||||
errors.New("You cannot enable Integrity Monitoring when vTPM is disabled."))
|
||||
}
|
||||
}
|
||||
|
||||
if c.ImageDescription == "" {
|
||||
c.ImageDescription = "Created by Packer"
|
||||
}
|
||||
|
||||
if c.OnHostMaintenance == "MIGRATE" && c.Preemptible {
|
||||
errs = packersdk.MultiErrorAppend(errs,
|
||||
errors.New("on_host_maintenance must be TERMINATE when using preemptible instances."))
|
||||
}
|
||||
// Setting OnHostMaintenance Correct Defaults
|
||||
// "MIGRATE" : Possible and default if Preemptible is false
|
||||
// "TERMINATE": Required if Preemptible is true
|
||||
if c.Preemptible {
|
||||
c.OnHostMaintenance = "TERMINATE"
|
||||
} else {
|
||||
if c.OnHostMaintenance == "" {
|
||||
c.OnHostMaintenance = "MIGRATE"
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure user sets a valid value for on_host_maintenance option
|
||||
if !(c.OnHostMaintenance == "MIGRATE" || c.OnHostMaintenance == "TERMINATE") {
|
||||
errs = packersdk.MultiErrorAppend(errs,
|
||||
errors.New("on_host_maintenance must be one of MIGRATE or TERMINATE."))
|
||||
}
|
||||
|
||||
if c.ImageName == "" {
|
||||
img, err := interpolate.Render("packer-{{timestamp}}", nil)
|
||||
if err != nil {
|
||||
errs = packersdk.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Unable to parse image name: %s ", err))
|
||||
} else {
|
||||
c.ImageName = img
|
||||
}
|
||||
}
|
||||
|
||||
// used for ImageName and ImageFamily
|
||||
imageErrorText := "Invalid image %s %q: The first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash"
|
||||
|
||||
if len(c.ImageName) > 63 {
|
||||
errs = packersdk.MultiErrorAppend(errs,
|
||||
errors.New("Invalid image name: Must not be longer than 63 characters"))
|
||||
}
|
||||
|
||||
if !validImageName.MatchString(c.ImageName) {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf(imageErrorText, "name", c.ImageName))
|
||||
}
|
||||
|
||||
if len(c.ImageFamily) > 63 {
|
||||
errs = packersdk.MultiErrorAppend(errs,
|
||||
errors.New("Invalid image family: Must not be longer than 63 characters"))
|
||||
}
|
||||
|
||||
if c.ImageFamily != "" {
|
||||
if !validImageName.MatchString(c.ImageFamily) {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf(imageErrorText, "family", c.ImageFamily))
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.ImageStorageLocations) > 1 {
|
||||
errs = packersdk.MultiErrorAppend(errs,
|
||||
errors.New("Invalid image storage locations: Must not have more than 1 region"))
|
||||
}
|
||||
|
||||
if c.InstanceName == "" {
|
||||
c.InstanceName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
if c.DiskName == "" {
|
||||
c.DiskName = c.InstanceName
|
||||
}
|
||||
|
||||
if c.MachineType == "" {
|
||||
c.MachineType = "n1-standard-1"
|
||||
}
|
||||
|
||||
if c.StateTimeout == 0 {
|
||||
c.StateTimeout = 5 * time.Minute
|
||||
}
|
||||
|
||||
// Set up communicator
|
||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||
errs = packersdk.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
|
||||
// set defaults for IAP
|
||||
if c.IAPConfig.IAPHashBang == "" {
|
||||
if runtime.GOOS == "windows" {
|
||||
c.IAPConfig.IAPHashBang = ""
|
||||
} else {
|
||||
c.IAPConfig.IAPHashBang = "/bin/sh"
|
||||
}
|
||||
}
|
||||
if c.IAPConfig.IAPExt == "" {
|
||||
if runtime.GOOS == "windows" {
|
||||
c.IAPConfig.IAPExt = ".cmd"
|
||||
}
|
||||
}
|
||||
if c.IAPConfig.IAPTunnelLaunchWait == 0 {
|
||||
if c.Comm.Type == "winrm" {
|
||||
// when starting up, WinRM can cause the tunnel to take 30 seconds
|
||||
// before timing out
|
||||
c.IAPConfig.IAPTunnelLaunchWait = 40
|
||||
} else {
|
||||
c.IAPConfig.IAPTunnelLaunchWait = 30
|
||||
}
|
||||
}
|
||||
|
||||
// Configure IAP: Update SSH config to use localhost proxy instead
|
||||
if c.IAPConfig.IAP {
|
||||
if !SupportsIAPTunnel(&c.Comm) {
|
||||
err := fmt.Errorf("Error: IAP tunnel is not implemented for %s communicator", c.Comm.Type)
|
||||
errs = packersdk.MultiErrorAppend(errs, err)
|
||||
}
|
||||
// These configuration values are copied early to the generic host parameter when configuring
|
||||
// StepConnect. As such they must be set now. Ideally we would handle this as part of
|
||||
// ApplyIAPTunnel and set them during StepStartTunnel but that means defering when the
|
||||
// CommHost function reads the value from the configuration, perhaps pass in b.config.Comm
|
||||
// instead of b.config.Comm.Host()?
|
||||
c.Comm.SSHHost = "localhost"
|
||||
c.Comm.WinRMHost = "localhost"
|
||||
}
|
||||
|
||||
// Process required parameters.
|
||||
if c.ProjectId == "" {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("a project_id must be specified"))
|
||||
}
|
||||
|
||||
if c.Scopes == nil {
|
||||
c.Scopes = []string{
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
"https://www.googleapis.com/auth/compute",
|
||||
"https://www.googleapis.com/auth/devstorage.full_control",
|
||||
}
|
||||
}
|
||||
|
||||
if c.SourceImage == "" && c.SourceImageFamily == "" {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("a source_image or source_image_family must be specified"))
|
||||
}
|
||||
|
||||
if c.Zone == "" {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, errors.New("a zone must be specified"))
|
||||
}
|
||||
if c.Region == "" && len(c.Zone) > 2 {
|
||||
// get region from Zone
|
||||
region := c.Zone[:len(c.Zone)-2]
|
||||
c.Region = region
|
||||
}
|
||||
|
||||
// Authenticating via an account file
|
||||
if c.AccountFile != "" {
|
||||
if c.VaultGCPOauthEngine != "" && c.ImpersonateServiceAccount != "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("You cannot "+
|
||||
"specify impersonate_service_account, account_file and vault_gcp_oauth_engine at the same time"))
|
||||
}
|
||||
cfg, err := ProcessAccountFile(c.AccountFile)
|
||||
if err != nil {
|
||||
errs = packersdk.MultiErrorAppend(errs, err)
|
||||
}
|
||||
c.account = cfg
|
||||
}
|
||||
|
||||
if c.OmitExternalIP && c.Address != "" {
|
||||
errs = packersdk.MultiErrorAppend(fmt.Errorf("you can not specify an external address when 'omit_external_ip' is true"))
|
||||
}
|
||||
|
||||
if c.OmitExternalIP && !c.UseInternalIP {
|
||||
errs = packersdk.MultiErrorAppend(fmt.Errorf("'use_internal_ip' must be true if 'omit_external_ip' is true"))
|
||||
}
|
||||
|
||||
if c.AcceleratorCount > 0 && len(c.AcceleratorType) == 0 {
|
||||
errs = packersdk.MultiErrorAppend(fmt.Errorf("'accelerator_type' must be set when 'accelerator_count' is more than 0"))
|
||||
}
|
||||
|
||||
if c.AcceleratorCount > 0 && c.OnHostMaintenance != "TERMINATE" {
|
||||
errs = packersdk.MultiErrorAppend(fmt.Errorf("'on_host_maintenance' must be set to 'TERMINATE' when 'accelerator_count' is more than 0"))
|
||||
}
|
||||
|
||||
// If DisableDefaultServiceAccount is provided, don't allow a value for ServiceAccountEmail
|
||||
if c.DisableDefaultServiceAccount && c.ServiceAccountEmail != "" {
|
||||
errs = packersdk.MultiErrorAppend(fmt.Errorf("you may not specify a 'service_account_email' when 'disable_default_service_account' is true"))
|
||||
}
|
||||
|
||||
if c.StartupScriptFile != "" {
|
||||
if _, err := os.Stat(c.StartupScriptFile); err != nil {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("startup_script_file: %v", err))
|
||||
}
|
||||
|
||||
if c.WrapStartupScriptFile == config.TriUnset {
|
||||
c.WrapStartupScriptFile = config.TriTrue
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any errors.
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type CustomerEncryptionKey struct {
|
||||
// KmsKeyName: The name of the encryption key that is stored in Google
|
||||
// Cloud KMS.
|
||||
KmsKeyName string `mapstructure:"kmsKeyName" json:"kmsKeyName,omitempty"`
|
||||
|
||||
// RawKey: Specifies a 256-bit customer-supplied encryption key, encoded
|
||||
// in RFC 4648 base64 to either encrypt or decrypt this resource.
|
||||
RawKey string `mapstructure:"rawKey" json:"rawKey,omitempty"`
|
||||
}
|
||||
|
||||
func (k *CustomerEncryptionKey) ComputeType() *compute.CustomerEncryptionKey {
|
||||
if k == nil {
|
||||
return nil
|
||||
}
|
||||
return &compute.CustomerEncryptionKey{
|
||||
KmsKeyName: k.KmsKeyName,
|
||||
RawKey: k.RawKey,
|
||||
}
|
||||
}
|
||||
|
||||
func SupportsIAPTunnel(c *communicator.Config) bool {
|
||||
switch c.Type {
|
||||
case "ssh", "winrm":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func ApplyIAPTunnel(c *communicator.Config, port int) error {
|
||||
switch c.Type {
|
||||
case "ssh":
|
||||
c.SSHPort = port
|
||||
return nil
|
||||
case "winrm":
|
||||
c.WinRMPort = port
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("IAP tunnel is not implemented for %s communicator", c.Type)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,CustomerEncryptionKey"; DO NOT EDIT.
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
||||
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"`
|
||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
||||
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
||||
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
||||
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
||||
AccountFile *string `mapstructure:"account_file" required:"false" cty:"account_file" hcl:"account_file"`
|
||||
ImpersonateServiceAccount *string `mapstructure:"impersonate_service_account" required:"false" cty:"impersonate_service_account" hcl:"impersonate_service_account"`
|
||||
ProjectId *string `mapstructure:"project_id" required:"true" cty:"project_id" hcl:"project_id"`
|
||||
AcceleratorType *string `mapstructure:"accelerator_type" required:"false" cty:"accelerator_type" hcl:"accelerator_type"`
|
||||
AcceleratorCount *int64 `mapstructure:"accelerator_count" required:"false" cty:"accelerator_count" hcl:"accelerator_count"`
|
||||
Address *string `mapstructure:"address" required:"false" cty:"address" hcl:"address"`
|
||||
DisableDefaultServiceAccount *bool `mapstructure:"disable_default_service_account" required:"false" cty:"disable_default_service_account" hcl:"disable_default_service_account"`
|
||||
DiskName *string `mapstructure:"disk_name" required:"false" cty:"disk_name" hcl:"disk_name"`
|
||||
DiskSizeGb *int64 `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"`
|
||||
DiskType *string `mapstructure:"disk_type" required:"false" cty:"disk_type" hcl:"disk_type"`
|
||||
EnableSecureBoot *bool `mapstructure:"enable_secure_boot" required:"false" cty:"enable_secure_boot" hcl:"enable_secure_boot"`
|
||||
EnableVtpm *bool `mapstructure:"enable_vtpm" required:"false" cty:"enable_vtpm" hcl:"enable_vtpm"`
|
||||
EnableIntegrityMonitoring *bool `mapstructure:"enable_integrity_monitoring" required:"false" cty:"enable_integrity_monitoring" hcl:"enable_integrity_monitoring"`
|
||||
IAP *bool `mapstructure:"use_iap" required:"false" cty:"use_iap" hcl:"use_iap"`
|
||||
IAPLocalhostPort *int `mapstructure:"iap_localhost_port" cty:"iap_localhost_port" hcl:"iap_localhost_port"`
|
||||
IAPHashBang *string `mapstructure:"iap_hashbang" required:"false" cty:"iap_hashbang" hcl:"iap_hashbang"`
|
||||
IAPExt *string `mapstructure:"iap_ext" required:"false" cty:"iap_ext" hcl:"iap_ext"`
|
||||
IAPTunnelLaunchWait *int `mapstructure:"iap_tunnel_launch_wait" required:"false" cty:"iap_tunnel_launch_wait" hcl:"iap_tunnel_launch_wait"`
|
||||
SkipCreateImage *bool `mapstructure:"skip_create_image" required:"false" cty:"skip_create_image" hcl:"skip_create_image"`
|
||||
ImageName *string `mapstructure:"image_name" required:"false" cty:"image_name" hcl:"image_name"`
|
||||
ImageDescription *string `mapstructure:"image_description" required:"false" cty:"image_description" hcl:"image_description"`
|
||||
ImageEncryptionKey *FlatCustomerEncryptionKey `mapstructure:"image_encryption_key" required:"false" cty:"image_encryption_key" hcl:"image_encryption_key"`
|
||||
ImageFamily *string `mapstructure:"image_family" required:"false" cty:"image_family" hcl:"image_family"`
|
||||
ImageLabels map[string]string `mapstructure:"image_labels" required:"false" cty:"image_labels" hcl:"image_labels"`
|
||||
ImageLicenses []string `mapstructure:"image_licenses" required:"false" cty:"image_licenses" hcl:"image_licenses"`
|
||||
ImageStorageLocations []string `mapstructure:"image_storage_locations" required:"false" cty:"image_storage_locations" hcl:"image_storage_locations"`
|
||||
InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name" hcl:"instance_name"`
|
||||
Labels map[string]string `mapstructure:"labels" required:"false" cty:"labels" hcl:"labels"`
|
||||
MachineType *string `mapstructure:"machine_type" required:"false" cty:"machine_type" hcl:"machine_type"`
|
||||
Metadata map[string]string `mapstructure:"metadata" required:"false" cty:"metadata" hcl:"metadata"`
|
||||
MetadataFiles map[string]string `mapstructure:"metadata_files" cty:"metadata_files" hcl:"metadata_files"`
|
||||
MinCpuPlatform *string `mapstructure:"min_cpu_platform" required:"false" cty:"min_cpu_platform" hcl:"min_cpu_platform"`
|
||||
Network *string `mapstructure:"network" required:"false" cty:"network" hcl:"network"`
|
||||
NetworkProjectId *string `mapstructure:"network_project_id" required:"false" cty:"network_project_id" hcl:"network_project_id"`
|
||||
OmitExternalIP *bool `mapstructure:"omit_external_ip" required:"false" cty:"omit_external_ip" hcl:"omit_external_ip"`
|
||||
OnHostMaintenance *string `mapstructure:"on_host_maintenance" required:"false" cty:"on_host_maintenance" hcl:"on_host_maintenance"`
|
||||
Preemptible *bool `mapstructure:"preemptible" required:"false" cty:"preemptible" hcl:"preemptible"`
|
||||
StateTimeout *string `mapstructure:"state_timeout" required:"false" cty:"state_timeout" hcl:"state_timeout"`
|
||||
Region *string `mapstructure:"region" required:"false" cty:"region" hcl:"region"`
|
||||
Scopes []string `mapstructure:"scopes" required:"false" cty:"scopes" hcl:"scopes"`
|
||||
ServiceAccountEmail *string `mapstructure:"service_account_email" required:"false" cty:"service_account_email" hcl:"service_account_email"`
|
||||
SourceImage *string `mapstructure:"source_image" required:"true" cty:"source_image" hcl:"source_image"`
|
||||
SourceImageFamily *string `mapstructure:"source_image_family" required:"true" cty:"source_image_family" hcl:"source_image_family"`
|
||||
SourceImageProjectId []string `mapstructure:"source_image_project_id" required:"false" cty:"source_image_project_id" hcl:"source_image_project_id"`
|
||||
StartupScriptFile *string `mapstructure:"startup_script_file" required:"false" cty:"startup_script_file" hcl:"startup_script_file"`
|
||||
WrapStartupScriptFile *bool `mapstructure:"wrap_startup_script" required:"false" cty:"wrap_startup_script" hcl:"wrap_startup_script"`
|
||||
Subnetwork *string `mapstructure:"subnetwork" required:"false" cty:"subnetwork" hcl:"subnetwork"`
|
||||
Tags []string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"`
|
||||
UseInternalIP *bool `mapstructure:"use_internal_ip" required:"false" cty:"use_internal_ip" hcl:"use_internal_ip"`
|
||||
UseOSLogin *bool `mapstructure:"use_os_login" required:"false" cty:"use_os_login" hcl:"use_os_login"`
|
||||
VaultGCPOauthEngine *string `mapstructure:"vault_gcp_oauth_engine" cty:"vault_gcp_oauth_engine" hcl:"vault_gcp_oauth_engine"`
|
||||
WaitToAddSSHKeys *string `mapstructure:"wait_to_add_ssh_keys" cty:"wait_to_add_ssh_keys" hcl:"wait_to_add_ssh_keys"`
|
||||
Zone *string `mapstructure:"zone" required:"true" cty:"zone" hcl:"zone"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||
"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},
|
||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
||||
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
||||
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
||||
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
||||
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
||||
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
||||
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
||||
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
||||
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
||||
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
||||
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
||||
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
||||
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
||||
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
||||
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
||||
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
||||
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
|
||||
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
||||
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
||||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
||||
"account_file": &hcldec.AttrSpec{Name: "account_file", Type: cty.String, Required: false},
|
||||
"impersonate_service_account": &hcldec.AttrSpec{Name: "impersonate_service_account", Type: cty.String, Required: false},
|
||||
"project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false},
|
||||
"accelerator_type": &hcldec.AttrSpec{Name: "accelerator_type", Type: cty.String, Required: false},
|
||||
"accelerator_count": &hcldec.AttrSpec{Name: "accelerator_count", Type: cty.Number, Required: false},
|
||||
"address": &hcldec.AttrSpec{Name: "address", Type: cty.String, Required: false},
|
||||
"disable_default_service_account": &hcldec.AttrSpec{Name: "disable_default_service_account", Type: cty.Bool, Required: false},
|
||||
"disk_name": &hcldec.AttrSpec{Name: "disk_name", Type: cty.String, Required: false},
|
||||
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
|
||||
"disk_type": &hcldec.AttrSpec{Name: "disk_type", Type: cty.String, Required: false},
|
||||
"enable_secure_boot": &hcldec.AttrSpec{Name: "enable_secure_boot", Type: cty.Bool, Required: false},
|
||||
"enable_vtpm": &hcldec.AttrSpec{Name: "enable_vtpm", Type: cty.Bool, Required: false},
|
||||
"enable_integrity_monitoring": &hcldec.AttrSpec{Name: "enable_integrity_monitoring", Type: cty.Bool, Required: false},
|
||||
"use_iap": &hcldec.AttrSpec{Name: "use_iap", Type: cty.Bool, Required: false},
|
||||
"iap_localhost_port": &hcldec.AttrSpec{Name: "iap_localhost_port", Type: cty.Number, Required: false},
|
||||
"iap_hashbang": &hcldec.AttrSpec{Name: "iap_hashbang", Type: cty.String, Required: false},
|
||||
"iap_ext": &hcldec.AttrSpec{Name: "iap_ext", Type: cty.String, Required: false},
|
||||
"iap_tunnel_launch_wait": &hcldec.AttrSpec{Name: "iap_tunnel_launch_wait", Type: cty.Number, Required: false},
|
||||
"skip_create_image": &hcldec.AttrSpec{Name: "skip_create_image", Type: cty.Bool, Required: false},
|
||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
|
||||
"image_encryption_key": &hcldec.BlockSpec{TypeName: "image_encryption_key", Nested: hcldec.ObjectSpec((*FlatCustomerEncryptionKey)(nil).HCL2Spec())},
|
||||
"image_family": &hcldec.AttrSpec{Name: "image_family", Type: cty.String, Required: false},
|
||||
"image_labels": &hcldec.AttrSpec{Name: "image_labels", Type: cty.Map(cty.String), Required: false},
|
||||
"image_licenses": &hcldec.AttrSpec{Name: "image_licenses", Type: cty.List(cty.String), Required: false},
|
||||
"image_storage_locations": &hcldec.AttrSpec{Name: "image_storage_locations", Type: cty.List(cty.String), Required: false},
|
||||
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
|
||||
"labels": &hcldec.AttrSpec{Name: "labels", Type: cty.Map(cty.String), Required: false},
|
||||
"machine_type": &hcldec.AttrSpec{Name: "machine_type", Type: cty.String, Required: false},
|
||||
"metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(cty.String), Required: false},
|
||||
"metadata_files": &hcldec.AttrSpec{Name: "metadata_files", Type: cty.Map(cty.String), Required: false},
|
||||
"min_cpu_platform": &hcldec.AttrSpec{Name: "min_cpu_platform", Type: cty.String, Required: false},
|
||||
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
|
||||
"network_project_id": &hcldec.AttrSpec{Name: "network_project_id", Type: cty.String, Required: false},
|
||||
"omit_external_ip": &hcldec.AttrSpec{Name: "omit_external_ip", Type: cty.Bool, Required: false},
|
||||
"on_host_maintenance": &hcldec.AttrSpec{Name: "on_host_maintenance", Type: cty.String, Required: false},
|
||||
"preemptible": &hcldec.AttrSpec{Name: "preemptible", Type: cty.Bool, Required: false},
|
||||
"state_timeout": &hcldec.AttrSpec{Name: "state_timeout", Type: cty.String, Required: false},
|
||||
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
|
||||
"scopes": &hcldec.AttrSpec{Name: "scopes", Type: cty.List(cty.String), Required: false},
|
||||
"service_account_email": &hcldec.AttrSpec{Name: "service_account_email", Type: cty.String, Required: false},
|
||||
"source_image": &hcldec.AttrSpec{Name: "source_image", Type: cty.String, Required: false},
|
||||
"source_image_family": &hcldec.AttrSpec{Name: "source_image_family", Type: cty.String, Required: false},
|
||||
"source_image_project_id": &hcldec.AttrSpec{Name: "source_image_project_id", Type: cty.List(cty.String), Required: false},
|
||||
"startup_script_file": &hcldec.AttrSpec{Name: "startup_script_file", Type: cty.String, Required: false},
|
||||
"wrap_startup_script": &hcldec.AttrSpec{Name: "wrap_startup_script", Type: cty.Bool, Required: false},
|
||||
"subnetwork": &hcldec.AttrSpec{Name: "subnetwork", Type: cty.String, Required: false},
|
||||
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.List(cty.String), Required: false},
|
||||
"use_internal_ip": &hcldec.AttrSpec{Name: "use_internal_ip", Type: cty.Bool, Required: false},
|
||||
"use_os_login": &hcldec.AttrSpec{Name: "use_os_login", Type: cty.Bool, Required: false},
|
||||
"vault_gcp_oauth_engine": &hcldec.AttrSpec{Name: "vault_gcp_oauth_engine", Type: cty.String, Required: false},
|
||||
"wait_to_add_ssh_keys": &hcldec.AttrSpec{Name: "wait_to_add_ssh_keys", Type: cty.String, Required: false},
|
||||
"zone": &hcldec.AttrSpec{Name: "zone", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FlatCustomerEncryptionKey is an auto-generated flat version of CustomerEncryptionKey.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatCustomerEncryptionKey struct {
|
||||
KmsKeyName *string `mapstructure:"kmsKeyName" json:"kmsKeyName,omitempty" cty:"kmsKeyName" hcl:"kmsKeyName"`
|
||||
RawKey *string `mapstructure:"rawKey" json:"rawKey,omitempty" cty:"rawKey" hcl:"rawKey"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatCustomerEncryptionKey.
|
||||
// FlatCustomerEncryptionKey is an auto-generated flat version of CustomerEncryptionKey.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*CustomerEncryptionKey) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatCustomerEncryptionKey)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a CustomerEncryptionKey.
|
||||
// This spec is used by HCL to read the fields of CustomerEncryptionKey.
|
||||
// The decoded values from this spec will then be applied to a FlatCustomerEncryptionKey.
|
||||
func (*FlatCustomerEncryptionKey) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"kmsKeyName": &hcldec.AttrSpec{Name: "kmsKeyName", Type: cty.String, Required: false},
|
||||
"rawKey": &hcldec.AttrSpec{Name: "rawKey", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,692 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||
)
|
||||
|
||||
func TestConfigPrepare(t *testing.T) {
|
||||
cases := []struct {
|
||||
Key string
|
||||
Value interface{}
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
"unknown_key",
|
||||
"bad",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"private_key_file",
|
||||
"/tmp/i/should/not/exist",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"project_id",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"project_id",
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"source_image",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"source_image",
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"source_image_family",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"source_image_family",
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"zone",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"zone",
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"ssh_timeout",
|
||||
"SO BAD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ssh_timeout",
|
||||
"5s",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"wait_to_add_ssh_keys",
|
||||
"SO BAD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"wait_to_add_ssh_keys",
|
||||
"5s",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"state_timeout",
|
||||
"SO BAD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"state_timeout",
|
||||
"5s",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"use_internal_ip",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"use_internal_ip",
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"use_internal_ip",
|
||||
"SO VERY BAD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"on_host_maintenance",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"on_host_maintenance",
|
||||
"TERMINATE",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"on_host_maintenance",
|
||||
"SO VERY BAD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"preemptible",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"preemptible",
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"preemptible",
|
||||
"SO VERY BAD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"image_family",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"image_family",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"image_family",
|
||||
"foo-bar",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"image_family",
|
||||
"foo bar",
|
||||
true,
|
||||
},
|
||||
{
|
||||
// underscore is not allowed
|
||||
"image_name",
|
||||
"foo_bar",
|
||||
true,
|
||||
},
|
||||
{
|
||||
// too long
|
||||
"image_name",
|
||||
"foobar123xyz_abc-456-one-two_three_five_nine_seventeen_eleventy-seven",
|
||||
true,
|
||||
},
|
||||
{
|
||||
// starts with non-alphabetic character
|
||||
"image_name",
|
||||
"1boohoo",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"image_encryption_key",
|
||||
map[string]string{"kmsKeyName": "foo"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"image_encryption_key",
|
||||
map[string]string{"No such key": "foo"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"image_encryption_key",
|
||||
map[string]string{"kmsKeyName": "foo", "RawKey": "foo"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"scopes",
|
||||
[]string{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"scopes",
|
||||
[]string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control", "https://www.googleapis.com/auth/sqlservice.admin"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"scopes",
|
||||
[]string{"https://www.googleapis.com/auth/cloud-platform"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"disable_default_service_account",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"disable_default_service_account",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"disable_default_service_account",
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"disable_default_service_account",
|
||||
true,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"disable_default_service_account",
|
||||
"NOT A BOOL",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
if tc.Value == nil {
|
||||
delete(raw, tc.Key)
|
||||
} else {
|
||||
raw[tc.Key] = tc.Value
|
||||
}
|
||||
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
|
||||
if tc.Err {
|
||||
testConfigErr(t, warns, errs, tc.Key)
|
||||
} else {
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepareAccelerator(t *testing.T) {
|
||||
cases := []struct {
|
||||
Keys []string
|
||||
Values []interface{}
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
[]string{"accelerator_count", "on_host_maintenance", "accelerator_type"},
|
||||
[]interface{}{1, "MIGRATE", "something_valid"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"accelerator_count", "on_host_maintenance", "accelerator_type"},
|
||||
[]interface{}{1, "TERMINATE", "something_valid"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]string{"accelerator_count", "on_host_maintenance", "accelerator_type"},
|
||||
[]interface{}{1, "TERMINATE", nil},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"accelerator_count", "on_host_maintenance", "accelerator_type"},
|
||||
[]interface{}{1, "TERMINATE", ""},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"accelerator_count", "on_host_maintenance", "accelerator_type"},
|
||||
[]interface{}{1, "TERMINATE", "something_valid"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
errStr := ""
|
||||
for k := range tc.Keys {
|
||||
|
||||
// Create the string for error reporting
|
||||
// convert value to string if it can be converted
|
||||
errStr += fmt.Sprintf("%s:%v, ", tc.Keys[k], tc.Values[k])
|
||||
if tc.Values[k] == nil {
|
||||
delete(raw, tc.Keys[k])
|
||||
} else {
|
||||
raw[tc.Keys[k]] = tc.Values[k]
|
||||
}
|
||||
}
|
||||
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
|
||||
if tc.Err {
|
||||
testConfigErr(t, warns, errs, strings.TrimRight(errStr, ", "))
|
||||
} else {
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepareServiceAccount(t *testing.T) {
|
||||
cases := []struct {
|
||||
Keys []string
|
||||
Values []interface{}
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
[]string{"disable_default_service_account", "service_account_email"},
|
||||
[]interface{}{true, "service@account.email.com"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"disable_default_service_account", "service_account_email"},
|
||||
[]interface{}{false, "service@account.email.com"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]string{"disable_default_service_account", "service_account_email"},
|
||||
[]interface{}{true, ""},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
errStr := ""
|
||||
for k := range tc.Keys {
|
||||
|
||||
// Create the string for error reporting
|
||||
// convert value to string if it can be converted
|
||||
errStr += fmt.Sprintf("%s:%v, ", tc.Keys[k], tc.Values[k])
|
||||
if tc.Values[k] == nil {
|
||||
delete(raw, tc.Keys[k])
|
||||
} else {
|
||||
raw[tc.Keys[k]] = tc.Values[k]
|
||||
}
|
||||
}
|
||||
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
|
||||
if tc.Err {
|
||||
testConfigErr(t, warns, errs, strings.TrimRight(errStr, ", "))
|
||||
} else {
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepareStartupScriptFile(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"project_id": "project",
|
||||
"source_image": "foo",
|
||||
"ssh_username": "packer",
|
||||
"startup_script_file": "no-such-file",
|
||||
"zone": "us-central1-a",
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, errs := c.Prepare(config)
|
||||
|
||||
if errs == nil || !strings.Contains(errs.Error(), "startup_script_file") {
|
||||
t.Fatalf("should error: startup_script_file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepareIAP_SSH(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"project_id": "project",
|
||||
"source_image": "foo",
|
||||
"ssh_username": "packer",
|
||||
"zone": "us-central1-a",
|
||||
"communicator": "ssh",
|
||||
"use_iap": true,
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have errors. Err = %s", err)
|
||||
}
|
||||
if c.Comm.SSHHost != "localhost" {
|
||||
t.Fatalf("Should have set SSHHost")
|
||||
}
|
||||
|
||||
testIAPScript(t, &c)
|
||||
}
|
||||
|
||||
func TestConfigPrepareIAP_WinRM(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"project_id": "project",
|
||||
"source_image": "foo",
|
||||
"winrm_username": "packer",
|
||||
"zone": "us-central1-a",
|
||||
"communicator": "winrm",
|
||||
"use_iap": true,
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have errors. Err = %s", err)
|
||||
}
|
||||
if c.Comm.WinRMHost != "localhost" {
|
||||
t.Fatalf("Should have set WinRMHost")
|
||||
}
|
||||
|
||||
testIAPScript(t, &c)
|
||||
}
|
||||
|
||||
func TestConfigPrepareIAP_failures(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"project_id": "project",
|
||||
"source_image": "foo",
|
||||
"winrm_username": "packer",
|
||||
"zone": "us-central1-a",
|
||||
"communicator": "none",
|
||||
"iap_hashbang": "/bin/bash",
|
||||
"iap_ext": ".ps1",
|
||||
"use_iap": true,
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, errs := c.Prepare(config)
|
||||
if errs == nil {
|
||||
t.Fatalf("Should have errored because we're using none.")
|
||||
}
|
||||
if c.IAPHashBang != "/bin/bash" {
|
||||
t.Fatalf("IAP hashbang defaulted even though set.")
|
||||
}
|
||||
if c.IAPExt != ".ps1" {
|
||||
t.Fatalf("IAP tempfile defaulted even though set.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigDefaults(t *testing.T) {
|
||||
cases := []struct {
|
||||
Read func(c *Config) interface{}
|
||||
Value interface{}
|
||||
}{
|
||||
{
|
||||
func(c *Config) interface{} { return c.Comm.Type },
|
||||
"ssh",
|
||||
},
|
||||
|
||||
{
|
||||
func(c *Config) interface{} { return c.Comm.SSHPort },
|
||||
22,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
|
||||
actual := tc.Read(&c)
|
||||
if actual != tc.Value {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageName(t *testing.T) {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
var c Config
|
||||
c.Prepare(raw)
|
||||
if !strings.HasPrefix(c.ImageName, "packer-") {
|
||||
t.Fatalf("ImageName should have 'packer-' prefix, found %s", c.ImageName)
|
||||
}
|
||||
if strings.Contains(c.ImageName, "{{timestamp}}") {
|
||||
t.Errorf("ImageName should be interpolated; found %s", c.ImageName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegion(t *testing.T) {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
var c Config
|
||||
c.Prepare(raw)
|
||||
if c.Region != "us-east1" {
|
||||
t.Fatalf("Region should be 'us-east1' given Zone of 'us-east1-a', but is %s", c.Region)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyIAPTunnel_SSH(t *testing.T) {
|
||||
c := &communicator.Config{
|
||||
Type: "ssh",
|
||||
SSH: communicator.SSH{
|
||||
SSHHost: "example",
|
||||
SSHPort: 1234,
|
||||
},
|
||||
}
|
||||
|
||||
err := ApplyIAPTunnel(c, 8447)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have errors")
|
||||
}
|
||||
if c.SSHPort != 8447 {
|
||||
t.Fatalf("Should have set SSHPort")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyIAPTunnel_WinRM(t *testing.T) {
|
||||
c := &communicator.Config{
|
||||
Type: "winrm",
|
||||
WinRM: communicator.WinRM{
|
||||
WinRMHost: "example",
|
||||
WinRMPort: 1234,
|
||||
},
|
||||
}
|
||||
|
||||
err := ApplyIAPTunnel(c, 8447)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have errors")
|
||||
}
|
||||
if c.WinRMPort != 8447 {
|
||||
t.Fatalf("Should have set WinRMPort")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyIAPTunnel_none(t *testing.T) {
|
||||
c := &communicator.Config{
|
||||
Type: "none",
|
||||
}
|
||||
|
||||
err := ApplyIAPTunnel(c, 8447)
|
||||
if err == nil {
|
||||
t.Fatalf("Should have errors, none is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
// Helper stuff below
|
||||
|
||||
func testConfig(t *testing.T) (config map[string]interface{}, tempAccountFile string) {
|
||||
tempAccountFile = testAccountFile(t)
|
||||
|
||||
config = map[string]interface{}{
|
||||
"account_file": tempAccountFile,
|
||||
"project_id": "hashicorp",
|
||||
"source_image": "foo",
|
||||
"ssh_username": "root",
|
||||
"image_family": "bar",
|
||||
"image_labels": map[string]string{
|
||||
"label-1": "value-1",
|
||||
"label-2": "value-2",
|
||||
},
|
||||
"image_licenses": []string{
|
||||
"test-license",
|
||||
},
|
||||
"image_storage_locations": []string{
|
||||
"us-east1",
|
||||
},
|
||||
"metadata_files": map[string]string{},
|
||||
"zone": "us-east1-a",
|
||||
}
|
||||
|
||||
return config, tempAccountFile
|
||||
}
|
||||
|
||||
func testConfigStruct(t *testing.T) *Config {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", len(warns))
|
||||
}
|
||||
if errs != nil {
|
||||
t.Fatalf("bad: %#v", errs)
|
||||
}
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
func testConfigErr(t *testing.T, warns []string, err error, extra string) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("should error: %s", extra)
|
||||
}
|
||||
}
|
||||
|
||||
func testConfigOk(t *testing.T, warns []string, err error) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccountFile(t *testing.T) string {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer tf.Close()
|
||||
|
||||
if _, err := tf.Write([]byte(testAccountContent)); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return tf.Name()
|
||||
}
|
||||
|
||||
func testIAPScript(t *testing.T, c *Config) {
|
||||
if runtime.GOOS == "windows" {
|
||||
if c.IAPExt != ".cmd" {
|
||||
t.Fatalf("IAP tempfile extension didn't default correctly to .cmd")
|
||||
}
|
||||
if c.IAPHashBang != "" {
|
||||
t.Fatalf("IAP hashbang didn't default correctly to nothing.")
|
||||
}
|
||||
} else {
|
||||
if c.IAPExt != "" {
|
||||
t.Fatalf("IAP tempfile extension should default to empty on unix mahcines")
|
||||
}
|
||||
if c.IAPHashBang != "/bin/sh" {
|
||||
t.Fatalf("IAP hashbang didn't default correctly to /bin/sh.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const testMetadataFileContent = `testMetadata`
|
||||
|
||||
func testMetadataFile(t *testing.T) string {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer tf.Close()
|
||||
if _, err := tf.Write([]byte(testMetadataFileContent)); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return tf.Name()
|
||||
}
|
||||
|
||||
// This is just some dummy data that doesn't actually work
|
||||
const testAccountContent = `{
|
||||
"type": "service_account",
|
||||
"project_id": "test-project-123456789",
|
||||
"private_key_id": "bananaphone",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nring_ring_ring_ring_ring_ring_ring_BANANAPHONE\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "raffi-compute@developer.gserviceaccount.com",
|
||||
"client_id": "1234567890",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://accounts.google.com/o/oauth2/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/12345-compute%40developer.gserviceaccount.com"
|
||||
}`
|
||||
@@ -0,0 +1,126 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"time"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
oslogin "google.golang.org/api/oslogin/v1"
|
||||
)
|
||||
|
||||
// Driver is the interface that has to be implemented to communicate
|
||||
// with GCE. The Driver interface exists mostly to allow a mock implementation
|
||||
// to be used to test the steps.
|
||||
type Driver interface {
|
||||
// CreateImage creates an image from the given disk in Google Compute
|
||||
// Engine.
|
||||
CreateImage(name, description, family, zone, disk string, image_labels map[string]string, image_licenses []string, image_encryption_key *compute.CustomerEncryptionKey, imageStorageLocation []string) (<-chan *Image, <-chan error)
|
||||
|
||||
// DeleteImage deletes the image with the given name.
|
||||
DeleteImage(name string) <-chan error
|
||||
|
||||
// DeleteInstance deletes the given instance, keeping the boot disk.
|
||||
DeleteInstance(zone, name string) (<-chan error, error)
|
||||
|
||||
// DeleteDisk deletes the disk with the given name.
|
||||
DeleteDisk(zone, name string) (<-chan error, error)
|
||||
|
||||
// GetImage gets an image; tries the default and public projects. If
|
||||
// fromFamily is true, name designates an image family instead of a
|
||||
// particular image.
|
||||
GetImage(name string, fromFamily bool) (*Image, error)
|
||||
|
||||
// GetImageFromProject gets an image from a specific projects.
|
||||
// Returns the image from the first project in slice it can find one
|
||||
// If fromFamily is true, name designates an image family instead of a particular image.
|
||||
GetImageFromProjects(project []string, name string, fromFamily bool) (*Image, error)
|
||||
|
||||
// GetImageFromProject gets an image from a specific project. If fromFamily
|
||||
// is true, name designates an image family instead of a particular image.
|
||||
GetImageFromProject(project, name string, fromFamily bool) (*Image, error)
|
||||
|
||||
// GetInstanceMetadata gets a metadata variable for the instance, name.
|
||||
GetInstanceMetadata(zone, name, key string) (string, error)
|
||||
|
||||
// GetInternalIP gets the GCE-internal IP address for the instance.
|
||||
GetInternalIP(zone, name string) (string, error)
|
||||
|
||||
// GetNatIP gets the NAT IP address for the instance.
|
||||
GetNatIP(zone, name string) (string, error)
|
||||
|
||||
// GetSerialPortOutput gets the Serial Port contents for the instance.
|
||||
GetSerialPortOutput(zone, name string) (string, error)
|
||||
|
||||
// ImageExists returns true if the specified image exists. If an error
|
||||
// occurs calling the API, this method returns false.
|
||||
ImageExists(name string) bool
|
||||
|
||||
// RunInstance takes the given config and launches an instance.
|
||||
RunInstance(*InstanceConfig) (<-chan error, error)
|
||||
|
||||
// WaitForInstance waits for an instance to reach the given state.
|
||||
WaitForInstance(state, zone, name string) <-chan error
|
||||
|
||||
// CreateOrResetWindowsPassword creates or resets the password for a user on an Windows instance.
|
||||
CreateOrResetWindowsPassword(zone, name string, config *WindowsPasswordConfig) (<-chan error, error)
|
||||
|
||||
// ImportOSLoginSSHKey imports SSH public key for OSLogin.
|
||||
ImportOSLoginSSHKey(user, sshPublicKey string) (*oslogin.LoginProfile, error)
|
||||
|
||||
// DeleteOSLoginSSHKey deletes the SSH public key for OSLogin with the given key.
|
||||
DeleteOSLoginSSHKey(user, fingerprint string) error
|
||||
|
||||
// Add to the instance metadata for the existing instance
|
||||
AddToInstanceMetadata(zone string, name string, metadata map[string]string) error
|
||||
}
|
||||
|
||||
type InstanceConfig struct {
|
||||
AcceleratorType string
|
||||
AcceleratorCount int64
|
||||
Address string
|
||||
Description string
|
||||
DisableDefaultServiceAccount bool
|
||||
DiskSizeGb int64
|
||||
DiskType string
|
||||
EnableSecureBoot bool
|
||||
EnableVtpm bool
|
||||
EnableIntegrityMonitoring bool
|
||||
Image *Image
|
||||
Labels map[string]string
|
||||
MachineType string
|
||||
Metadata map[string]string
|
||||
MinCpuPlatform string
|
||||
Name string
|
||||
Network string
|
||||
NetworkProjectId string
|
||||
OmitExternalIP bool
|
||||
OnHostMaintenance string
|
||||
Preemptible bool
|
||||
Region string
|
||||
ServiceAccountEmail string
|
||||
Scopes []string
|
||||
Subnetwork string
|
||||
Tags []string
|
||||
Zone string
|
||||
}
|
||||
|
||||
// WindowsPasswordConfig is the data structure that GCE needs to encrypt the created
|
||||
// windows password.
|
||||
type WindowsPasswordConfig struct {
|
||||
key *rsa.PrivateKey
|
||||
password string
|
||||
UserName string `json:"userName"`
|
||||
Modulus string `json:"modulus"`
|
||||
Exponent string `json:"exponent"`
|
||||
Email string `json:"email"`
|
||||
ExpireOn time.Time `json:"expireOn"`
|
||||
}
|
||||
|
||||
type windowsPasswordResponse struct {
|
||||
UserName string `json:"userName"`
|
||||
PasswordFound bool `json:"passwordFound"`
|
||||
EncryptedPassword string `json:"encryptedPassword"`
|
||||
Modulus string `json:"modulus"`
|
||||
Exponent string `json:"exponent"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
}
|
||||
+3
-2
@@ -17,10 +17,10 @@ import (
|
||||
"google.golang.org/api/option"
|
||||
oslogin "google.golang.org/api/oslogin/v1"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-googlecompute/builder/googlecompute/version"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/retry"
|
||||
"github.com/hashicorp/packer-plugin-sdk/useragent"
|
||||
"github.com/hashicorp/packer/builder/googlecompute/version"
|
||||
vaultapi "github.com/hashicorp/vault/api"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
@@ -253,6 +253,7 @@ func (d *driverGCE) GetImage(name string, fromFamily bool) (*Image, error) {
|
||||
"ubuntu-os-cloud",
|
||||
"windows-cloud",
|
||||
"windows-sql-cloud",
|
||||
"gce-uefi-images",
|
||||
"gce-nvme",
|
||||
// misc
|
||||
"google-containers",
|
||||
@@ -567,7 +568,7 @@ func (d *driverGCE) createWindowsPassword(errCh chan<- error, name, zone string,
|
||||
return
|
||||
}
|
||||
|
||||
timeout := time.Now().Add(c.WindowsPasswordTimeout)
|
||||
timeout := time.Now().Add(time.Minute * 3)
|
||||
hash := sha1.New()
|
||||
random := rand.Reader
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user