Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 247380f586 | |||
| ccbd0a29cc | |||
| d08aa6f6b0 | |||
| 4be2c350bf | |||
| bc35a737c0 | |||
| b5666b84cd | |||
| 469f033c36 | |||
| 2db338e322 | |||
| 0f6a081724 | |||
| 4b093aab78 | |||
| 5145893ae5 | |||
| 9044deeb05 | |||
| dc63b9a7a4 | |||
| d8c3584b46 | |||
| 386f7c3f56 | |||
| 8bf03cbca7 | |||
| eb6527c8b6 | |||
| fd028b71b3 | |||
| 0ec3ad3db1 | |||
| a29f3340c2 | |||
| 45dfb97ff4 | |||
| 30bcf44c2c | |||
| 2da9c21733 | |||
| 987080a409 | |||
| 9bdb809edd | |||
| 88192f1fdd | |||
| 6fa213235f | |||
| af34218909 | |||
| 2dbfef5750 | |||
| 2f927177d9 | |||
| 50fadc4118 | |||
| b54121a72d | |||
| 4de2954d01 | |||
| 25a999978b | |||
| d6904502ac | |||
| 2061aa9e69 | |||
| cbe06050b4 | |||
| d22ba61ae0 | |||
| 9eaad88ac0 | |||
| 20faaef05c | |||
| d0a4a71da8 | |||
| 3af472be9a | |||
| 5a00020830 | |||
| 6094c97998 | |||
| 1a41eac70b | |||
| 7a85b7328e | |||
| 3dac34766c | |||
| 642ed07476 | |||
| 88f8feecfe | |||
| b448c3182c | |||
| 9230a06920 | |||
| 16658a9f47 | |||
| ceb96d061a | |||
| bb1a025f60 | |||
| 87ba7258b3 | |||
| da312e2785 | |||
| 84af0ba6da | |||
| 3c6b7841bc | |||
| c7ee5f1efd | |||
| a00846102b | |||
| 41c66d6935 | |||
| f6854f5528 | |||
| 38fe79948b | |||
| a6c5958c67 | |||
| c17f236e85 | |||
| bb5d7b6c40 | |||
| 1ea5a547e2 | |||
| 86b8ce8df0 | |||
| b51bf9250e | |||
| 2d5a32629a | |||
| 3e2db82cab | |||
| bc9dd69669 | |||
| 734e91b97c | |||
| ab0d1ee363 | |||
| cf94fd1778 | |||
| 15a2e59bba | |||
| 8c4eb5f4aa | |||
| 86788220a9 | |||
| e3bcb4f2ac | |||
| ccd0430fda | |||
| 49474f8f37 | |||
| e0614cabf4 | |||
| eaec3e5564 | |||
| eaaf22dcde | |||
| f2f33fa344 | |||
| 9189f1228e | |||
| 5e7b5729e6 | |||
| 9365c90c0b | |||
| f27bdf85f4 | |||
| 9f7bb4da25 | |||
| b1967e99c7 | |||
| 7efb41868f | |||
| 260906c3e4 | |||
| f65e1d5d55 | |||
| 23e8684aae | |||
| e22d9861aa | |||
| 1c8fc65223 | |||
| 42ca66752f | |||
| 33461126e2 | |||
| 1f834e229a | |||
| 4417f8b3bf | |||
| 8db540a935 | |||
| e8780bf7b8 | |||
| 3b0226d496 | |||
| 4c08789642 | |||
| 634bf87d99 | |||
| d566419c45 | |||
| cce1f5c1e3 | |||
| 7f26429a2a | |||
| d81c02b456 | |||
| 794e83b171 | |||
| 58fb58c2ea | |||
| 15a9e1b20a | |||
| 3f23a5ec74 | |||
| f4cbb5d7dc | |||
| fb04fa7a25 | |||
| 830140157d | |||
| cdcdf6a618 | |||
| 74434b3c3e | |||
| 3a11352dfa | |||
| 56728a937b | |||
| f044a64014 | |||
| cd370aaaad | |||
| af865b1591 |
+5
-19
@@ -1,6 +1,5 @@
|
|||||||
orbs:
|
orbs:
|
||||||
win: circleci/windows@1.0.0
|
win: circleci/windows@1.0.0
|
||||||
codecov: codecov/codecov@1.0.5
|
|
||||||
|
|
||||||
version: 2.1
|
version: 2.1
|
||||||
|
|
||||||
@@ -20,10 +19,13 @@ commands:
|
|||||||
type: string
|
type: string
|
||||||
GOVERSION:
|
GOVERSION:
|
||||||
type: string
|
type: string
|
||||||
|
HOME:
|
||||||
|
type: string
|
||||||
|
default: "~"
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: curl https://dl.google.com/go/go<< parameters.GOVERSION >>.<< parameters.GOOS >>-amd64.tar.gz | tar -C ~/ -xz
|
- run: curl https://dl.google.com/go/go<< parameters.GOVERSION >>.<< parameters.GOOS >>-amd64.tar.gz | tar -C << parameters.HOME >>/ -xz
|
||||||
- run: ~/go/bin/go test ./... -coverprofile=coverage.txt -covermode=atomic
|
- run: << parameters.HOME >>/go/bin/go test ./... -coverprofile=coverage.txt -covermode=atomic
|
||||||
install-go-run-tests-windows:
|
install-go-run-tests-windows:
|
||||||
parameters:
|
parameters:
|
||||||
GOVERSION:
|
GOVERSION:
|
||||||
@@ -61,8 +63,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: TESTARGS="-coverprofile=coverage.txt -covermode=atomic" make ci
|
- run: TESTARGS="-coverprofile=coverage.txt -covermode=atomic" make ci
|
||||||
- codecov/upload:
|
|
||||||
file: coverage.txt
|
|
||||||
test-darwin:
|
test-darwin:
|
||||||
executor: darwin
|
executor: darwin
|
||||||
working_directory: ~/go/github.com/hashicorp/packer
|
working_directory: ~/go/github.com/hashicorp/packer
|
||||||
@@ -70,8 +70,6 @@ jobs:
|
|||||||
- install-go-run-tests-unix:
|
- install-go-run-tests-unix:
|
||||||
GOOS: darwin
|
GOOS: darwin
|
||||||
GOVERSION: "1.16"
|
GOVERSION: "1.16"
|
||||||
- codecov/upload:
|
|
||||||
file: coverage.txt
|
|
||||||
test-windows:
|
test-windows:
|
||||||
executor:
|
executor:
|
||||||
name: win/vs2019
|
name: win/vs2019
|
||||||
@@ -79,8 +77,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- install-go-run-tests-windows:
|
- install-go-run-tests-windows:
|
||||||
GOVERSION: "1.16"
|
GOVERSION: "1.16"
|
||||||
- codecov/upload:
|
|
||||||
file: coverage.txt
|
|
||||||
check-lint:
|
check-lint:
|
||||||
executor: golang
|
executor: golang
|
||||||
resource_class: xlarge
|
resource_class: xlarge
|
||||||
@@ -90,15 +86,6 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
command: make ci-lint
|
command: make ci-lint
|
||||||
no_output_timeout: 30m
|
no_output_timeout: 30m
|
||||||
check-vendor-vs-mod:
|
|
||||||
executor: golang
|
|
||||||
working_directory: /go/src/github.com/hashicorp/packer
|
|
||||||
environment:
|
|
||||||
GO111MODULE: "off"
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- run: GO111MODULE=on go run . --help
|
|
||||||
- run: make check-vendor-vs-mod
|
|
||||||
check-fmt:
|
check-fmt:
|
||||||
executor: golang
|
executor: golang
|
||||||
steps:
|
steps:
|
||||||
@@ -207,7 +194,6 @@ workflows:
|
|||||||
check-code:
|
check-code:
|
||||||
jobs:
|
jobs:
|
||||||
- check-lint
|
- check-lint
|
||||||
- check-vendor-vs-mod
|
|
||||||
- check-fmt
|
- check-fmt
|
||||||
- check-generate
|
- check-generate
|
||||||
build_packer_binaries:
|
build_packer_binaries:
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ async function checkPluginDocs() {
|
|||||||
console.log(`\n${COLOR_BLUE}${repo}${COLOR_RESET} | ${title}`);
|
console.log(`\n${COLOR_BLUE}${repo}${COLOR_RESET} | ${title}`);
|
||||||
console.log(`Fetching docs from release "${version}" …`);
|
console.log(`Fetching docs from release "${version}" …`);
|
||||||
try {
|
try {
|
||||||
|
// Validate that all required properties are present
|
||||||
const undefinedProps = ["title", "repo", "version", "path"].filter(
|
const undefinedProps = ["title", "repo", "version", "path"].filter(
|
||||||
(key) => typeof pluginEntry[key] == "undefined"
|
(key) => typeof pluginEntry[key] == "undefined"
|
||||||
);
|
);
|
||||||
@@ -34,6 +35,22 @@ async function checkPluginDocs() {
|
|||||||
)} are defined. Additional information on this configuration can be found in "website/README.md".`
|
)} 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 docsMdxFiles = await fetchPluginDocs({ repo, tag: version });
|
||||||
const mdxFilesByComponent = docsMdxFiles.reduce((acc, mdxFile) => {
|
const mdxFilesByComponent = docsMdxFiles.reduce((acc, mdxFile) => {
|
||||||
const componentType = mdxFile.filePath.split("/")[1];
|
const componentType = mdxFile.filePath.split("/")[1];
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ test/.env
|
|||||||
*.received.*
|
*.received.*
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
|
vendor/
|
||||||
|
|
||||||
website/.bundle
|
website/.bundle
|
||||||
website/vendor
|
website/vendor
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -85,8 +85,7 @@ run:
|
|||||||
# If invoked with -mod=vendor, the go command assumes that the vendor
|
# If invoked with -mod=vendor, the go command assumes that the vendor
|
||||||
# directory holds the correct copies of dependencies and ignores
|
# directory holds the correct copies of dependencies and ignores
|
||||||
# the dependency descriptions in go.mod.
|
# the dependency descriptions in go.mod.
|
||||||
modules-download-mode: vendor
|
modules-download-mode: readonly
|
||||||
|
|
||||||
|
|
||||||
# output configuration options
|
# output configuration options
|
||||||
output:
|
output:
|
||||||
|
|||||||
+15
-1
@@ -1,4 +1,18 @@
|
|||||||
## 1.7.2 (Upcoming)
|
## 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)
|
## 1.7.1 (March 31, 2021)
|
||||||
|
|
||||||
|
|||||||
-47
@@ -2,10 +2,6 @@
|
|||||||
|
|
||||||
# builders
|
# builders
|
||||||
|
|
||||||
/examples/alicloud/ @chhaj5236 @alexyueer
|
|
||||||
/builder/alicloud/ @chhaj5236 @alexyueer
|
|
||||||
/website/pages/docs/builders/alicloud* @chhaj5236 @alexyueer
|
|
||||||
|
|
||||||
/examples/azure/ @paulmey
|
/examples/azure/ @paulmey
|
||||||
/builder/azure/ @paulmey
|
/builder/azure/ @paulmey
|
||||||
/website/pages/docs/builders/azure* @paulmey
|
/website/pages/docs/builders/azure* @paulmey
|
||||||
@@ -13,16 +9,6 @@
|
|||||||
/builder/digitalocean/ @andrewsomething
|
/builder/digitalocean/ @andrewsomething
|
||||||
/website/pages/docs/builders/digitalocean* @andrewsomething
|
/website/pages/docs/builders/digitalocean* @andrewsomething
|
||||||
|
|
||||||
/builder/hyperv/ @taliesins
|
|
||||||
/website/pages/docs/builders/hyperv* @taliesins
|
|
||||||
|
|
||||||
/examples/jdcloud/ @XiaohanLiang @remrain
|
|
||||||
/builder/jdcloud/ @XiaohanLiang @remrain
|
|
||||||
/website/pages/docs/builders/jdcloud* @XiaohanLiang @remrain
|
|
||||||
|
|
||||||
/builder/linode/ @displague @ctreatma @stvnjacobs @charliekenney23 @phillc
|
|
||||||
/website/pages/docs/builders/linode* @displague @ctreatma @stvnjacobs @charliekenney23 @phillc
|
|
||||||
|
|
||||||
/builder/lxc/ @ChrisLundquist
|
/builder/lxc/ @ChrisLundquist
|
||||||
/website/pages/docs/builders/lxc* @ChrisLundquist
|
/website/pages/docs/builders/lxc* @ChrisLundquist
|
||||||
/test/fixtures/builder-lxc/ @ChrisLundquist
|
/test/fixtures/builder-lxc/ @ChrisLundquist
|
||||||
@@ -43,52 +29,19 @@
|
|||||||
/builder/triton/ @sean-
|
/builder/triton/ @sean-
|
||||||
/website/pages/docs/builders/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
|
|
||||||
|
|
||||||
/builder/scaleway/ @scaleway/devtools
|
|
||||||
/website/pages/docs/builders/scaleway* @scaleway/devtools
|
|
||||||
|
|
||||||
/builder/hcloud/ @LKaemmerling
|
|
||||||
/website/pages/docs/builders/hcloud* @LKaemmerling
|
|
||||||
|
|
||||||
/examples/hyperone/ @m110 @gregorybrzeski @ad-m
|
|
||||||
/builder/hyperone/ @m110 @gregorybrzeski @ad-m
|
|
||||||
/website/pages/docs/builders/hyperone* @m110 @gregorybrzeski @ad-m
|
|
||||||
/test/builder_hyperone* @m110 @gregorybrzeski @ad-m
|
|
||||||
/test/fixtures/builder-hyperone/ @m110 @gregorybrzeski @ad-m
|
|
||||||
|
|
||||||
/examples/ucloud/ @shawnmssu
|
|
||||||
/builder/ucloud/ @shawnmssu
|
|
||||||
/website/pages/docs/builders/ucloud* @shawnmssu
|
|
||||||
|
|
||||||
/builder/yandex/ @GennadySpb @alexanderKhaustov @seukyaso
|
/builder/yandex/ @GennadySpb @alexanderKhaustov @seukyaso
|
||||||
/website/pages/docs/builders/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
|
/examples/tencentcloud/ @likexian
|
||||||
/builder/tencentcloud/ @likexian
|
/builder/tencentcloud/ @likexian
|
||||||
/website/pages/docs/builders/tencentcloud* @likexian
|
/website/pages/docs/builders/tencentcloud* @likexian
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# provisioners
|
# provisioners
|
||||||
|
|
||||||
/examples/ansible/ @bhcleek
|
/examples/ansible/ @bhcleek
|
||||||
/provisioner/ansible/ @bhcleek
|
|
||||||
/provisioner/converge/ @stevendborrelli
|
|
||||||
|
|
||||||
# post-processors
|
# post-processors
|
||||||
|
|
||||||
/post-processor/alicloud-import/ dongxiao.zzh@alibaba-inc.com
|
|
||||||
/post-processor/checksum/ v.tolstov@selfip.ru
|
/post-processor/checksum/ v.tolstov@selfip.ru
|
||||||
/post-processor/googlecompute-export/ crunkleton@google.com
|
|
||||||
/post-processor/yandex-export/ @GennadySpb
|
/post-processor/yandex-export/ @GennadySpb
|
||||||
/post-processor/yandex-import/ @GennadySpb
|
/post-processor/yandex-import/ @GennadySpb
|
||||||
/post-processor/vsphere-template/ nelson@bennu.cl
|
|
||||||
/post-processor/ucloud-import/ @shawnmssu
|
|
||||||
|
|||||||
@@ -58,8 +58,7 @@ install-gen-deps: ## Install dependencies for code generation
|
|||||||
# install` seems to install the last tagged version and we want to install
|
# install` seems to install the last tagged version and we want to install
|
||||||
# master.
|
# master.
|
||||||
@(cd $(TEMPDIR) && GO111MODULE=on go get github.com/alvaroloes/enumer@master)
|
@(cd $(TEMPDIR) && GO111MODULE=on go get github.com/alvaroloes/enumer@master)
|
||||||
@go install ./cmd/struct-markdown
|
@go install github.com/hashicorp/packer-plugin-sdk/cmd/packer-sdc@latest
|
||||||
@go install ./cmd/mapstructure-to-hcl2
|
|
||||||
|
|
||||||
install-lint-deps: ## Install linter dependencies
|
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
|
# 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
|
||||||
@@ -147,7 +146,7 @@ testacc: # install-build-deps generate ## Run acceptance tests
|
|||||||
PACKER_ACC=1 go test -count $(COUNT) -v $(TEST) $(TESTARGS) -timeout=120m
|
PACKER_ACC=1 go test -count $(COUNT) -v $(TEST) $(TESTARGS) -timeout=120m
|
||||||
|
|
||||||
testrace: mode-check vet ## Test with race detection enabled
|
testrace: mode-check vet ## Test with race detection enabled
|
||||||
@GO111MODULE=off go test -count $(COUNT) -race $(TEST) $(TESTARGS) -timeout=3m -p=8
|
@go test -count $(COUNT) -race $(TEST) $(TESTARGS) -timeout=3m -p=8
|
||||||
|
|
||||||
# Runs code coverage and open a html page with report
|
# Runs code coverage and open a html page with report
|
||||||
cover:
|
cover:
|
||||||
@@ -155,13 +154,6 @@ cover:
|
|||||||
go tool cover -html=coverage.out
|
go tool cover -html=coverage.out
|
||||||
rm coverage.out
|
rm coverage.out
|
||||||
|
|
||||||
check-vendor-vs-mod: ## Check that go modules and vendored code are on par
|
|
||||||
@GO111MODULE=on go mod vendor
|
|
||||||
@git diff --exit-code --ignore-space-change --ignore-space-at-eol -- vendor ; if [ $$? -eq 1 ]; then \
|
|
||||||
echo "ERROR: vendor dir is not on par with go modules definition." && \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
vet: ## Vet Go code
|
vet: ## Vet Go code
|
||||||
@go vet $(VET) ; if [ $$? -eq 1 ]; then \
|
@go vet $(VET) ; if [ $$? -eq 1 ]; then \
|
||||||
echo "ERROR: Vet found problems in the code."; \
|
echo "ERROR: Vet found problems in the code."; \
|
||||||
|
|||||||
@@ -4,14 +4,13 @@
|
|||||||
[](https://discuss.hashicorp.com/c/packer)
|
[](https://discuss.hashicorp.com/c/packer)
|
||||||
[](https://pkg.go.dev/github.com/hashicorp/packer)
|
[](https://pkg.go.dev/github.com/hashicorp/packer)
|
||||||
[![GoReportCard][report-badge]][report]
|
[![GoReportCard][report-badge]][report]
|
||||||
[](https://codecov.io/gh/hashicorp/packer)
|
|
||||||
|
|
||||||
[circleci-badge]: https://circleci.com/gh/hashicorp/packer.svg?style=svg
|
[circleci-badge]: https://circleci.com/gh/hashicorp/packer.svg?style=svg
|
||||||
[circleci]: https://app.circleci.com/pipelines/github/hashicorp/packer
|
[circleci]: https://app.circleci.com/pipelines/github/hashicorp/packer
|
||||||
[appveyor-badge]: https://ci.appveyor.com/api/projects/status/miavlgnp989e5obc/branch/master?svg=true
|
[appveyor-badge]: https://ci.appveyor.com/api/projects/status/miavlgnp989e5obc/branch/master?svg=true
|
||||||
[godoc-badge]: https://godoc.org/github.com/hashicorp/packer?status.svg
|
[godoc-badge]: https://godoc.org/github.com/hashicorp/packer?status.svg
|
||||||
[godoc]: https://godoc.org/github.com/hashicorp/packer
|
[godoc]: https://godoc.org/github.com/hashicorp/packer
|
||||||
[report-badge]: https://goreportcard.com/badge/github.com/hashicorp/packer
|
[report-badge]: https://goreportcard.com/badge/github.com/hashicorp/packer
|
||||||
[report]: https://goreportcard.com/report/github.com/hashicorp/packer
|
[report]: https://goreportcard.com/report/github.com/hashicorp/packer
|
||||||
|
|
||||||
<p align="center" style="text-align:center;">
|
<p align="center" style="text-align:center;">
|
||||||
@@ -48,7 +47,7 @@ yourself](https://github.com/hashicorp/packer/blob/master/.github/CONTRIBUTING.m
|
|||||||
|
|
||||||
After Packer is installed, create your first template, which tells Packer
|
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
|
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.
|
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
|
Save this file as `quick-start.pkr.hcl`. Export your AWS credentials as the
|
||||||
`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables.
|
`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables.
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
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,226 +0,0 @@
|
|||||||
//go:generate struct-markdown
|
|
||||||
|
|
||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
"github.com/hashicorp/packer/builder/alicloud/version"
|
|
||||||
"github.com/mitchellh/go-homedir"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config of alicloud
|
|
||||||
type AlicloudAccessConfig struct {
|
|
||||||
// Alicloud access key must be provided unless `profile` is set, but it can
|
|
||||||
// also be sourced from the `ALICLOUD_ACCESS_KEY` environment variable.
|
|
||||||
AlicloudAccessKey string `mapstructure:"access_key" required:"true"`
|
|
||||||
// Alicloud secret key must be provided unless `profile` is set, but it can
|
|
||||||
// also be sourced from the `ALICLOUD_SECRET_KEY` environment variable.
|
|
||||||
AlicloudSecretKey string `mapstructure:"secret_key" required:"true"`
|
|
||||||
// Alicloud region must be provided unless `profile` is set, but it can
|
|
||||||
// also be sourced from the `ALICLOUD_REGION` environment variable.
|
|
||||||
AlicloudRegion string `mapstructure:"region" required:"true"`
|
|
||||||
// The region validation can be skipped if this value is true, the default
|
|
||||||
// value is false.
|
|
||||||
AlicloudSkipValidation bool `mapstructure:"skip_region_validation" required:"false"`
|
|
||||||
// The image validation can be skipped if this value is true, the default
|
|
||||||
// value is false.
|
|
||||||
AlicloudSkipImageValidation bool `mapstructure:"skip_image_validation" required:"false"`
|
|
||||||
// Alicloud profile must be set unless `access_key` is set; it can also be
|
|
||||||
// sourced from the `ALICLOUD_PROFILE` environment variable.
|
|
||||||
AlicloudProfile string `mapstructure:"profile" required:"false"`
|
|
||||||
// Alicloud shared credentials file path. If this file exists, access and
|
|
||||||
// secret keys will be read from this file.
|
|
||||||
AlicloudSharedCredentialsFile string `mapstructure:"shared_credentials_file" required:"false"`
|
|
||||||
// STS access token, can be set through template or by exporting as
|
|
||||||
// environment variable such as `export SECURITY_TOKEN=value`.
|
|
||||||
SecurityToken string `mapstructure:"security_token" required:"false"`
|
|
||||||
|
|
||||||
client *ClientWrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
const Packer = "HashiCorp-Packer"
|
|
||||||
const DefaultRequestReadTimeout = 10 * time.Second
|
|
||||||
|
|
||||||
// Client for AlicloudClient
|
|
||||||
func (c *AlicloudAccessConfig) Client() (*ClientWrapper, error) {
|
|
||||||
if c.client != nil {
|
|
||||||
return c.client, nil
|
|
||||||
}
|
|
||||||
if c.SecurityToken == "" {
|
|
||||||
c.SecurityToken = os.Getenv("SECURITY_TOKEN")
|
|
||||||
}
|
|
||||||
|
|
||||||
var getProviderConfig = func(str string, key string) string {
|
|
||||||
value, err := getConfigFromProfile(c, key)
|
|
||||||
if err == nil && value != nil {
|
|
||||||
str = value.(string)
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "" {
|
|
||||||
c.AlicloudAccessKey = getProviderConfig(c.AlicloudAccessKey, "access_key_id")
|
|
||||||
c.AlicloudSecretKey = getProviderConfig(c.AlicloudSecretKey, "access_key_secret")
|
|
||||||
c.AlicloudRegion = getProviderConfig(c.AlicloudRegion, "region_id")
|
|
||||||
c.SecurityToken = getProviderConfig(c.SecurityToken, "sts_token")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := ecs.NewClientWithStsToken(c.AlicloudRegion, c.AlicloudAccessKey, c.AlicloudSecretKey, c.SecurityToken)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client.AppendUserAgent(Packer, version.AlicloudPluginVersion.FormattedVersion())
|
|
||||||
client.SetReadTimeout(DefaultRequestReadTimeout)
|
|
||||||
c.client = &ClientWrapper{client}
|
|
||||||
|
|
||||||
return c.client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AlicloudAccessConfig) Prepare(ctx *interpolate.Context) []error {
|
|
||||||
var errs []error
|
|
||||||
if err := c.Config(); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.AlicloudRegion == "" {
|
|
||||||
c.AlicloudRegion = os.Getenv("ALICLOUD_REGION")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.AlicloudRegion == "" {
|
|
||||||
errs = append(errs, fmt.Errorf("region option or ALICLOUD_REGION must be provided in template file or environment variables."))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AlicloudAccessConfig) Config() error {
|
|
||||||
if c.AlicloudAccessKey == "" {
|
|
||||||
c.AlicloudAccessKey = os.Getenv("ALICLOUD_ACCESS_KEY")
|
|
||||||
}
|
|
||||||
if c.AlicloudSecretKey == "" {
|
|
||||||
c.AlicloudSecretKey = os.Getenv("ALICLOUD_SECRET_KEY")
|
|
||||||
}
|
|
||||||
if c.AlicloudProfile == "" {
|
|
||||||
c.AlicloudProfile = os.Getenv("ALICLOUD_PROFILE")
|
|
||||||
}
|
|
||||||
if c.AlicloudSharedCredentialsFile == "" {
|
|
||||||
c.AlicloudSharedCredentialsFile = os.Getenv("ALICLOUD_SHARED_CREDENTIALS_FILE")
|
|
||||||
}
|
|
||||||
if (c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "") && c.AlicloudProfile == "" {
|
|
||||||
return fmt.Errorf("ALICLOUD_ACCESS_KEY and ALICLOUD_SECRET_KEY must be set in template file or environment variables.")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AlicloudAccessConfig) ValidateRegion(region string) error {
|
|
||||||
|
|
||||||
supportedRegions, err := c.getSupportedRegions()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, supportedRegion := range supportedRegions {
|
|
||||||
if region == supportedRegion {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("Not a valid alicloud region: %s", region)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AlicloudAccessConfig) getSupportedRegions() ([]string, error) {
|
|
||||||
client, err := c.Client()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
regionsRequest := ecs.CreateDescribeRegionsRequest()
|
|
||||||
regionsResponse, err := client.DescribeRegions(regionsRequest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
validRegions := make([]string, len(regionsResponse.Regions.Region))
|
|
||||||
for _, valid := range regionsResponse.Regions.Region {
|
|
||||||
validRegions = append(validRegions, valid.RegionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return validRegions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConfigFromProfile(c *AlicloudAccessConfig, ProfileKey string) (interface{}, error) {
|
|
||||||
providerConfig := make(map[string]interface{})
|
|
||||||
current := c.AlicloudProfile
|
|
||||||
if current != "" {
|
|
||||||
profilePath, err := homedir.Expand(c.AlicloudSharedCredentialsFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if profilePath == "" {
|
|
||||||
profilePath = fmt.Sprintf("%s/.aliyun/config.json", os.Getenv("HOME"))
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
profilePath = fmt.Sprintf("%s/.aliyun/config.json", os.Getenv("USERPROFILE"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = os.Stat(profilePath)
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
data, err := ioutil.ReadFile(profilePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config := map[string]interface{}{}
|
|
||||||
err = json.Unmarshal(data, &config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, v := range config["profiles"].([]interface{}) {
|
|
||||||
if current == v.(map[string]interface{})["name"] {
|
|
||||||
providerConfig = v.(map[string]interface{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mode := ""
|
|
||||||
if v, ok := providerConfig["mode"]; ok {
|
|
||||||
mode = v.(string)
|
|
||||||
} else {
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
switch ProfileKey {
|
|
||||||
case "access_key_id", "access_key_secret":
|
|
||||||
if mode == "EcsRamRole" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
case "ram_role_name":
|
|
||||||
if mode != "EcsRamRole" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
case "sts_token":
|
|
||||||
if mode != "StsToken" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
case "ram_role_arn", "ram_session_name":
|
|
||||||
if mode != "RamRoleArn" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
case "expired_seconds":
|
|
||||||
if mode != "RamRoleArn" {
|
|
||||||
return float64(0), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return providerConfig[ProfileKey], nil
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testAlicloudAccessConfig() *AlicloudAccessConfig {
|
|
||||||
return &AlicloudAccessConfig{
|
|
||||||
AlicloudAccessKey: "ak",
|
|
||||||
AlicloudSecretKey: "acs",
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAlicloudAccessConfigPrepareRegion(t *testing.T) {
|
|
||||||
c := testAlicloudAccessConfig()
|
|
||||||
|
|
||||||
c.AlicloudRegion = ""
|
|
||||||
if err := c.Prepare(nil); err == nil {
|
|
||||||
t.Fatalf("should have err")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AlicloudRegion = "cn-beijing"
|
|
||||||
if err := c.Prepare(nil); err != nil {
|
|
||||||
t.Fatalf("shouldn't have err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Setenv("ALICLOUD_REGION", "cn-hangzhou")
|
|
||||||
c.AlicloudRegion = ""
|
|
||||||
if err := c.Prepare(nil); err != nil {
|
|
||||||
t.Fatalf("shouldn't have err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AlicloudAccessKey = ""
|
|
||||||
if err := c.Prepare(nil); err == nil {
|
|
||||||
t.Fatalf("should have err")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AlicloudProfile = "default"
|
|
||||||
if err := c.Prepare(nil); err != nil {
|
|
||||||
t.Fatalf("shouldn't have err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AlicloudProfile = ""
|
|
||||||
os.Setenv("ALICLOUD_PROFILE", "default")
|
|
||||||
if err := c.Prepare(nil); err != nil {
|
|
||||||
t.Fatalf("shouldn't have err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AlicloudSkipValidation = false
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Artifact struct {
|
|
||||||
// A map of regions to alicloud image IDs.
|
|
||||||
AlicloudImages map[string]string
|
|
||||||
|
|
||||||
// BuilderId is the unique ID for the builder that created this alicloud image
|
|
||||||
BuilderIdValue string
|
|
||||||
|
|
||||||
// Alcloud connection for performing API stuff.
|
|
||||||
Client *ClientWrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) BuilderId() string {
|
|
||||||
return a.BuilderIdValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Artifact) Files() []string {
|
|
||||||
// We have no files
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Id() string {
|
|
||||||
parts := make([]string, 0, len(a.AlicloudImages))
|
|
||||||
for region, ecsImageId := range a.AlicloudImages {
|
|
||||||
parts = append(parts, fmt.Sprintf("%s:%s", region, ecsImageId))
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(parts)
|
|
||||||
return strings.Join(parts, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) String() string {
|
|
||||||
alicloudImageStrings := make([]string, 0, len(a.AlicloudImages))
|
|
||||||
for region, id := range a.AlicloudImages {
|
|
||||||
single := fmt.Sprintf("%s: %s", region, id)
|
|
||||||
alicloudImageStrings = append(alicloudImageStrings, single)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(alicloudImageStrings)
|
|
||||||
return fmt.Sprintf("Alicloud images were created:\n\n%s", strings.Join(alicloudImageStrings, "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
|
||||||
switch name {
|
|
||||||
case "atlas.artifact.metadata":
|
|
||||||
return a.stateAtlasMetadata()
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Destroy() error {
|
|
||||||
errors := make([]error, 0)
|
|
||||||
|
|
||||||
copyingImages := make(map[string]string, len(a.AlicloudImages))
|
|
||||||
sourceImage := make(map[string]*ecs.Image, 1)
|
|
||||||
for regionId, imageId := range a.AlicloudImages {
|
|
||||||
describeImagesRequest := ecs.CreateDescribeImagesRequest()
|
|
||||||
describeImagesRequest.RegionId = regionId
|
|
||||||
describeImagesRequest.ImageId = imageId
|
|
||||||
describeImagesRequest.Status = ImageStatusQueried
|
|
||||||
imagesResponse, err := a.Client.DescribeImages(describeImagesRequest)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
images := imagesResponse.Images.Image
|
|
||||||
if len(images) == 0 {
|
|
||||||
err := fmt.Errorf("Error retrieving details for alicloud image(%s), no alicloud images found", imageId)
|
|
||||||
errors = append(errors, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if images[0].IsCopied && images[0].Status != ImageStatusAvailable {
|
|
||||||
copyingImages[regionId] = imageId
|
|
||||||
} else {
|
|
||||||
sourceImage[regionId] = &images[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for regionId, imageId := range copyingImages {
|
|
||||||
log.Printf("Cancel copying alicloud image (%s) from region (%s)", imageId, regionId)
|
|
||||||
|
|
||||||
errs := a.unsharedAccountsOnImages(regionId, imageId)
|
|
||||||
if errs != nil {
|
|
||||||
errors = append(errors, errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelImageCopyRequest := ecs.CreateCancelCopyImageRequest()
|
|
||||||
cancelImageCopyRequest.RegionId = regionId
|
|
||||||
cancelImageCopyRequest.ImageId = imageId
|
|
||||||
if _, err := a.Client.CancelCopyImage(cancelImageCopyRequest); err != nil {
|
|
||||||
errors = append(errors, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for regionId, image := range sourceImage {
|
|
||||||
imageId := image.ImageId
|
|
||||||
log.Printf("Delete alicloud image (%s) from region (%s)", imageId, regionId)
|
|
||||||
|
|
||||||
errs := a.unsharedAccountsOnImages(regionId, imageId)
|
|
||||||
if errs != nil {
|
|
||||||
errors = append(errors, errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteImageRequest := ecs.CreateDeleteImageRequest()
|
|
||||||
deleteImageRequest.RegionId = regionId
|
|
||||||
deleteImageRequest.ImageId = imageId
|
|
||||||
if _, err := a.Client.DeleteImage(deleteImageRequest); err != nil {
|
|
||||||
errors = append(errors, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Delete the snapshot of this images
|
|
||||||
for _, diskDevices := range image.DiskDeviceMappings.DiskDeviceMapping {
|
|
||||||
deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest()
|
|
||||||
deleteSnapshotRequest.SnapshotId = diskDevices.SnapshotId
|
|
||||||
_, err := a.Client.DeleteSnapshot(deleteSnapshotRequest)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
if len(errors) == 1 {
|
|
||||||
return errors[0]
|
|
||||||
} else {
|
|
||||||
return &packersdk.MultiError{Errors: errors}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) unsharedAccountsOnImages(regionId string, imageId string) []error {
|
|
||||||
var errors []error
|
|
||||||
|
|
||||||
describeImageShareRequest := ecs.CreateDescribeImageSharePermissionRequest()
|
|
||||||
describeImageShareRequest.RegionId = regionId
|
|
||||||
describeImageShareRequest.ImageId = imageId
|
|
||||||
imageShareResponse, err := a.Client.DescribeImageSharePermission(describeImageShareRequest)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, err)
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
accountsNumber := len(imageShareResponse.Accounts.Account)
|
|
||||||
if accountsNumber > 0 {
|
|
||||||
accounts := make([]string, accountsNumber)
|
|
||||||
for index, account := range imageShareResponse.Accounts.Account {
|
|
||||||
accounts[index] = account.AliyunId
|
|
||||||
}
|
|
||||||
|
|
||||||
modifyImageShareRequest := ecs.CreateModifyImageSharePermissionRequest()
|
|
||||||
modifyImageShareRequest.RegionId = regionId
|
|
||||||
modifyImageShareRequest.ImageId = imageId
|
|
||||||
modifyImageShareRequest.RemoveAccount = &accounts
|
|
||||||
_, err := a.Client.ModifyImageSharePermission(modifyImageShareRequest)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) stateAtlasMetadata() interface{} {
|
|
||||||
metadata := make(map[string]string)
|
|
||||||
for region, imageId := range a.AlicloudImages {
|
|
||||||
k := fmt.Sprintf("region.%s", region)
|
|
||||||
metadata[k] = imageId
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadata
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestArtifact_Impl(t *testing.T) {
|
|
||||||
var _ packersdk.Artifact = new(Artifact)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactId(t *testing.T) {
|
|
||||||
expected := `east:foo,west:bar`
|
|
||||||
|
|
||||||
ecsImages := make(map[string]string)
|
|
||||||
ecsImages["east"] = "foo"
|
|
||||||
ecsImages["west"] = "bar"
|
|
||||||
|
|
||||||
a := &Artifact{
|
|
||||||
AlicloudImages: ecsImages,
|
|
||||||
}
|
|
||||||
|
|
||||||
result := a.Id()
|
|
||||||
if result != expected {
|
|
||||||
t.Fatalf("bad: %s", result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactState_atlasMetadata(t *testing.T) {
|
|
||||||
a := &Artifact{
|
|
||||||
AlicloudImages: map[string]string{
|
|
||||||
"east": "foo",
|
|
||||||
"west": "bar",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := a.State("atlas.artifact.metadata")
|
|
||||||
expected := map[string]string{
|
|
||||||
"region.east": "foo",
|
|
||||||
"region.west": "bar",
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
|
||||||
t.Fatalf("bad: %#v", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
//go:generate mapstructure-to-hcl2 -type Config,AlicloudDiskDevice
|
|
||||||
|
|
||||||
// The alicloud contains a packersdk.Builder implementation that
|
|
||||||
// builds ecs images for alicloud.
|
|
||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The unique ID for this builder
|
|
||||||
const BuilderId = "alibaba.alicloud"
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
|
||||||
AlicloudAccessConfig `mapstructure:",squash"`
|
|
||||||
AlicloudImageConfig `mapstructure:",squash"`
|
|
||||||
RunConfig `mapstructure:",squash"`
|
|
||||||
|
|
||||||
ctx interpolate.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
type Builder struct {
|
|
||||||
config Config
|
|
||||||
runner multistep.Runner
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstanceNetWork string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ALICLOUD_DEFAULT_SHORT_TIMEOUT = 180
|
|
||||||
ALICLOUD_DEFAULT_TIMEOUT = 1800
|
|
||||||
ALICLOUD_DEFAULT_LONG_TIMEOUT = 3600
|
|
||||||
)
|
|
||||||
|
|
||||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
|
||||||
PluginType: BuilderId,
|
|
||||||
Interpolate: true,
|
|
||||||
InterpolateContext: &b.config.ctx,
|
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
|
||||||
Exclude: []string{
|
|
||||||
"run_command",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, raws...)
|
|
||||||
b.config.ctx.EnableEnv = true
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.PackerConfig.PackerForce {
|
|
||||||
b.config.AlicloudImageForceDelete = true
|
|
||||||
b.config.AlicloudImageForceDeleteSnapshots = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accumulate any errors
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, b.config.AlicloudAccessConfig.Prepare(&b.config.ctx)...)
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, b.config.AlicloudImageConfig.Prepare(&b.config.ctx)...)
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
|
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return nil, nil, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
packersdk.LogSecretFilter.Set(b.config.AlicloudAccessKey, b.config.AlicloudSecretKey)
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
|
||||||
|
|
||||||
client, err := b.config.Client()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("config", &b.config)
|
|
||||||
state.Put("client", client)
|
|
||||||
state.Put("hook", hook)
|
|
||||||
state.Put("ui", ui)
|
|
||||||
state.Put("networktype", b.chooseNetworkType())
|
|
||||||
var steps []multistep.Step
|
|
||||||
|
|
||||||
// Build the steps
|
|
||||||
steps = []multistep.Step{
|
|
||||||
&stepPreValidate{
|
|
||||||
AlicloudDestImageName: b.config.AlicloudImageName,
|
|
||||||
ForceDelete: b.config.AlicloudImageForceDelete,
|
|
||||||
},
|
|
||||||
&stepCheckAlicloudSourceImage{
|
|
||||||
SourceECSImageId: b.config.AlicloudSourceImage,
|
|
||||||
},
|
|
||||||
&stepConfigAlicloudKeyPair{
|
|
||||||
Debug: b.config.PackerDebug,
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
DebugKeyPath: fmt.Sprintf("ecs_%s.pem", b.config.PackerBuildName),
|
|
||||||
RegionId: b.config.AlicloudRegion,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if b.chooseNetworkType() == InstanceNetworkVpc {
|
|
||||||
steps = append(steps,
|
|
||||||
&stepConfigAlicloudVPC{
|
|
||||||
VpcId: b.config.VpcId,
|
|
||||||
CidrBlock: b.config.CidrBlock,
|
|
||||||
VpcName: b.config.VpcName,
|
|
||||||
},
|
|
||||||
&stepConfigAlicloudVSwitch{
|
|
||||||
VSwitchId: b.config.VSwitchId,
|
|
||||||
ZoneId: b.config.ZoneId,
|
|
||||||
CidrBlock: b.config.CidrBlock,
|
|
||||||
VSwitchName: b.config.VSwitchName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
steps = append(steps,
|
|
||||||
&stepConfigAlicloudSecurityGroup{
|
|
||||||
SecurityGroupId: b.config.SecurityGroupId,
|
|
||||||
SecurityGroupName: b.config.SecurityGroupId,
|
|
||||||
RegionId: b.config.AlicloudRegion,
|
|
||||||
},
|
|
||||||
&stepCreateAlicloudInstance{
|
|
||||||
IOOptimized: b.config.IOOptimized,
|
|
||||||
InstanceType: b.config.InstanceType,
|
|
||||||
UserData: b.config.UserData,
|
|
||||||
UserDataFile: b.config.UserDataFile,
|
|
||||||
RegionId: b.config.AlicloudRegion,
|
|
||||||
InternetChargeType: b.config.InternetChargeType,
|
|
||||||
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
|
|
||||||
InstanceName: b.config.InstanceName,
|
|
||||||
ZoneId: b.config.ZoneId,
|
|
||||||
})
|
|
||||||
if b.chooseNetworkType() == InstanceNetworkVpc {
|
|
||||||
steps = append(steps, &stepConfigAlicloudEIP{
|
|
||||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
|
||||||
RegionId: b.config.AlicloudRegion,
|
|
||||||
InternetChargeType: b.config.InternetChargeType,
|
|
||||||
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
|
|
||||||
SSHPrivateIp: b.config.SSHPrivateIp,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
steps = append(steps, &stepConfigAlicloudPublicIP{
|
|
||||||
RegionId: b.config.AlicloudRegion,
|
|
||||||
SSHPrivateIp: b.config.SSHPrivateIp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
steps = append(steps,
|
|
||||||
&stepAttachKeyPair{},
|
|
||||||
&stepRunAlicloudInstance{},
|
|
||||||
&communicator.StepConnect{
|
|
||||||
Config: &b.config.RunConfig.Comm,
|
|
||||||
Host: SSHHost(
|
|
||||||
client,
|
|
||||||
b.config.SSHPrivateIp),
|
|
||||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
|
||||||
},
|
|
||||||
&commonsteps.StepProvision{},
|
|
||||||
&commonsteps.StepCleanupTempKeys{
|
|
||||||
Comm: &b.config.RunConfig.Comm,
|
|
||||||
},
|
|
||||||
&stepStopAlicloudInstance{
|
|
||||||
ForceStop: b.config.ForceStopInstance,
|
|
||||||
DisableStop: b.config.DisableStopInstance,
|
|
||||||
},
|
|
||||||
&stepDeleteAlicloudImageSnapshots{
|
|
||||||
AlicloudImageForceDeleteSnapshots: b.config.AlicloudImageForceDeleteSnapshots,
|
|
||||||
AlicloudImageForceDelete: b.config.AlicloudImageForceDelete,
|
|
||||||
AlicloudImageName: b.config.AlicloudImageName,
|
|
||||||
AlicloudImageDestinationRegions: b.config.AlicloudImageConfig.AlicloudImageDestinationRegions,
|
|
||||||
AlicloudImageDestinationNames: b.config.AlicloudImageConfig.AlicloudImageDestinationNames,
|
|
||||||
})
|
|
||||||
|
|
||||||
if b.config.AlicloudImageIgnoreDataDisks {
|
|
||||||
steps = append(steps, &stepCreateAlicloudSnapshot{
|
|
||||||
WaitSnapshotReadyTimeout: b.getSnapshotReadyTimeout(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
steps = append(steps,
|
|
||||||
&stepCreateAlicloudImage{
|
|
||||||
AlicloudImageIgnoreDataDisks: b.config.AlicloudImageIgnoreDataDisks,
|
|
||||||
WaitSnapshotReadyTimeout: b.getSnapshotReadyTimeout(),
|
|
||||||
},
|
|
||||||
&stepCreateTags{
|
|
||||||
Tags: b.config.AlicloudImageTags,
|
|
||||||
},
|
|
||||||
&stepRegionCopyAlicloudImage{
|
|
||||||
AlicloudImageDestinationRegions: b.config.AlicloudImageDestinationRegions,
|
|
||||||
AlicloudImageDestinationNames: b.config.AlicloudImageDestinationNames,
|
|
||||||
RegionId: b.config.AlicloudRegion,
|
|
||||||
},
|
|
||||||
&stepShareAlicloudImage{
|
|
||||||
AlicloudImageShareAccounts: b.config.AlicloudImageShareAccounts,
|
|
||||||
AlicloudImageUNShareAccounts: b.config.AlicloudImageUNShareAccounts,
|
|
||||||
RegionId: b.config.AlicloudRegion,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Run!
|
|
||||||
b.runner = commonsteps.NewRunner(steps, b.config.PackerConfig, ui)
|
|
||||||
b.runner.Run(ctx, state)
|
|
||||||
|
|
||||||
// If there was an error, return that
|
|
||||||
if rawErr, ok := state.GetOk("error"); ok {
|
|
||||||
return nil, rawErr.(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are no ECS images, then just return
|
|
||||||
if _, ok := state.GetOk("alicloudimages"); !ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the artifact and return it
|
|
||||||
artifact := &Artifact{
|
|
||||||
AlicloudImages: state.Get("alicloudimages").(map[string]string),
|
|
||||||
BuilderIdValue: BuilderId,
|
|
||||||
Client: client,
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) chooseNetworkType() InstanceNetWork {
|
|
||||||
if b.isVpcNetRequired() {
|
|
||||||
return InstanceNetworkVpc
|
|
||||||
} else {
|
|
||||||
return InstanceNetworkClassic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) isVpcNetRequired() bool {
|
|
||||||
// UserData and KeyPair only works in VPC
|
|
||||||
return b.isVpcSpecified() || b.isUserDataNeeded() || b.isKeyPairNeeded()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) isVpcSpecified() bool {
|
|
||||||
return b.config.VpcId != "" || b.config.VSwitchId != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) isUserDataNeeded() bool {
|
|
||||||
// Public key setup requires userdata
|
|
||||||
if b.config.RunConfig.Comm.SSHPrivateKeyFile != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.config.UserData != "" || b.config.UserDataFile != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) isKeyPairNeeded() bool {
|
|
||||||
return b.config.Comm.SSHKeyPairName != "" || b.config.Comm.SSHTemporaryKeyPairName != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) getSnapshotReadyTimeout() int {
|
|
||||||
if b.config.WaitSnapshotReadyTimeout > 0 {
|
|
||||||
return b.config.WaitSnapshotReadyTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
return ALICLOUD_DEFAULT_LONG_TIMEOUT
|
|
||||||
}
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
// Code generated by "mapstructure-to-hcl2 -type Config,AlicloudDiskDevice"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlatAlicloudDiskDevice is an auto-generated flat version of AlicloudDiskDevice.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatAlicloudDiskDevice struct {
|
|
||||||
DiskName *string `mapstructure:"disk_name" required:"false" cty:"disk_name" hcl:"disk_name"`
|
|
||||||
DiskCategory *string `mapstructure:"disk_category" required:"false" cty:"disk_category" hcl:"disk_category"`
|
|
||||||
DiskSize *int `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"`
|
|
||||||
SnapshotId *string `mapstructure:"disk_snapshot_id" required:"false" cty:"disk_snapshot_id" hcl:"disk_snapshot_id"`
|
|
||||||
Description *string `mapstructure:"disk_description" required:"false" cty:"disk_description" hcl:"disk_description"`
|
|
||||||
DeleteWithInstance *bool `mapstructure:"disk_delete_with_instance" required:"false" cty:"disk_delete_with_instance" hcl:"disk_delete_with_instance"`
|
|
||||||
Device *string `mapstructure:"disk_device" required:"false" cty:"disk_device" hcl:"disk_device"`
|
|
||||||
Encrypted *bool `mapstructure:"disk_encrypted" required:"false" cty:"disk_encrypted" hcl:"disk_encrypted"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatAlicloudDiskDevice.
|
|
||||||
// FlatAlicloudDiskDevice is an auto-generated flat version of AlicloudDiskDevice.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*AlicloudDiskDevice) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatAlicloudDiskDevice)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a AlicloudDiskDevice.
|
|
||||||
// This spec is used by HCL to read the fields of AlicloudDiskDevice.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatAlicloudDiskDevice.
|
|
||||||
func (*FlatAlicloudDiskDevice) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"disk_name": &hcldec.AttrSpec{Name: "disk_name", Type: cty.String, Required: false},
|
|
||||||
"disk_category": &hcldec.AttrSpec{Name: "disk_category", Type: cty.String, Required: false},
|
|
||||||
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
|
|
||||||
"disk_snapshot_id": &hcldec.AttrSpec{Name: "disk_snapshot_id", Type: cty.String, Required: false},
|
|
||||||
"disk_description": &hcldec.AttrSpec{Name: "disk_description", Type: cty.String, Required: false},
|
|
||||||
"disk_delete_with_instance": &hcldec.AttrSpec{Name: "disk_delete_with_instance", Type: cty.Bool, Required: false},
|
|
||||||
"disk_device": &hcldec.AttrSpec{Name: "disk_device", Type: cty.String, Required: false},
|
|
||||||
"disk_encrypted": &hcldec.AttrSpec{Name: "disk_encrypted", Type: cty.Bool, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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"`
|
|
||||||
AlicloudAccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
|
|
||||||
AlicloudSecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
|
|
||||||
AlicloudRegion *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"`
|
|
||||||
AlicloudSkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
|
|
||||||
AlicloudSkipImageValidation *bool `mapstructure:"skip_image_validation" required:"false" cty:"skip_image_validation" hcl:"skip_image_validation"`
|
|
||||||
AlicloudProfile *string `mapstructure:"profile" required:"false" cty:"profile" hcl:"profile"`
|
|
||||||
AlicloudSharedCredentialsFile *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
|
|
||||||
SecurityToken *string `mapstructure:"security_token" required:"false" cty:"security_token" hcl:"security_token"`
|
|
||||||
AlicloudImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
|
|
||||||
AlicloudImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version" hcl:"image_version"`
|
|
||||||
AlicloudImageDescription *string `mapstructure:"image_description" required:"false" cty:"image_description" hcl:"image_description"`
|
|
||||||
AlicloudImageShareAccounts []string `mapstructure:"image_share_account" required:"false" cty:"image_share_account" hcl:"image_share_account"`
|
|
||||||
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account" cty:"image_unshare_account" hcl:"image_unshare_account"`
|
|
||||||
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions" required:"false" cty:"image_copy_regions" hcl:"image_copy_regions"`
|
|
||||||
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names" required:"false" cty:"image_copy_names" hcl:"image_copy_names"`
|
|
||||||
ImageEncrypted *bool `mapstructure:"image_encrypted" required:"false" cty:"image_encrypted" hcl:"image_encrypted"`
|
|
||||||
AlicloudImageForceDelete *bool `mapstructure:"image_force_delete" required:"false" cty:"image_force_delete" hcl:"image_force_delete"`
|
|
||||||
AlicloudImageForceDeleteSnapshots *bool `mapstructure:"image_force_delete_snapshots" required:"false" cty:"image_force_delete_snapshots" hcl:"image_force_delete_snapshots"`
|
|
||||||
AlicloudImageForceDeleteInstances *bool `mapstructure:"image_force_delete_instances" cty:"image_force_delete_instances" hcl:"image_force_delete_instances"`
|
|
||||||
AlicloudImageIgnoreDataDisks *bool `mapstructure:"image_ignore_data_disks" required:"false" cty:"image_ignore_data_disks" hcl:"image_ignore_data_disks"`
|
|
||||||
AlicloudImageTags map[string]string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"`
|
|
||||||
AlicloudImageTag []config.FlatKeyValue `mapstructure:"tag" required:"false" cty:"tag" hcl:"tag"`
|
|
||||||
ECSSystemDiskMapping *FlatAlicloudDiskDevice `mapstructure:"system_disk_mapping" required:"false" cty:"system_disk_mapping" hcl:"system_disk_mapping"`
|
|
||||||
ECSImagesDiskMappings []FlatAlicloudDiskDevice `mapstructure:"image_disk_mappings" required:"false" cty:"image_disk_mappings" hcl:"image_disk_mappings"`
|
|
||||||
AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" cty:"associate_public_ip_address" hcl:"associate_public_ip_address"`
|
|
||||||
ZoneId *string `mapstructure:"zone_id" required:"false" cty:"zone_id" hcl:"zone_id"`
|
|
||||||
IOOptimized *bool `mapstructure:"io_optimized" required:"false" cty:"io_optimized" hcl:"io_optimized"`
|
|
||||||
InstanceType *string `mapstructure:"instance_type" required:"true" cty:"instance_type" hcl:"instance_type"`
|
|
||||||
Description *string `mapstructure:"description" cty:"description" hcl:"description"`
|
|
||||||
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"`
|
|
||||||
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"`
|
|
||||||
UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file" hcl:"user_data_file"`
|
|
||||||
VpcId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id" hcl:"vpc_id"`
|
|
||||||
VpcName *string `mapstructure:"vpc_name" required:"false" cty:"vpc_name" hcl:"vpc_name"`
|
|
||||||
CidrBlock *string `mapstructure:"vpc_cidr_block" required:"false" cty:"vpc_cidr_block" hcl:"vpc_cidr_block"`
|
|
||||||
VSwitchId *string `mapstructure:"vswitch_id" required:"false" cty:"vswitch_id" hcl:"vswitch_id"`
|
|
||||||
VSwitchName *string `mapstructure:"vswitch_name" required:"false" cty:"vswitch_name" hcl:"vswitch_name"`
|
|
||||||
InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name" hcl:"instance_name"`
|
|
||||||
InternetChargeType *string `mapstructure:"internet_charge_type" required:"false" cty:"internet_charge_type" hcl:"internet_charge_type"`
|
|
||||||
InternetMaxBandwidthOut *int `mapstructure:"internet_max_bandwidth_out" required:"false" cty:"internet_max_bandwidth_out" hcl:"internet_max_bandwidth_out"`
|
|
||||||
WaitSnapshotReadyTimeout *int `mapstructure:"wait_snapshot_ready_timeout" required:"false" cty:"wait_snapshot_ready_timeout" hcl:"wait_snapshot_ready_timeout"`
|
|
||||||
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"`
|
|
||||||
SSHPrivateIp *bool `mapstructure:"ssh_private_ip" required:"false" cty:"ssh_private_ip" hcl:"ssh_private_ip"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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},
|
|
||||||
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
|
|
||||||
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
|
|
||||||
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
|
|
||||||
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
|
|
||||||
"skip_image_validation": &hcldec.AttrSpec{Name: "skip_image_validation", Type: cty.Bool, Required: false},
|
|
||||||
"profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false},
|
|
||||||
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
|
|
||||||
"security_token": &hcldec.AttrSpec{Name: "security_token", Type: cty.String, Required: false},
|
|
||||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
|
||||||
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
|
|
||||||
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
|
|
||||||
"image_share_account": &hcldec.AttrSpec{Name: "image_share_account", Type: cty.List(cty.String), Required: false},
|
|
||||||
"image_unshare_account": &hcldec.AttrSpec{Name: "image_unshare_account", Type: cty.List(cty.String), Required: false},
|
|
||||||
"image_copy_regions": &hcldec.AttrSpec{Name: "image_copy_regions", Type: cty.List(cty.String), Required: false},
|
|
||||||
"image_copy_names": &hcldec.AttrSpec{Name: "image_copy_names", Type: cty.List(cty.String), Required: false},
|
|
||||||
"image_encrypted": &hcldec.AttrSpec{Name: "image_encrypted", Type: cty.Bool, Required: false},
|
|
||||||
"image_force_delete": &hcldec.AttrSpec{Name: "image_force_delete", Type: cty.Bool, Required: false},
|
|
||||||
"image_force_delete_snapshots": &hcldec.AttrSpec{Name: "image_force_delete_snapshots", Type: cty.Bool, Required: false},
|
|
||||||
"image_force_delete_instances": &hcldec.AttrSpec{Name: "image_force_delete_instances", Type: cty.Bool, Required: false},
|
|
||||||
"image_ignore_data_disks": &hcldec.AttrSpec{Name: "image_ignore_data_disks", Type: cty.Bool, Required: false},
|
|
||||||
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"tag": &hcldec.BlockListSpec{TypeName: "tag", Nested: hcldec.ObjectSpec((*config.FlatKeyValue)(nil).HCL2Spec())},
|
|
||||||
"system_disk_mapping": &hcldec.BlockSpec{TypeName: "system_disk_mapping", Nested: hcldec.ObjectSpec((*FlatAlicloudDiskDevice)(nil).HCL2Spec())},
|
|
||||||
"image_disk_mappings": &hcldec.BlockListSpec{TypeName: "image_disk_mappings", Nested: hcldec.ObjectSpec((*FlatAlicloudDiskDevice)(nil).HCL2Spec())},
|
|
||||||
"associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false},
|
|
||||||
"zone_id": &hcldec.AttrSpec{Name: "zone_id", Type: cty.String, Required: false},
|
|
||||||
"io_optimized": &hcldec.AttrSpec{Name: "io_optimized", Type: cty.Bool, Required: false},
|
|
||||||
"instance_type": &hcldec.AttrSpec{Name: "instance_type", Type: cty.String, Required: false},
|
|
||||||
"description": &hcldec.AttrSpec{Name: "description", Type: cty.String, Required: false},
|
|
||||||
"source_image": &hcldec.AttrSpec{Name: "source_image", Type: cty.String, Required: false},
|
|
||||||
"force_stop_instance": &hcldec.AttrSpec{Name: "force_stop_instance", Type: cty.Bool, Required: false},
|
|
||||||
"disable_stop_instance": &hcldec.AttrSpec{Name: "disable_stop_instance", Type: cty.Bool, 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},
|
|
||||||
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
|
|
||||||
"vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false},
|
|
||||||
"vpc_name": &hcldec.AttrSpec{Name: "vpc_name", Type: cty.String, Required: false},
|
|
||||||
"vpc_cidr_block": &hcldec.AttrSpec{Name: "vpc_cidr_block", Type: cty.String, Required: false},
|
|
||||||
"vswitch_id": &hcldec.AttrSpec{Name: "vswitch_id", Type: cty.String, Required: false},
|
|
||||||
"vswitch_name": &hcldec.AttrSpec{Name: "vswitch_name", Type: cty.String, Required: false},
|
|
||||||
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
|
|
||||||
"internet_charge_type": &hcldec.AttrSpec{Name: "internet_charge_type", Type: cty.String, Required: false},
|
|
||||||
"internet_max_bandwidth_out": &hcldec.AttrSpec{Name: "internet_max_bandwidth_out", Type: cty.Number, Required: false},
|
|
||||||
"wait_snapshot_ready_timeout": &hcldec.AttrSpec{Name: "wait_snapshot_ready_timeout", Type: cty.Number, 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},
|
|
||||||
"ssh_private_ip": &hcldec.AttrSpec{Name: "ssh_private_ip", Type: cty.Bool, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,891 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultTestRegion = "cn-beijing"
|
|
||||||
|
|
||||||
func TestBuilderAcc_validateRegion(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if os.Getenv(builderT.TestEnvVar) == "" {
|
|
||||||
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", builderT.TestEnvVar))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
testAccPreCheck(t)
|
|
||||||
|
|
||||||
access := &AlicloudAccessConfig{AlicloudRegion: "cn-beijing"}
|
|
||||||
err := access.Config()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("init AlicloudAccessConfig failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = access.ValidateRegion("cn-hangzhou")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expect pass with valid region id but failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = access.ValidateRegion("invalidRegionId")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Expect failure due to invalid region id but passed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: testBuilderAccBasic,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccBasic = `
|
|
||||||
{ "builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "cn-beijing",
|
|
||||||
"instance_type": "ecs.n1.tiny",
|
|
||||||
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"image_name": "packer-test-basic_{{timestamp}}"
|
|
||||||
}]
|
|
||||||
}`
|
|
||||||
|
|
||||||
func TestBuilderAcc_withDiskSettings(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: testBuilderAccWithDiskSettings,
|
|
||||||
Check: checkImageDisksSettings(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccWithDiskSettings = `
|
|
||||||
{ "builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "cn-beijing",
|
|
||||||
"instance_type": "ecs.n1.tiny",
|
|
||||||
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"image_name": "packer-test-withDiskSettings_{{timestamp}}",
|
|
||||||
"system_disk_mapping": {
|
|
||||||
"disk_size": 60
|
|
||||||
},
|
|
||||||
"image_disk_mappings": [
|
|
||||||
{
|
|
||||||
"disk_name": "datadisk1",
|
|
||||||
"disk_size": 25,
|
|
||||||
"disk_delete_with_instance": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"disk_name": "datadisk2",
|
|
||||||
"disk_size": 25,
|
|
||||||
"disk_delete_with_instance": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}`
|
|
||||||
|
|
||||||
func checkImageDisksSettings() builderT.TestCheckFunc {
|
|
||||||
return func(artifacts []packersdk.Artifact) error {
|
|
||||||
if len(artifacts) > 1 {
|
|
||||||
return fmt.Errorf("more than 1 artifact")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
|
||||||
artifactRaw := artifacts[0]
|
|
||||||
artifact, ok := artifactRaw.(*Artifact)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
|
||||||
}
|
|
||||||
imageId := artifact.AlicloudImages[defaultTestRegion]
|
|
||||||
|
|
||||||
// describe the image, get block devices with a snapshot
|
|
||||||
client, _ := testAliyunClient()
|
|
||||||
|
|
||||||
describeImagesRequest := ecs.CreateDescribeImagesRequest()
|
|
||||||
describeImagesRequest.RegionId = defaultTestRegion
|
|
||||||
describeImagesRequest.ImageId = imageId
|
|
||||||
imagesResponse, err := client.DescribeImages(describeImagesRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("describe images failed due to %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(imagesResponse.Images.Image) == 0 {
|
|
||||||
return fmt.Errorf("image %s generated can not be found", imageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
image := imagesResponse.Images.Image[0]
|
|
||||||
if image.Size != 60 {
|
|
||||||
return fmt.Errorf("the size of image %s should be equal to 60G but got %dG", imageId, image.Size)
|
|
||||||
}
|
|
||||||
if len(image.DiskDeviceMappings.DiskDeviceMapping) != 3 {
|
|
||||||
return fmt.Errorf("image %s should contains 3 disks", imageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
var snapshotIds []string
|
|
||||||
for _, mapping := range image.DiskDeviceMappings.DiskDeviceMapping {
|
|
||||||
if mapping.Type == DiskTypeSystem {
|
|
||||||
if mapping.Size != "60" {
|
|
||||||
return fmt.Errorf("the system snapshot size of image %s should be equal to 60G but got %sG", imageId, mapping.Size)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if mapping.Size != "25" {
|
|
||||||
return fmt.Errorf("the data disk size of image %s should be equal to 25G but got %sG", imageId, mapping.Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshotIds = append(snapshotIds, mapping.SnapshotId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data, _ := json.Marshal(snapshotIds)
|
|
||||||
|
|
||||||
describeSnapshotRequest := ecs.CreateDescribeSnapshotsRequest()
|
|
||||||
describeSnapshotRequest.RegionId = defaultTestRegion
|
|
||||||
describeSnapshotRequest.SnapshotIds = string(data)
|
|
||||||
describeSnapshotsResponse, err := client.DescribeSnapshots(describeSnapshotRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("describe data snapshots failed due to %s", err)
|
|
||||||
}
|
|
||||||
if len(describeSnapshotsResponse.Snapshots.Snapshot) != 2 {
|
|
||||||
return fmt.Errorf("expect %d data snapshots but got %d", len(snapshotIds), len(describeSnapshotsResponse.Snapshots.Snapshot))
|
|
||||||
}
|
|
||||||
|
|
||||||
var dataDiskIds []string
|
|
||||||
for _, snapshot := range describeSnapshotsResponse.Snapshots.Snapshot {
|
|
||||||
dataDiskIds = append(dataDiskIds, snapshot.SourceDiskId)
|
|
||||||
}
|
|
||||||
data, _ = json.Marshal(dataDiskIds)
|
|
||||||
|
|
||||||
describeDisksRequest := ecs.CreateDescribeDisksRequest()
|
|
||||||
describeDisksRequest.RegionId = defaultTestRegion
|
|
||||||
describeDisksRequest.DiskIds = string(data)
|
|
||||||
describeDisksResponse, err := client.DescribeDisks(describeDisksRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("describe snapshots failed due to %s", err)
|
|
||||||
}
|
|
||||||
if len(describeDisksResponse.Disks.Disk) != 0 {
|
|
||||||
return fmt.Errorf("data disks should be deleted but %d left", len(describeDisksResponse.Disks.Disk))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderAcc_withIgnoreDataDisks(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: testBuilderAccIgnoreDataDisks,
|
|
||||||
Check: checkIgnoreDataDisks(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccIgnoreDataDisks = `
|
|
||||||
{ "builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "cn-beijing",
|
|
||||||
"instance_type": "ecs.gn5-c8g1.2xlarge",
|
|
||||||
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"image_name": "packer-test-ignoreDataDisks_{{timestamp}}",
|
|
||||||
"image_ignore_data_disks": true
|
|
||||||
}]
|
|
||||||
}`
|
|
||||||
|
|
||||||
func checkIgnoreDataDisks() builderT.TestCheckFunc {
|
|
||||||
return func(artifacts []packersdk.Artifact) error {
|
|
||||||
if len(artifacts) > 1 {
|
|
||||||
return fmt.Errorf("more than 1 artifact")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
|
||||||
artifactRaw := artifacts[0]
|
|
||||||
artifact, ok := artifactRaw.(*Artifact)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
|
||||||
}
|
|
||||||
imageId := artifact.AlicloudImages[defaultTestRegion]
|
|
||||||
|
|
||||||
// describe the image, get block devices with a snapshot
|
|
||||||
client, _ := testAliyunClient()
|
|
||||||
|
|
||||||
describeImagesRequest := ecs.CreateDescribeImagesRequest()
|
|
||||||
describeImagesRequest.RegionId = defaultTestRegion
|
|
||||||
describeImagesRequest.ImageId = imageId
|
|
||||||
imagesResponse, err := client.DescribeImages(describeImagesRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("describe images failed due to %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(imagesResponse.Images.Image) == 0 {
|
|
||||||
return fmt.Errorf("image %s generated can not be found", imageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
image := imagesResponse.Images.Image[0]
|
|
||||||
if len(image.DiskDeviceMappings.DiskDeviceMapping) != 1 {
|
|
||||||
return fmt.Errorf("image %s should only contain one disks", imageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderAcc_windows(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: testBuilderAccWindows,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccWindows = `
|
|
||||||
{ "builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "cn-beijing",
|
|
||||||
"instance_type": "ecs.n1.tiny",
|
|
||||||
"source_image":"winsvr_64_dtcC_1809_en-us_40G_alibase_20190318.vhd",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"communicator": "winrm",
|
|
||||||
"winrm_port": 5985,
|
|
||||||
"winrm_username": "Administrator",
|
|
||||||
"winrm_password": "Test1234",
|
|
||||||
"image_name": "packer-test-windows_{{timestamp}}",
|
|
||||||
"user_data_file": "../../../examples/alicloud/basic/winrm_enable_userdata.ps1"
|
|
||||||
}]
|
|
||||||
}`
|
|
||||||
|
|
||||||
func TestBuilderAcc_regionCopy(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: testBuilderAccRegionCopy,
|
|
||||||
Check: checkRegionCopy([]string{"cn-hangzhou", "cn-shenzhen"}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccRegionCopy = `
|
|
||||||
{
|
|
||||||
"builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "cn-beijing",
|
|
||||||
"instance_type": "ecs.n1.tiny",
|
|
||||||
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"image_name": "packer-test-regionCopy_{{timestamp}}",
|
|
||||||
"image_copy_regions": ["cn-hangzhou", "cn-shenzhen"],
|
|
||||||
"image_copy_names": ["packer-copy-test-hz_{{timestamp}}", "packer-copy-test-sz_{{timestamp}}"]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
func checkRegionCopy(regions []string) builderT.TestCheckFunc {
|
|
||||||
return func(artifacts []packersdk.Artifact) error {
|
|
||||||
if len(artifacts) > 1 {
|
|
||||||
return fmt.Errorf("more than 1 artifact")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
|
||||||
artifactRaw := artifacts[0]
|
|
||||||
artifact, ok := artifactRaw.(*Artifact)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that we copied to only the regions given
|
|
||||||
regionSet := make(map[string]struct{})
|
|
||||||
for _, r := range regions {
|
|
||||||
regionSet[r] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for r := range artifact.AlicloudImages {
|
|
||||||
if r == "cn-beijing" {
|
|
||||||
delete(regionSet, r)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := regionSet[r]; !ok {
|
|
||||||
return fmt.Errorf("region %s is not the target region but found in artifacts", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(regionSet, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(regionSet) > 0 {
|
|
||||||
return fmt.Errorf("following region(s) should be the copying targets but corresponding artifact(s) not found: %#v", regionSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, _ := testAliyunClient()
|
|
||||||
for regionId, imageId := range artifact.AlicloudImages {
|
|
||||||
describeImagesRequest := ecs.CreateDescribeImagesRequest()
|
|
||||||
describeImagesRequest.RegionId = regionId
|
|
||||||
describeImagesRequest.ImageId = imageId
|
|
||||||
describeImagesRequest.Status = ImageStatusQueried
|
|
||||||
describeImagesResponse, err := client.DescribeImages(describeImagesRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("describe generated image %s failed due to %s", imageId, err)
|
|
||||||
}
|
|
||||||
if len(describeImagesResponse.Images.Image) == 0 {
|
|
||||||
return fmt.Errorf("image %s in artifacts can not be found", imageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
image := describeImagesResponse.Images.Image[0]
|
|
||||||
if image.IsCopied && regionId == "cn-hangzhou" && !strings.HasPrefix(image.ImageName, "packer-copy-test-hz") {
|
|
||||||
return fmt.Errorf("the name of image %s in artifacts should begin with %s but got %s", imageId, "packer-copy-test-hz", image.ImageName)
|
|
||||||
}
|
|
||||||
if image.IsCopied && regionId == "cn-shenzhen" && !strings.HasPrefix(image.ImageName, "packer-copy-test-sz") {
|
|
||||||
return fmt.Errorf("the name of image %s in artifacts should begin with %s but got %s", imageId, "packer-copy-test-sz", image.ImageName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderAcc_forceDelete(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
// Build the same alicloud image twice, with ecs_image_force_delete on the second run
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: buildForceDeregisterConfig("false", "delete"),
|
|
||||||
SkipArtifactTeardown: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: buildForceDeregisterConfig("true", "delete"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildForceDeregisterConfig(val, name string) string {
|
|
||||||
return fmt.Sprintf(testBuilderAccForceDelete, val, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccForceDelete = `
|
|
||||||
{
|
|
||||||
"builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "cn-beijing",
|
|
||||||
"instance_type": "ecs.n1.tiny",
|
|
||||||
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"image_force_delete": "%s",
|
|
||||||
"image_name": "packer-test-forceDelete_%s"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
func TestBuilderAcc_ECSImageSharing(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: testBuilderAccSharing,
|
|
||||||
Check: checkECSImageSharing("1309208528360047"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccSharing = `
|
|
||||||
{
|
|
||||||
"builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "cn-beijing",
|
|
||||||
"instance_type": "ecs.n1.tiny",
|
|
||||||
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"image_name": "packer-test-ECSImageSharing_{{timestamp}}",
|
|
||||||
"image_share_account":["1309208528360047"]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
func checkECSImageSharing(uid string) builderT.TestCheckFunc {
|
|
||||||
return func(artifacts []packersdk.Artifact) error {
|
|
||||||
if len(artifacts) > 1 {
|
|
||||||
return fmt.Errorf("more than 1 artifact")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
|
||||||
artifactRaw := artifacts[0]
|
|
||||||
artifact, ok := artifactRaw.(*Artifact)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// describe the image, get block devices with a snapshot
|
|
||||||
client, _ := testAliyunClient()
|
|
||||||
|
|
||||||
describeImageShareRequest := ecs.CreateDescribeImageSharePermissionRequest()
|
|
||||||
describeImageShareRequest.RegionId = "cn-beijing"
|
|
||||||
describeImageShareRequest.ImageId = artifact.AlicloudImages["cn-beijing"]
|
|
||||||
imageShareResponse, err := client.DescribeImageSharePermission(describeImageShareRequest)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) "+
|
|
||||||
"in ECS Image Sharing Test: %s", artifact, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(imageShareResponse.Accounts.Account) != 1 && imageShareResponse.Accounts.Account[0].AliyunId != uid {
|
|
||||||
return fmt.Errorf("share account is incorrect %d", len(imageShareResponse.Accounts.Account))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
destImageName := "delete"
|
|
||||||
|
|
||||||
// Build the same alicloud image name twice, with force_delete_snapshot on the second run
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: buildForceDeleteSnapshotConfig("false", destImageName),
|
|
||||||
SkipArtifactTeardown: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get image data by image image name
|
|
||||||
client, _ := testAliyunClient()
|
|
||||||
|
|
||||||
describeImagesRequest := ecs.CreateDescribeImagesRequest()
|
|
||||||
describeImagesRequest.RegionId = "cn-beijing"
|
|
||||||
describeImagesRequest.ImageName = "packer-test-" + destImageName
|
|
||||||
images, _ := client.DescribeImages(describeImagesRequest)
|
|
||||||
|
|
||||||
image := images.Images.Image[0]
|
|
||||||
|
|
||||||
// Get snapshot ids for image
|
|
||||||
snapshotIds := []string{}
|
|
||||||
for _, device := range image.DiskDeviceMappings.DiskDeviceMapping {
|
|
||||||
if device.Device != "" && device.SnapshotId != "" {
|
|
||||||
snapshotIds = append(snapshotIds, device.SnapshotId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: buildForceDeleteSnapshotConfig("true", destImageName),
|
|
||||||
Check: checkSnapshotsDeleted(snapshotIds),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildForceDeleteSnapshotConfig(val, name string) string {
|
|
||||||
return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccForceDeleteSnapshot = `
|
|
||||||
{
|
|
||||||
"builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "cn-beijing",
|
|
||||||
"instance_type": "ecs.n1.tiny",
|
|
||||||
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"image_force_delete_snapshots": "%s",
|
|
||||||
"image_force_delete": "%s",
|
|
||||||
"image_name": "packer-test-%s"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
func checkSnapshotsDeleted(snapshotIds []string) builderT.TestCheckFunc {
|
|
||||||
return func(artifacts []packersdk.Artifact) error {
|
|
||||||
// Verify the snapshots are gone
|
|
||||||
client, _ := testAliyunClient()
|
|
||||||
data, err := json.Marshal(snapshotIds)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Marshal snapshotIds array failed %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
describeSnapshotsRequest := ecs.CreateDescribeSnapshotsRequest()
|
|
||||||
describeSnapshotsRequest.RegionId = "cn-beijing"
|
|
||||||
describeSnapshotsRequest.SnapshotIds = string(data)
|
|
||||||
snapshotResp, err := client.DescribeSnapshots(describeSnapshotsRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Query snapshot failed %v", err)
|
|
||||||
}
|
|
||||||
snapshots := snapshotResp.Snapshots.Snapshot
|
|
||||||
if len(snapshots) > 0 {
|
|
||||||
return fmt.Errorf("Snapshots weren't successfully deleted by " +
|
|
||||||
"`ecs_image_force_delete_snapshots`")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderAcc_imageTags(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: testBuilderAccImageTags,
|
|
||||||
Check: checkImageTags(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccImageTags = `
|
|
||||||
{ "builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "cn-beijing",
|
|
||||||
"instance_type": "ecs.n1.tiny",
|
|
||||||
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
|
|
||||||
"ssh_username": "root",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"image_name": "packer-test-imageTags_{{timestamp}}",
|
|
||||||
"tags": {
|
|
||||||
"TagKey1": "TagValue1",
|
|
||||||
"TagKey2": "TagValue2"
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}`
|
|
||||||
|
|
||||||
func checkImageTags() builderT.TestCheckFunc {
|
|
||||||
return func(artifacts []packersdk.Artifact) error {
|
|
||||||
if len(artifacts) > 1 {
|
|
||||||
return fmt.Errorf("more than 1 artifact")
|
|
||||||
}
|
|
||||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
|
||||||
artifactRaw := artifacts[0]
|
|
||||||
artifact, ok := artifactRaw.(*Artifact)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
|
||||||
}
|
|
||||||
imageId := artifact.AlicloudImages[defaultTestRegion]
|
|
||||||
|
|
||||||
// describe the image, get block devices with a snapshot
|
|
||||||
client, _ := testAliyunClient()
|
|
||||||
|
|
||||||
describeImageTagsRequest := ecs.CreateDescribeTagsRequest()
|
|
||||||
describeImageTagsRequest.RegionId = defaultTestRegion
|
|
||||||
describeImageTagsRequest.ResourceType = TagResourceImage
|
|
||||||
describeImageTagsRequest.ResourceId = imageId
|
|
||||||
imageTagsResponse, err := client.DescribeTags(describeImageTagsRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) "+
|
|
||||||
"in ECS Image Tags Test: %s", artifact, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(imageTagsResponse.Tags.Tag) != 2 {
|
|
||||||
return fmt.Errorf("expect 2 tags set on image %s but got %d", imageId, len(imageTagsResponse.Tags.Tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tag := range imageTagsResponse.Tags.Tag {
|
|
||||||
if tag.TagKey != "TagKey1" && tag.TagKey != "TagKey2" {
|
|
||||||
return fmt.Errorf("tags on image %s should be within the list of TagKey1 and TagKey2 but got %s", imageId, tag.TagKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tag.TagKey == "TagKey1" && tag.TagValue != "TagValue1" {
|
|
||||||
return fmt.Errorf("the value for tag %s on image %s should be TagValue1 but got %s", tag.TagKey, imageId, tag.TagValue)
|
|
||||||
} else if tag.TagKey == "TagKey2" && tag.TagValue != "TagValue2" {
|
|
||||||
return fmt.Errorf("the value for tag %s on image %s should be TagValue2 but got %s", tag.TagKey, imageId, tag.TagValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describeImagesRequest := ecs.CreateDescribeImagesRequest()
|
|
||||||
describeImagesRequest.RegionId = defaultTestRegion
|
|
||||||
describeImagesRequest.ImageId = imageId
|
|
||||||
imagesResponse, err := client.DescribeImages(describeImagesRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("describe images failed due to %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(imagesResponse.Images.Image) == 0 {
|
|
||||||
return fmt.Errorf("image %s generated can not be found", imageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
image := imagesResponse.Images.Image[0]
|
|
||||||
for _, mapping := range image.DiskDeviceMappings.DiskDeviceMapping {
|
|
||||||
describeSnapshotTagsRequest := ecs.CreateDescribeTagsRequest()
|
|
||||||
describeSnapshotTagsRequest.RegionId = defaultTestRegion
|
|
||||||
describeSnapshotTagsRequest.ResourceType = TagResourceSnapshot
|
|
||||||
describeSnapshotTagsRequest.ResourceId = mapping.SnapshotId
|
|
||||||
snapshotTagsResponse, err := client.DescribeTags(describeSnapshotTagsRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get snapshot tags due to %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(snapshotTagsResponse.Tags.Tag) != 2 {
|
|
||||||
return fmt.Errorf("expect 2 tags set on snapshot %s but got %d", mapping.SnapshotId, len(snapshotTagsResponse.Tags.Tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tag := range snapshotTagsResponse.Tags.Tag {
|
|
||||||
if tag.TagKey != "TagKey1" && tag.TagKey != "TagKey2" {
|
|
||||||
return fmt.Errorf("tags on snapshot %s should be within the list of TagKey1 and TagKey2 but got %s", mapping.SnapshotId, tag.TagKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tag.TagKey == "TagKey1" && tag.TagValue != "TagValue1" {
|
|
||||||
return fmt.Errorf("the value for tag %s on snapshot %s should be TagValue1 but got %s", tag.TagKey, mapping.SnapshotId, tag.TagValue)
|
|
||||||
} else if tag.TagKey == "TagKey2" && tag.TagValue != "TagValue2" {
|
|
||||||
return fmt.Errorf("the value for tag %s on snapshot %s should be TagValue2 but got %s", tag.TagKey, mapping.SnapshotId, tag.TagValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderAcc_dataDiskEncrypted(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: testBuilderAccDataDiskEncrypted,
|
|
||||||
Check: checkDataDiskEncrypted(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccDataDiskEncrypted = `
|
|
||||||
{ "builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "cn-beijing",
|
|
||||||
"instance_type": "ecs.n1.tiny",
|
|
||||||
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"image_name": "packer-test-dataDiskEncrypted_{{timestamp}}",
|
|
||||||
"image_disk_mappings": [
|
|
||||||
{
|
|
||||||
"disk_name": "data_disk1",
|
|
||||||
"disk_size": 25,
|
|
||||||
"disk_encrypted": true,
|
|
||||||
"disk_delete_with_instance": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"disk_name": "data_disk2",
|
|
||||||
"disk_size": 35,
|
|
||||||
"disk_encrypted": false,
|
|
||||||
"disk_delete_with_instance": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"disk_name": "data_disk3",
|
|
||||||
"disk_size": 45,
|
|
||||||
"disk_delete_with_instance": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}`
|
|
||||||
|
|
||||||
func checkDataDiskEncrypted() builderT.TestCheckFunc {
|
|
||||||
return func(artifacts []packersdk.Artifact) error {
|
|
||||||
if len(artifacts) > 1 {
|
|
||||||
return fmt.Errorf("more than 1 artifact")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
|
||||||
artifactRaw := artifacts[0]
|
|
||||||
artifact, ok := artifactRaw.(*Artifact)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
|
||||||
}
|
|
||||||
imageId := artifact.AlicloudImages[defaultTestRegion]
|
|
||||||
|
|
||||||
// describe the image, get block devices with a snapshot
|
|
||||||
client, _ := testAliyunClient()
|
|
||||||
|
|
||||||
describeImagesRequest := ecs.CreateDescribeImagesRequest()
|
|
||||||
describeImagesRequest.RegionId = defaultTestRegion
|
|
||||||
describeImagesRequest.ImageId = imageId
|
|
||||||
imagesResponse, err := client.DescribeImages(describeImagesRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("describe images failed due to %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(imagesResponse.Images.Image) == 0 {
|
|
||||||
return fmt.Errorf("image %s generated can not be found", imageId)
|
|
||||||
}
|
|
||||||
image := imagesResponse.Images.Image[0]
|
|
||||||
|
|
||||||
var snapshotIds []string
|
|
||||||
for _, mapping := range image.DiskDeviceMappings.DiskDeviceMapping {
|
|
||||||
snapshotIds = append(snapshotIds, mapping.SnapshotId)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, _ := json.Marshal(snapshotIds)
|
|
||||||
|
|
||||||
describeSnapshotRequest := ecs.CreateDescribeSnapshotsRequest()
|
|
||||||
describeSnapshotRequest.RegionId = defaultTestRegion
|
|
||||||
describeSnapshotRequest.SnapshotIds = string(data)
|
|
||||||
describeSnapshotsResponse, err := client.DescribeSnapshots(describeSnapshotRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("describe data snapshots failed due to %s", err)
|
|
||||||
}
|
|
||||||
if len(describeSnapshotsResponse.Snapshots.Snapshot) != 4 {
|
|
||||||
return fmt.Errorf("expect %d data snapshots but got %d", len(snapshotIds), len(describeSnapshotsResponse.Snapshots.Snapshot))
|
|
||||||
}
|
|
||||||
snapshots := describeSnapshotsResponse.Snapshots.Snapshot
|
|
||||||
for _, snapshot := range snapshots {
|
|
||||||
if snapshot.SourceDiskType == DiskTypeSystem {
|
|
||||||
if snapshot.Encrypted != false {
|
|
||||||
return fmt.Errorf("the system snapshot expected to be non-encrypted but got true")
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if snapshot.SourceDiskSize == "25" && snapshot.Encrypted != true {
|
|
||||||
return fmt.Errorf("the first snapshot expected to be encrypted but got false")
|
|
||||||
}
|
|
||||||
|
|
||||||
if snapshot.SourceDiskSize == "35" && snapshot.Encrypted != false {
|
|
||||||
return fmt.Errorf("the second snapshot expected to be non-encrypted but got true")
|
|
||||||
}
|
|
||||||
|
|
||||||
if snapshot.SourceDiskSize == "45" && snapshot.Encrypted != false {
|
|
||||||
return fmt.Errorf("the third snapshot expected to be non-encrypted but got true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderAcc_systemDiskEncrypted(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() {
|
|
||||||
testAccPreCheck(t)
|
|
||||||
},
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: testBuilderAccSystemDiskEncrypted,
|
|
||||||
Check: checkSystemDiskEncrypted(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccSystemDiskEncrypted = `
|
|
||||||
{
|
|
||||||
"builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "cn-beijing",
|
|
||||||
"instance_type": "ecs.n1.tiny",
|
|
||||||
"source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"image_name": "packer-test_{{timestamp}}",
|
|
||||||
"image_encrypted": "true"
|
|
||||||
}]
|
|
||||||
}`
|
|
||||||
|
|
||||||
func checkSystemDiskEncrypted() builderT.TestCheckFunc {
|
|
||||||
return func(artifacts []packersdk.Artifact) error {
|
|
||||||
if len(artifacts) > 1 {
|
|
||||||
return fmt.Errorf("more than 1 artifact")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
|
||||||
artifactRaw := artifacts[0]
|
|
||||||
artifact, ok := artifactRaw.(*Artifact)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// describe the image, get block devices with a snapshot
|
|
||||||
client, _ := testAliyunClient()
|
|
||||||
imageId := artifact.AlicloudImages[defaultTestRegion]
|
|
||||||
|
|
||||||
describeImagesRequest := ecs.CreateDescribeImagesRequest()
|
|
||||||
describeImagesRequest.RegionId = defaultTestRegion
|
|
||||||
describeImagesRequest.ImageId = imageId
|
|
||||||
describeImagesRequest.Status = ImageStatusQueried
|
|
||||||
imagesResponse, err := client.DescribeImages(describeImagesRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("describe images failed due to %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(imagesResponse.Images.Image) == 0 {
|
|
||||||
return fmt.Errorf("image %s generated can not be found", imageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
image := imagesResponse.Images.Image[0]
|
|
||||||
if image.IsCopied == false {
|
|
||||||
return fmt.Errorf("image %s generated expexted to be copied but false", image.ImageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
describeSnapshotRequest := ecs.CreateDescribeSnapshotsRequest()
|
|
||||||
describeSnapshotRequest.RegionId = defaultTestRegion
|
|
||||||
describeSnapshotRequest.SnapshotIds = fmt.Sprintf("[\"%s\"]", image.DiskDeviceMappings.DiskDeviceMapping[0].SnapshotId)
|
|
||||||
describeSnapshotsResponse, err := client.DescribeSnapshots(describeSnapshotRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("describe system snapshots failed due to %s", err)
|
|
||||||
}
|
|
||||||
snapshots := describeSnapshotsResponse.Snapshots.Snapshot[0]
|
|
||||||
|
|
||||||
if snapshots.Encrypted != true {
|
|
||||||
return fmt.Errorf("system snapshot of image %s expected to be encrypted but got false", imageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAccPreCheck(t *testing.T) {
|
|
||||||
if v := os.Getenv("ALICLOUD_ACCESS_KEY"); v == "" {
|
|
||||||
t.Fatal("ALICLOUD_ACCESS_KEY must be set for acceptance tests")
|
|
||||||
}
|
|
||||||
|
|
||||||
if v := os.Getenv("ALICLOUD_SECRET_KEY"); v == "" {
|
|
||||||
t.Fatal("ALICLOUD_SECRET_KEY must be set for acceptance tests")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAliyunClient() (*ClientWrapper, error) {
|
|
||||||
access := &AlicloudAccessConfig{AlicloudRegion: "cn-beijing"}
|
|
||||||
err := access.Config()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client, err := access.Client()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
helperconfig "github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testBuilderConfig() map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"access_key": "foo",
|
|
||||||
"secret_key": "bar",
|
|
||||||
"source_image": "foo",
|
|
||||||
"instance_type": "ecs.n1.tiny",
|
|
||||||
"region": "cn-beijing",
|
|
||||||
"ssh_username": "root",
|
|
||||||
"image_name": "foo",
|
|
||||||
"io_optimized": true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &Builder{}
|
|
||||||
if _, ok := raw.(packersdk.Builder); !ok {
|
|
||||||
t.Fatalf("Builder should be a builder")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilder_Prepare_BadType(t *testing.T) {
|
|
||||||
b := &Builder{}
|
|
||||||
c := map[string]interface{}{
|
|
||||||
"access_key": []string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, warnings, err := b.Prepare(c)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("prepare should fail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_ECSImageName(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testBuilderConfig()
|
|
||||||
|
|
||||||
// Test good
|
|
||||||
config["image_name"] = "ecs.n1.tiny"
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test bad
|
|
||||||
config["ecs_image_name"] = "foo {{"
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test bad
|
|
||||||
delete(config, "image_name")
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testBuilderConfig()
|
|
||||||
|
|
||||||
// Add a random key
|
|
||||||
config["i_should_not_be_valid"] = true
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_Devices(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testBuilderConfig()
|
|
||||||
config["system_disk_mapping"] = map[string]interface{}{
|
|
||||||
"disk_category": "cloud",
|
|
||||||
"disk_description": "system disk",
|
|
||||||
"disk_name": "system_disk",
|
|
||||||
"disk_size": 60,
|
|
||||||
}
|
|
||||||
config["image_disk_mappings"] = []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"disk_category": "cloud_efficiency",
|
|
||||||
"disk_name": "data_disk1",
|
|
||||||
"disk_size": 100,
|
|
||||||
"disk_snapshot_id": "s-1",
|
|
||||||
"disk_description": "data disk1",
|
|
||||||
"disk_device": "/dev/xvdb",
|
|
||||||
"disk_delete_with_instance": false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"disk_name": "data_disk2",
|
|
||||||
"disk_device": "/dev/xvdc",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
expected := AlicloudDiskDevice{
|
|
||||||
DiskCategory: "cloud",
|
|
||||||
Description: "system disk",
|
|
||||||
DiskName: "system_disk",
|
|
||||||
DiskSize: 60,
|
|
||||||
Encrypted: helperconfig.TriUnset,
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(b.config.ECSSystemDiskMapping, expected) {
|
|
||||||
t.Fatalf("system disk is not set properly, actual: %v; expected: %v", b.config.ECSSystemDiskMapping, expected)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(b.config.ECSImagesDiskMappings, []AlicloudDiskDevice{
|
|
||||||
{
|
|
||||||
DiskCategory: "cloud_efficiency",
|
|
||||||
DiskName: "data_disk1",
|
|
||||||
DiskSize: 100,
|
|
||||||
SnapshotId: "s-1",
|
|
||||||
Description: "data disk1",
|
|
||||||
Device: "/dev/xvdb",
|
|
||||||
DeleteWithInstance: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DiskName: "data_disk2",
|
|
||||||
Device: "/dev/xvdc",
|
|
||||||
},
|
|
||||||
}) {
|
|
||||||
t.Fatalf("data disks are not set properly, actual: %#v", b.config.ECSImagesDiskMappings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_IgnoreDataDisks(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testBuilderConfig()
|
|
||||||
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.AlicloudImageIgnoreDataDisks != false {
|
|
||||||
t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", false, b.config.AlicloudImageIgnoreDataDisks)
|
|
||||||
}
|
|
||||||
|
|
||||||
config["image_ignore_data_disks"] = "false"
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.AlicloudImageIgnoreDataDisks != false {
|
|
||||||
t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", false, b.config.AlicloudImageIgnoreDataDisks)
|
|
||||||
}
|
|
||||||
|
|
||||||
config["image_ignore_data_disks"] = "true"
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.AlicloudImageIgnoreDataDisks != true {
|
|
||||||
t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", true, b.config.AlicloudImageIgnoreDataDisks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_WaitSnapshotReadyTimeout(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testBuilderConfig()
|
|
||||||
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.WaitSnapshotReadyTimeout != 0 {
|
|
||||||
t.Fatalf("wait_snapshot_ready_timeout is not set properly, expect: %d, actual: %d", 0, b.config.WaitSnapshotReadyTimeout)
|
|
||||||
}
|
|
||||||
if b.getSnapshotReadyTimeout() != ALICLOUD_DEFAULT_LONG_TIMEOUT {
|
|
||||||
t.Fatalf("default timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_LONG_TIMEOUT, b.getSnapshotReadyTimeout())
|
|
||||||
}
|
|
||||||
|
|
||||||
config["wait_snapshot_ready_timeout"] = ALICLOUD_DEFAULT_TIMEOUT
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.WaitSnapshotReadyTimeout != ALICLOUD_DEFAULT_TIMEOUT {
|
|
||||||
t.Fatalf("wait_snapshot_ready_timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_TIMEOUT, b.config.WaitSnapshotReadyTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.getSnapshotReadyTimeout() != ALICLOUD_DEFAULT_TIMEOUT {
|
|
||||||
t.Fatalf("default timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_TIMEOUT, b.getSnapshotReadyTimeout())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ClientWrapper struct {
|
|
||||||
*ecs.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
InstanceStatusRunning = "Running"
|
|
||||||
InstanceStatusStarting = "Starting"
|
|
||||||
InstanceStatusStopped = "Stopped"
|
|
||||||
InstanceStatusStopping = "Stopping"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ImageStatusWaiting = "Waiting"
|
|
||||||
ImageStatusCreating = "Creating"
|
|
||||||
ImageStatusCreateFailed = "CreateFailed"
|
|
||||||
ImageStatusAvailable = "Available"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ImageStatusQueried = fmt.Sprintf("%s,%s,%s,%s", ImageStatusWaiting, ImageStatusCreating, ImageStatusCreateFailed, ImageStatusAvailable)
|
|
||||||
|
|
||||||
const (
|
|
||||||
SnapshotStatusAll = "all"
|
|
||||||
SnapshotStatusProgressing = "progressing"
|
|
||||||
SnapshotStatusAccomplished = "accomplished"
|
|
||||||
SnapshotStatusFailed = "failed"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DiskStatusInUse = "In_use"
|
|
||||||
DiskStatusAvailable = "Available"
|
|
||||||
DiskStatusAttaching = "Attaching"
|
|
||||||
DiskStatusDetaching = "Detaching"
|
|
||||||
DiskStatusCreating = "Creating"
|
|
||||||
DiskStatusReIniting = "ReIniting"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
VpcStatusPending = "Pending"
|
|
||||||
VpcStatusAvailable = "Available"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
VSwitchStatusPending = "Pending"
|
|
||||||
VSwitchStatusAvailable = "Available"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
EipStatusAssociating = "Associating"
|
|
||||||
EipStatusUnassociating = "Unassociating"
|
|
||||||
EipStatusInUse = "InUse"
|
|
||||||
EipStatusAvailable = "Available"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ImageOwnerSystem = "system"
|
|
||||||
ImageOwnerSelf = "self"
|
|
||||||
ImageOwnerOthers = "others"
|
|
||||||
ImageOwnerMarketplace = "marketplace"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IOOptimizedNone = "none"
|
|
||||||
IOOptimizedOptimized = "optimized"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
InstanceNetworkClassic = "classic"
|
|
||||||
InstanceNetworkVpc = "vpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DiskTypeSystem = "system"
|
|
||||||
DiskTypeData = "data"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
TagResourceImage = "image"
|
|
||||||
TagResourceInstance = "instance"
|
|
||||||
TagResourceSnapshot = "snapshot"
|
|
||||||
TagResourceDisk = "disk"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IpProtocolAll = "all"
|
|
||||||
IpProtocolTCP = "tcp"
|
|
||||||
IpProtocolUDP = "udp"
|
|
||||||
IpProtocolICMP = "icmp"
|
|
||||||
IpProtocolGRE = "gre"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
NicTypeInternet = "internet"
|
|
||||||
NicTypeIntranet = "intranet"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DefaultPortRange = "-1/-1"
|
|
||||||
DefaultCidrIp = "0.0.0.0/0"
|
|
||||||
DefaultCidrBlock = "172.16.0.0/24"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultRetryInterval = 5 * time.Second
|
|
||||||
defaultRetryTimes = 12
|
|
||||||
shortRetryTimes = 36
|
|
||||||
mediumRetryTimes = 360
|
|
||||||
longRetryTimes = 720
|
|
||||||
)
|
|
||||||
|
|
||||||
type WaitForExpectEvalResult struct {
|
|
||||||
evalPass bool
|
|
||||||
stopRetry bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
WaitForExpectSuccess = WaitForExpectEvalResult{
|
|
||||||
evalPass: true,
|
|
||||||
stopRetry: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitForExpectToRetry = WaitForExpectEvalResult{
|
|
||||||
evalPass: false,
|
|
||||||
stopRetry: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitForExpectFailToStop = WaitForExpectEvalResult{
|
|
||||||
evalPass: false,
|
|
||||||
stopRetry: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type WaitForExpectArgs struct {
|
|
||||||
RequestFunc func() (responses.AcsResponse, error)
|
|
||||||
EvalFunc func(response responses.AcsResponse, err error) WaitForExpectEvalResult
|
|
||||||
RetryInterval time.Duration
|
|
||||||
RetryTimes int
|
|
||||||
RetryTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientWrapper) WaitForExpected(args *WaitForExpectArgs) (responses.AcsResponse, error) {
|
|
||||||
if args.RetryInterval <= 0 {
|
|
||||||
args.RetryInterval = defaultRetryInterval
|
|
||||||
}
|
|
||||||
if args.RetryTimes <= 0 {
|
|
||||||
args.RetryTimes = defaultRetryTimes
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeoutPoint time.Time
|
|
||||||
if args.RetryTimeout > 0 {
|
|
||||||
timeoutPoint = time.Now().Add(args.RetryTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastResponse responses.AcsResponse
|
|
||||||
var lastError error
|
|
||||||
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
if args.RetryTimeout > 0 && time.Now().After(timeoutPoint) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.RetryTimeout <= 0 && i >= args.RetryTimes {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := args.RequestFunc()
|
|
||||||
lastResponse = response
|
|
||||||
lastError = err
|
|
||||||
|
|
||||||
evalResult := args.EvalFunc(response, err)
|
|
||||||
if evalResult.evalPass {
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
if evalResult.stopRetry {
|
|
||||||
return response, err
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(args.RetryInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
if lastError == nil {
|
|
||||||
lastError = fmt.Errorf("<no error>")
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.RetryTimeout > 0 {
|
|
||||||
return lastResponse, fmt.Errorf("evaluate failed after %d seconds timeout with %d seconds retry interval: %s", int(args.RetryTimeout.Seconds()), int(args.RetryInterval.Seconds()), lastError)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lastResponse, fmt.Errorf("evaluate failed after %d times retry with %d seconds retry interval: %s", args.RetryTimes, int(args.RetryInterval.Seconds()), lastError)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientWrapper) WaitForInstanceStatus(regionId string, instanceId string, expectedStatus string) (responses.AcsResponse, error) {
|
|
||||||
return c.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
request := ecs.CreateDescribeInstancesRequest()
|
|
||||||
request.RegionId = regionId
|
|
||||||
request.InstanceIds = fmt.Sprintf("[\"%s\"]", instanceId)
|
|
||||||
return c.DescribeInstances(request)
|
|
||||||
},
|
|
||||||
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
|
|
||||||
if err != nil {
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
}
|
|
||||||
|
|
||||||
instancesResponse := response.(*ecs.DescribeInstancesResponse)
|
|
||||||
instances := instancesResponse.Instances.Instance
|
|
||||||
for _, instance := range instances {
|
|
||||||
if instance.Status == expectedStatus {
|
|
||||||
return WaitForExpectSuccess
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
},
|
|
||||||
RetryTimes: mediumRetryTimes,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientWrapper) WaitForImageStatus(regionId string, imageId string, expectedStatus string, timeout time.Duration) (responses.AcsResponse, error) {
|
|
||||||
return c.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
request := ecs.CreateDescribeImagesRequest()
|
|
||||||
request.RegionId = regionId
|
|
||||||
request.ImageId = imageId
|
|
||||||
request.Status = ImageStatusQueried
|
|
||||||
return c.DescribeImages(request)
|
|
||||||
},
|
|
||||||
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
|
|
||||||
if err != nil {
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
}
|
|
||||||
|
|
||||||
imagesResponse := response.(*ecs.DescribeImagesResponse)
|
|
||||||
images := imagesResponse.Images.Image
|
|
||||||
for _, image := range images {
|
|
||||||
if image.Status == expectedStatus {
|
|
||||||
return WaitForExpectSuccess
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
},
|
|
||||||
RetryTimeout: timeout,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientWrapper) WaitForSnapshotStatus(regionId string, snapshotId string, expectedStatus string, timeout time.Duration) (responses.AcsResponse, error) {
|
|
||||||
return c.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
request := ecs.CreateDescribeSnapshotsRequest()
|
|
||||||
request.RegionId = regionId
|
|
||||||
request.SnapshotIds = fmt.Sprintf("[\"%s\"]", snapshotId)
|
|
||||||
return c.DescribeSnapshots(request)
|
|
||||||
},
|
|
||||||
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
|
|
||||||
if err != nil {
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshotsResponse := response.(*ecs.DescribeSnapshotsResponse)
|
|
||||||
snapshots := snapshotsResponse.Snapshots.Snapshot
|
|
||||||
for _, snapshot := range snapshots {
|
|
||||||
if snapshot.Status == expectedStatus {
|
|
||||||
return WaitForExpectSuccess
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
},
|
|
||||||
RetryTimeout: timeout,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type EvalErrorType bool
|
|
||||||
|
|
||||||
const (
|
|
||||||
EvalRetryErrorType = EvalErrorType(true)
|
|
||||||
EvalNotRetryErrorType = EvalErrorType(false)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *ClientWrapper) EvalCouldRetryResponse(evalErrors []string, evalErrorType EvalErrorType) func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
|
|
||||||
return func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
|
|
||||||
if err == nil {
|
|
||||||
return WaitForExpectSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
e, ok := err.(errors.Error)
|
|
||||||
if !ok {
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
}
|
|
||||||
|
|
||||||
if evalErrorType == EvalRetryErrorType && !ContainsInArray(evalErrors, e.ErrorCode()) {
|
|
||||||
return WaitForExpectFailToStop
|
|
||||||
}
|
|
||||||
|
|
||||||
if evalErrorType == EvalNotRetryErrorType && ContainsInArray(evalErrors, e.ErrorCode()) {
|
|
||||||
return WaitForExpectFailToStop
|
|
||||||
}
|
|
||||||
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWaitForExpectedExceedRetryTimes(t *testing.T) {
|
|
||||||
c := ClientWrapper{}
|
|
||||||
|
|
||||||
iter := 0
|
|
||||||
waitDone := make(chan bool, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
_, _ = c.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
iter++
|
|
||||||
return nil, fmt.Errorf("test: let iteration %d failed", iter)
|
|
||||||
},
|
|
||||||
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("need retry: %s\n", err)
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
}
|
|
||||||
|
|
||||||
return WaitForExpectSuccess
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
waitDone <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-waitDone:
|
|
||||||
if iter != defaultRetryTimes {
|
|
||||||
t.Fatalf("WaitForExpected should terminate at the %d iterations", defaultRetryTimes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWaitForExpectedExceedRetryTimeout(t *testing.T) {
|
|
||||||
c := ClientWrapper{}
|
|
||||||
|
|
||||||
expectTimeout := 10 * time.Second
|
|
||||||
iter := 0
|
|
||||||
waitDone := make(chan bool, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
_, _ = c.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
iter++
|
|
||||||
return nil, fmt.Errorf("test: let iteration %d failed", iter)
|
|
||||||
},
|
|
||||||
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("need retry: %s\n", err)
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
}
|
|
||||||
|
|
||||||
return WaitForExpectSuccess
|
|
||||||
},
|
|
||||||
RetryTimeout: expectTimeout,
|
|
||||||
})
|
|
||||||
|
|
||||||
waitDone <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
timeTolerance := 1 * time.Second
|
|
||||||
select {
|
|
||||||
case <-waitDone:
|
|
||||||
if iter > int(expectTimeout/defaultRetryInterval) {
|
|
||||||
t.Fatalf("WaitForExpected should terminate before the %d iterations", int(expectTimeout/defaultRetryInterval))
|
|
||||||
}
|
|
||||||
case <-time.After(expectTimeout + timeTolerance):
|
|
||||||
t.Fatalf("WaitForExpected should terminate within %f seconds", (expectTimeout + timeTolerance).Seconds())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
//go:generate struct-markdown
|
|
||||||
|
|
||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The "AlicloudDiskDevice" object us used for the `ECSSystemDiskMapping` and
|
|
||||||
// `ECSImagesDiskMappings` options, and contains the following fields:
|
|
||||||
type AlicloudDiskDevice struct {
|
|
||||||
// The value of disk name is blank by default. [2,
|
|
||||||
// 128] English or Chinese characters, must begin with an
|
|
||||||
// uppercase/lowercase letter or Chinese character. Can contain numbers,
|
|
||||||
// ., _ and -. The disk name will appear on the console. It cannot
|
|
||||||
// begin with `http://` or `https://`.
|
|
||||||
DiskName string `mapstructure:"disk_name" required:"false"`
|
|
||||||
// Category of the system disk. Optional values are:
|
|
||||||
// - cloud - general cloud disk
|
|
||||||
// - cloud_efficiency - efficiency cloud disk
|
|
||||||
// - cloud_ssd - cloud SSD
|
|
||||||
DiskCategory string `mapstructure:"disk_category" required:"false"`
|
|
||||||
// Size of the system disk, measured in GiB. Value
|
|
||||||
// range: [20, 500]. The specified value must be equal to or greater
|
|
||||||
// than max{20, ImageSize}. Default value: max{40, ImageSize}.
|
|
||||||
DiskSize int `mapstructure:"disk_size" required:"false"`
|
|
||||||
// Snapshots are used to create the data
|
|
||||||
// disk After this parameter is specified, Size is ignored. The actual
|
|
||||||
// size of the created disk is the size of the specified snapshot.
|
|
||||||
// This field is only used in the ECSImagesDiskMappings option, not
|
|
||||||
// the ECSSystemDiskMapping option.
|
|
||||||
SnapshotId string `mapstructure:"disk_snapshot_id" required:"false"`
|
|
||||||
// The value of disk description is blank by
|
|
||||||
// default. [2, 256] characters. The disk description will appear on the
|
|
||||||
// console. It cannot begin with `http://` or `https://`.
|
|
||||||
Description string `mapstructure:"disk_description" required:"false"`
|
|
||||||
// Whether or not the disk is
|
|
||||||
// released along with the instance:
|
|
||||||
DeleteWithInstance bool `mapstructure:"disk_delete_with_instance" required:"false"`
|
|
||||||
// Device information of the related instance:
|
|
||||||
// such as /dev/xvdb It is null unless the Status is In_use.
|
|
||||||
Device string `mapstructure:"disk_device" required:"false"`
|
|
||||||
// Whether or not to encrypt the data disk.
|
|
||||||
// If this option is set to true, the data disk will be encryped and
|
|
||||||
// corresponding snapshot in the target image will also be encrypted. By
|
|
||||||
// default, if this is an extra data disk, Packer will not encrypt the
|
|
||||||
// data disk. Otherwise, Packer will keep the encryption setting to what
|
|
||||||
// it was in the source image. Please refer to Introduction of ECS disk
|
|
||||||
// encryption for more details.
|
|
||||||
Encrypted config.Trilean `mapstructure:"disk_encrypted" required:"false"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// The "AlicloudDiskDevices" object is used to define disk mappings for your
|
|
||||||
// instance.
|
|
||||||
type AlicloudDiskDevices struct {
|
|
||||||
// Image disk mapping for the system disk.
|
|
||||||
// See the [disk device configuration](#disk-devices-configuration) section
|
|
||||||
// for more information on options.
|
|
||||||
// Usage example:
|
|
||||||
//
|
|
||||||
// ```json
|
|
||||||
// "builders": [{
|
|
||||||
// "type":"alicloud-ecs",
|
|
||||||
// "system_disk_mapping": {
|
|
||||||
// "disk_size": 50,
|
|
||||||
// "disk_name": "mydisk"
|
|
||||||
// },
|
|
||||||
// ...
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
ECSSystemDiskMapping AlicloudDiskDevice `mapstructure:"system_disk_mapping" required:"false"`
|
|
||||||
// Add one or more data disks to the image.
|
|
||||||
// See the [disk device configuration](#disk-devices-configuration) section
|
|
||||||
// for more information on options.
|
|
||||||
// Usage example:
|
|
||||||
//
|
|
||||||
// ```json
|
|
||||||
// "builders": [{
|
|
||||||
// "type":"alicloud-ecs",
|
|
||||||
// "image_disk_mappings": [
|
|
||||||
// {
|
|
||||||
// "disk_snapshot_id": "someid",
|
|
||||||
// "disk_device": "dev/xvdb"
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// ...
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings" required:"false"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlicloudImageConfig struct {
|
|
||||||
// The name of the user-defined image, [2, 128] English or Chinese
|
|
||||||
// characters. It must begin with an uppercase/lowercase letter or a
|
|
||||||
// Chinese character, and may contain numbers, `_` or `-`. It cannot begin
|
|
||||||
// with `http://` or `https://`.
|
|
||||||
AlicloudImageName string `mapstructure:"image_name" required:"true"`
|
|
||||||
// The version number of the image, with a length limit of 1 to 40 English
|
|
||||||
// characters.
|
|
||||||
AlicloudImageVersion string `mapstructure:"image_version" required:"false"`
|
|
||||||
// The description of the image, with a length limit of 0 to 256
|
|
||||||
// characters. Leaving it blank means null, which is the default value. It
|
|
||||||
// cannot begin with `http://` or `https://`.
|
|
||||||
AlicloudImageDescription string `mapstructure:"image_description" required:"false"`
|
|
||||||
// The IDs of to-be-added Aliyun accounts to which the image is shared. The
|
|
||||||
// number of accounts is 1 to 10. If number of accounts is greater than 10,
|
|
||||||
// this parameter is ignored.
|
|
||||||
AlicloudImageShareAccounts []string `mapstructure:"image_share_account" required:"false"`
|
|
||||||
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account"`
|
|
||||||
// Copy to the destination regionIds.
|
|
||||||
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions" required:"false"`
|
|
||||||
// The name of the destination image, [2, 128] English or Chinese
|
|
||||||
// characters. It must begin with an uppercase/lowercase letter or a
|
|
||||||
// Chinese character, and may contain numbers, _ or -. It cannot begin with
|
|
||||||
// `http://` or `https://`.
|
|
||||||
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names" required:"false"`
|
|
||||||
// Whether or not to encrypt the target images, including those
|
|
||||||
// copied if image_copy_regions is specified. If this option is set to
|
|
||||||
// true, a temporary image will be created from the provisioned instance in
|
|
||||||
// the main region and an encrypted copy will be generated in the same
|
|
||||||
// region. By default, Packer will keep the encryption setting to what it
|
|
||||||
// was in the source image.
|
|
||||||
ImageEncrypted config.Trilean `mapstructure:"image_encrypted" required:"false"`
|
|
||||||
// If this value is true, when the target image names including those
|
|
||||||
// copied are duplicated with existing images, it will delete the existing
|
|
||||||
// images and then create the target images, otherwise, the creation will
|
|
||||||
// fail. The default value is false. Check `image_name` and
|
|
||||||
// `image_copy_names` options for names of target images. If
|
|
||||||
// [-force](/docs/commands/build#force) option is provided in `build`
|
|
||||||
// command, this option can be omitted and taken as true.
|
|
||||||
AlicloudImageForceDelete bool `mapstructure:"image_force_delete" required:"false"`
|
|
||||||
// If this value is true, when delete the duplicated existing images, the
|
|
||||||
// source snapshots of those images will be delete either. If
|
|
||||||
// [-force](/docs/commands/build#force) option is provided in `build`
|
|
||||||
// command, this option can be omitted and taken as true.
|
|
||||||
AlicloudImageForceDeleteSnapshots bool `mapstructure:"image_force_delete_snapshots" required:"false"`
|
|
||||||
AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"`
|
|
||||||
// If this value is true, the image created will not include any snapshot
|
|
||||||
// of data disks. This option would be useful for any circumstance that
|
|
||||||
// default data disks with instance types are not concerned. The default
|
|
||||||
// value is false.
|
|
||||||
AlicloudImageIgnoreDataDisks bool `mapstructure:"image_ignore_data_disks" required:"false"`
|
|
||||||
// The region validation can be skipped if this value is true, the default
|
|
||||||
// value is false.
|
|
||||||
AlicloudImageSkipRegionValidation bool `mapstructure:"skip_region_validation" required:"false"`
|
|
||||||
// Key/value pair tags applied to the destination image and relevant
|
|
||||||
// snapshots.
|
|
||||||
AlicloudImageTags map[string]string `mapstructure:"tags" required:"false"`
|
|
||||||
// Same as [`tags`](#tags) but defined as a singular repeatable block
|
|
||||||
// containing a `key` and a `value` field. In HCL2 mode the
|
|
||||||
// [`dynamic_block`](/docs/templates/hcl_templates/expressions#dynamic-blocks)
|
|
||||||
// will allow you to create those programatically.
|
|
||||||
AlicloudImageTag config.KeyValues `mapstructure:"tag" required:"false"`
|
|
||||||
AlicloudDiskDevices `mapstructure:",squash"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AlicloudImageConfig) Prepare(ctx *interpolate.Context) []error {
|
|
||||||
var errs []error
|
|
||||||
errs = append(errs, c.AlicloudImageTag.CopyOn(&c.AlicloudImageTags)...)
|
|
||||||
if c.AlicloudImageName == "" {
|
|
||||||
errs = append(errs, fmt.Errorf("image_name must be specified"))
|
|
||||||
} else if len(c.AlicloudImageName) < 2 || len(c.AlicloudImageName) > 128 {
|
|
||||||
errs = append(errs, fmt.Errorf("image_name must less than 128 letters and more than 1 letters"))
|
|
||||||
} else if strings.HasPrefix(c.AlicloudImageName, "http://") ||
|
|
||||||
strings.HasPrefix(c.AlicloudImageName, "https://") {
|
|
||||||
errs = append(errs, fmt.Errorf("image_name can't start with 'http://' or 'https://'"))
|
|
||||||
}
|
|
||||||
reg := regexp.MustCompile(`\s+`)
|
|
||||||
if reg.FindString(c.AlicloudImageName) != "" {
|
|
||||||
errs = append(errs, fmt.Errorf("image_name can't include spaces"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.AlicloudImageDestinationRegions) > 0 {
|
|
||||||
regionSet := make(map[string]struct{})
|
|
||||||
regions := make([]string, 0, len(c.AlicloudImageDestinationRegions))
|
|
||||||
|
|
||||||
for _, region := range c.AlicloudImageDestinationRegions {
|
|
||||||
// If we already saw the region, then don't look again
|
|
||||||
if _, ok := regionSet[region]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark that we saw the region
|
|
||||||
regionSet[region] = struct{}{}
|
|
||||||
regions = append(regions, region)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AlicloudImageDestinationRegions = regions
|
|
||||||
}
|
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testAlicloudImageConfig() *AlicloudImageConfig {
|
|
||||||
return &AlicloudImageConfig{
|
|
||||||
AlicloudImageName: "foo",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestECSImageConfigPrepare_name(t *testing.T) {
|
|
||||||
c := testAlicloudImageConfig()
|
|
||||||
if err := c.Prepare(nil); err != nil {
|
|
||||||
t.Fatalf("shouldn't have err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AlicloudImageName = ""
|
|
||||||
if err := c.Prepare(nil); err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAMIConfigPrepare_regions(t *testing.T) {
|
|
||||||
c := testAlicloudImageConfig()
|
|
||||||
c.AlicloudImageDestinationRegions = nil
|
|
||||||
if err := c.Prepare(nil); err != nil {
|
|
||||||
t.Fatalf("shouldn't have err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AlicloudImageDestinationRegions = []string{"cn-beijing", "cn-hangzhou", "eu-central-1"}
|
|
||||||
if err := c.Prepare(nil); err != nil {
|
|
||||||
t.Fatalf("bad: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AlicloudImageDestinationRegions = nil
|
|
||||||
c.AlicloudImageSkipRegionValidation = true
|
|
||||||
if err := c.Prepare(nil); err != nil {
|
|
||||||
t.Fatal("shouldn't have error")
|
|
||||||
}
|
|
||||||
c.AlicloudImageSkipRegionValidation = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestECSImageConfigPrepare_imageTags(t *testing.T) {
|
|
||||||
c := testAlicloudImageConfig()
|
|
||||||
c.AlicloudImageTags = map[string]string{
|
|
||||||
"TagKey1": "TagValue1",
|
|
||||||
"TagKey2": "TagValue2",
|
|
||||||
}
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if len(c.AlicloudImageTags) != 2 || c.AlicloudImageTags["TagKey1"] != "TagValue1" ||
|
|
||||||
c.AlicloudImageTags["TagKey2"] != "TagValue2" {
|
|
||||||
t.Fatalf("invalid value, expected: %s, actual: %s", map[string]string{
|
|
||||||
"TagKey1": "TagValue1",
|
|
||||||
"TagKey2": "TagValue2",
|
|
||||||
}, c.AlicloudImageTags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func cleanUpMessage(state multistep.StateBag, module string) {
|
|
||||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
|
||||||
_, halted := state.GetOk(multistep.StateHalted)
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
if cancelled || halted {
|
|
||||||
ui.Say(fmt.Sprintf("Deleting %s because of cancellation or error...", module))
|
|
||||||
} else {
|
|
||||||
ui.Say(fmt.Sprintf("Cleaning up '%s'", module))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func halt(state multistep.StateBag, err error, prefix string) multistep.StepAction {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
if prefix != "" {
|
|
||||||
err = fmt.Errorf("%s: %s", prefix, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertNumber(value int) string {
|
|
||||||
if value <= 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.Itoa(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ContainsInArray(arr []string, value string) bool {
|
|
||||||
for _, item := range arr {
|
|
||||||
if item == value {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
//go:generate struct-markdown
|
|
||||||
|
|
||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RunConfig struct {
|
|
||||||
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
|
|
||||||
// ID of the zone to which the disk belongs.
|
|
||||||
ZoneId string `mapstructure:"zone_id" required:"false"`
|
|
||||||
// Whether an ECS instance is I/O optimized or not. If this option is not
|
|
||||||
// provided, the value will be determined by product API according to what
|
|
||||||
// `instance_type` is used.
|
|
||||||
IOOptimized config.Trilean `mapstructure:"io_optimized" required:"false"`
|
|
||||||
// Type of the instance. For values, see [Instance Type
|
|
||||||
// Table](https://www.alibabacloud.com/help/doc-detail/25378.htm?spm=a3c0i.o25499en.a3.9.14a36ac8iYqKRA).
|
|
||||||
// You can also obtain the latest instance type table by invoking the
|
|
||||||
// [Querying Instance Type
|
|
||||||
// Table](https://intl.aliyun.com/help/doc-detail/25620.htm?spm=a3c0i.o25499en.a3.6.Dr1bik)
|
|
||||||
// interface.
|
|
||||||
InstanceType string `mapstructure:"instance_type" required:"true"`
|
|
||||||
Description string `mapstructure:"description"`
|
|
||||||
// This is the base image id which you want to
|
|
||||||
// create your customized images.
|
|
||||||
AlicloudSourceImage string `mapstructure:"source_image" required:"true"`
|
|
||||||
// Whether to force shutdown upon device
|
|
||||||
// restart. The default value is `false`.
|
|
||||||
//
|
|
||||||
// If it is set to `false`, the system is shut down normally; if it is set to
|
|
||||||
// `true`, the system is forced to shut down.
|
|
||||||
ForceStopInstance bool `mapstructure:"force_stop_instance" required:"false"`
|
|
||||||
// If this option is set to true, Packer
|
|
||||||
// will not stop the instance for you, and you need to make sure the instance
|
|
||||||
// will be stopped in the final provisioner command. Otherwise, Packer will
|
|
||||||
// timeout while waiting the instance to be stopped. This option is provided
|
|
||||||
// for some specific scenarios that you want to stop the instance by yourself.
|
|
||||||
// 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"`
|
|
||||||
// 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
|
|
||||||
// to the default security group. If the default group doesn’t exist, or the
|
|
||||||
// number of instances in it has reached the maximum limit, a new security
|
|
||||||
// group will be created automatically.
|
|
||||||
SecurityGroupId string `mapstructure:"security_group_id" required:"false"`
|
|
||||||
// The security group name. The default value
|
|
||||||
// is blank. [2, 128] English or Chinese characters, must begin with an
|
|
||||||
// uppercase/lowercase letter or Chinese character. Can contain numbers, .,
|
|
||||||
// _ or -. It cannot begin with `http://` or `https://`.
|
|
||||||
SecurityGroupName string `mapstructure:"security_group_name" required:"false"`
|
|
||||||
// User data to apply when launching the instance. Note
|
|
||||||
// that you need to be careful about escaping characters due to the templates
|
|
||||||
// being JSON. It is often more convenient to use user_data_file, instead.
|
|
||||||
// Packer will not automatically wait for a user script to finish before
|
|
||||||
// shutting down the instance this must be handled in a provisioner.
|
|
||||||
UserData string `mapstructure:"user_data" required:"false"`
|
|
||||||
// Path to a file that will be used for the user
|
|
||||||
// data when launching the instance.
|
|
||||||
UserDataFile string `mapstructure:"user_data_file" required:"false"`
|
|
||||||
// VPC ID allocated by the system.
|
|
||||||
VpcId string `mapstructure:"vpc_id" required:"false"`
|
|
||||||
// The VPC name. The default value is blank. [2, 128]
|
|
||||||
// English or Chinese characters, must begin with an uppercase/lowercase
|
|
||||||
// letter or Chinese character. Can contain numbers, _ and -. The disk
|
|
||||||
// description will appear on the console. Cannot begin with `http://` or
|
|
||||||
// `https://`.
|
|
||||||
VpcName string `mapstructure:"vpc_name" required:"false"`
|
|
||||||
// Value options: 192.168.0.0/16 and
|
|
||||||
// 172.16.0.0/16. When not specified, the default value is 172.16.0.0/16.
|
|
||||||
CidrBlock string `mapstructure:"vpc_cidr_block" required:"false"`
|
|
||||||
// The ID of the VSwitch to be used.
|
|
||||||
VSwitchId string `mapstructure:"vswitch_id" required:"false"`
|
|
||||||
// The ID of the VSwitch to be used.
|
|
||||||
VSwitchName string `mapstructure:"vswitch_name" required:"false"`
|
|
||||||
// Display name of the instance, which is a string of 2 to 128 Chinese or
|
|
||||||
// English characters. It must begin with an uppercase/lowercase letter or
|
|
||||||
// a Chinese character and can contain numerals, `.`, `_`, or `-`. The
|
|
||||||
// instance name is displayed on the Alibaba Cloud console. If this
|
|
||||||
// parameter is not specified, the default value is InstanceId of the
|
|
||||||
// instance. It cannot begin with `http://` or `https://`.
|
|
||||||
InstanceName string `mapstructure:"instance_name" required:"false"`
|
|
||||||
// Internet charge type, which can be
|
|
||||||
// `PayByTraffic` or `PayByBandwidth`. Optional values:
|
|
||||||
// - `PayByBandwidth`
|
|
||||||
// - `PayByTraffic`
|
|
||||||
//
|
|
||||||
// If this parameter is not specified, the default value is `PayByBandwidth`.
|
|
||||||
// For the regions out of China, currently only support `PayByTraffic`, you
|
|
||||||
// must set it manfully.
|
|
||||||
InternetChargeType string `mapstructure:"internet_charge_type" required:"false"`
|
|
||||||
// Maximum outgoing bandwidth to the
|
|
||||||
// public network, measured in Mbps (Mega bits per second).
|
|
||||||
//
|
|
||||||
// Value range:
|
|
||||||
// - `PayByBandwidth`: \[0, 100\]. If this parameter is not specified, API
|
|
||||||
// automatically sets it to 0 Mbps.
|
|
||||||
// - `PayByTraffic`: \[1, 100\]. If this parameter is not specified, an
|
|
||||||
// error is returned.
|
|
||||||
InternetMaxBandwidthOut int `mapstructure:"internet_max_bandwidth_out" required:"false"`
|
|
||||||
// Timeout of creating snapshot(s).
|
|
||||||
// The default timeout is 3600 seconds if this option is not set or is set
|
|
||||||
// to 0. For those disks containing lots of data, it may require a higher
|
|
||||||
// timeout value.
|
|
||||||
WaitSnapshotReadyTimeout int `mapstructure:"wait_snapshot_ready_timeout" required:"false"`
|
|
||||||
// Communicator settings
|
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
|
||||||
// If this value is true, packer will connect to
|
|
||||||
// the ECS created through private ip instead of allocating a public ip or an
|
|
||||||
// EIP. The default value is false.
|
|
||||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip" required:"false"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
|
||||||
if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" &&
|
|
||||||
c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" && c.Comm.WinRMPassword == "" {
|
|
||||||
|
|
||||||
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
errs := c.Comm.Prepare(ctx)
|
|
||||||
if c.AlicloudSourceImage == "" {
|
|
||||||
errs = append(errs, errors.New("A source_image must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.TrimSpace(c.AlicloudSourceImage) != c.AlicloudSourceImage {
|
|
||||||
errs = append(errs, errors.New("The source_image can't include spaces"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.InstanceType == "" {
|
|
||||||
errs = append(errs, errors.New("An alicloud_instance_type must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.UserData != "" && c.UserDataFile != "" {
|
|
||||||
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
|
|
||||||
} else if c.UserDataFile != "" {
|
|
||||||
if _, err := os.Stat(c.UserDataFile); err != nil {
|
|
||||||
errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testConfig() *RunConfig {
|
|
||||||
return &RunConfig{
|
|
||||||
AlicloudSourceImage: "alicloud_images",
|
|
||||||
InstanceType: "ecs.n1.tiny",
|
|
||||||
Comm: communicator.Config{
|
|
||||||
SSH: communicator.SSH{
|
|
||||||
SSHUsername: "alicloud",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunConfigPrepare(t *testing.T) {
|
|
||||||
c := testConfig()
|
|
||||||
err := c.Prepare(nil)
|
|
||||||
if len(err) > 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunConfigPrepare_InstanceType(t *testing.T) {
|
|
||||||
c := testConfig()
|
|
||||||
c.InstanceType = ""
|
|
||||||
if err := c.Prepare(nil); len(err) != 1 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunConfigPrepare_SourceECSImage(t *testing.T) {
|
|
||||||
c := testConfig()
|
|
||||||
c.AlicloudSourceImage = ""
|
|
||||||
if err := c.Prepare(nil); len(err) != 1 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunConfigPrepare_SSHPort(t *testing.T) {
|
|
||||||
c := testConfig()
|
|
||||||
c.Comm.SSHPort = 0
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Comm.SSHPort != 22 {
|
|
||||||
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Comm.SSHPort = 44
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Comm.SSHPort != 44 {
|
|
||||||
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunConfigPrepare_UserData(t *testing.T) {
|
|
||||||
c := testConfig()
|
|
||||||
tf, err := ioutil.TempFile("", "packer")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
defer os.Remove(tf.Name())
|
|
||||||
defer tf.Close()
|
|
||||||
|
|
||||||
c.UserData = "foo"
|
|
||||||
c.UserDataFile = tf.Name()
|
|
||||||
if err := c.Prepare(nil); len(err) != 1 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunConfigPrepare_UserDataFile(t *testing.T) {
|
|
||||||
c := testConfig()
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.UserDataFile = "idontexistidontthink"
|
|
||||||
if err := c.Prepare(nil); len(err) != 1 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tf, err := ioutil.TempFile("", "packer")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
defer os.Remove(tf.Name())
|
|
||||||
defer tf.Close()
|
|
||||||
|
|
||||||
c.UserDataFile = tf.Name()
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
|
|
||||||
c := testConfig()
|
|
||||||
c.Comm.SSHTemporaryKeyPairName = ""
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Comm.SSHTemporaryKeyPairName == "" {
|
|
||||||
t.Fatal("keypair name is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
|
|
||||||
t.Fatal("keypair name does not match")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunConfigPrepare_SSHPrivateIp(t *testing.T) {
|
|
||||||
c := testConfig()
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if c.SSHPrivateIp != false {
|
|
||||||
t.Fatalf("invalid value, expected: %t, actul: %t", false, c.SSHPrivateIp)
|
|
||||||
}
|
|
||||||
c.SSHPrivateIp = true
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if c.SSHPrivateIp != true {
|
|
||||||
t.Fatalf("invalid value, expected: %t, actul: %t", true, c.SSHPrivateIp)
|
|
||||||
}
|
|
||||||
c.SSHPrivateIp = false
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if c.SSHPrivateIp != false {
|
|
||||||
t.Fatalf("invalid value, expected: %t, actul: %t", false, c.SSHPrivateIp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunConfigPrepare_DisableStopInstance(t *testing.T) {
|
|
||||||
c := testConfig()
|
|
||||||
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if c.DisableStopInstance != false {
|
|
||||||
t.Fatalf("invalid value, expected: %t, actul: %t", false, c.DisableStopInstance)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.DisableStopInstance = true
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if c.DisableStopInstance != true {
|
|
||||||
t.Fatalf("invalid value, expected: %t, actul: %t", true, c.DisableStopInstance)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.DisableStopInstance = false
|
|
||||||
if err := c.Prepare(nil); len(err) != 0 {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if c.DisableStopInstance != false {
|
|
||||||
t.Fatalf("invalid value, expected: %t, actul: %t", false, c.DisableStopInstance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// modified in tests
|
|
||||||
sshHostSleepDuration = time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
type alicloudSSHHelper interface {
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSHHost returns a function that can be given to the SSH communicator
|
|
||||||
func SSHHost(e alicloudSSHHelper, private bool) func(multistep.StateBag) (string, error) {
|
|
||||||
return func(state multistep.StateBag) (string, error) {
|
|
||||||
ipAddress := state.Get("ipaddress").(string)
|
|
||||||
return ipAddress, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepAttachKeyPair struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
var attachKeyPairNotRetryErrors = []string{
|
|
||||||
"MissingParameter",
|
|
||||||
"DependencyViolation.WindowsInstance",
|
|
||||||
"InvalidKeyPairName.NotFound",
|
|
||||||
"InvalidRegionId.NotFound",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepAttachKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
instance := state.Get("instance").(*ecs.Instance)
|
|
||||||
keyPairName := config.Comm.SSHKeyPairName
|
|
||||||
if keyPairName == "" {
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
request := ecs.CreateAttachKeyPairRequest()
|
|
||||||
request.RegionId = config.AlicloudRegion
|
|
||||||
request.KeyPairName = keyPairName
|
|
||||||
request.InstanceIds = "[\"" + instance.InstanceId + "\"]"
|
|
||||||
return client.AttachKeyPair(request)
|
|
||||||
},
|
|
||||||
EvalFunc: client.EvalCouldRetryResponse(attachKeyPairNotRetryErrors, EvalNotRetryErrorType),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, fmt.Sprintf("Error attaching keypair %s to instance %s", keyPairName, instance.InstanceId))
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Attach keypair %s to instance: %s", keyPairName, instance.InstanceId))
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepAttachKeyPair) Cleanup(state multistep.StateBag) {
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
instance := state.Get("instance").(*ecs.Instance)
|
|
||||||
keyPairName := config.Comm.SSHKeyPairName
|
|
||||||
if keyPairName == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
detachKeyPairRequest := ecs.CreateDetachKeyPairRequest()
|
|
||||||
detachKeyPairRequest.RegionId = config.AlicloudRegion
|
|
||||||
detachKeyPairRequest.KeyPairName = keyPairName
|
|
||||||
detachKeyPairRequest.InstanceIds = fmt.Sprintf("[\"%s\"]", instance.InstanceId)
|
|
||||||
_, err := client.DetachKeyPair(detachKeyPairRequest)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error Detaching keypair %s to instance %s : %s", keyPairName,
|
|
||||||
instance.InstanceId, err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Detach keypair %s from instance: %s", keyPairName, instance.InstanceId))
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCheckAlicloudSourceImage struct {
|
|
||||||
SourceECSImageId string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCheckAlicloudSourceImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
describeImagesRequest := ecs.CreateDescribeImagesRequest()
|
|
||||||
describeImagesRequest.RegionId = config.AlicloudRegion
|
|
||||||
describeImagesRequest.ImageId = config.AlicloudSourceImage
|
|
||||||
if config.AlicloudSkipImageValidation {
|
|
||||||
describeImagesRequest.ShowExpired = "true"
|
|
||||||
}
|
|
||||||
imagesResponse, err := client.DescribeImages(describeImagesRequest)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error querying alicloud image")
|
|
||||||
}
|
|
||||||
|
|
||||||
images := imagesResponse.Images.Image
|
|
||||||
|
|
||||||
// Describe marketplace image
|
|
||||||
describeImagesRequest.ImageOwnerAlias = "marketplace"
|
|
||||||
marketImagesResponse, err := client.DescribeImages(describeImagesRequest)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error querying alicloud marketplace image")
|
|
||||||
}
|
|
||||||
|
|
||||||
marketImages := marketImagesResponse.Images.Image
|
|
||||||
if len(marketImages) > 0 {
|
|
||||||
images = append(images, marketImages...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(images) == 0 {
|
|
||||||
err := fmt.Errorf("No alicloud image was found matching filters: %v", config.AlicloudSourceImage)
|
|
||||||
return halt(state, err, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Found image ID: %s", images[0].ImageId))
|
|
||||||
|
|
||||||
state.Put("source_image", &images[0])
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCheckAlicloudSourceImage) Cleanup(multistep.StateBag) {}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepConfigAlicloudEIP struct {
|
|
||||||
AssociatePublicIpAddress bool
|
|
||||||
RegionId string
|
|
||||||
InternetChargeType string
|
|
||||||
InternetMaxBandwidthOut int
|
|
||||||
allocatedId string
|
|
||||||
SSHPrivateIp bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var allocateEipAddressRetryErrors = []string{
|
|
||||||
"LastTokenProcessing",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudEIP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
instance := state.Get("instance").(*ecs.Instance)
|
|
||||||
|
|
||||||
if s.SSHPrivateIp {
|
|
||||||
ipaddress := instance.VpcAttributes.PrivateIpAddress.IpAddress
|
|
||||||
if len(ipaddress) == 0 {
|
|
||||||
ui.Say("Failed to get private ip of instance")
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put("ipaddress", ipaddress[0])
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Allocating eip...")
|
|
||||||
|
|
||||||
allocateEipAddressRequest := s.buildAllocateEipAddressRequest(state)
|
|
||||||
allocateEipAddressResponse, err := client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
return client.AllocateEipAddress(allocateEipAddressRequest)
|
|
||||||
},
|
|
||||||
EvalFunc: client.EvalCouldRetryResponse(allocateEipAddressRetryErrors, EvalRetryErrorType),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error allocating eip")
|
|
||||||
}
|
|
||||||
|
|
||||||
ipaddress := allocateEipAddressResponse.(*ecs.AllocateEipAddressResponse).EipAddress
|
|
||||||
ui.Message(fmt.Sprintf("Allocated eip: %s", ipaddress))
|
|
||||||
|
|
||||||
allocateId := allocateEipAddressResponse.(*ecs.AllocateEipAddressResponse).AllocationId
|
|
||||||
s.allocatedId = allocateId
|
|
||||||
|
|
||||||
err = s.waitForEipStatus(client, instance.RegionId, s.allocatedId, EipStatusAvailable)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error wait eip available timeout")
|
|
||||||
}
|
|
||||||
|
|
||||||
associateEipAddressRequest := ecs.CreateAssociateEipAddressRequest()
|
|
||||||
associateEipAddressRequest.AllocationId = allocateId
|
|
||||||
associateEipAddressRequest.InstanceId = instance.InstanceId
|
|
||||||
if _, err := client.AssociateEipAddress(associateEipAddressRequest); err != nil {
|
|
||||||
e, ok := err.(errors.Error)
|
|
||||||
if !ok || e.ErrorCode() != "TaskConflict" {
|
|
||||||
return halt(state, err, "Error associating eip")
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Error(fmt.Sprintf("Error associate eip: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.waitForEipStatus(client, instance.RegionId, s.allocatedId, EipStatusInUse)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error wait eip associated timeout")
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Put("ipaddress", ipaddress)
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudEIP) Cleanup(state multistep.StateBag) {
|
|
||||||
if len(s.allocatedId) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanUpMessage(state, "EIP")
|
|
||||||
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
instance := state.Get("instance").(*ecs.Instance)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
unassociateEipAddressRequest := ecs.CreateUnassociateEipAddressRequest()
|
|
||||||
unassociateEipAddressRequest.AllocationId = s.allocatedId
|
|
||||||
unassociateEipAddressRequest.InstanceId = instance.InstanceId
|
|
||||||
if _, err := client.UnassociateEipAddress(unassociateEipAddressRequest); err != nil {
|
|
||||||
ui.Say(fmt.Sprintf("Failed to unassociate eip: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.waitForEipStatus(client, instance.RegionId, s.allocatedId, EipStatusAvailable); err != nil {
|
|
||||||
ui.Say(fmt.Sprintf("Timeout while unassociating eip: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseEipAddressRequest := ecs.CreateReleaseEipAddressRequest()
|
|
||||||
releaseEipAddressRequest.AllocationId = s.allocatedId
|
|
||||||
if _, err := client.ReleaseEipAddress(releaseEipAddressRequest); err != nil {
|
|
||||||
ui.Say(fmt.Sprintf("Failed to release eip: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudEIP) waitForEipStatus(client *ClientWrapper, regionId string, allocationId string, expectedStatus string) error {
|
|
||||||
describeEipAddressesRequest := ecs.CreateDescribeEipAddressesRequest()
|
|
||||||
describeEipAddressesRequest.RegionId = regionId
|
|
||||||
describeEipAddressesRequest.AllocationId = s.allocatedId
|
|
||||||
|
|
||||||
_, err := client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
response, err := client.DescribeEipAddresses(describeEipAddressesRequest)
|
|
||||||
if err == nil && len(response.EipAddresses.EipAddress) == 0 {
|
|
||||||
err = fmt.Errorf("eip allocated is not find")
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, err
|
|
||||||
},
|
|
||||||
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
|
|
||||||
if err != nil {
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
}
|
|
||||||
|
|
||||||
eipAddressesResponse := response.(*ecs.DescribeEipAddressesResponse)
|
|
||||||
eipAddresses := eipAddressesResponse.EipAddresses.EipAddress
|
|
||||||
|
|
||||||
for _, eipAddress := range eipAddresses {
|
|
||||||
if eipAddress.Status == expectedStatus {
|
|
||||||
return WaitForExpectSuccess
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
},
|
|
||||||
RetryTimes: shortRetryTimes,
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudEIP) buildAllocateEipAddressRequest(state multistep.StateBag) *ecs.AllocateEipAddressRequest {
|
|
||||||
instance := state.Get("instance").(*ecs.Instance)
|
|
||||||
|
|
||||||
request := ecs.CreateAllocateEipAddressRequest()
|
|
||||||
request.ClientToken = uuid.TimeOrderedUUID()
|
|
||||||
request.RegionId = instance.RegionId
|
|
||||||
request.InternetChargeType = s.InternetChargeType
|
|
||||||
request.Bandwidth = string(convertNumber(s.InternetMaxBandwidthOut))
|
|
||||||
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepConfigAlicloudKeyPair struct {
|
|
||||||
Debug bool
|
|
||||||
Comm *communicator.Config
|
|
||||||
DebugKeyPath string
|
|
||||||
RegionId string
|
|
||||||
|
|
||||||
keyName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
if s.Comm.SSHPrivateKeyFile != "" {
|
|
||||||
ui.Say("Using existing SSH private key")
|
|
||||||
privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Comm.SSHPrivateKey = privateKeyBytes
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName == "" {
|
|
||||||
ui.Say("Using SSH Agent with key pair in source image")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName != "" {
|
|
||||||
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.Comm.SSHKeyPairName))
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Comm.SSHTemporaryKeyPairName == "" {
|
|
||||||
ui.Say("Not using temporary keypair")
|
|
||||||
s.Comm.SSHKeyPairName = ""
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
|
|
||||||
|
|
||||||
createKeyPairRequest := ecs.CreateCreateKeyPairRequest()
|
|
||||||
createKeyPairRequest.RegionId = s.RegionId
|
|
||||||
createKeyPairRequest.KeyPairName = s.Comm.SSHTemporaryKeyPairName
|
|
||||||
keyResp, err := client.CreateKeyPair(createKeyPairRequest)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error creating temporary keypair")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the keyname so we know to delete it later
|
|
||||||
s.keyName = s.Comm.SSHTemporaryKeyPairName
|
|
||||||
|
|
||||||
// Set some state data for use in future steps
|
|
||||||
s.Comm.SSHKeyPairName = s.keyName
|
|
||||||
s.Comm.SSHPrivateKey = []byte(keyResp.PrivateKeyBody)
|
|
||||||
|
|
||||||
// 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([]byte(keyResp.PrivateKeyBody)); 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudKeyPair) Cleanup(state multistep.StateBag) {
|
|
||||||
// If no key name is set, then we never created it, so just return
|
|
||||||
// If we used an SSH private key file, do not go about deleting
|
|
||||||
// keypairs
|
|
||||||
if s.Comm.SSHPrivateKeyFile != "" || (s.Comm.SSHKeyPairName == "" && s.keyName == "") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
// Remove the keypair
|
|
||||||
ui.Say("Deleting temporary keypair...")
|
|
||||||
|
|
||||||
deleteKeyPairsRequest := ecs.CreateDeleteKeyPairsRequest()
|
|
||||||
deleteKeyPairsRequest.RegionId = s.RegionId
|
|
||||||
deleteKeyPairsRequest.KeyPairNames = fmt.Sprintf("[\"%s\"]", s.keyName)
|
|
||||||
_, err := client.DeleteKeyPairs(deleteKeyPairsRequest)
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf(
|
|
||||||
"Error cleaning up keypair. Please delete the key manually: %s", s.keyName))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also remove the physical key if we're debugging.
|
|
||||||
if s.Debug {
|
|
||||||
if err := os.Remove(s.DebugKeyPath); err != nil {
|
|
||||||
ui.Error(fmt.Sprintf(
|
|
||||||
"Error removing debug key '%s': %s", s.DebugKeyPath, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepConfigAlicloudPublicIP struct {
|
|
||||||
publicIPAddress string
|
|
||||||
RegionId string
|
|
||||||
SSHPrivateIp bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudPublicIP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
instance := state.Get("instance").(*ecs.Instance)
|
|
||||||
|
|
||||||
if s.SSHPrivateIp {
|
|
||||||
ipaddress := instance.InnerIpAddress.IpAddress
|
|
||||||
if len(ipaddress) == 0 {
|
|
||||||
ui.Say("Failed to get private ip of instance")
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put("ipaddress", ipaddress[0])
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
allocatePublicIpAddressRequest := ecs.CreateAllocatePublicIpAddressRequest()
|
|
||||||
allocatePublicIpAddressRequest.InstanceId = instance.InstanceId
|
|
||||||
ipaddress, err := client.AllocatePublicIpAddress(allocatePublicIpAddressRequest)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error allocating public ip")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.publicIPAddress = ipaddress.IpAddress
|
|
||||||
ui.Say(fmt.Sprintf("Allocated public ip address %s.", ipaddress.IpAddress))
|
|
||||||
state.Put("ipaddress", ipaddress.IpAddress)
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudPublicIP) Cleanup(state multistep.StateBag) {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepConfigAlicloudSecurityGroup struct {
|
|
||||||
SecurityGroupId string
|
|
||||||
SecurityGroupName string
|
|
||||||
Description string
|
|
||||||
VpcId string
|
|
||||||
RegionId string
|
|
||||||
isCreate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var createSecurityGroupRetryErrors = []string{
|
|
||||||
"IdempotentProcessing",
|
|
||||||
}
|
|
||||||
|
|
||||||
var deleteSecurityGroupRetryErrors = []string{
|
|
||||||
"DependencyViolation",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudSecurityGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
networkType := state.Get("networktype").(InstanceNetWork)
|
|
||||||
|
|
||||||
if len(s.SecurityGroupId) != 0 {
|
|
||||||
describeSecurityGroupsRequest := ecs.CreateDescribeSecurityGroupsRequest()
|
|
||||||
describeSecurityGroupsRequest.RegionId = s.RegionId
|
|
||||||
describeSecurityGroupsRequest.SecurityGroupId = s.SecurityGroupId
|
|
||||||
if networkType == InstanceNetworkVpc {
|
|
||||||
vpcId := state.Get("vpcid").(string)
|
|
||||||
describeSecurityGroupsRequest.VpcId = vpcId
|
|
||||||
}
|
|
||||||
|
|
||||||
securityGroupsResponse, err := client.DescribeSecurityGroups(describeSecurityGroupsRequest)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Failed querying security group")
|
|
||||||
}
|
|
||||||
|
|
||||||
securityGroupItems := securityGroupsResponse.SecurityGroups.SecurityGroup
|
|
||||||
for _, securityGroupItem := range securityGroupItems {
|
|
||||||
if securityGroupItem.SecurityGroupId == s.SecurityGroupId {
|
|
||||||
state.Put("securitygroupid", s.SecurityGroupId)
|
|
||||||
s.isCreate = false
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.isCreate = false
|
|
||||||
err = fmt.Errorf("The specified security group {%s} doesn't exist.", s.SecurityGroupId)
|
|
||||||
return halt(state, err, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Creating security group...")
|
|
||||||
|
|
||||||
createSecurityGroupRequest := s.buildCreateSecurityGroupRequest(state)
|
|
||||||
securityGroupResponse, err := client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
return client.CreateSecurityGroup(createSecurityGroupRequest)
|
|
||||||
},
|
|
||||||
EvalFunc: client.EvalCouldRetryResponse(createSecurityGroupRetryErrors, EvalRetryErrorType),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Failed creating security group")
|
|
||||||
}
|
|
||||||
|
|
||||||
securityGroupId := securityGroupResponse.(*ecs.CreateSecurityGroupResponse).SecurityGroupId
|
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Created security group: %s", securityGroupId))
|
|
||||||
state.Put("securitygroupid", securityGroupId)
|
|
||||||
s.isCreate = true
|
|
||||||
s.SecurityGroupId = securityGroupId
|
|
||||||
|
|
||||||
authorizeSecurityGroupEgressRequest := ecs.CreateAuthorizeSecurityGroupEgressRequest()
|
|
||||||
authorizeSecurityGroupEgressRequest.SecurityGroupId = securityGroupId
|
|
||||||
authorizeSecurityGroupEgressRequest.RegionId = s.RegionId
|
|
||||||
authorizeSecurityGroupEgressRequest.IpProtocol = IpProtocolAll
|
|
||||||
authorizeSecurityGroupEgressRequest.PortRange = DefaultPortRange
|
|
||||||
authorizeSecurityGroupEgressRequest.NicType = NicTypeInternet
|
|
||||||
authorizeSecurityGroupEgressRequest.DestCidrIp = DefaultCidrIp
|
|
||||||
|
|
||||||
if _, err := client.AuthorizeSecurityGroupEgress(authorizeSecurityGroupEgressRequest); err != nil {
|
|
||||||
return halt(state, err, "Failed authorizing security group")
|
|
||||||
}
|
|
||||||
|
|
||||||
authorizeSecurityGroupRequest := ecs.CreateAuthorizeSecurityGroupRequest()
|
|
||||||
authorizeSecurityGroupRequest.SecurityGroupId = securityGroupId
|
|
||||||
authorizeSecurityGroupRequest.RegionId = s.RegionId
|
|
||||||
authorizeSecurityGroupRequest.IpProtocol = IpProtocolAll
|
|
||||||
authorizeSecurityGroupRequest.PortRange = DefaultPortRange
|
|
||||||
authorizeSecurityGroupRequest.NicType = NicTypeInternet
|
|
||||||
authorizeSecurityGroupRequest.SourceCidrIp = DefaultCidrIp
|
|
||||||
|
|
||||||
if _, err := client.AuthorizeSecurityGroup(authorizeSecurityGroupRequest); err != nil {
|
|
||||||
return halt(state, err, "Failed authorizing security group")
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudSecurityGroup) Cleanup(state multistep.StateBag) {
|
|
||||||
if !s.isCreate {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanUpMessage(state, "security group")
|
|
||||||
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
_, err := client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
request := ecs.CreateDeleteSecurityGroupRequest()
|
|
||||||
request.RegionId = s.RegionId
|
|
||||||
request.SecurityGroupId = s.SecurityGroupId
|
|
||||||
return client.DeleteSecurityGroup(request)
|
|
||||||
},
|
|
||||||
EvalFunc: client.EvalCouldRetryResponse(deleteSecurityGroupRetryErrors, EvalRetryErrorType),
|
|
||||||
RetryTimes: shortRetryTimes,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Failed to delete security group, it may still be around: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudSecurityGroup) buildCreateSecurityGroupRequest(state multistep.StateBag) *ecs.CreateSecurityGroupRequest {
|
|
||||||
networkType := state.Get("networktype").(InstanceNetWork)
|
|
||||||
|
|
||||||
request := ecs.CreateCreateSecurityGroupRequest()
|
|
||||||
request.ClientToken = uuid.TimeOrderedUUID()
|
|
||||||
request.RegionId = s.RegionId
|
|
||||||
request.SecurityGroupName = s.SecurityGroupName
|
|
||||||
|
|
||||||
if networkType == InstanceNetworkVpc {
|
|
||||||
vpcId := state.Get("vpcid").(string)
|
|
||||||
request.VpcId = vpcId
|
|
||||||
}
|
|
||||||
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
errorsNew "errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepConfigAlicloudVPC struct {
|
|
||||||
VpcId string
|
|
||||||
CidrBlock string //192.168.0.0/16 or 172.16.0.0/16 (default)
|
|
||||||
VpcName string
|
|
||||||
isCreate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var createVpcRetryErrors = []string{
|
|
||||||
"TOKEN_PROCESSING",
|
|
||||||
}
|
|
||||||
|
|
||||||
var deleteVpcRetryErrors = []string{
|
|
||||||
"DependencyViolation.Instance",
|
|
||||||
"DependencyViolation.RouteEntry",
|
|
||||||
"DependencyViolation.VSwitch",
|
|
||||||
"DependencyViolation.SecurityGroup",
|
|
||||||
"Forbbiden",
|
|
||||||
"TaskConflict",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudVPC) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
if len(s.VpcId) != 0 {
|
|
||||||
describeVpcsRequest := ecs.CreateDescribeVpcsRequest()
|
|
||||||
describeVpcsRequest.VpcId = s.VpcId
|
|
||||||
describeVpcsRequest.RegionId = config.AlicloudRegion
|
|
||||||
|
|
||||||
vpcsResponse, err := client.DescribeVpcs(describeVpcsRequest)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Failed querying vpcs")
|
|
||||||
}
|
|
||||||
|
|
||||||
vpcs := vpcsResponse.Vpcs.Vpc
|
|
||||||
if len(vpcs) > 0 {
|
|
||||||
state.Put("vpcid", vpcs[0].VpcId)
|
|
||||||
s.isCreate = false
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
message := fmt.Sprintf("The specified vpc {%s} doesn't exist.", s.VpcId)
|
|
||||||
return halt(state, errorsNew.New(message), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Creating vpc...")
|
|
||||||
|
|
||||||
createVpcRequest := s.buildCreateVpcRequest(state)
|
|
||||||
createVpcResponse, err := client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
return client.CreateVpc(createVpcRequest)
|
|
||||||
},
|
|
||||||
EvalFunc: client.EvalCouldRetryResponse(createVpcRetryErrors, EvalRetryErrorType),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Failed creating vpc")
|
|
||||||
}
|
|
||||||
|
|
||||||
vpcId := createVpcResponse.(*ecs.CreateVpcResponse).VpcId
|
|
||||||
_, err = client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
request := ecs.CreateDescribeVpcsRequest()
|
|
||||||
request.RegionId = config.AlicloudRegion
|
|
||||||
request.VpcId = vpcId
|
|
||||||
return client.DescribeVpcs(request)
|
|
||||||
},
|
|
||||||
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
|
|
||||||
if err != nil {
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
}
|
|
||||||
|
|
||||||
vpcsResponse := response.(*ecs.DescribeVpcsResponse)
|
|
||||||
vpcs := vpcsResponse.Vpcs.Vpc
|
|
||||||
if len(vpcs) > 0 {
|
|
||||||
for _, vpc := range vpcs {
|
|
||||||
if vpc.Status == VpcStatusAvailable {
|
|
||||||
return WaitForExpectSuccess
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
},
|
|
||||||
RetryTimes: shortRetryTimes,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Failed waiting for vpc to become available")
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Created vpc: %s", vpcId))
|
|
||||||
state.Put("vpcid", vpcId)
|
|
||||||
s.isCreate = true
|
|
||||||
s.VpcId = vpcId
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudVPC) Cleanup(state multistep.StateBag) {
|
|
||||||
if !s.isCreate {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanUpMessage(state, "VPC")
|
|
||||||
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
_, err := client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
request := ecs.CreateDeleteVpcRequest()
|
|
||||||
request.VpcId = s.VpcId
|
|
||||||
return client.DeleteVpc(request)
|
|
||||||
},
|
|
||||||
EvalFunc: client.EvalCouldRetryResponse(deleteVpcRetryErrors, EvalRetryErrorType),
|
|
||||||
RetryTimes: shortRetryTimes,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting vpc, it may still be around: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudVPC) buildCreateVpcRequest(state multistep.StateBag) *ecs.CreateVpcRequest {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
request := ecs.CreateCreateVpcRequest()
|
|
||||||
request.ClientToken = uuid.TimeOrderedUUID()
|
|
||||||
request.RegionId = config.AlicloudRegion
|
|
||||||
request.CidrBlock = s.CidrBlock
|
|
||||||
request.VpcName = s.VpcName
|
|
||||||
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepConfigAlicloudVSwitch struct {
|
|
||||||
VSwitchId string
|
|
||||||
ZoneId string
|
|
||||||
isCreate bool
|
|
||||||
CidrBlock string
|
|
||||||
VSwitchName string
|
|
||||||
}
|
|
||||||
|
|
||||||
var createVSwitchRetryErrors = []string{
|
|
||||||
"TOKEN_PROCESSING",
|
|
||||||
}
|
|
||||||
|
|
||||||
var deleteVSwitchRetryErrors = []string{
|
|
||||||
"IncorrectVSwitchStatus",
|
|
||||||
"DependencyViolation",
|
|
||||||
"DependencyViolation.HaVip",
|
|
||||||
"IncorrectRouteEntryStatus",
|
|
||||||
"TaskConflict",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudVSwitch) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
vpcId := state.Get("vpcid").(string)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
if len(s.VSwitchId) != 0 {
|
|
||||||
describeVSwitchesRequest := ecs.CreateDescribeVSwitchesRequest()
|
|
||||||
describeVSwitchesRequest.VpcId = vpcId
|
|
||||||
describeVSwitchesRequest.VSwitchId = s.VSwitchId
|
|
||||||
describeVSwitchesRequest.ZoneId = s.ZoneId
|
|
||||||
|
|
||||||
vswitchesResponse, err := client.DescribeVSwitches(describeVSwitchesRequest)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Failed querying vswitch")
|
|
||||||
}
|
|
||||||
|
|
||||||
vswitch := vswitchesResponse.VSwitches.VSwitch
|
|
||||||
if len(vswitch) > 0 {
|
|
||||||
state.Put("vswitchid", vswitch[0].VSwitchId)
|
|
||||||
s.isCreate = false
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
s.isCreate = false
|
|
||||||
return halt(state, fmt.Errorf("The specified vswitch {%s} doesn't exist.", s.VSwitchId), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.ZoneId == "" {
|
|
||||||
describeZonesRequest := ecs.CreateDescribeZonesRequest()
|
|
||||||
describeZonesRequest.RegionId = config.AlicloudRegion
|
|
||||||
|
|
||||||
zonesResponse, err := client.DescribeZones(describeZonesRequest)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Query for available zones failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
var instanceTypes []string
|
|
||||||
zones := zonesResponse.Zones.Zone
|
|
||||||
for _, zone := range zones {
|
|
||||||
isVSwitchSupported := false
|
|
||||||
for _, resourceType := range zone.AvailableResourceCreation.ResourceTypes {
|
|
||||||
if resourceType == "VSwitch" {
|
|
||||||
isVSwitchSupported = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isVSwitchSupported {
|
|
||||||
for _, instanceType := range zone.AvailableInstanceTypes.InstanceTypes {
|
|
||||||
if instanceType == config.InstanceType {
|
|
||||||
s.ZoneId = zone.ZoneId
|
|
||||||
break
|
|
||||||
}
|
|
||||||
instanceTypes = append(instanceTypes, instanceType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.ZoneId == "" {
|
|
||||||
if len(instanceTypes) > 0 {
|
|
||||||
ui.Say(fmt.Sprintf("The instance type %s isn't available in this region."+
|
|
||||||
"\n You can either change the instance to one of following: %v \n"+
|
|
||||||
"or choose another region.", config.InstanceType, instanceTypes))
|
|
||||||
|
|
||||||
state.Put("error", fmt.Errorf("The instance type %s isn't available in this region."+
|
|
||||||
"\n You can either change the instance to one of following: %v \n"+
|
|
||||||
"or choose another region.", config.InstanceType, instanceTypes))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
} else {
|
|
||||||
ui.Say(fmt.Sprintf("The instance type %s isn't available in this region."+
|
|
||||||
"\n You can change to other regions.", config.InstanceType))
|
|
||||||
|
|
||||||
state.Put("error", fmt.Errorf("The instance type %s isn't available in this region."+
|
|
||||||
"\n You can change to other regions.", config.InstanceType))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.CidrBlock == "" {
|
|
||||||
s.CidrBlock = DefaultCidrBlock //use the default CirdBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Creating vswitch...")
|
|
||||||
|
|
||||||
createVSwitchRequest := s.buildCreateVSwitchRequest(state)
|
|
||||||
createVSwitchResponse, err := client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
return client.CreateVSwitch(createVSwitchRequest)
|
|
||||||
},
|
|
||||||
EvalFunc: client.EvalCouldRetryResponse(createVSwitchRetryErrors, EvalRetryErrorType),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error Creating vswitch")
|
|
||||||
}
|
|
||||||
|
|
||||||
vSwitchId := createVSwitchResponse.(*ecs.CreateVSwitchResponse).VSwitchId
|
|
||||||
|
|
||||||
describeVSwitchesRequest := ecs.CreateDescribeVSwitchesRequest()
|
|
||||||
describeVSwitchesRequest.VpcId = vpcId
|
|
||||||
describeVSwitchesRequest.VSwitchId = vSwitchId
|
|
||||||
|
|
||||||
_, err = client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
return client.DescribeVSwitches(describeVSwitchesRequest)
|
|
||||||
},
|
|
||||||
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
|
|
||||||
if err != nil {
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
}
|
|
||||||
|
|
||||||
vSwitchesResponse := response.(*ecs.DescribeVSwitchesResponse)
|
|
||||||
vSwitches := vSwitchesResponse.VSwitches.VSwitch
|
|
||||||
if len(vSwitches) > 0 {
|
|
||||||
for _, vSwitch := range vSwitches {
|
|
||||||
if vSwitch.Status == VSwitchStatusAvailable {
|
|
||||||
return WaitForExpectSuccess
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return WaitForExpectToRetry
|
|
||||||
},
|
|
||||||
RetryTimes: shortRetryTimes,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Timeout waiting for vswitch to become available")
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Created vswitch: %s", vSwitchId))
|
|
||||||
state.Put("vswitchid", vSwitchId)
|
|
||||||
s.isCreate = true
|
|
||||||
s.VSwitchId = vSwitchId
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudVSwitch) Cleanup(state multistep.StateBag) {
|
|
||||||
if !s.isCreate {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanUpMessage(state, "vSwitch")
|
|
||||||
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
_, err := client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
request := ecs.CreateDeleteVSwitchRequest()
|
|
||||||
request.VSwitchId = s.VSwitchId
|
|
||||||
return client.DeleteVSwitch(request)
|
|
||||||
},
|
|
||||||
EvalFunc: client.EvalCouldRetryResponse(deleteVSwitchRetryErrors, EvalRetryErrorType),
|
|
||||||
RetryTimes: shortRetryTimes,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting vswitch, it may still be around: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConfigAlicloudVSwitch) buildCreateVSwitchRequest(state multistep.StateBag) *ecs.CreateVSwitchRequest {
|
|
||||||
vpcId := state.Get("vpcid").(string)
|
|
||||||
|
|
||||||
request := ecs.CreateCreateVSwitchRequest()
|
|
||||||
request.ClientToken = uuid.TimeOrderedUUID()
|
|
||||||
request.CidrBlock = s.CidrBlock
|
|
||||||
request.ZoneId = s.ZoneId
|
|
||||||
request.VpcId = vpcId
|
|
||||||
request.VSwitchName = s.VSwitchName
|
|
||||||
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/random"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateAlicloudImage struct {
|
|
||||||
AlicloudImageIgnoreDataDisks bool
|
|
||||||
WaitSnapshotReadyTimeout int
|
|
||||||
image *ecs.Image
|
|
||||||
}
|
|
||||||
|
|
||||||
var createImageRetryErrors = []string{
|
|
||||||
"IdempotentProcessing",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
tempImageName := config.AlicloudImageName
|
|
||||||
if config.ImageEncrypted.True() {
|
|
||||||
tempImageName = fmt.Sprintf("packer_%s", random.AlphaNum(7))
|
|
||||||
ui.Say(fmt.Sprintf("Creating temporary image for encryption: %s", tempImageName))
|
|
||||||
} else {
|
|
||||||
ui.Say(fmt.Sprintf("Creating image: %s", tempImageName))
|
|
||||||
}
|
|
||||||
|
|
||||||
createImageRequest := s.buildCreateImageRequest(state, tempImageName)
|
|
||||||
createImageResponse, err := client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
return client.CreateImage(createImageRequest)
|
|
||||||
},
|
|
||||||
EvalFunc: client.EvalCouldRetryResponse(createImageRetryErrors, EvalRetryErrorType),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error creating image")
|
|
||||||
}
|
|
||||||
|
|
||||||
imageId := createImageResponse.(*ecs.CreateImageResponse).ImageId
|
|
||||||
|
|
||||||
imagesResponse, err := client.WaitForImageStatus(config.AlicloudRegion, imageId, ImageStatusAvailable, time.Duration(s.WaitSnapshotReadyTimeout)*time.Second)
|
|
||||||
|
|
||||||
// save image first for cleaning up if timeout
|
|
||||||
images := imagesResponse.(*ecs.DescribeImagesResponse).Images.Image
|
|
||||||
if len(images) == 0 {
|
|
||||||
return halt(state, err, "Unable to find created image")
|
|
||||||
}
|
|
||||||
s.image = &images[0]
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Timeout waiting for image to be created")
|
|
||||||
}
|
|
||||||
|
|
||||||
var snapshotIds []string
|
|
||||||
for _, device := range images[0].DiskDeviceMappings.DiskDeviceMapping {
|
|
||||||
snapshotIds = append(snapshotIds, device.SnapshotId)
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Put("alicloudimage", imageId)
|
|
||||||
state.Put("alicloudsnapshots", snapshotIds)
|
|
||||||
|
|
||||||
alicloudImages := make(map[string]string)
|
|
||||||
alicloudImages[config.AlicloudRegion] = images[0].ImageId
|
|
||||||
state.Put("alicloudimages", alicloudImages)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateAlicloudImage) Cleanup(state multistep.StateBag) {
|
|
||||||
if s.image == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
encryptedSet := config.ImageEncrypted.True()
|
|
||||||
|
|
||||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
|
||||||
_, halted := state.GetOk(multistep.StateHalted)
|
|
||||||
|
|
||||||
if !cancelled && !halted && !encryptedSet {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
if !cancelled && !halted && encryptedSet {
|
|
||||||
ui.Say(fmt.Sprintf("Deleting temporary image %s(%s) and related snapshots after finishing encryption...", s.image.ImageId, s.image.ImageName))
|
|
||||||
} else {
|
|
||||||
ui.Say("Deleting the image and related snapshots because of cancellation or error...")
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteImageRequest := ecs.CreateDeleteImageRequest()
|
|
||||||
deleteImageRequest.RegionId = config.AlicloudRegion
|
|
||||||
deleteImageRequest.ImageId = s.image.ImageId
|
|
||||||
if _, err := client.DeleteImage(deleteImageRequest); err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting image, it may still be around: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//Delete the snapshot of this image
|
|
||||||
for _, diskDevices := range s.image.DiskDeviceMappings.DiskDeviceMapping {
|
|
||||||
deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest()
|
|
||||||
deleteSnapshotRequest.SnapshotId = diskDevices.SnapshotId
|
|
||||||
if _, err := client.DeleteSnapshot(deleteSnapshotRequest); err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting snapshot, it may still be around: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateAlicloudImage) buildCreateImageRequest(state multistep.StateBag, imageName string) *ecs.CreateImageRequest {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
request := ecs.CreateCreateImageRequest()
|
|
||||||
request.ClientToken = uuid.TimeOrderedUUID()
|
|
||||||
request.RegionId = config.AlicloudRegion
|
|
||||||
request.ImageName = imageName
|
|
||||||
request.ImageVersion = config.AlicloudImageVersion
|
|
||||||
request.Description = config.AlicloudImageDescription
|
|
||||||
|
|
||||||
if s.AlicloudImageIgnoreDataDisks {
|
|
||||||
snapshotId := state.Get("alicloudsnapshot").(string)
|
|
||||||
request.SnapshotId = snapshotId
|
|
||||||
} else {
|
|
||||||
instance := state.Get("instance").(*ecs.Instance)
|
|
||||||
request.InstanceId = instance.InstanceId
|
|
||||||
}
|
|
||||||
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
confighelper "github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateAlicloudInstance struct {
|
|
||||||
IOOptimized confighelper.Trilean
|
|
||||||
InstanceType string
|
|
||||||
UserData string
|
|
||||||
UserDataFile string
|
|
||||||
instanceId string
|
|
||||||
RegionId string
|
|
||||||
InternetChargeType string
|
|
||||||
InternetMaxBandwidthOut int
|
|
||||||
InstanceName string
|
|
||||||
ZoneId string
|
|
||||||
instance *ecs.Instance
|
|
||||||
}
|
|
||||||
|
|
||||||
var createInstanceRetryErrors = []string{
|
|
||||||
"IdempotentProcessing",
|
|
||||||
}
|
|
||||||
|
|
||||||
var deleteInstanceRetryErrors = []string{
|
|
||||||
"IncorrectInstanceStatus.Initializing",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateAlicloudInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say("Creating instance...")
|
|
||||||
createInstanceRequest, err := s.buildCreateInstanceRequest(state)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
createInstanceResponse, err := client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
return client.CreateInstance(createInstanceRequest)
|
|
||||||
},
|
|
||||||
EvalFunc: client.EvalCouldRetryResponse(createInstanceRetryErrors, EvalRetryErrorType),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error creating instance")
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceId := createInstanceResponse.(*ecs.CreateInstanceResponse).InstanceId
|
|
||||||
|
|
||||||
_, err = client.WaitForInstanceStatus(s.RegionId, instanceId, InstanceStatusStopped)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error waiting create instance")
|
|
||||||
}
|
|
||||||
|
|
||||||
describeInstancesRequest := ecs.CreateDescribeInstancesRequest()
|
|
||||||
describeInstancesRequest.InstanceIds = fmt.Sprintf("[\"%s\"]", instanceId)
|
|
||||||
instances, err := client.DescribeInstances(describeInstancesRequest)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Created instance: %s", instanceId))
|
|
||||||
s.instance = &instances.Instances.Instance[0]
|
|
||||||
state.Put("instance", s.instance)
|
|
||||||
// instance_id is the generic term used so that users can have access to the
|
|
||||||
// instance id inside of the provisioners, used in step_provision.
|
|
||||||
state.Put("instance_id", instanceId)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateAlicloudInstance) Cleanup(state multistep.StateBag) {
|
|
||||||
if s.instance == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cleanUpMessage(state, "instance")
|
|
||||||
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
_, err := client.WaitForExpected(&WaitForExpectArgs{
|
|
||||||
RequestFunc: func() (responses.AcsResponse, error) {
|
|
||||||
request := ecs.CreateDeleteInstanceRequest()
|
|
||||||
request.InstanceId = s.instance.InstanceId
|
|
||||||
request.Force = requests.NewBoolean(true)
|
|
||||||
return client.DeleteInstance(request)
|
|
||||||
},
|
|
||||||
EvalFunc: client.EvalCouldRetryResponse(deleteInstanceRetryErrors, EvalRetryErrorType),
|
|
||||||
RetryTimes: shortRetryTimes,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ui.Say(fmt.Sprintf("Failed to clean up instance %s: %s", s.instance.InstanceId, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep.StateBag) (*ecs.CreateInstanceRequest, error) {
|
|
||||||
request := ecs.CreateCreateInstanceRequest()
|
|
||||||
request.ClientToken = uuid.TimeOrderedUUID()
|
|
||||||
request.RegionId = s.RegionId
|
|
||||||
request.InstanceType = s.InstanceType
|
|
||||||
request.InstanceName = s.InstanceName
|
|
||||||
request.ZoneId = s.ZoneId
|
|
||||||
|
|
||||||
sourceImage := state.Get("source_image").(*ecs.Image)
|
|
||||||
request.ImageId = sourceImage.ImageId
|
|
||||||
|
|
||||||
securityGroupId := state.Get("securitygroupid").(string)
|
|
||||||
request.SecurityGroupId = securityGroupId
|
|
||||||
|
|
||||||
networkType := state.Get("networktype").(InstanceNetWork)
|
|
||||||
if networkType == InstanceNetworkVpc {
|
|
||||||
vswitchId := state.Get("vswitchid").(string)
|
|
||||||
request.VSwitchId = vswitchId
|
|
||||||
|
|
||||||
userData, err := s.getUserData(state)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
request.UserData = userData
|
|
||||||
} else {
|
|
||||||
if s.InternetChargeType == "" {
|
|
||||||
s.InternetChargeType = "PayByTraffic"
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.InternetMaxBandwidthOut == 0 {
|
|
||||||
s.InternetMaxBandwidthOut = 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request.InternetChargeType = s.InternetChargeType
|
|
||||||
request.InternetMaxBandwidthOut = requests.Integer(convertNumber(s.InternetMaxBandwidthOut))
|
|
||||||
|
|
||||||
if s.IOOptimized.True() {
|
|
||||||
request.IoOptimized = IOOptimizedOptimized
|
|
||||||
} else if s.IOOptimized.False() {
|
|
||||||
request.IoOptimized = IOOptimizedNone
|
|
||||||
}
|
|
||||||
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
password := config.Comm.SSHPassword
|
|
||||||
if password == "" && config.Comm.WinRMPassword != "" {
|
|
||||||
password = config.Comm.WinRMPassword
|
|
||||||
}
|
|
||||||
request.Password = password
|
|
||||||
|
|
||||||
systemDisk := config.AlicloudImageConfig.ECSSystemDiskMapping
|
|
||||||
request.SystemDiskDiskName = systemDisk.DiskName
|
|
||||||
request.SystemDiskCategory = systemDisk.DiskCategory
|
|
||||||
request.SystemDiskSize = requests.Integer(convertNumber(systemDisk.DiskSize))
|
|
||||||
request.SystemDiskDescription = systemDisk.Description
|
|
||||||
|
|
||||||
imageDisks := config.AlicloudImageConfig.ECSImagesDiskMappings
|
|
||||||
var dataDisks []ecs.CreateInstanceDataDisk
|
|
||||||
for _, imageDisk := range imageDisks {
|
|
||||||
var dataDisk ecs.CreateInstanceDataDisk
|
|
||||||
dataDisk.DiskName = imageDisk.DiskName
|
|
||||||
dataDisk.Category = imageDisk.DiskCategory
|
|
||||||
dataDisk.Size = string(convertNumber(imageDisk.DiskSize))
|
|
||||||
dataDisk.SnapshotId = imageDisk.SnapshotId
|
|
||||||
dataDisk.Description = imageDisk.Description
|
|
||||||
dataDisk.DeleteWithInstance = strconv.FormatBool(imageDisk.DeleteWithInstance)
|
|
||||||
dataDisk.Device = imageDisk.Device
|
|
||||||
if imageDisk.Encrypted != confighelper.TriUnset {
|
|
||||||
dataDisk.Encrypted = strconv.FormatBool(imageDisk.Encrypted.True())
|
|
||||||
}
|
|
||||||
|
|
||||||
dataDisks = append(dataDisks, dataDisk)
|
|
||||||
}
|
|
||||||
request.DataDisk = &dataDisks
|
|
||||||
|
|
||||||
return request, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateAlicloudInstance) getUserData(state multistep.StateBag) (string, error) {
|
|
||||||
userData := s.UserData
|
|
||||||
|
|
||||||
if s.UserDataFile != "" {
|
|
||||||
data, err := ioutil.ReadFile(s.UserDataFile)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
userData = string(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
if userData != "" {
|
|
||||||
userData = base64.StdEncoding.EncodeToString([]byte(userData))
|
|
||||||
}
|
|
||||||
|
|
||||||
return userData, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateAlicloudSnapshot struct {
|
|
||||||
snapshot *ecs.Snapshot
|
|
||||||
WaitSnapshotReadyTimeout int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateAlicloudSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
instance := state.Get("instance").(*ecs.Instance)
|
|
||||||
|
|
||||||
describeDisksRequest := ecs.CreateDescribeDisksRequest()
|
|
||||||
describeDisksRequest.RegionId = config.AlicloudRegion
|
|
||||||
describeDisksRequest.InstanceId = instance.InstanceId
|
|
||||||
describeDisksRequest.DiskType = DiskTypeSystem
|
|
||||||
disksResponse, err := client.DescribeDisks(describeDisksRequest)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error describe disks")
|
|
||||||
}
|
|
||||||
|
|
||||||
disks := disksResponse.Disks.Disk
|
|
||||||
if len(disks) == 0 {
|
|
||||||
return halt(state, err, "Unable to find system disk of instance")
|
|
||||||
}
|
|
||||||
|
|
||||||
createSnapshotRequest := ecs.CreateCreateSnapshotRequest()
|
|
||||||
createSnapshotRequest.DiskId = disks[0].DiskId
|
|
||||||
snapshot, err := client.CreateSnapshot(createSnapshotRequest)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error creating snapshot")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the alicloud snapshot
|
|
||||||
ui.Say(fmt.Sprintf("Creating snapshot from system disk %s: %s", disks[0].DiskId, snapshot.SnapshotId))
|
|
||||||
|
|
||||||
snapshotsResponse, err := client.WaitForSnapshotStatus(config.AlicloudRegion, snapshot.SnapshotId, SnapshotStatusAccomplished, time.Duration(s.WaitSnapshotReadyTimeout)*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
_, ok := err.(errors.Error)
|
|
||||||
if ok {
|
|
||||||
return halt(state, err, "Error querying created snapshot")
|
|
||||||
}
|
|
||||||
|
|
||||||
return halt(state, err, "Timeout waiting for snapshot to be created")
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshots := snapshotsResponse.(*ecs.DescribeSnapshotsResponse).Snapshots.Snapshot
|
|
||||||
if len(snapshots) == 0 {
|
|
||||||
return halt(state, err, "Unable to find created snapshot")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.snapshot = &snapshots[0]
|
|
||||||
state.Put("alicloudsnapshot", snapshot.SnapshotId)
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateAlicloudSnapshot) Cleanup(state multistep.StateBag) {
|
|
||||||
if s.snapshot == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
|
||||||
_, halted := state.GetOk(multistep.StateHalted)
|
|
||||||
if !cancelled && !halted {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say("Deleting the snapshot because of cancellation or error...")
|
|
||||||
|
|
||||||
deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest()
|
|
||||||
deleteSnapshotRequest.SnapshotId = s.snapshot.SnapshotId
|
|
||||||
if _, err := client.DeleteSnapshot(deleteSnapshotRequest); err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting snapshot, it may still be around: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateTags struct {
|
|
||||||
Tags map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateTags) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
imageId := state.Get("alicloudimage").(string)
|
|
||||||
snapshotIds := state.Get("alicloudsnapshots").([]string)
|
|
||||||
|
|
||||||
if len(s.Tags) == 0 {
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Adding tags(%s) to image: %s", s.Tags, imageId))
|
|
||||||
|
|
||||||
var tags []ecs.AddTagsTag
|
|
||||||
for key, value := range s.Tags {
|
|
||||||
var tag ecs.AddTagsTag
|
|
||||||
tag.Key = key
|
|
||||||
tag.Value = value
|
|
||||||
tags = append(tags, tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
addTagsRequest := ecs.CreateAddTagsRequest()
|
|
||||||
addTagsRequest.RegionId = config.AlicloudRegion
|
|
||||||
addTagsRequest.ResourceId = imageId
|
|
||||||
addTagsRequest.ResourceType = TagResourceImage
|
|
||||||
addTagsRequest.Tag = &tags
|
|
||||||
|
|
||||||
if _, err := client.AddTags(addTagsRequest); err != nil {
|
|
||||||
return halt(state, err, "Error Adding tags to image")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, snapshotId := range snapshotIds {
|
|
||||||
ui.Say(fmt.Sprintf("Adding tags(%s) to snapshot: %s", s.Tags, snapshotId))
|
|
||||||
addTagsRequest := ecs.CreateAddTagsRequest()
|
|
||||||
|
|
||||||
addTagsRequest.RegionId = config.AlicloudRegion
|
|
||||||
addTagsRequest.ResourceId = snapshotId
|
|
||||||
addTagsRequest.ResourceType = TagResourceSnapshot
|
|
||||||
addTagsRequest.Tag = &tags
|
|
||||||
|
|
||||||
if _, err := client.AddTags(addTagsRequest); err != nil {
|
|
||||||
return halt(state, err, "Error Adding tags to snapshot")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
func (s *stepCreateTags) Cleanup(state multistep.StateBag) {
|
|
||||||
// Nothing need to do, tags will be cleaned when the resource is cleaned
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepDeleteAlicloudImageSnapshots struct {
|
|
||||||
AlicloudImageForceDelete bool
|
|
||||||
AlicloudImageForceDeleteSnapshots bool
|
|
||||||
AlicloudImageName string
|
|
||||||
AlicloudImageDestinationRegions []string
|
|
||||||
AlicloudImageDestinationNames []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepDeleteAlicloudImageSnapshots) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
// Check for force delete
|
|
||||||
if s.AlicloudImageForceDelete {
|
|
||||||
err := s.deleteImageAndSnapshots(state, s.AlicloudImageName, config.AlicloudRegion)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
numberOfName := len(s.AlicloudImageDestinationNames)
|
|
||||||
if numberOfName == 0 {
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
for index, destinationRegion := range s.AlicloudImageDestinationRegions {
|
|
||||||
if destinationRegion == config.AlicloudRegion {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if index < numberOfName {
|
|
||||||
err = s.deleteImageAndSnapshots(state, s.AlicloudImageDestinationNames[index], destinationRegion)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepDeleteAlicloudImageSnapshots) deleteImageAndSnapshots(state multistep.StateBag, imageName string, region string) error {
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
describeImagesRequest := ecs.CreateDescribeImagesRequest()
|
|
||||||
describeImagesRequest.RegionId = region
|
|
||||||
describeImagesRequest.ImageName = imageName
|
|
||||||
describeImagesRequest.Status = ImageStatusQueried
|
|
||||||
imageResponse, _ := client.DescribeImages(describeImagesRequest)
|
|
||||||
images := imageResponse.Images.Image
|
|
||||||
if len(images) < 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Deleting duplicated image and snapshot in %s: %s", region, imageName))
|
|
||||||
|
|
||||||
for _, image := range images {
|
|
||||||
if image.ImageOwnerAlias != ImageOwnerSelf {
|
|
||||||
log.Printf("You can not delete non-customized images: %s ", image.ImageId)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteImageRequest := ecs.CreateDeleteImageRequest()
|
|
||||||
deleteImageRequest.RegionId = region
|
|
||||||
deleteImageRequest.ImageId = image.ImageId
|
|
||||||
if _, err := client.DeleteImage(deleteImageRequest); err != nil {
|
|
||||||
err := fmt.Errorf("Failed to delete image: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.AlicloudImageForceDeleteSnapshots {
|
|
||||||
for _, diskDevice := range image.DiskDeviceMappings.DiskDeviceMapping {
|
|
||||||
deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest()
|
|
||||||
deleteSnapshotRequest.SnapshotId = diskDevice.SnapshotId
|
|
||||||
if _, err := client.DeleteSnapshot(deleteSnapshotRequest); err != nil {
|
|
||||||
err := fmt.Errorf("Deleting ECS snapshot failed: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepDeleteAlicloudImageSnapshots) Cleanup(state multistep.StateBag) {
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepPreValidate struct {
|
|
||||||
AlicloudDestImageName string
|
|
||||||
ForceDelete bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepPreValidate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
if err := s.validateRegions(state); err != nil {
|
|
||||||
return halt(state, err, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.validateDestImageName(state); err != nil {
|
|
||||||
return halt(state, err, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepPreValidate) validateRegions(state multistep.StateBag) error {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
if config.AlicloudSkipValidation {
|
|
||||||
ui.Say("Skip region validation flag found, skipping prevalidating source region and copied regions.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Prevalidating source region and copied regions...")
|
|
||||||
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
if err := config.ValidateRegion(config.AlicloudRegion); err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, err)
|
|
||||||
}
|
|
||||||
for _, region := range config.AlicloudImageDestinationRegions {
|
|
||||||
if err := config.ValidateRegion(region); err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepPreValidate) validateDestImageName(state multistep.StateBag) error {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
if s.ForceDelete {
|
|
||||||
ui.Say("Force delete flag found, skipping prevalidating image name.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Prevalidating image name...")
|
|
||||||
|
|
||||||
describeImagesRequest := ecs.CreateDescribeImagesRequest()
|
|
||||||
describeImagesRequest.RegionId = config.AlicloudRegion
|
|
||||||
describeImagesRequest.ImageName = s.AlicloudDestImageName
|
|
||||||
describeImagesRequest.Status = ImageStatusQueried
|
|
||||||
|
|
||||||
imagesResponse, err := client.DescribeImages(describeImagesRequest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error querying alicloud image: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
images := imagesResponse.Images.Image
|
|
||||||
if len(images) > 0 {
|
|
||||||
return fmt.Errorf("Error: Image Name: '%s' is used by an existing alicloud image: %s", images[0].ImageName, images[0].ImageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepPreValidate) Cleanup(multistep.StateBag) {}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
confighelper "github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepRegionCopyAlicloudImage struct {
|
|
||||||
AlicloudImageDestinationRegions []string
|
|
||||||
AlicloudImageDestinationNames []string
|
|
||||||
RegionId string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepRegionCopyAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
if config.ImageEncrypted != confighelper.TriUnset {
|
|
||||||
s.AlicloudImageDestinationRegions = append(s.AlicloudImageDestinationRegions, s.RegionId)
|
|
||||||
s.AlicloudImageDestinationNames = append(s.AlicloudImageDestinationNames, config.AlicloudImageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(s.AlicloudImageDestinationRegions) == 0 {
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
srcImageId := state.Get("alicloudimage").(string)
|
|
||||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
|
||||||
numberOfName := len(s.AlicloudImageDestinationNames)
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Coping image %s from %s...", srcImageId, s.RegionId))
|
|
||||||
for index, destinationRegion := range s.AlicloudImageDestinationRegions {
|
|
||||||
if destinationRegion == s.RegionId && config.ImageEncrypted == confighelper.TriUnset {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ecsImageName := ""
|
|
||||||
if numberOfName > 0 && index < numberOfName {
|
|
||||||
ecsImageName = s.AlicloudImageDestinationNames[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
copyImageRequest := ecs.CreateCopyImageRequest()
|
|
||||||
copyImageRequest.RegionId = s.RegionId
|
|
||||||
copyImageRequest.ImageId = srcImageId
|
|
||||||
copyImageRequest.DestinationRegionId = destinationRegion
|
|
||||||
copyImageRequest.DestinationImageName = ecsImageName
|
|
||||||
if config.ImageEncrypted != confighelper.TriUnset {
|
|
||||||
copyImageRequest.Encrypted = requests.NewBoolean(config.ImageEncrypted.True())
|
|
||||||
}
|
|
||||||
|
|
||||||
imageResponse, err := client.CopyImage(copyImageRequest)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error copying images")
|
|
||||||
}
|
|
||||||
|
|
||||||
alicloudImages[destinationRegion] = imageResponse.ImageId
|
|
||||||
ui.Message(fmt.Sprintf("Copy image from %s(%s) to %s(%s)", s.RegionId, srcImageId, destinationRegion, imageResponse.ImageId))
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.ImageEncrypted != confighelper.TriUnset {
|
|
||||||
if _, err := client.WaitForImageStatus(s.RegionId, alicloudImages[s.RegionId], ImageStatusAvailable, time.Duration(ALICLOUD_DEFAULT_LONG_TIMEOUT)*time.Second); err != nil {
|
|
||||||
return halt(state, err, fmt.Sprintf("Timeout waiting image %s finish copying", alicloudImages[s.RegionId]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepRegionCopyAlicloudImage) Cleanup(state multistep.StateBag) {
|
|
||||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
|
||||||
_, halted := state.GetOk(multistep.StateHalted)
|
|
||||||
|
|
||||||
if !cancelled && !halted {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
ui.Say(fmt.Sprintf("Stopping copy image because cancellation or error..."))
|
|
||||||
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
|
||||||
srcImageId := state.Get("alicloudimage").(string)
|
|
||||||
|
|
||||||
for copiedRegionId, copiedImageId := range alicloudImages {
|
|
||||||
if copiedImageId == srcImageId {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelCopyImageRequest := ecs.CreateCancelCopyImageRequest()
|
|
||||||
cancelCopyImageRequest.RegionId = copiedRegionId
|
|
||||||
cancelCopyImageRequest.ImageId = copiedImageId
|
|
||||||
if _, err := client.CancelCopyImage(cancelCopyImageRequest); err != nil {
|
|
||||||
|
|
||||||
ui.Error(fmt.Sprintf("Error cancelling copy image: %v", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepRunAlicloudInstance struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepRunAlicloudInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
instance := state.Get("instance").(*ecs.Instance)
|
|
||||||
|
|
||||||
startInstanceRequest := ecs.CreateStartInstanceRequest()
|
|
||||||
startInstanceRequest.InstanceId = instance.InstanceId
|
|
||||||
if _, err := client.StartInstance(startInstanceRequest); err != nil {
|
|
||||||
return halt(state, err, "Error starting instance")
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Starting instance: %s", instance.InstanceId))
|
|
||||||
|
|
||||||
_, err := client.WaitForInstanceStatus(instance.RegionId, instance.InstanceId, InstanceStatusRunning)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Timeout waiting for instance to start")
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepRunAlicloudInstance) Cleanup(state multistep.StateBag) {
|
|
||||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
|
||||||
_, halted := state.GetOk(multistep.StateHalted)
|
|
||||||
|
|
||||||
if !cancelled && !halted {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
instance := state.Get("instance").(*ecs.Instance)
|
|
||||||
|
|
||||||
describeInstancesRequest := ecs.CreateDescribeInstancesRequest()
|
|
||||||
describeInstancesRequest.InstanceIds = fmt.Sprintf("[\"%s\"]", instance.InstanceId)
|
|
||||||
instancesResponse, _ := client.DescribeInstances(describeInstancesRequest)
|
|
||||||
|
|
||||||
if len(instancesResponse.Instances.Instance) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceAttribute := instancesResponse.Instances.Instance[0]
|
|
||||||
if instanceAttribute.Status == InstanceStatusStarting || instanceAttribute.Status == InstanceStatusRunning {
|
|
||||||
stopInstanceRequest := ecs.CreateStopInstanceRequest()
|
|
||||||
stopInstanceRequest.InstanceId = instance.InstanceId
|
|
||||||
stopInstanceRequest.ForceStop = requests.NewBoolean(true)
|
|
||||||
if _, err := client.StopInstance(stopInstanceRequest); err != nil {
|
|
||||||
ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := client.WaitForInstanceStatus(instance.RegionId, instance.InstanceId, InstanceStatusStopped)
|
|
||||||
if err != nil {
|
|
||||||
ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepShareAlicloudImage struct {
|
|
||||||
AlicloudImageShareAccounts []string
|
|
||||||
AlicloudImageUNShareAccounts []string
|
|
||||||
RegionId string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepShareAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
|
||||||
|
|
||||||
for regionId, imageId := range alicloudImages {
|
|
||||||
modifyImageShareRequest := ecs.CreateModifyImageSharePermissionRequest()
|
|
||||||
modifyImageShareRequest.RegionId = regionId
|
|
||||||
modifyImageShareRequest.ImageId = imageId
|
|
||||||
modifyImageShareRequest.AddAccount = &s.AlicloudImageShareAccounts
|
|
||||||
modifyImageShareRequest.RemoveAccount = &s.AlicloudImageUNShareAccounts
|
|
||||||
|
|
||||||
if _, err := client.ModifyImageSharePermission(modifyImageShareRequest); err != nil {
|
|
||||||
return halt(state, err, "Failed modifying image share permissions")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepShareAlicloudImage) Cleanup(state multistep.StateBag) {
|
|
||||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
|
||||||
_, halted := state.GetOk(multistep.StateHalted)
|
|
||||||
|
|
||||||
if !cancelled && !halted {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
alicloudImages := state.Get("alicloudimages").(map[string]string)
|
|
||||||
|
|
||||||
ui.Say("Restoring image share permission because cancellations or error...")
|
|
||||||
|
|
||||||
for regionId, imageId := range alicloudImages {
|
|
||||||
modifyImageShareRequest := ecs.CreateModifyImageSharePermissionRequest()
|
|
||||||
modifyImageShareRequest.RegionId = regionId
|
|
||||||
modifyImageShareRequest.ImageId = imageId
|
|
||||||
modifyImageShareRequest.AddAccount = &s.AlicloudImageUNShareAccounts
|
|
||||||
modifyImageShareRequest.RemoveAccount = &s.AlicloudImageShareAccounts
|
|
||||||
if _, err := client.ModifyImageSharePermission(modifyImageShareRequest); err != nil {
|
|
||||||
ui.Say(fmt.Sprintf("Restoring image share permission failed: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package ecs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepStopAlicloudInstance struct {
|
|
||||||
ForceStop bool
|
|
||||||
DisableStop bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepStopAlicloudInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*ClientWrapper)
|
|
||||||
instance := state.Get("instance").(*ecs.Instance)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
if !s.DisableStop {
|
|
||||||
ui.Say(fmt.Sprintf("Stopping instance: %s", instance.InstanceId))
|
|
||||||
|
|
||||||
stopInstanceRequest := ecs.CreateStopInstanceRequest()
|
|
||||||
stopInstanceRequest.InstanceId = instance.InstanceId
|
|
||||||
stopInstanceRequest.ForceStop = requests.Boolean(strconv.FormatBool(s.ForceStop))
|
|
||||||
if _, err := client.StopInstance(stopInstanceRequest); err != nil {
|
|
||||||
return halt(state, err, "Error stopping alicloud instance")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Waiting instance stopped: %s", instance.InstanceId))
|
|
||||||
|
|
||||||
_, err := client.WaitForInstanceStatus(instance.RegionId, instance.InstanceId, InstanceStatusStopped)
|
|
||||||
if err != nil {
|
|
||||||
return halt(state, err, "Error waiting for alicloud instance to stop")
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepStopAlicloudInstance) Cleanup(multistep.StateBag) {
|
|
||||||
// No cleanup...
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"variables": {
|
|
||||||
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
|
|
||||||
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
|
|
||||||
},
|
|
||||||
"builders": [{
|
|
||||||
"type":"alicloud-ecs",
|
|
||||||
"access_key":"{{user `access_key`}}",
|
|
||||||
"secret_key":"{{user `secret_key`}}",
|
|
||||||
"region":"cn-beijing",
|
|
||||||
"image_name":"packer_basic",
|
|
||||||
"source_image":"centos_7_03_64_20G_alibase_20170818.vhd",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"instance_type":"ecs.n1.tiny",
|
|
||||||
"internet_charge_type":"PayByTraffic",
|
|
||||||
"io_optimized":"true"
|
|
||||||
}],
|
|
||||||
"provisioners": [{
|
|
||||||
"type": "shell",
|
|
||||||
"inline": [
|
|
||||||
"sleep 30",
|
|
||||||
"yum install redis.x86_64 -y"
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"variables": {
|
|
||||||
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
|
|
||||||
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
|
|
||||||
},
|
|
||||||
"builders": [{
|
|
||||||
"type":"alicloud-ecs",
|
|
||||||
"access_key":"{{user `access_key`}}",
|
|
||||||
"secret_key":"{{user `secret_key`}}",
|
|
||||||
"region":"cn-beijing",
|
|
||||||
"image_name":"packer_test",
|
|
||||||
"source_image":"winsvr_64_dtcC_1809_en-us_40G_alibase_20190318.vhd",
|
|
||||||
"instance_type":"ecs.n1.tiny",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"internet_charge_type":"PayByTraffic",
|
|
||||||
"image_force_delete":"true",
|
|
||||||
"communicator": "winrm",
|
|
||||||
"winrm_port": 5985,
|
|
||||||
"winrm_username": "Administrator",
|
|
||||||
"winrm_password": "Test1234",
|
|
||||||
"user_data_file": "examples/alicloud/basic/winrm_enable_userdata.ps1"
|
|
||||||
}],
|
|
||||||
"provisioners": [{
|
|
||||||
"type": "powershell",
|
|
||||||
"inline": ["dir c:\\"]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"variables": {
|
|
||||||
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
|
|
||||||
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
|
|
||||||
},
|
|
||||||
"builders": [{
|
|
||||||
"type":"alicloud-ecs",
|
|
||||||
"access_key":"{{user `access_key`}}",
|
|
||||||
"secret_key":"{{user `secret_key`}}",
|
|
||||||
"region":"cn-beijing",
|
|
||||||
"image_name":"packer_with_data_disk",
|
|
||||||
"source_image":"centos_7_03_64_20G_alibase_20170818.vhd",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"instance_type":"ecs.n1.tiny",
|
|
||||||
"internet_charge_type":"PayByTraffic",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"image_disk_mappings":[
|
|
||||||
{
|
|
||||||
"disk_name":"data1",
|
|
||||||
"disk_size":20,
|
|
||||||
"disk_delete_with_instance": true
|
|
||||||
},{
|
|
||||||
"disk_name":"data2",
|
|
||||||
"disk_size":20,
|
|
||||||
"disk_device":"/dev/xvdz",
|
|
||||||
"disk_delete_with_instance": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
"provisioners": [{
|
|
||||||
"type": "shell",
|
|
||||||
"inline": [
|
|
||||||
"sleep 30",
|
|
||||||
"yum install redis.x86_64 -y"
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#powershell
|
|
||||||
write-output "Running User Data Script"
|
|
||||||
write-host "(host) Running User Data Script"
|
|
||||||
Set-ExecutionPolicy Unrestricted -Scope LocalMachine -Force -ErrorAction Ignore
|
|
||||||
# Don't set this before Set-ExecutionPolicy as it throws an error
|
|
||||||
$ErrorActionPreference = "stop"
|
|
||||||
# Remove HTTP listener
|
|
||||||
Remove-Item -Path WSMan:\Localhost\listener\listener* -Recurse
|
|
||||||
# WinRM
|
|
||||||
write-output "Setting up WinRM"
|
|
||||||
write-host "(host) setting up WinRM"
|
|
||||||
cmd.exe /c winrm quickconfig -q
|
|
||||||
cmd.exe /c winrm quickconfig '-transport:http'
|
|
||||||
cmd.exe /c winrm set "winrm/config" '@{MaxTimeoutms="1800000"}'
|
|
||||||
cmd.exe /c winrm set "winrm/config/winrs" '@{MaxMemoryPerShellMB="10240"}'
|
|
||||||
cmd.exe /c winrm set "winrm/config/service" '@{AllowUnencrypted="true"}'
|
|
||||||
cmd.exe /c winrm set "winrm/config/client" '@{AllowUnencrypted="true"}'
|
|
||||||
cmd.exe /c winrm set "winrm/config/service/auth" '@{Basic="true"}'
|
|
||||||
cmd.exe /c winrm set "winrm/config/client/auth" '@{Basic="true"}'
|
|
||||||
cmd.exe /c winrm set "winrm/config/service/auth" '@{CredSSP="true"}'
|
|
||||||
cmd.exe /c winrm set "winrm/config/listener?Address=*+Transport=HTTP" '@{Port="5985"}'
|
|
||||||
cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes
|
|
||||||
cmd.exe /c netsh firewall add portopening TCP 5985 "Port 5985"
|
|
||||||
cmd.exe /c net stop winrm
|
|
||||||
cmd.exe /c sc config winrm start= auto
|
|
||||||
cmd.exe /c net start winrm
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"variables": {
|
|
||||||
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
|
|
||||||
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
|
|
||||||
},
|
|
||||||
"builders": [{
|
|
||||||
"type":"alicloud-ecs",
|
|
||||||
"access_key":"{{user `access_key`}}",
|
|
||||||
"secret_key":"{{user `secret_key`}}",
|
|
||||||
"region":"cn-beijing",
|
|
||||||
"image_name":"packer_chef2",
|
|
||||||
"source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"instance_type":"ecs.n1.medium",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"image_force_delete":"true",
|
|
||||||
"internet_charge_type":"PayByTraffic",
|
|
||||||
"ssh_password":"Test1234",
|
|
||||||
"user_data_file":"examples/alicloud/chef/user_data.sh"
|
|
||||||
}],
|
|
||||||
"provisioners": [{
|
|
||||||
"type": "file",
|
|
||||||
"source": "examples/alicloud/chef/chef.sh",
|
|
||||||
"destination": "/root/"
|
|
||||||
},{
|
|
||||||
"type": "shell",
|
|
||||||
"inline": [
|
|
||||||
"cd /root/",
|
|
||||||
"chmod 755 chef.sh",
|
|
||||||
"./chef.sh",
|
|
||||||
"chef-server-ctl reconfigure"
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
#if the related deb pkg not found, please replace with it other available repository url
|
|
||||||
HOSTNAME=`ifconfig eth1|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
|
|
||||||
if [ not $HOSTNAME ] ; then
|
|
||||||
HOSTNAME=`ifconfig eth0|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
|
|
||||||
fi
|
|
||||||
CHEF_SERVER_URL='http://dubbo.oss-cn-shenzhen.aliyuncs.com/chef-server-core_12.8.0-1_amd64.deb'
|
|
||||||
CHEF_CONSOLE_URL='http://dubbo.oss-cn-shenzhen.aliyuncs.com/chef-manage_2.4.3-1_amd64.deb'
|
|
||||||
CHEF_SERVER_ADMIN='admin'
|
|
||||||
CHEF_SERVER_ADMIN_PASSWORD='vmADMIN123'
|
|
||||||
ORGANIZATION='aliyun'
|
|
||||||
ORGANIZATION_FULL_NAME='Aliyun, Inc'
|
|
||||||
#specify hostname
|
|
||||||
hostname $HOSTNAME
|
|
||||||
|
|
||||||
mkdir ~/.pemfile
|
|
||||||
#install chef server
|
|
||||||
wget $CHEF_SERVER_URL
|
|
||||||
sudo dpkg -i chef-server-core_*.deb
|
|
||||||
sudo chef-server-ctl reconfigure
|
|
||||||
|
|
||||||
#create admin user
|
|
||||||
sudo chef-server-ctl user-create $CHEF_SERVER_ADMIN $CHEF_SERVER_ADMIN $CHEF_SERVER_ADMIN 641002259@qq.com $CHEF_SERVER_ADMIN_PASSWORD -f ~/.pemfile/admin.pem
|
|
||||||
|
|
||||||
#create aliyun organization
|
|
||||||
sudo chef-server-ctl org-create $ORGANIZATION $ORGANIZATION_FULL_NAME --association_user $CHEF_SERVER_ADMIN -f ~/.pemfile/aliyun-validator.pem
|
|
||||||
|
|
||||||
#install chef management console
|
|
||||||
wget $CHEF_CONSOLE_URL
|
|
||||||
sudo dpkg -i chef-manage_*.deb
|
|
||||||
sudo chef-server-ctl reconfigure
|
|
||||||
|
|
||||||
type expect >/dev/null 2>&1 || { echo >&2 "Install Expect..."; apt-get -y install expect; }
|
|
||||||
echo "spawn sudo chef-manage-ctl reconfigure" >> chef-manage-confirm.exp
|
|
||||||
echo "expect \"*Press any key to continue\"" >> chef-manage-confirm.exp
|
|
||||||
echo "send \"a\\\n\"" >> chef-manage-confirm.exp
|
|
||||||
echo "expect \".*chef-manage 2.4.3 license: \\\"Chef-MLSA\\\".*\"" >> chef-manage-confirm.exp
|
|
||||||
echo "send \"q\"" >> chef-manage-confirm.exp
|
|
||||||
echo "expect \".*Type 'yes' to accept the software license agreement, or anything else to cancel.\"" >> chef-manage-confirm.exp
|
|
||||||
echo "send \"yes\\\n\"" >> chef-manage-confirm.exp
|
|
||||||
echo "interact" >> chef-manage-confirm.exp
|
|
||||||
expect chef-manage-confirm.exp
|
|
||||||
rm -f chef-manage-confirm.exp
|
|
||||||
|
|
||||||
#clean
|
|
||||||
rm -rf chef-manage_2.4.3-1_amd64.deb
|
|
||||||
rm -rf chef-server-core_12.8.0-1_amd64.deb
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
HOSTNAME=`ifconfig eth1|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
|
|
||||||
if [ not $HOSTNAME ] ; then
|
|
||||||
HOSTNAME=`ifconfig eth0|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
|
|
||||||
fi
|
|
||||||
hostname $HOSTNAME
|
|
||||||
chef-server-ctl reconfigure
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"variables": {
|
|
||||||
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
|
|
||||||
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
|
|
||||||
},
|
|
||||||
"builders": [{
|
|
||||||
"type":"alicloud-ecs",
|
|
||||||
"access_key":"{{user `access_key`}}",
|
|
||||||
"secret_key":"{{user `secret_key`}}",
|
|
||||||
"region":"cn-beijing",
|
|
||||||
"image_name":"packer_jenkins",
|
|
||||||
"source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd",
|
|
||||||
"ssh_username":"root",
|
|
||||||
"instance_type":"ecs.n1.medium",
|
|
||||||
"io_optimized":"true",
|
|
||||||
"internet_charge_type":"PayByTraffic",
|
|
||||||
"image_force_delete":"true",
|
|
||||||
"ssh_password":"Test12345"
|
|
||||||
}],
|
|
||||||
"provisioners": [{
|
|
||||||
"type": "file",
|
|
||||||
"source": "examples/alicloud/jenkins/jenkins.sh",
|
|
||||||
"destination": "/root/"
|
|
||||||
},{
|
|
||||||
"type": "shell",
|
|
||||||
"inline": [
|
|
||||||
"cd /root/",
|
|
||||||
"chmod 755 jenkins.sh",
|
|
||||||
"./jenkins.sh"
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
JENKINS_URL='http://mirrors.jenkins.io/war-stable/2.32.2/jenkins.war'
|
|
||||||
|
|
||||||
TOMCAT_VERSION='7.0.77'
|
|
||||||
TOMCAT_NAME="apache-tomcat-$TOMCAT_VERSION"
|
|
||||||
TOMCAT_PACKAGE="$TOMCAT_NAME.tar.gz"
|
|
||||||
TOMCAT_URL="http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v$TOMCAT_VERSION/bin/$TOMCAT_PACKAGE"
|
|
||||||
TOMCAT_PATH="/opt/$TOMCAT_NAME"
|
|
||||||
|
|
||||||
#install jdk
|
|
||||||
if grep -Eqi "Ubuntu|Debian|Raspbian" /etc/issue || grep -Eq "Ubuntu|Debian|Raspbian" /etc/*-release; then
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get install -y openjdk-7-jdk
|
|
||||||
elif grep -Eqi "CentOS|Fedora|Red Hat Enterprise Linux Server" /etc/issue || grep -Eq "CentOS|Fedora|Red Hat Enterprise Linux Server" /etc/*-release; then
|
|
||||||
sudo yum update -y
|
|
||||||
sudo yum install -y openjdk-7-jdk
|
|
||||||
else
|
|
||||||
echo "Unknown OS type."
|
|
||||||
fi
|
|
||||||
|
|
||||||
#install jenkins server
|
|
||||||
mkdir ~/work
|
|
||||||
cd ~/work
|
|
||||||
|
|
||||||
#install tomcat
|
|
||||||
wget $TOMCAT_URL
|
|
||||||
tar -zxvf $TOMCAT_PACKAGE
|
|
||||||
mv $TOMCAT_NAME /opt
|
|
||||||
|
|
||||||
#install
|
|
||||||
wget $JENKINS_URL
|
|
||||||
mv jenkins.war $TOMCAT_PATH/webapps/
|
|
||||||
|
|
||||||
#set environment
|
|
||||||
echo "TOMCAT_PATH=\"$TOMCAT_PATH\"">>/etc/profile
|
|
||||||
echo "JENKINS_HOME=\"$TOMCAT_PATH/webapps/jenkins\"">>/etc/profile
|
|
||||||
echo PATH="\"\$PATH:\$TOMCAT_PATH:\$JENKINS_HOME\"">>/etc/profile
|
|
||||||
. /etc/profile
|
|
||||||
|
|
||||||
#start tomcat & jenkins
|
|
||||||
$TOMCAT_PATH/bin/startup.sh
|
|
||||||
|
|
||||||
#set start on boot
|
|
||||||
sed -i "/#!\/bin\/sh/a$TOMCAT_PATH/bin/startup.sh" /etc/rc.local
|
|
||||||
|
|
||||||
#clean
|
|
||||||
rm -rf ~/work
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
{"variables": {
|
|
||||||
"box_basename": "centos-6.8",
|
|
||||||
"build_timestamp": "{{isotime \"20060102150405\"}}",
|
|
||||||
"cpus": "1",
|
|
||||||
"disk_size": "4096",
|
|
||||||
"git_revision": "__unknown_git_revision__",
|
|
||||||
"headless": "",
|
|
||||||
"http_proxy": "{{env `http_proxy`}}",
|
|
||||||
"https_proxy": "{{env `https_proxy`}}",
|
|
||||||
"iso_checksum": "md5:0ca12fe5f28c2ceed4f4084b41ff8a0b",
|
|
||||||
"iso_name": "CentOS-6.8-x86_64-minimal.iso",
|
|
||||||
"ks_path": "centos-6.8/ks.cfg",
|
|
||||||
"memory": "512",
|
|
||||||
"metadata": "floppy/dummy_metadata.json",
|
|
||||||
"mirror": "http://mirrors.aliyun.com/centos",
|
|
||||||
"mirror_directory": "6.8/isos/x86_64",
|
|
||||||
"name": "centos-6.8",
|
|
||||||
"no_proxy": "{{env `no_proxy`}}",
|
|
||||||
"template": "centos-6.8-x86_64",
|
|
||||||
"version": "2.1.TIMESTAMP"
|
|
||||||
},
|
|
||||||
"builders":[
|
|
||||||
{
|
|
||||||
"boot_command": [
|
|
||||||
"<tab> text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/{{user `ks_path`}}<enter><wait>"
|
|
||||||
],
|
|
||||||
"boot_wait": "10s",
|
|
||||||
"disk_size": "{{user `disk_size`}}",
|
|
||||||
"headless": "{{ user `headless` }}",
|
|
||||||
"http_directory": "http",
|
|
||||||
"iso_checksum": "{{user `iso_checksum`}}",
|
|
||||||
"iso_checksum_type": "{{user `iso_checksum_type`}}",
|
|
||||||
"iso_url": "{{user `mirror`}}/{{user `mirror_directory`}}/{{user `iso_name`}}",
|
|
||||||
"output_directory": "packer-{{user `template`}}-qemu",
|
|
||||||
"shutdown_command": "echo 'vagrant'|sudo -S /sbin/halt -h -p",
|
|
||||||
"ssh_password": "vagrant",
|
|
||||||
"ssh_port": 22,
|
|
||||||
"ssh_username": "root",
|
|
||||||
"ssh_timeout": "10000s",
|
|
||||||
"type": "qemu",
|
|
||||||
"vm_name": "{{ user `template` }}.raw",
|
|
||||||
"net_device": "virtio-net",
|
|
||||||
"disk_interface": "virtio",
|
|
||||||
"format": "raw"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"post-processors":[
|
|
||||||
{
|
|
||||||
"type":"alicloud-import",
|
|
||||||
"oss_bucket_name": "packer",
|
|
||||||
"image_name": "packer_import",
|
|
||||||
"image_os_type": "linux",
|
|
||||||
"image_platform": "CentOS",
|
|
||||||
"image_architecture": "x86_64",
|
|
||||||
"image_system_size": "40",
|
|
||||||
"region":"cn-beijing"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
install
|
|
||||||
cdrom
|
|
||||||
lang en_US.UTF-8
|
|
||||||
keyboard us
|
|
||||||
network --bootproto=dhcp
|
|
||||||
rootpw vagrant
|
|
||||||
firewall --disabled
|
|
||||||
selinux --permissive
|
|
||||||
timezone UTC
|
|
||||||
unsupported_hardware
|
|
||||||
bootloader --location=mbr
|
|
||||||
text
|
|
||||||
skipx
|
|
||||||
zerombr
|
|
||||||
clearpart --all --initlabel
|
|
||||||
autopart
|
|
||||||
auth --enableshadow --passalgo=sha512 --kickstart
|
|
||||||
firstboot --disabled
|
|
||||||
reboot
|
|
||||||
user --name=vagrant --plaintext --password vagrant
|
|
||||||
key --skip
|
|
||||||
|
|
||||||
%packages --nobase --ignoremissing --excludedocs
|
|
||||||
# vagrant needs this to copy initial files via scp
|
|
||||||
openssh-clients
|
|
||||||
sudo
|
|
||||||
kernel-headers
|
|
||||||
kernel-devel
|
|
||||||
gcc
|
|
||||||
make
|
|
||||||
perl
|
|
||||||
wget
|
|
||||||
nfs-utils
|
|
||||||
-fprintd-pam
|
|
||||||
-intltool
|
|
||||||
|
|
||||||
# unnecessary firmware
|
|
||||||
-aic94xx-firmware
|
|
||||||
-atmel-firmware
|
|
||||||
-b43-openfwwf
|
|
||||||
-bfa-firmware
|
|
||||||
-ipw2100-firmware
|
|
||||||
-ipw2200-firmware
|
|
||||||
-ivtv-firmware
|
|
||||||
-iwl100-firmware
|
|
||||||
-iwl1000-firmware
|
|
||||||
-iwl3945-firmware
|
|
||||||
-iwl4965-firmware
|
|
||||||
-iwl5000-firmware
|
|
||||||
-iwl5150-firmware
|
|
||||||
-iwl6000-firmware
|
|
||||||
-iwl6000g2a-firmware
|
|
||||||
-iwl6050-firmware
|
|
||||||
-libertas-usb8388-firmware
|
|
||||||
-ql2100-firmware
|
|
||||||
-ql2200-firmware
|
|
||||||
-ql23xx-firmware
|
|
||||||
-ql2400-firmware
|
|
||||||
-ql2500-firmware
|
|
||||||
-rt61pci-firmware
|
|
||||||
-rt73usb-firmware
|
|
||||||
-xorg-x11-drv-ati-firmware
|
|
||||||
-zd1211-firmware
|
|
||||||
|
|
||||||
%post
|
|
||||||
# Force to set SELinux to a permissive mode
|
|
||||||
sed -i -e 's/\(^SELINUX=\).*$/\1permissive/' /etc/selinux/config
|
|
||||||
# sudo
|
|
||||||
echo "%vagrant ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/vagrant
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
|
||||||
packerVersion "github.com/hashicorp/packer/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var AlicloudPluginVersion *version.PluginVersion
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
AlicloudPluginVersion = version.InitializePluginVersion(
|
|
||||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
|
||||||
}
|
|
||||||
@@ -30,8 +30,8 @@ import (
|
|||||||
|
|
||||||
"github.com/Azure/go-autorest/autorest"
|
"github.com/Azure/go-autorest/autorest"
|
||||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//go:generate struct-markdown
|
//go:generate packer-sdc struct-markdown
|
||||||
//go:generate mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation
|
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation
|
||||||
|
|
||||||
package arm
|
package arm
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by "mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation"; DO NOT EDIT.
|
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||||
|
|
||||||
package arm
|
package arm
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//go:generate struct-markdown
|
//go:generate packer-sdc struct-markdown
|
||||||
//go:generate mapstructure-to-hcl2 -type Config
|
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||||
|
|
||||||
// Package chroot is able to create an Azure managed image without requiring the
|
// 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
|
// launch of a new virtual machine for every build. It does this by attaching and
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||||
|
|
||||||
package chroot
|
package chroot
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//go:generate struct-markdown
|
//go:generate packer-sdc struct-markdown
|
||||||
//go:generate mapstructure-to-hcl2 -type SharedImageGalleryDestination,TargetRegion
|
//go:generate packer-sdc mapstructure-to-hcl2 -type SharedImageGalleryDestination,TargetRegion
|
||||||
|
|
||||||
package chroot
|
package chroot
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by "mapstructure-to-hcl2 -type SharedImageGalleryDestination,TargetRegion"; DO NOT EDIT.
|
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||||
|
|
||||||
package chroot
|
package chroot
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:generate struct-markdown
|
//go:generate packer-sdc struct-markdown
|
||||||
|
|
||||||
package client
|
package client
|
||||||
|
|
||||||
|
|||||||
@@ -181,7 +181,6 @@ func NewAzureClient(subscriptionID, resourceGroupName string,
|
|||||||
azureClient.GalleryImageVersionsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
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.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.GalleryImageVersionsClient.UserAgent)
|
||||||
azureClient.GalleryImageVersionsClient.Client.PollingDuration = SharedGalleryTimeout
|
azureClient.GalleryImageVersionsClient.Client.PollingDuration = SharedGalleryTimeout
|
||||||
azureClient.GalleryImageVersionsClient.Client.PollingDuration = PollingDuration
|
|
||||||
|
|
||||||
azureClient.GalleryImagesClient = newCompute.NewGalleryImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
azureClient.GalleryImagesClient = newCompute.NewGalleryImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||||
azureClient.GalleryImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
azureClient.GalleryImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ package dtl
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//go:generate struct-markdown
|
//go:generate packer-sdc struct-markdown
|
||||||
//go:generate mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,DtlArtifact,ArtifactParameter
|
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,DtlArtifact,ArtifactParameter
|
||||||
|
|
||||||
package dtl
|
package dtl
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by "mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,DtlArtifact,ArtifactParameter"; DO NOT EDIT.
|
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||||
|
|
||||||
package dtl
|
package dtl
|
||||||
|
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Artifact represents a CloudStack template as the result of a Packer build.
|
|
||||||
type Artifact struct {
|
|
||||||
client *cloudstack.CloudStackClient
|
|
||||||
config *Config
|
|
||||||
template *cloudstack.CreateTemplateResponse
|
|
||||||
|
|
||||||
// StateData should store data such as GeneratedData
|
|
||||||
// to be shared with post-processors
|
|
||||||
StateData map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuilderId returns the builder ID.
|
|
||||||
func (a *Artifact) BuilderId() string {
|
|
||||||
return BuilderId
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy the CloudStack template represented by the artifact.
|
|
||||||
func (a *Artifact) Destroy() error {
|
|
||||||
// Create a new parameter struct.
|
|
||||||
p := a.client.Template.NewDeleteTemplateParams(a.template.Id)
|
|
||||||
|
|
||||||
// Destroy the template.
|
|
||||||
log.Printf("Destroying template: %s", a.template.Name)
|
|
||||||
_, err := a.client.Template.DeleteTemplate(p)
|
|
||||||
if err != nil {
|
|
||||||
// This is a very poor way to be told the ID does no longer exist :(
|
|
||||||
if strings.Contains(err.Error(), fmt.Sprintf(
|
|
||||||
"Invalid parameter id value=%s due to incorrect long value format, "+
|
|
||||||
"or entity does not exist", a.template.Id)) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("Error destroying template %s: %s", a.template.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Files returns the files represented by the artifact.
|
|
||||||
func (a *Artifact) Files() []string {
|
|
||||||
// We have no files.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Id returns CloudStack template ID.
|
|
||||||
func (a *Artifact) Id() string {
|
|
||||||
return a.template.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string representation of the artifact.
|
|
||||||
func (a *Artifact) String() string {
|
|
||||||
return fmt.Sprintf("A template was created: %s", a.template.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// State returns specific details from the artifact.
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
|
||||||
return a.StateData[name]
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
|
||||||
)
|
|
||||||
|
|
||||||
const templateID = "286dd44a-ec6b-4789-b192-804f08f04b4c"
|
|
||||||
|
|
||||||
func TestArtifact_Impl(t *testing.T) {
|
|
||||||
var raw interface{} = &Artifact{}
|
|
||||||
|
|
||||||
if _, ok := raw.(packersdk.Artifact); !ok {
|
|
||||||
t.Fatalf("Artifact does not implement packersdk.Artifact")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactId(t *testing.T) {
|
|
||||||
a := &Artifact{
|
|
||||||
client: nil,
|
|
||||||
config: nil,
|
|
||||||
template: &cloudstack.CreateTemplateResponse{
|
|
||||||
Id: "286dd44a-ec6b-4789-b192-804f08f04b4c",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Id() != templateID {
|
|
||||||
t.Fatalf("artifact ID should match: %s", templateID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactString(t *testing.T) {
|
|
||||||
a := &Artifact{
|
|
||||||
client: nil,
|
|
||||||
config: nil,
|
|
||||||
template: &cloudstack.CreateTemplateResponse{
|
|
||||||
Name: "packer-foobar",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
expected := "A template was created: packer-foobar"
|
|
||||||
|
|
||||||
if a.String() != expected {
|
|
||||||
t.Fatalf("artifact string should match: %s", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
|
||||||
)
|
|
||||||
|
|
||||||
const BuilderId = "packer.cloudstack"
|
|
||||||
|
|
||||||
// Builder represents the CloudStack builder.
|
|
||||||
type Builder struct {
|
|
||||||
config Config
|
|
||||||
runner multistep.Runner
|
|
||||||
ui packersdk.Ui
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
||||||
errs := b.config.Prepare(raws...)
|
|
||||||
if errs != nil {
|
|
||||||
return nil, nil, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run implements the packersdk.Builder interface.
|
|
||||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
|
||||||
b.ui = ui
|
|
||||||
|
|
||||||
// Create a CloudStack API client.
|
|
||||||
client := cloudstack.NewAsyncClient(
|
|
||||||
b.config.APIURL,
|
|
||||||
b.config.APIKey,
|
|
||||||
b.config.SecretKey,
|
|
||||||
!b.config.SSLNoVerify,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set the time to wait before timing out
|
|
||||||
client.AsyncTimeout(int64(b.config.AsyncTimeout.Seconds()))
|
|
||||||
|
|
||||||
// Some CloudStack service providers only allow HTTP GET calls.
|
|
||||||
client.HTTPGETOnly = b.config.HTTPGetOnly
|
|
||||||
|
|
||||||
// Set up the state.
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("client", client)
|
|
||||||
state.Put("config", &b.config)
|
|
||||||
state.Put("hook", hook)
|
|
||||||
state.Put("ui", ui)
|
|
||||||
|
|
||||||
// Build the steps.
|
|
||||||
steps := []multistep.Step{
|
|
||||||
&stepPrepareConfig{},
|
|
||||||
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
|
|
||||||
&stepKeypair{
|
|
||||||
Debug: b.config.PackerDebug,
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
DebugKeyPath: fmt.Sprintf("cs_%s.pem", b.config.PackerBuildName),
|
|
||||||
},
|
|
||||||
&stepCreateSecurityGroup{},
|
|
||||||
&stepCreateInstance{
|
|
||||||
Ctx: b.config.ctx,
|
|
||||||
Debug: b.config.PackerDebug,
|
|
||||||
},
|
|
||||||
&stepSetupNetworking{},
|
|
||||||
&stepDetachIso{},
|
|
||||||
&communicator.StepConnect{
|
|
||||||
Config: &b.config.Comm,
|
|
||||||
Host: communicator.CommHost(b.config.Comm.Host(), "ipaddress"),
|
|
||||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
|
||||||
SSHPort: commPort,
|
|
||||||
WinRMPort: commPort,
|
|
||||||
},
|
|
||||||
&commonsteps.StepProvision{},
|
|
||||||
&commonsteps.StepCleanupTempKeys{
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
},
|
|
||||||
&stepShutdownInstance{},
|
|
||||||
&stepCreateTemplate{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the runner and run the steps.
|
|
||||||
b.runner = commonsteps.NewRunner(steps, b.config.PackerConfig, ui)
|
|
||||||
b.runner.Run(ctx, state)
|
|
||||||
|
|
||||||
// If there was an error, return that
|
|
||||||
if rawErr, ok := state.GetOk("error"); ok {
|
|
||||||
ui.Error(rawErr.(error).Error())
|
|
||||||
return nil, rawErr.(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there was no template created, just return
|
|
||||||
if _, ok := state.GetOk("template"); !ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the artifact and return it
|
|
||||||
artifact := &Artifact{
|
|
||||||
client: client,
|
|
||||||
config: &b.config,
|
|
||||||
template: state.Get("template").(*cloudstack.CreateTemplateResponse),
|
|
||||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuilder_Impl(t *testing.T) {
|
|
||||||
var raw interface{} = &Builder{}
|
|
||||||
|
|
||||||
if _, ok := raw.(packersdk.Builder); !ok {
|
|
||||||
t.Fatalf("Builder does not implement packersdk.Builder")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilder_Prepare(t *testing.T) {
|
|
||||||
cases := map[string]struct {
|
|
||||||
Config map[string]interface{}
|
|
||||||
Err bool
|
|
||||||
}{
|
|
||||||
"good": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"api_url": "https://cloudstack.com/client/api",
|
|
||||||
"api_key": "some-api-key",
|
|
||||||
"secret_key": "some-secret-key",
|
|
||||||
"cidr_list": []interface{}{"0.0.0.0/0"},
|
|
||||||
"disk_size": "20",
|
|
||||||
"network": "c5ed8a14-3f21-4fa9-bd74-bb887fc0ed0d",
|
|
||||||
"service_offering": "a29c52b1-a83d-4123-a57d-4548befa47a0",
|
|
||||||
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
|
|
||||||
"ssh_username": "ubuntu",
|
|
||||||
"template_os": "52d54d24-cef1-480b-b963-527703aa4ff9",
|
|
||||||
"zone": "a3b594d9-25e9-47c1-9c03-7a5fc61e3f43",
|
|
||||||
},
|
|
||||||
Err: false,
|
|
||||||
},
|
|
||||||
"bad": {
|
|
||||||
Err: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for desc, tc := range cases {
|
|
||||||
_, _, errs := (&Builder{}).Prepare(tc.Config)
|
|
||||||
|
|
||||||
if tc.Err {
|
|
||||||
if errs == nil {
|
|
||||||
t.Fatalf("%s prepare should err", desc)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("%s prepare should not fail: %s", desc, errs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,316 +0,0 @@
|
|||||||
//go:generate struct-markdown
|
|
||||||
//go:generate mapstructure-to-hcl2 -type Config
|
|
||||||
|
|
||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config holds all the details needed to configure the builder.
|
|
||||||
type Config struct {
|
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
|
||||||
commonsteps.HTTPConfig `mapstructure:",squash"`
|
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
|
||||||
|
|
||||||
// The CloudStack API endpoint we will connect to. It can
|
|
||||||
// also be specified via environment variable CLOUDSTACK_API_URL, if set.
|
|
||||||
APIURL string `mapstructure:"api_url" required:"true"`
|
|
||||||
// The API key used to sign all API requests. It can also
|
|
||||||
// be specified via environment variable CLOUDSTACK_API_KEY, if set.
|
|
||||||
APIKey string `mapstructure:"api_key" required:"true"`
|
|
||||||
// The secret key used to sign all API requests. It
|
|
||||||
// can also be specified via environment variable CLOUDSTACK_SECRET_KEY, if
|
|
||||||
// set.
|
|
||||||
SecretKey string `mapstructure:"secret_key" required:"true"`
|
|
||||||
// The time duration to wait for async calls to
|
|
||||||
// finish. Defaults to 30m.
|
|
||||||
AsyncTimeout time.Duration `mapstructure:"async_timeout" required:"false"`
|
|
||||||
// Some cloud providers only allow HTTP GET calls
|
|
||||||
// to their CloudStack API. If using such a provider, you need to set this to
|
|
||||||
// true in order for the provider to only make GET calls and no POST calls.
|
|
||||||
HTTPGetOnly bool `mapstructure:"http_get_only" required:"false"`
|
|
||||||
// Set to true to skip SSL verification.
|
|
||||||
// Defaults to false.
|
|
||||||
SSLNoVerify bool `mapstructure:"ssl_no_verify" required:"false"`
|
|
||||||
// List of CIDR's that will have access to the new
|
|
||||||
// instance. This is needed in order for any provisioners to be able to
|
|
||||||
// connect to the instance. Defaults to [ "0.0.0.0/0" ]. Only required when
|
|
||||||
// use_local_ip_address is false.
|
|
||||||
CIDRList []string `mapstructure:"cidr_list" required:"false"`
|
|
||||||
// If true a temporary security group
|
|
||||||
// will be created which allows traffic towards the instance from the
|
|
||||||
// cidr_list. This option will be ignored if security_groups is also
|
|
||||||
// defined. Requires expunge set to true. Defaults to false.
|
|
||||||
CreateSecurityGroup bool `mapstructure:"create_security_group" required:"false"`
|
|
||||||
// The name or ID of the disk offering used for the
|
|
||||||
// instance. This option is only available (and also required) when using
|
|
||||||
// source_iso.
|
|
||||||
DiskOffering string `mapstructure:"disk_offering" required:"false"`
|
|
||||||
// The size (in GB) of the root disk of the new
|
|
||||||
// instance. This option is only available when using source_template.
|
|
||||||
DiskSize int64 `mapstructure:"disk_size" required:"false"`
|
|
||||||
// If `true` make a call to the CloudStack API, after loading image to
|
|
||||||
// cache, requesting to check and detach ISO file (if any) currently
|
|
||||||
// attached to a virtual machine. Defaults to `false`. This option is only
|
|
||||||
// available when using `source_iso`.
|
|
||||||
EjectISO bool `mapstructure:"eject_iso"`
|
|
||||||
// Configure the duration time to wait, making sure virtual machine is able
|
|
||||||
// to finish installing OS before it ejects safely. Requires `eject_iso`
|
|
||||||
// set to `true` and this option is only available when using `source_iso`.
|
|
||||||
EjectISODelay time.Duration `mapstructure:"eject_iso_delay"`
|
|
||||||
// Set to true to expunge the instance when it is
|
|
||||||
// destroyed. Defaults to false.
|
|
||||||
Expunge bool `mapstructure:"expunge" required:"false"`
|
|
||||||
// The target hypervisor (e.g. XenServer, KVM) for
|
|
||||||
// the new template. This option is required when using source_iso.
|
|
||||||
Hypervisor string `mapstructure:"hypervisor" required:"false"`
|
|
||||||
// The name of the instance. Defaults to
|
|
||||||
// "packer-UUID" where UUID is dynamically generated.
|
|
||||||
InstanceName string `mapstructure:"instance_name" required:"false"`
|
|
||||||
// The display name of the instance. Defaults to "Created by Packer".
|
|
||||||
InstanceDisplayName string `mapstructure:"instance_display_name" required:"false"`
|
|
||||||
// The name or ID of the network to connect the instance
|
|
||||||
// to.
|
|
||||||
Network string `mapstructure:"network" required:"true"`
|
|
||||||
// The name or ID of the project to deploy the instance
|
|
||||||
// to.
|
|
||||||
Project string `mapstructure:"project" required:"false"`
|
|
||||||
// The public IP address or it's ID used for
|
|
||||||
// connecting any provisioners to. If not provided, a temporary public IP
|
|
||||||
// address will be associated and released during the Packer run.
|
|
||||||
PublicIPAddress string `mapstructure:"public_ip_address" required:"false"`
|
|
||||||
// The fixed port you want to configure in the port
|
|
||||||
// forwarding rule. Set this attribute if you do not want to use the a random
|
|
||||||
// public port.
|
|
||||||
PublicPort int `mapstructure:"public_port" required:"false"`
|
|
||||||
// A list of security group IDs or
|
|
||||||
// names to associate the instance with.
|
|
||||||
SecurityGroups []string `mapstructure:"security_groups" required:"false"`
|
|
||||||
// The name or ID of the service offering used
|
|
||||||
// for the instance.
|
|
||||||
ServiceOffering string `mapstructure:"service_offering" required:"true"`
|
|
||||||
// Set to true to prevent network
|
|
||||||
// ACLs or firewall rules creation. Defaults to false.
|
|
||||||
PreventFirewallChanges bool `mapstructure:"prevent_firewall_changes" required:"false"`
|
|
||||||
// The name or ID of an ISO that will be mounted
|
|
||||||
// before booting the instance. This option is mutually exclusive with
|
|
||||||
// source_template. When using source_iso, both disk_offering and
|
|
||||||
// hypervisor are required.
|
|
||||||
SourceISO string `mapstructure:"source_iso" required:"true"`
|
|
||||||
// The name or ID of the template used as base
|
|
||||||
// template for the instance. This option is mutually exclusive with
|
|
||||||
// source_iso.
|
|
||||||
SourceTemplate string `mapstructure:"source_template" required:"true"`
|
|
||||||
// The name of the temporary SSH key pair
|
|
||||||
// to generate. By default, Packer generates a name that looks like
|
|
||||||
// `packer_<UUID>`, where `<UUID>` is a 36 character unique identifier.
|
|
||||||
TemporaryKeypairName string `mapstructure:"temporary_keypair_name" required:"false"`
|
|
||||||
// Set to true to indicate that the
|
|
||||||
// provisioners should connect to the local IP address of the instance.
|
|
||||||
UseLocalIPAddress bool `mapstructure:"use_local_ip_address" required:"false"`
|
|
||||||
// User data to launch with the instance. This is a
|
|
||||||
// template engine; see "User Data" below for
|
|
||||||
// more details. Packer will not automatically wait for a user script to
|
|
||||||
// finish before shutting down the instance this must be handled in a
|
|
||||||
// provisioner.
|
|
||||||
UserData string `mapstructure:"user_data" required:"false"`
|
|
||||||
// Path to a file that will be used for the user
|
|
||||||
// data when launching the instance. This file will be parsed as a template
|
|
||||||
// engine see User Data below for more
|
|
||||||
// details.
|
|
||||||
UserDataFile string `mapstructure:"user_data_file" required:"false"`
|
|
||||||
// The name or ID of the zone where the instance will be
|
|
||||||
// created.
|
|
||||||
Zone string `mapstructure:"zone" required:"true"`
|
|
||||||
// The name of the new template. Defaults to
|
|
||||||
// `packer-{{timestamp}}` where timestamp will be the current time.
|
|
||||||
TemplateName string `mapstructure:"template_name" required:"false"`
|
|
||||||
// The display text of the new template.
|
|
||||||
// Defaults to the template_name.
|
|
||||||
TemplateDisplayText string `mapstructure:"template_display_text" required:"false"`
|
|
||||||
// The name or ID of the template OS for the new
|
|
||||||
// template that will be created.
|
|
||||||
TemplateOS string `mapstructure:"template_os" required:"true"`
|
|
||||||
// Set to true to indicate that the template
|
|
||||||
// is featured. Defaults to false.
|
|
||||||
TemplateFeatured bool `mapstructure:"template_featured" required:"false"`
|
|
||||||
// Set to true to indicate that the template
|
|
||||||
// is available for all accounts. Defaults to false.
|
|
||||||
TemplatePublic bool `mapstructure:"template_public" required:"false"`
|
|
||||||
// Set to true to indicate the
|
|
||||||
// template should be password enabled. Defaults to false.
|
|
||||||
TemplatePasswordEnabled bool `mapstructure:"template_password_enabled" required:"false"`
|
|
||||||
// Set to true to indicate the template
|
|
||||||
// requires hardware-assisted virtualization. Defaults to false.
|
|
||||||
TemplateRequiresHVM bool `mapstructure:"template_requires_hvm" required:"false"`
|
|
||||||
// Set to true to indicate that the template
|
|
||||||
// contains tools to support dynamic scaling of VM cpu/memory. Defaults to
|
|
||||||
// false.
|
|
||||||
TemplateScalable bool `mapstructure:"template_scalable" required:"false"`
|
|
||||||
//
|
|
||||||
TemplateTag string `mapstructure:"template_tag"`
|
|
||||||
|
|
||||||
Tags map[string]string `mapstructure:"tags"`
|
|
||||||
|
|
||||||
ctx interpolate.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConfig parses and validates the given config.
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) error {
|
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
|
||||||
Interpolate: true,
|
|
||||||
InterpolateContext: &c.ctx,
|
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
|
||||||
Exclude: []string{
|
|
||||||
"user_data",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, raws...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
|
|
||||||
// Set some defaults.
|
|
||||||
if c.APIURL == "" {
|
|
||||||
// Default to environment variable for api_url, if it exists
|
|
||||||
c.APIURL = os.Getenv("CLOUDSTACK_API_URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.APIKey == "" {
|
|
||||||
// Default to environment variable for api_key, if it exists
|
|
||||||
c.APIKey = os.Getenv("CLOUDSTACK_API_KEY")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SecretKey == "" {
|
|
||||||
// Default to environment variable for secret_key, if it exists
|
|
||||||
c.SecretKey = os.Getenv("CLOUDSTACK_SECRET_KEY")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.AsyncTimeout == 0 {
|
|
||||||
c.AsyncTimeout = 30 * time.Minute
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.CIDRList) == 0 {
|
|
||||||
c.CIDRList = []string{"0.0.0.0/0"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.InstanceName == "" {
|
|
||||||
c.InstanceName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.InstanceDisplayName == "" {
|
|
||||||
c.InstanceDisplayName = "Created by Packer"
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.TemplateName == "" {
|
|
||||||
name, err := interpolate.Render("packer-{{timestamp}}", nil)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs,
|
|
||||||
fmt.Errorf("Unable to parse template name: %s ", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
c.TemplateName = name
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.TemplateDisplayText == "" {
|
|
||||||
c.TemplateDisplayText = c.TemplateName
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are not given an explicit keypair, ssh_password or ssh_private_key_file,
|
|
||||||
// then create a temporary one, but only if the temporary_keypair_name has not
|
|
||||||
// been provided.
|
|
||||||
if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" &&
|
|
||||||
c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" {
|
|
||||||
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process required parameters.
|
|
||||||
if c.APIURL == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, errors.New("a api_url must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.APIKey == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, errors.New("a api_key must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SecretKey == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, errors.New("a secret_key must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Network == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, errors.New("a network must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.CreateSecurityGroup && !c.Expunge {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, errors.New("auto creating a temporary security group requires expunge"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.ServiceOffering == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, errors.New("a service_offering must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SourceISO == "" && c.SourceTemplate == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("either source_iso or source_template must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SourceISO != "" && c.SourceTemplate != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("only one of source_iso or source_template can be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SourceISO != "" && c.DiskOffering == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("a disk_offering must be specified when using source_iso"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SourceISO != "" && c.Hypervisor == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("a hypervisor must be specified when using source_iso"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.TemplateOS == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, errors.New("a template_os must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.UserData != "" && c.UserDataFile != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("only one of user_data or user_data_file can be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.UserDataFile != "" {
|
|
||||||
if _, err := os.Stat(c.UserDataFile); err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Zone == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, errors.New("a zone must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, es...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for errors and return if we have any.
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package cloudstack
|
|
||||||
|
|
||||||
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"`
|
|
||||||
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"`
|
|
||||||
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
|
|
||||||
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"`
|
|
||||||
APIURL *string `mapstructure:"api_url" required:"true" cty:"api_url" hcl:"api_url"`
|
|
||||||
APIKey *string `mapstructure:"api_key" required:"true" cty:"api_key" hcl:"api_key"`
|
|
||||||
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
|
|
||||||
AsyncTimeout *string `mapstructure:"async_timeout" required:"false" cty:"async_timeout" hcl:"async_timeout"`
|
|
||||||
HTTPGetOnly *bool `mapstructure:"http_get_only" required:"false" cty:"http_get_only" hcl:"http_get_only"`
|
|
||||||
SSLNoVerify *bool `mapstructure:"ssl_no_verify" required:"false" cty:"ssl_no_verify" hcl:"ssl_no_verify"`
|
|
||||||
CIDRList []string `mapstructure:"cidr_list" required:"false" cty:"cidr_list" hcl:"cidr_list"`
|
|
||||||
CreateSecurityGroup *bool `mapstructure:"create_security_group" required:"false" cty:"create_security_group" hcl:"create_security_group"`
|
|
||||||
DiskOffering *string `mapstructure:"disk_offering" required:"false" cty:"disk_offering" hcl:"disk_offering"`
|
|
||||||
DiskSize *int64 `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"`
|
|
||||||
EjectISO *bool `mapstructure:"eject_iso" cty:"eject_iso" hcl:"eject_iso"`
|
|
||||||
EjectISODelay *string `mapstructure:"eject_iso_delay" cty:"eject_iso_delay" hcl:"eject_iso_delay"`
|
|
||||||
Expunge *bool `mapstructure:"expunge" required:"false" cty:"expunge" hcl:"expunge"`
|
|
||||||
Hypervisor *string `mapstructure:"hypervisor" required:"false" cty:"hypervisor" hcl:"hypervisor"`
|
|
||||||
InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name" hcl:"instance_name"`
|
|
||||||
InstanceDisplayName *string `mapstructure:"instance_display_name" required:"false" cty:"instance_display_name" hcl:"instance_display_name"`
|
|
||||||
Network *string `mapstructure:"network" required:"true" cty:"network" hcl:"network"`
|
|
||||||
Project *string `mapstructure:"project" required:"false" cty:"project" hcl:"project"`
|
|
||||||
PublicIPAddress *string `mapstructure:"public_ip_address" required:"false" cty:"public_ip_address" hcl:"public_ip_address"`
|
|
||||||
PublicPort *int `mapstructure:"public_port" required:"false" cty:"public_port" hcl:"public_port"`
|
|
||||||
SecurityGroups []string `mapstructure:"security_groups" required:"false" cty:"security_groups" hcl:"security_groups"`
|
|
||||||
ServiceOffering *string `mapstructure:"service_offering" required:"true" cty:"service_offering" hcl:"service_offering"`
|
|
||||||
PreventFirewallChanges *bool `mapstructure:"prevent_firewall_changes" required:"false" cty:"prevent_firewall_changes" hcl:"prevent_firewall_changes"`
|
|
||||||
SourceISO *string `mapstructure:"source_iso" required:"true" cty:"source_iso" hcl:"source_iso"`
|
|
||||||
SourceTemplate *string `mapstructure:"source_template" required:"true" cty:"source_template" hcl:"source_template"`
|
|
||||||
TemporaryKeypairName *string `mapstructure:"temporary_keypair_name" required:"false" cty:"temporary_keypair_name" hcl:"temporary_keypair_name"`
|
|
||||||
UseLocalIPAddress *bool `mapstructure:"use_local_ip_address" required:"false" cty:"use_local_ip_address" hcl:"use_local_ip_address"`
|
|
||||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
|
||||||
UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file" hcl:"user_data_file"`
|
|
||||||
Zone *string `mapstructure:"zone" required:"true" cty:"zone" hcl:"zone"`
|
|
||||||
TemplateName *string `mapstructure:"template_name" required:"false" cty:"template_name" hcl:"template_name"`
|
|
||||||
TemplateDisplayText *string `mapstructure:"template_display_text" required:"false" cty:"template_display_text" hcl:"template_display_text"`
|
|
||||||
TemplateOS *string `mapstructure:"template_os" required:"true" cty:"template_os" hcl:"template_os"`
|
|
||||||
TemplateFeatured *bool `mapstructure:"template_featured" required:"false" cty:"template_featured" hcl:"template_featured"`
|
|
||||||
TemplatePublic *bool `mapstructure:"template_public" required:"false" cty:"template_public" hcl:"template_public"`
|
|
||||||
TemplatePasswordEnabled *bool `mapstructure:"template_password_enabled" required:"false" cty:"template_password_enabled" hcl:"template_password_enabled"`
|
|
||||||
TemplateRequiresHVM *bool `mapstructure:"template_requires_hvm" required:"false" cty:"template_requires_hvm" hcl:"template_requires_hvm"`
|
|
||||||
TemplateScalable *bool `mapstructure:"template_scalable" required:"false" cty:"template_scalable" hcl:"template_scalable"`
|
|
||||||
TemplateTag *string `mapstructure:"template_tag" cty:"template_tag" hcl:"template_tag"`
|
|
||||||
Tags map[string]string `mapstructure:"tags" cty:"tags" hcl:"tags"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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},
|
|
||||||
"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},
|
|
||||||
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: 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},
|
|
||||||
"api_url": &hcldec.AttrSpec{Name: "api_url", Type: cty.String, Required: false},
|
|
||||||
"api_key": &hcldec.AttrSpec{Name: "api_key", Type: cty.String, Required: false},
|
|
||||||
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
|
|
||||||
"async_timeout": &hcldec.AttrSpec{Name: "async_timeout", Type: cty.String, Required: false},
|
|
||||||
"http_get_only": &hcldec.AttrSpec{Name: "http_get_only", Type: cty.Bool, Required: false},
|
|
||||||
"ssl_no_verify": &hcldec.AttrSpec{Name: "ssl_no_verify", Type: cty.Bool, Required: false},
|
|
||||||
"cidr_list": &hcldec.AttrSpec{Name: "cidr_list", Type: cty.List(cty.String), Required: false},
|
|
||||||
"create_security_group": &hcldec.AttrSpec{Name: "create_security_group", Type: cty.Bool, Required: false},
|
|
||||||
"disk_offering": &hcldec.AttrSpec{Name: "disk_offering", Type: cty.String, Required: false},
|
|
||||||
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
|
|
||||||
"eject_iso": &hcldec.AttrSpec{Name: "eject_iso", Type: cty.Bool, Required: false},
|
|
||||||
"eject_iso_delay": &hcldec.AttrSpec{Name: "eject_iso_delay", Type: cty.String, Required: false},
|
|
||||||
"expunge": &hcldec.AttrSpec{Name: "expunge", Type: cty.Bool, Required: false},
|
|
||||||
"hypervisor": &hcldec.AttrSpec{Name: "hypervisor", Type: cty.String, Required: false},
|
|
||||||
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
|
|
||||||
"instance_display_name": &hcldec.AttrSpec{Name: "instance_display_name", Type: cty.String, Required: false},
|
|
||||||
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
|
|
||||||
"project": &hcldec.AttrSpec{Name: "project", Type: cty.String, Required: false},
|
|
||||||
"public_ip_address": &hcldec.AttrSpec{Name: "public_ip_address", Type: cty.String, Required: false},
|
|
||||||
"public_port": &hcldec.AttrSpec{Name: "public_port", Type: cty.Number, Required: false},
|
|
||||||
"security_groups": &hcldec.AttrSpec{Name: "security_groups", Type: cty.List(cty.String), Required: false},
|
|
||||||
"service_offering": &hcldec.AttrSpec{Name: "service_offering", Type: cty.String, Required: false},
|
|
||||||
"prevent_firewall_changes": &hcldec.AttrSpec{Name: "prevent_firewall_changes", Type: cty.Bool, Required: false},
|
|
||||||
"source_iso": &hcldec.AttrSpec{Name: "source_iso", Type: cty.String, Required: false},
|
|
||||||
"source_template": &hcldec.AttrSpec{Name: "source_template", Type: cty.String, Required: false},
|
|
||||||
"temporary_keypair_name": &hcldec.AttrSpec{Name: "temporary_keypair_name", Type: cty.String, Required: false},
|
|
||||||
"use_local_ip_address": &hcldec.AttrSpec{Name: "use_local_ip_address", Type: cty.Bool, Required: false},
|
|
||||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
|
||||||
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
|
|
||||||
"zone": &hcldec.AttrSpec{Name: "zone", Type: cty.String, Required: false},
|
|
||||||
"template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false},
|
|
||||||
"template_display_text": &hcldec.AttrSpec{Name: "template_display_text", Type: cty.String, Required: false},
|
|
||||||
"template_os": &hcldec.AttrSpec{Name: "template_os", Type: cty.String, Required: false},
|
|
||||||
"template_featured": &hcldec.AttrSpec{Name: "template_featured", Type: cty.Bool, Required: false},
|
|
||||||
"template_public": &hcldec.AttrSpec{Name: "template_public", Type: cty.Bool, Required: false},
|
|
||||||
"template_password_enabled": &hcldec.AttrSpec{Name: "template_password_enabled", Type: cty.Bool, Required: false},
|
|
||||||
"template_requires_hvm": &hcldec.AttrSpec{Name: "template_requires_hvm", Type: cty.Bool, Required: false},
|
|
||||||
"template_scalable": &hcldec.AttrSpec{Name: "template_scalable", Type: cty.Bool, Required: false},
|
|
||||||
"template_tag": &hcldec.AttrSpec{Name: "template_tag", Type: cty.String, Required: false},
|
|
||||||
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.Map(cty.String), Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestNewConfig(t *testing.T) {
|
|
||||||
cases := map[string]struct {
|
|
||||||
Config map[string]interface{}
|
|
||||||
Nullify string
|
|
||||||
Err bool
|
|
||||||
}{
|
|
||||||
"no_api_url": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_size": "20",
|
|
||||||
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
|
|
||||||
},
|
|
||||||
Nullify: "api_url",
|
|
||||||
Err: true,
|
|
||||||
},
|
|
||||||
"no_api_key": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_size": "20",
|
|
||||||
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
|
|
||||||
},
|
|
||||||
Nullify: "api_key",
|
|
||||||
Err: true,
|
|
||||||
},
|
|
||||||
"no_secret_key": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_size": "20",
|
|
||||||
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
|
|
||||||
},
|
|
||||||
Nullify: "secret_key",
|
|
||||||
Err: true,
|
|
||||||
},
|
|
||||||
"no_cidr_list": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_size": "20",
|
|
||||||
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
|
|
||||||
},
|
|
||||||
Nullify: "cidr_list",
|
|
||||||
Err: false,
|
|
||||||
},
|
|
||||||
"no_cidr_list_with_use_local_ip_address": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_size": "20",
|
|
||||||
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
|
|
||||||
"use_local_ip_address": true,
|
|
||||||
},
|
|
||||||
Nullify: "cidr_list",
|
|
||||||
Err: false,
|
|
||||||
},
|
|
||||||
"no_network": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_size": "20",
|
|
||||||
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
|
|
||||||
},
|
|
||||||
Nullify: "network",
|
|
||||||
Err: true,
|
|
||||||
},
|
|
||||||
"no_service_offering": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_size": "20",
|
|
||||||
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
|
|
||||||
},
|
|
||||||
Nullify: "service_offering",
|
|
||||||
Err: true,
|
|
||||||
},
|
|
||||||
"no_template_os": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_size": "20",
|
|
||||||
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
|
|
||||||
},
|
|
||||||
Nullify: "template_os",
|
|
||||||
Err: true,
|
|
||||||
},
|
|
||||||
"no_zone": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_size": "20",
|
|
||||||
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
|
|
||||||
},
|
|
||||||
Nullify: "zone",
|
|
||||||
Err: true,
|
|
||||||
},
|
|
||||||
"no_source": {
|
|
||||||
Err: true,
|
|
||||||
},
|
|
||||||
"both_sources": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_offering": "f043d193-242f-4941-a847-29408b998711",
|
|
||||||
"disk_size": "20",
|
|
||||||
"hypervisor": "KVM",
|
|
||||||
"source_iso": "fbd904dc-f46c-42e7-a467-f27480c667d5",
|
|
||||||
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
|
|
||||||
},
|
|
||||||
Err: true,
|
|
||||||
},
|
|
||||||
"source_iso_good": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_offering": "f043d193-242f-4941-a847-29408b998711",
|
|
||||||
"hypervisor": "KVM",
|
|
||||||
"source_iso": "fbd904dc-f46c-42e7-a467-f27480c667d5",
|
|
||||||
},
|
|
||||||
Err: false,
|
|
||||||
},
|
|
||||||
"source_iso_without_disk_offering": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"hypervisor": "KVM",
|
|
||||||
"source_iso": "fbd904dc-f46c-42e7-a467-f27480c667d5",
|
|
||||||
},
|
|
||||||
Err: true,
|
|
||||||
},
|
|
||||||
"source_iso_without_hypervisor": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_offering": "f043d193-242f-4941-a847-29408b998711",
|
|
||||||
"source_iso": "fbd904dc-f46c-42e7-a467-f27480c667d5",
|
|
||||||
},
|
|
||||||
Err: true,
|
|
||||||
},
|
|
||||||
"source_template_good": {
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"disk_size": "20",
|
|
||||||
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
|
|
||||||
},
|
|
||||||
Err: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for desc, tc := range cases {
|
|
||||||
raw := testConfig(tc.Config)
|
|
||||||
|
|
||||||
if tc.Nullify != "" {
|
|
||||||
raw[tc.Nullify] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
|
|
||||||
if tc.Err {
|
|
||||||
if errs == nil {
|
|
||||||
t.Fatalf("%q should error", desc)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("%q should not error: %s", desc, errs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testConfig(config map[string]interface{}) map[string]interface{} {
|
|
||||||
raw := map[string]interface{}{
|
|
||||||
"api_url": "https://cloudstack.com/client/api",
|
|
||||||
"api_key": "some-api-key",
|
|
||||||
"secret_key": "some-secret-key",
|
|
||||||
"ssh_username": "root",
|
|
||||||
"cidr_list": []interface{}{"0.0.0.0/0"},
|
|
||||||
"network": "c5ed8a14-3f21-4fa9-bd74-bb887fc0ed0d",
|
|
||||||
"service_offering": "a29c52b1-a83d-4123-a57d-4548befa47a0",
|
|
||||||
"template_os": "52d54d24-cef1-480b-b963-527703aa4ff9",
|
|
||||||
"zone": "a3b594d9-25e9-47c1-9c03-7a5fc61e3f43",
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range config {
|
|
||||||
raw[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
)
|
|
||||||
|
|
||||||
func commPort(state multistep.StateBag) (int, error) {
|
|
||||||
commPort, hasPort := state.Get("commPort").(int)
|
|
||||||
if !hasPort {
|
|
||||||
return 0, fmt.Errorf("Failed to retrieve communication port")
|
|
||||||
}
|
|
||||||
|
|
||||||
return commPort, nil
|
|
||||||
}
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepSetupNetworking struct {
|
|
||||||
privatePort int
|
|
||||||
publicPort int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepSetupNetworking) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*cloudstack.CloudStackClient)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say("Setup networking...")
|
|
||||||
|
|
||||||
if config.UseLocalIPAddress {
|
|
||||||
ui.Message("Using the local IP address...")
|
|
||||||
state.Put("commPort", config.Comm.Port())
|
|
||||||
ui.Message("Networking has been setup!")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.PublicPort != 0 {
|
|
||||||
s.publicPort = config.PublicPort
|
|
||||||
} else {
|
|
||||||
// Generate a random public port used to configure our port forward.
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
s.publicPort = 50000 + rand.Intn(10000)
|
|
||||||
}
|
|
||||||
state.Put("commPort", s.publicPort)
|
|
||||||
|
|
||||||
// Set the currently configured port to be the private port.
|
|
||||||
s.privatePort = config.Comm.Port()
|
|
||||||
|
|
||||||
// Retrieve the instance ID from the previously saved state.
|
|
||||||
instanceID, ok := state.Get("instance_id").(string)
|
|
||||||
if !ok || instanceID == "" {
|
|
||||||
err := fmt.Errorf("Could not retrieve instance_id from state!")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
network, _, err := client.Network.GetNetworkByID(
|
|
||||||
config.Network,
|
|
||||||
cloudstack.WithProject(config.Project),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Failed to retrieve the network object: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.PublicIPAddress == "" {
|
|
||||||
ui.Message("Associating public IP address...")
|
|
||||||
p := client.Address.NewAssociateIpAddressParams()
|
|
||||||
|
|
||||||
if config.Project != "" {
|
|
||||||
p.SetProjectid(config.Project)
|
|
||||||
}
|
|
||||||
|
|
||||||
if network.Vpcid != "" {
|
|
||||||
p.SetVpcid(network.Vpcid)
|
|
||||||
} else {
|
|
||||||
p.SetNetworkid(network.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.SetZoneid(config.Zone)
|
|
||||||
|
|
||||||
// Associate a new public IP address.
|
|
||||||
ipAddr, err := client.Address.AssociateIpAddress(p)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Failed to associate public IP address: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the IP address and it's ID.
|
|
||||||
config.PublicIPAddress = ipAddr.Id
|
|
||||||
state.Put("ipaddress", ipAddr.Ipaddress)
|
|
||||||
|
|
||||||
// Store the IP address ID.
|
|
||||||
state.Put("ip_address_id", ipAddr.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message("Creating port forward...")
|
|
||||||
p := client.Firewall.NewCreatePortForwardingRuleParams(
|
|
||||||
config.PublicIPAddress,
|
|
||||||
s.privatePort,
|
|
||||||
"TCP",
|
|
||||||
s.publicPort,
|
|
||||||
instanceID,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Configure the port forward.
|
|
||||||
p.SetNetworkid(network.Id)
|
|
||||||
p.SetOpenfirewall(false)
|
|
||||||
|
|
||||||
// Create the port forward.
|
|
||||||
forward, err := client.Firewall.CreatePortForwardingRule(p)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Failed to create port forward: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the port forward ID.
|
|
||||||
state.Put("port_forward_id", forward.Id)
|
|
||||||
|
|
||||||
if config.PreventFirewallChanges {
|
|
||||||
ui.Message("Networking has been setup (without firewall changes)!")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
if network.Vpcid != "" {
|
|
||||||
ui.Message("Creating network ACL rule...")
|
|
||||||
|
|
||||||
if network.Aclid == "" {
|
|
||||||
err := fmt.Errorf("Failed to configure the firewall: no ACL connected to the VPC network")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new parameter struct.
|
|
||||||
p := client.NetworkACL.NewCreateNetworkACLParams("TCP")
|
|
||||||
|
|
||||||
// Configure the network ACL rule.
|
|
||||||
p.SetAclid(network.Aclid)
|
|
||||||
p.SetAction("allow")
|
|
||||||
p.SetCidrlist(config.CIDRList)
|
|
||||||
p.SetStartport(s.privatePort)
|
|
||||||
p.SetEndport(s.privatePort)
|
|
||||||
p.SetTraffictype("ingress")
|
|
||||||
|
|
||||||
// Create the network ACL rule.
|
|
||||||
aclRule, err := client.NetworkACL.CreateNetworkACL(p)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Failed to create network ACL rule: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the network ACL rule ID.
|
|
||||||
state.Put("network_acl_rule_id", aclRule.Id)
|
|
||||||
} else {
|
|
||||||
ui.Message("Creating firewall rule...")
|
|
||||||
|
|
||||||
// Create a new parameter struct.
|
|
||||||
p := client.Firewall.NewCreateFirewallRuleParams(config.PublicIPAddress, "TCP")
|
|
||||||
|
|
||||||
// Configure the firewall rule.
|
|
||||||
p.SetCidrlist(config.CIDRList)
|
|
||||||
p.SetStartport(s.publicPort)
|
|
||||||
p.SetEndport(s.publicPort)
|
|
||||||
|
|
||||||
fwRule, err := client.Firewall.CreateFirewallRule(p)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Failed to create firewall rule: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the firewall rule ID.
|
|
||||||
state.Put("firewall_rule_id", fwRule.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message("Networking has been setup!")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup any resources that may have been created during the Run phase.
|
|
||||||
func (s *stepSetupNetworking) Cleanup(state multistep.StateBag) {
|
|
||||||
client := state.Get("client").(*cloudstack.CloudStackClient)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say("Cleanup networking...")
|
|
||||||
|
|
||||||
if fwRuleID, ok := state.Get("firewall_rule_id").(string); ok && fwRuleID != "" {
|
|
||||||
// Create a new parameter struct.
|
|
||||||
p := client.Firewall.NewDeleteFirewallRuleParams(fwRuleID)
|
|
||||||
|
|
||||||
ui.Message("Deleting firewall rule...")
|
|
||||||
if _, err := client.Firewall.DeleteFirewallRule(p); err != nil {
|
|
||||||
// This is a very poor way to be told the ID does no longer exist :(
|
|
||||||
if !strings.Contains(err.Error(), fmt.Sprintf(
|
|
||||||
"Invalid parameter id value=%s due to incorrect long value format, "+
|
|
||||||
"or entity does not exist", fwRuleID)) {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting firewall rule: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if aclRuleID, ok := state.Get("network_acl_rule_id").(string); ok && aclRuleID != "" {
|
|
||||||
// Create a new parameter struct.
|
|
||||||
p := client.NetworkACL.NewDeleteNetworkACLParams(aclRuleID)
|
|
||||||
|
|
||||||
ui.Message("Deleting network ACL rule...")
|
|
||||||
if _, err := client.NetworkACL.DeleteNetworkACL(p); err != nil {
|
|
||||||
// This is a very poor way to be told the ID does no longer exist :(
|
|
||||||
if !strings.Contains(err.Error(), fmt.Sprintf(
|
|
||||||
"Invalid parameter id value=%s due to incorrect long value format, "+
|
|
||||||
"or entity does not exist", aclRuleID)) {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting network ACL rule: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if forwardID, ok := state.Get("port_forward_id").(string); ok && forwardID != "" {
|
|
||||||
// Create a new parameter struct.
|
|
||||||
p := client.Firewall.NewDeletePortForwardingRuleParams(forwardID)
|
|
||||||
|
|
||||||
ui.Message("Deleting port forward...")
|
|
||||||
if _, err := client.Firewall.DeletePortForwardingRule(p); err != nil {
|
|
||||||
// This is a very poor way to be told the ID does no longer exist :(
|
|
||||||
if !strings.Contains(err.Error(), fmt.Sprintf(
|
|
||||||
"Invalid parameter id value=%s due to incorrect long value format, "+
|
|
||||||
"or entity does not exist", forwardID)) {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting port forward: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipAddrID, ok := state.Get("ip_address_id").(string); ok && ipAddrID != "" {
|
|
||||||
// Create a new parameter struct.
|
|
||||||
p := client.Address.NewDisassociateIpAddressParams(ipAddrID)
|
|
||||||
|
|
||||||
ui.Message("Releasing public IP address...")
|
|
||||||
if _, err := client.Address.DisassociateIpAddress(p); err != nil {
|
|
||||||
// This is a very poor way to be told the ID does no longer exist :(
|
|
||||||
if !strings.Contains(err.Error(), fmt.Sprintf(
|
|
||||||
"Invalid parameter id value=%s due to incorrect long value format, "+
|
|
||||||
"or entity does not exist", ipAddrID)) {
|
|
||||||
ui.Error(fmt.Sprintf("Error releasing public IP address: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message("Networking has been cleaned!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
|
||||||
)
|
|
||||||
|
|
||||||
// userDataTemplateData represents variables for user_data interpolation
|
|
||||||
type userDataTemplateData struct {
|
|
||||||
HTTPIP string
|
|
||||||
HTTPPort int
|
|
||||||
}
|
|
||||||
|
|
||||||
// stepCreateInstance represents a Packer build step that creates CloudStack instances.
|
|
||||||
type stepCreateInstance struct {
|
|
||||||
Debug bool
|
|
||||||
Ctx interpolate.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run executes the Packer build step that creates a CloudStack instance.
|
|
||||||
func (s *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*cloudstack.CloudStackClient)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say("Creating instance...")
|
|
||||||
|
|
||||||
// Create a new parameter struct.
|
|
||||||
p := client.VirtualMachine.NewDeployVirtualMachineParams(
|
|
||||||
config.ServiceOffering,
|
|
||||||
state.Get("source").(string),
|
|
||||||
config.Zone,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Configure the instance.
|
|
||||||
p.SetName(config.InstanceName)
|
|
||||||
p.SetDisplayname(config.InstanceDisplayName)
|
|
||||||
|
|
||||||
if len(config.Comm.SSHKeyPairName) != 0 {
|
|
||||||
ui.Message(fmt.Sprintf("Using keypair: %s", config.Comm.SSHKeyPairName))
|
|
||||||
p.SetKeypair(config.Comm.SSHKeyPairName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if securitygroups, ok := state.GetOk("security_groups"); ok {
|
|
||||||
p.SetSecuritygroupids(securitygroups.([]string))
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we use an ISO, configure the disk offering.
|
|
||||||
if config.SourceISO != "" {
|
|
||||||
p.SetDiskofferingid(config.DiskOffering)
|
|
||||||
p.SetHypervisor(config.Hypervisor)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we use a template, set the root disk size.
|
|
||||||
if config.SourceTemplate != "" && config.DiskSize > 0 {
|
|
||||||
p.SetRootdisksize(config.DiskSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the zone object.
|
|
||||||
zone, _, err := client.Zone.GetZoneByID(config.Zone)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Failed to get zone %s by ID: %s", config.Zone, err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
if zone.Networktype == "Advanced" {
|
|
||||||
// Set the network ID's.
|
|
||||||
p.SetNetworkids([]string{config.Network})
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a project supplied, set the project id.
|
|
||||||
if config.Project != "" {
|
|
||||||
p.SetProjectid(config.Project)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.UserData != "" {
|
|
||||||
httpPort := state.Get("http_port").(int)
|
|
||||||
httpIP, err := hostIP()
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Failed to determine host IP: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put("http_ip", httpIP)
|
|
||||||
|
|
||||||
s.Ctx.Data = &userDataTemplateData{
|
|
||||||
httpIP,
|
|
||||||
httpPort,
|
|
||||||
}
|
|
||||||
|
|
||||||
ud, err := s.generateUserData(config.UserData, config.HTTPGetOnly)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
p.SetUserdata(ud)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the new instance.
|
|
||||||
instance, err := client.VirtualMachine.DeployVirtualMachine(p)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating new instance %s: %s", config.InstanceName, err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message("Instance has been created!")
|
|
||||||
ui.Message(fmt.Sprintf("Instance ID: %s", instance.Id))
|
|
||||||
|
|
||||||
// In debug-mode, we output the password
|
|
||||||
if s.Debug {
|
|
||||||
ui.Message(fmt.Sprintf(
|
|
||||||
"Password (since debug is enabled) \"%s\"", instance.Password))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the auto generated password if a password was not explicitly configured.
|
|
||||||
switch config.Comm.Type {
|
|
||||||
case "ssh":
|
|
||||||
if config.Comm.SSHPassword == "" {
|
|
||||||
config.Comm.SSHPassword = instance.Password
|
|
||||||
}
|
|
||||||
case "winrm":
|
|
||||||
if config.Comm.WinRMPassword == "" {
|
|
||||||
config.Comm.WinRMPassword = instance.Password
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the host address when using the local IP address to connect.
|
|
||||||
if config.UseLocalIPAddress {
|
|
||||||
state.Put("ipaddress", instance.Nic[0].Ipaddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the instance ID so we can remove it later.
|
|
||||||
state.Put("instance_id", instance.Id)
|
|
||||||
|
|
||||||
// Set instance tags
|
|
||||||
if len(config.Tags) > 0 {
|
|
||||||
resourceID := []string{instance.Id}
|
|
||||||
tp := client.Resourcetags.NewCreateTagsParams(resourceID, "UserVm", config.Tags)
|
|
||||||
|
|
||||||
_, err = client.Resourcetags.CreateTags(tp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup any resources that may have been created during the Run phase.
|
|
||||||
func (s *stepCreateInstance) Cleanup(state multistep.StateBag) {
|
|
||||||
client := state.Get("client").(*cloudstack.CloudStackClient)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
instanceID, ok := state.Get("instance_id").(string)
|
|
||||||
if !ok || instanceID == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new parameter struct.
|
|
||||||
p := client.VirtualMachine.NewDestroyVirtualMachineParams(instanceID)
|
|
||||||
|
|
||||||
ui.Say("Deleting instance...")
|
|
||||||
if _, err := client.VirtualMachine.DestroyVirtualMachine(p); err != nil {
|
|
||||||
// This is a very poor way to be told the ID does no longer exist :(
|
|
||||||
if strings.Contains(err.Error(), fmt.Sprintf(
|
|
||||||
"Invalid parameter id value=%s due to incorrect long value format, "+
|
|
||||||
"or entity does not exist", instanceID)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Error(fmt.Sprintf("Error destroying instance. Please destroy it manually.\n\n"+
|
|
||||||
"\tName: %s\n"+
|
|
||||||
"\tError: %s", config.InstanceName, err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We could expunge the VM while destroying it, but if the user doesn't have
|
|
||||||
// rights that single call could error out leaving the VM running. So but
|
|
||||||
// splitting these calls we make sure the VM is always deleted, even when the
|
|
||||||
// expunge fails.
|
|
||||||
if config.Expunge {
|
|
||||||
// Create a new parameter struct.
|
|
||||||
p := client.VirtualMachine.NewExpungeVirtualMachineParams(instanceID)
|
|
||||||
|
|
||||||
ui.Say("Expunging instance...")
|
|
||||||
if _, err := client.VirtualMachine.ExpungeVirtualMachine(p); err != nil {
|
|
||||||
// This is a very poor way to be told the ID does no longer exist :(
|
|
||||||
if strings.Contains(err.Error(), fmt.Sprintf(
|
|
||||||
"Invalid parameter id value=%s due to incorrect long value format, "+
|
|
||||||
"or entity does not exist", instanceID)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Error(fmt.Sprintf("Error expunging instance. Please expunge it manually.\n\n"+
|
|
||||||
"\tName: %s\n"+
|
|
||||||
"\tError: %s", config.InstanceName, err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message("Instance has been deleted!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateUserData returns the user data as a base64 encoded string.
|
|
||||||
func (s *stepCreateInstance) generateUserData(userData string, httpGETOnly bool) (string, error) {
|
|
||||||
renderedUserData, err := interpolate.Render(userData, &s.Ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Error rendering user_data: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ud := base64.StdEncoding.EncodeToString([]byte(renderedUserData))
|
|
||||||
|
|
||||||
// DeployVirtualMachine uses POST by default which allows 32K of
|
|
||||||
// userdata. If using GET instead the userdata is limited to 2K.
|
|
||||||
maxUD := 32768
|
|
||||||
if httpGETOnly {
|
|
||||||
maxUD = 2048
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ud) > maxUD {
|
|
||||||
return "", fmt.Errorf(
|
|
||||||
"The supplied user_data contains %d bytes after encoding, "+
|
|
||||||
"this exceeds the limit of %d bytes", len(ud), maxUD)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ud, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func hostIP() (string, error) {
|
|
||||||
addrs, err := net.InterfaceAddrs()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range addrs {
|
|
||||||
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
|
||||||
if ipnet.IP.To4() != nil {
|
|
||||||
return ipnet.IP.String(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", errors.New("No host IP found")
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateSecurityGroup struct {
|
|
||||||
tempSG string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateSecurityGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*cloudstack.CloudStackClient)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
if len(config.SecurityGroups) > 0 {
|
|
||||||
state.Put("security_groups", config.SecurityGroups)
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !config.CreateSecurityGroup {
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Creating temporary Security Group...")
|
|
||||||
|
|
||||||
p := client.SecurityGroup.NewCreateSecurityGroupParams(
|
|
||||||
fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()),
|
|
||||||
)
|
|
||||||
p.SetDescription("Temporary SG created by Packer")
|
|
||||||
if config.Project != "" {
|
|
||||||
p.SetProjectid(config.Project)
|
|
||||||
}
|
|
||||||
|
|
||||||
sg, err := client.SecurityGroup.CreateSecurityGroup(p)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Failed to create security group: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
s.tempSG = sg.Id
|
|
||||||
state.Put("security_groups", []string{sg.Id})
|
|
||||||
|
|
||||||
// Create Ingress rule
|
|
||||||
i := client.SecurityGroup.NewAuthorizeSecurityGroupIngressParams()
|
|
||||||
i.SetCidrlist(config.CIDRList)
|
|
||||||
i.SetProtocol("TCP")
|
|
||||||
i.SetSecuritygroupid(sg.Id)
|
|
||||||
i.SetStartport(config.Comm.Port())
|
|
||||||
i.SetEndport(config.Comm.Port())
|
|
||||||
if config.Project != "" {
|
|
||||||
i.SetProjectid(config.Project)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.SecurityGroup.AuthorizeSecurityGroupIngress(i)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Failed to authorize security group ingress rule: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup any resources that may have been created during the Run phase.
|
|
||||||
func (s *stepCreateSecurityGroup) Cleanup(state multistep.StateBag) {
|
|
||||||
client := state.Get("client").(*cloudstack.CloudStackClient)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
if s.tempSG == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Cleanup temporary security group: %s ...", s.tempSG))
|
|
||||||
p := client.SecurityGroup.NewDeleteSecurityGroupParams()
|
|
||||||
p.SetId(s.tempSG)
|
|
||||||
if config.Project != "" {
|
|
||||||
p.SetProjectid(config.Project)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := client.SecurityGroup.DeleteSecurityGroup(p); err != nil {
|
|
||||||
ui.Error(err.Error())
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting security group: %s. Please destroy it manually.\n", s.tempSG))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateTemplate struct{}
|
|
||||||
|
|
||||||
func (s *stepCreateTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*cloudstack.CloudStackClient)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Creating template: %s", config.TemplateName))
|
|
||||||
|
|
||||||
// Retrieve the instance ID from the previously saved state.
|
|
||||||
instanceID, ok := state.Get("instance_id").(string)
|
|
||||||
if !ok || instanceID == "" {
|
|
||||||
err := fmt.Errorf("Could not retrieve instance_id from state!")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new parameter struct.
|
|
||||||
p := client.Template.NewCreateTemplateParams(
|
|
||||||
config.TemplateDisplayText,
|
|
||||||
config.TemplateName,
|
|
||||||
config.TemplateOS,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Configure the template according to the supplied config.
|
|
||||||
p.SetIsfeatured(config.TemplateFeatured)
|
|
||||||
p.SetIspublic(config.TemplatePublic)
|
|
||||||
p.SetIsdynamicallyscalable(config.TemplateScalable)
|
|
||||||
p.SetPasswordenabled(config.TemplatePasswordEnabled)
|
|
||||||
p.SetRequireshvm(config.TemplateRequiresHVM)
|
|
||||||
|
|
||||||
if config.Project != "" {
|
|
||||||
p.SetProjectid(config.Project)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.TemplateTag != "" {
|
|
||||||
p.SetTemplatetag(config.TemplateTag)
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message("Retrieving the ROOT volume ID...")
|
|
||||||
volumeID, err := getRootVolumeID(client, instanceID, config.Project)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the volume ID from which to create the template.
|
|
||||||
p.SetVolumeid(volumeID)
|
|
||||||
|
|
||||||
ui.Message("Creating the new template...")
|
|
||||||
template, err := client.Template.CreateTemplate(p)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating the new template %s: %s", config.TemplateName, err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is kind of nasty, but it appears to be needed to prevent corrupt templates.
|
|
||||||
// When CloudStack says the template creation is done and you then delete the source
|
|
||||||
// volume shortly after, it seems to corrupt the newly created template. Giving it an
|
|
||||||
// additional 30 seconds to really finish up, seem to prevent that from happening.
|
|
||||||
time.Sleep(30 * time.Second)
|
|
||||||
|
|
||||||
ui.Message("Template has been created!")
|
|
||||||
|
|
||||||
// Store the template.
|
|
||||||
state.Put("template", template)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup any resources that may have been created during the Run phase.
|
|
||||||
func (s *stepCreateTemplate) Cleanup(state multistep.StateBag) {
|
|
||||||
// Nothing to cleanup for this step.
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRootVolumeID(client *cloudstack.CloudStackClient, instanceID, projectID string) (string, error) {
|
|
||||||
// Retrieve the virtual machine object.
|
|
||||||
p := client.Volume.NewListVolumesParams()
|
|
||||||
|
|
||||||
// Set the type and virtual machine ID
|
|
||||||
p.SetType("ROOT")
|
|
||||||
p.SetVirtualmachineid(instanceID)
|
|
||||||
if projectID != "" {
|
|
||||||
p.SetProjectid(projectID)
|
|
||||||
}
|
|
||||||
|
|
||||||
volumes, err := client.Volume.ListVolumes(p)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Failed to retrieve ROOT volume: %s", err)
|
|
||||||
}
|
|
||||||
if volumes.Count != 1 {
|
|
||||||
return "", fmt.Errorf("Could not find ROOT disk of instance %s", instanceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return volumes.Volumes[0].Id, nil
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepDetachIso struct{}
|
|
||||||
|
|
||||||
// Detaches currently ISO file attached to a virtual machine if any.
|
|
||||||
func (s *stepDetachIso) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
// Check if state uses iso file and has need to eject it
|
|
||||||
if !config.EjectISO || config.SourceISO == "" {
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Checking attached iso...")
|
|
||||||
|
|
||||||
// Wait to make call detachIso
|
|
||||||
if config.EjectISODelay > 0 {
|
|
||||||
ui.Message(fmt.Sprintf("Waiting for %v before detaching ISO from virtual machine...", config.EjectISODelay))
|
|
||||||
time.Sleep(config.EjectISODelay)
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*cloudstack.CloudStackClient)
|
|
||||||
|
|
||||||
instanceID, ok := state.Get("instance_id").(string)
|
|
||||||
if !ok || instanceID == "" {
|
|
||||||
err := fmt.Errorf("Could not retrieve instance_id from state")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message("Detaching iso from virtual machine...")
|
|
||||||
|
|
||||||
// Get a new DetachIsoParams and detaches Iso file from given virtualMachine instance
|
|
||||||
detachIsoParams := client.ISO.NewDetachIsoParams(instanceID)
|
|
||||||
response, err := client.ISO.DetachIso(detachIsoParams)
|
|
||||||
if err != nil || response == nil {
|
|
||||||
err := fmt.Errorf("Error detaching ISO from virtual machine: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepDetachIso) Cleanup(state multistep.StateBag) {
|
|
||||||
// Nothing to cleanup for this step.
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepKeypair struct {
|
|
||||||
Debug bool
|
|
||||||
Comm *communicator.Config
|
|
||||||
DebugKeyPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepKeypair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
if s.Comm.SSHPrivateKeyFile != "" {
|
|
||||||
ui.Say("Using existing SSH private key")
|
|
||||||
privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Comm.SSHPrivateKey = privateKeyBytes
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName == "" {
|
|
||||||
ui.Say("Using SSH Agent with keypair in Source image")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName != "" {
|
|
||||||
ui.Say(fmt.Sprintf("Using SSH Agent for existing keypair %s", s.Comm.SSHKeyPairName))
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Comm.SSHTemporaryKeyPairName == "" {
|
|
||||||
ui.Say("Not using a keypair")
|
|
||||||
s.Comm.SSHKeyPairName = ""
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*cloudstack.CloudStackClient)
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s ...", s.Comm.SSHTemporaryKeyPairName))
|
|
||||||
|
|
||||||
p := client.SSH.NewCreateSSHKeyPairParams(s.Comm.SSHTemporaryKeyPairName)
|
|
||||||
|
|
||||||
cfg := state.Get("config").(*Config)
|
|
||||||
if cfg.Project != "" {
|
|
||||||
p.SetProjectid(cfg.Project)
|
|
||||||
}
|
|
||||||
|
|
||||||
keypair, err := client.SSH.CreateSSHKeyPair(p)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating temporary keypair: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
if keypair.Privatekey == "" {
|
|
||||||
err := fmt.Errorf("The temporary keypair returned was blank")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Created temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
|
|
||||||
|
|
||||||
// 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([]byte(keypair.Privatekey)); err != nil {
|
|
||||||
err := fmt.Errorf("Error saving debug key: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chmod it so that it is SSH ready
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
if err := f.Chmod(0600); err != nil {
|
|
||||||
err := fmt.Errorf("Error setting permissions of debug key: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set some data for use in future steps
|
|
||||||
s.Comm.SSHKeyPairName = s.Comm.SSHTemporaryKeyPairName
|
|
||||||
s.Comm.SSHPrivateKey = []byte(keypair.Privatekey)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepKeypair) Cleanup(state multistep.StateBag) {
|
|
||||||
if s.Comm.SSHTemporaryKeyPairName == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
client := state.Get("client").(*cloudstack.CloudStackClient)
|
|
||||||
cfg := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
p := client.SSH.NewDeleteSSHKeyPairParams(s.Comm.SSHTemporaryKeyPairName)
|
|
||||||
if cfg.Project != "" {
|
|
||||||
p.SetProjectid(cfg.Project)
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Deleting temporary keypair: %s ...", s.Comm.SSHTemporaryKeyPairName))
|
|
||||||
|
|
||||||
_, err := client.SSH.DeleteSSHKeyPair(p)
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(err.Error())
|
|
||||||
ui.Error(fmt.Sprintf(
|
|
||||||
"Error cleaning up keypair. Please delete the key manually: %s", s.Comm.SSHTemporaryKeyPairName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepPrepareConfig struct{}
|
|
||||||
|
|
||||||
func (s *stepPrepareConfig) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*cloudstack.CloudStackClient)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say("Preparing config...")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
|
|
||||||
// First get the project and zone UUID's so we can use them in other calls when needed.
|
|
||||||
if config.Project != "" && !isUUID(config.Project) {
|
|
||||||
config.Project, _, err = client.Project.GetProjectID(config.Project)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, &retrieveErr{"project", config.Project, err})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.UserDataFile != "" {
|
|
||||||
userdata, err := ioutil.ReadFile(config.UserDataFile)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("problem reading user data file: %s", err))
|
|
||||||
}
|
|
||||||
config.UserData = string(userdata)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isUUID(config.Zone) {
|
|
||||||
config.Zone, _, err = client.Zone.GetZoneID(config.Zone)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, &retrieveErr{"zone", config.Zone, err})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then try to get the remaining UUID's.
|
|
||||||
if config.DiskOffering != "" && !isUUID(config.DiskOffering) {
|
|
||||||
config.DiskOffering, _, err = client.DiskOffering.GetDiskOfferingID(config.DiskOffering)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, &retrieveErr{"disk offering", config.DiskOffering, err})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.PublicIPAddress != "" {
|
|
||||||
if isUUID(config.PublicIPAddress) {
|
|
||||||
ip, _, err := client.Address.GetPublicIpAddressByID(config.PublicIPAddress)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("Failed to retrieve IP address: %s", err))
|
|
||||||
}
|
|
||||||
state.Put("ipaddress", ip.Ipaddress)
|
|
||||||
} else {
|
|
||||||
// Save the public IP address before replacing it with it's UUID.
|
|
||||||
state.Put("ipaddress", config.PublicIPAddress)
|
|
||||||
|
|
||||||
p := client.Address.NewListPublicIpAddressesParams()
|
|
||||||
p.SetIpaddress(config.PublicIPAddress)
|
|
||||||
|
|
||||||
if config.Project != "" {
|
|
||||||
p.SetProjectid(config.Project)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipAddrs, err := client.Address.ListPublicIpAddresses(p)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, &retrieveErr{"IP address", config.PublicIPAddress, err})
|
|
||||||
}
|
|
||||||
if err == nil && ipAddrs.Count != 1 {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, &retrieveErr{"IP address", config.PublicIPAddress, ipAddrs})
|
|
||||||
}
|
|
||||||
if err == nil && ipAddrs.Count == 1 {
|
|
||||||
config.PublicIPAddress = ipAddrs.PublicIpAddresses[0].Id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isUUID(config.Network) {
|
|
||||||
config.Network, _, err = client.Network.GetNetworkID(config.Network, cloudstack.WithProject(config.Project))
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, &retrieveErr{"network", config.Network, err})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then try to get the SG's UUID's.
|
|
||||||
if len(config.SecurityGroups) > 0 {
|
|
||||||
for i := range config.SecurityGroups {
|
|
||||||
if !isUUID(config.SecurityGroups[i]) {
|
|
||||||
config.SecurityGroups[i], _, err = client.SecurityGroup.GetSecurityGroupID(config.SecurityGroups[i], cloudstack.WithProject(config.Project))
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, &retrieveErr{"network", config.SecurityGroups[i], err})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isUUID(config.ServiceOffering) {
|
|
||||||
config.ServiceOffering, _, err = client.ServiceOffering.GetServiceOfferingID(config.ServiceOffering)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, &retrieveErr{"service offering", config.ServiceOffering, err})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.SourceISO != "" {
|
|
||||||
if isUUID(config.SourceISO) {
|
|
||||||
state.Put("source", config.SourceISO)
|
|
||||||
} else {
|
|
||||||
isoID, _, err := client.ISO.GetIsoID(config.SourceISO, "executable", config.Zone)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, &retrieveErr{"ISO", config.SourceISO, err})
|
|
||||||
}
|
|
||||||
state.Put("source", isoID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.SourceTemplate != "" {
|
|
||||||
if isUUID(config.SourceTemplate) {
|
|
||||||
state.Put("source", config.SourceTemplate)
|
|
||||||
} else {
|
|
||||||
templateID, _, err := client.Template.GetTemplateID(config.SourceTemplate, "executable", config.Zone)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, &retrieveErr{"template", config.SourceTemplate, err})
|
|
||||||
}
|
|
||||||
state.Put("source", templateID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isUUID(config.TemplateOS) {
|
|
||||||
p := client.GuestOS.NewListOsTypesParams()
|
|
||||||
p.SetDescription(config.TemplateOS)
|
|
||||||
|
|
||||||
types, err := client.GuestOS.ListOsTypes(p)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, &retrieveErr{"OS type", config.TemplateOS, err})
|
|
||||||
}
|
|
||||||
if err == nil && types.Count != 1 {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, &retrieveErr{"OS type", config.TemplateOS, types})
|
|
||||||
}
|
|
||||||
if err == nil && types.Count == 1 {
|
|
||||||
config.TemplateOS = types.OsTypes[0].Id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is needed because nil is not always nil. When returning *packersdk.MultiError(nil)
|
|
||||||
// as an error interface, that interface will no longer be equal to nil but it will be
|
|
||||||
// an interface with type *packersdk.MultiError and value nil which is different then a
|
|
||||||
// nil interface.
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
state.Put("error", errs)
|
|
||||||
ui.Error(errs.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message("Config has been prepared!")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepPrepareConfig) Cleanup(state multistep.StateBag) {
|
|
||||||
// Nothing to cleanup for this step.
|
|
||||||
}
|
|
||||||
|
|
||||||
type retrieveErr struct {
|
|
||||||
name string
|
|
||||||
value string
|
|
||||||
result interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *retrieveErr) Error() string {
|
|
||||||
if err, ok := e.result.(error); ok {
|
|
||||||
e.result = err.Error()
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("Error retrieving UUID of %s %s: %v", e.name, e.value, e.result)
|
|
||||||
}
|
|
||||||
|
|
||||||
var uuidRegex = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
|
|
||||||
|
|
||||||
func isUUID(uuid string) bool {
|
|
||||||
return uuidRegex.MatchString(uuid)
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package cloudstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepShutdownInstance struct{}
|
|
||||||
|
|
||||||
func (s *stepShutdownInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*cloudstack.CloudStackClient)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say("Shutting down instance...")
|
|
||||||
|
|
||||||
// Retrieve the instance ID from the previously saved state.
|
|
||||||
instanceID, ok := state.Get("instance_id").(string)
|
|
||||||
if !ok || instanceID == "" {
|
|
||||||
state.Put("error", fmt.Errorf("Could not retrieve instance_id from state!"))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new parameter struct.
|
|
||||||
p := client.VirtualMachine.NewStopVirtualMachineParams(instanceID)
|
|
||||||
|
|
||||||
// Shutdown the virtual machine.
|
|
||||||
_, err := client.VirtualMachine.StopVirtualMachine(p)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error shutting down instance %s: %s", config.InstanceName, err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message("Instance has been shutdown!")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup any resources that may have been created during the Run phase.
|
|
||||||
func (s *stepShutdownInstance) Cleanup(state multistep.StateBag) {
|
|
||||||
// Nothing to cleanup for this step.
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
|
||||||
packerVersion "github.com/hashicorp/packer/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var CloudstackPluginVersion *version.PluginVersion
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
CloudstackPluginVersion = version.InitializePluginVersion(
|
|
||||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
|
||||||
}
|
|
||||||
@@ -80,10 +80,17 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
|
|||||||
|
|
||||||
// Build the steps
|
// Build the steps
|
||||||
steps := []multistep.Step{
|
steps := []multistep.Step{
|
||||||
&stepCreateSSHKey{
|
&communicator.StepSSHKeyGen{
|
||||||
Debug: b.config.PackerDebug,
|
CommConf: &b.config.Comm,
|
||||||
DebugKeyPath: fmt.Sprintf("do_%s.pem", b.config.PackerBuildName),
|
SSHTemporaryKeyPair: b.config.Comm.SSH.SSHTemporaryKeyPair,
|
||||||
},
|
},
|
||||||
|
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(stepCreateDroplet),
|
||||||
new(stepDropletInfo),
|
new(stepDropletInfo),
|
||||||
&communicator.StepConnect{
|
&communicator.StepConnect{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
"github.com/digitalocean/godo"
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//go:generate struct-markdown
|
//go:generate packer-sdc struct-markdown
|
||||||
//go:generate mapstructure-to-hcl2 -type Config
|
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||||
|
|
||||||
package digitalocean
|
package digitalocean
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||||
|
|
||||||
package digitalocean
|
package digitalocean
|
||||||
|
|
||||||
|
|||||||
@@ -2,26 +2,16 @@ package digitalocean
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
"github.com/digitalocean/godo"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type stepCreateSSHKey struct {
|
type stepCreateSSHKey struct {
|
||||||
Debug bool
|
|
||||||
DebugKeyPath string
|
|
||||||
|
|
||||||
keyId int
|
keyId int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,31 +20,12 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
|
|||||||
ui := state.Get("ui").(packersdk.Ui)
|
ui := state.Get("ui").(packersdk.Ui)
|
||||||
c := state.Get("config").(*Config)
|
c := state.Get("config").(*Config)
|
||||||
|
|
||||||
ui.Say("Creating temporary ssh key for droplet...")
|
if c.Comm.SSHPublicKey == nil {
|
||||||
|
ui.Say("No public SSH key found; skipping SSH public key import...")
|
||||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
return multistep.ActionContinue
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("error generating RSA key: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ASN.1 DER encoded form
|
ui.Say("Importing SSH public key...")
|
||||||
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
|
// The name of the public key on DO
|
||||||
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||||
@@ -62,7 +33,7 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
|
|||||||
// Create the key!
|
// Create the key!
|
||||||
key, _, err := client.Keys.Create(context.TODO(), &godo.KeyCreateRequest{
|
key, _, err := client.Keys.Create(context.TODO(), &godo.KeyCreateRequest{
|
||||||
Name: name,
|
Name: name,
|
||||||
PublicKey: pub_sshformat,
|
PublicKey: string(c.Comm.SSHPublicKey),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
|
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
|
||||||
@@ -79,31 +50,6 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
|
|||||||
// Remember some state for the future
|
// Remember some state for the future
|
||||||
state.Put("ssh_key_id", key.ID)
|
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
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilder_implBuilder(t *testing.T) {
|
func TestBuilder_implBuilder(t *testing.T) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:generate mapstructure-to-hcl2 -type Config
|
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||||
|
|
||||||
package file
|
package file
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||||
|
|
||||||
package file
|
package file
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
package googlecompute
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/oauth2/google"
|
|
||||||
"golang.org/x/oauth2/jwt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ServiceAccount struct {
|
|
||||||
jsonKey []byte
|
|
||||||
jwt *jwt.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessAccountFile will return a ServiceAccount for the JSON account file stored in text.
|
|
||||||
// Otherwise it will return an error if text does not look or reference a valid account file.
|
|
||||||
func ProcessAccountFile(text string) (*ServiceAccount, error) {
|
|
||||||
// Assume text is a JSON string
|
|
||||||
if conf, err := google.JWTConfigFromJSON([]byte(text), DriverScopes...); err == nil {
|
|
||||||
return &ServiceAccount{
|
|
||||||
jsonKey: []byte(text),
|
|
||||||
jwt: conf,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If text was not JSON, assume it is a file path instead
|
|
||||||
if _, err := os.Stat(text); os.IsNotExist(err) {
|
|
||||||
return nil, fmt.Errorf("account_file path does not exist: %s", text)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(text)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error reading account_file from path '%s': %s", text, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
conf, err := google.JWTConfigFromJSON(data, DriverScopes...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error parsing account_file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ServiceAccount{
|
|
||||||
jsonKey: data,
|
|
||||||
jwt: conf,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package googlecompute
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Artifact represents a GCE image as the result of a Packer build.
|
|
||||||
type Artifact struct {
|
|
||||||
image *Image
|
|
||||||
driver Driver
|
|
||||||
config *Config
|
|
||||||
// StateData should store data such as GeneratedData
|
|
||||||
// to be shared with post-processors
|
|
||||||
StateData map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuilderId returns the builder Id.
|
|
||||||
func (*Artifact) BuilderId() string {
|
|
||||||
return BuilderId
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy destroys the GCE image represented by the artifact.
|
|
||||||
func (a *Artifact) Destroy() error {
|
|
||||||
log.Printf("Destroying image: %s", a.image.Name)
|
|
||||||
errCh := a.driver.DeleteImage(a.image.Name)
|
|
||||||
return <-errCh
|
|
||||||
}
|
|
||||||
|
|
||||||
// Files returns the files represented by the artifact.
|
|
||||||
func (*Artifact) Files() []string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Id returns the GCE image name.
|
|
||||||
func (a *Artifact) Id() string {
|
|
||||||
return a.image.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string representation of the artifact.
|
|
||||||
func (a *Artifact) String() string {
|
|
||||||
return fmt.Sprintf("A disk image was created: %v", a.image.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
|
||||||
if _, ok := a.StateData[name]; ok {
|
|
||||||
return a.StateData[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
switch name {
|
|
||||||
case "ImageName":
|
|
||||||
return a.image.Name
|
|
||||||
case "ImageSizeGb":
|
|
||||||
return a.image.SizeGb
|
|
||||||
case "AccountFilePath":
|
|
||||||
return a.config.AccountFile
|
|
||||||
case "ProjectId":
|
|
||||||
return a.config.ProjectId
|
|
||||||
case "BuildZone":
|
|
||||||
return a.config.Zone
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user