Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a091b7bd3 | |||
| c56677d399 | |||
| 8a3912c54b | |||
| 38837848f9 | |||
| e681669c70 | |||
| 58bf783a2f | |||
| bcb25f1916 | |||
| ef612c0eb1 | |||
| 972497589e | |||
| 2cd296874e | |||
| f161f2bed2 | |||
| 6b59525408 | |||
| d0a15f9a15 | |||
| af37f53439 | |||
| d0588580fa | |||
| ccbd0a29cc | |||
| d08aa6f6b0 | |||
| bb511e9592 | |||
| b3ba270f2d | |||
| 4be2c350bf | |||
| bc35a737c0 | |||
| b5666b84cd | |||
| 469f033c36 | |||
| 2db338e322 | |||
| 0f6a081724 | |||
| 4b093aab78 | |||
| 5145893ae5 | |||
| 9044deeb05 | |||
| dc63b9a7a4 | |||
| d8c3584b46 | |||
| 386f7c3f56 | |||
| 8bf03cbca7 | |||
| eb6527c8b6 | |||
| fd028b71b3 | |||
| 0ec3ad3db1 | |||
| a29f3340c2 | |||
| 45dfb97ff4 | |||
| 30bcf44c2c | |||
| 2da9c21733 | |||
| 987080a409 | |||
| ed4ca8e6dc | |||
| 9bdb809edd | |||
| 88192f1fdd | |||
| 6fa213235f | |||
| af34218909 | |||
| 2dbfef5750 | |||
| 2f927177d9 | |||
| 50fadc4118 | |||
| b54121a72d | |||
| 4de2954d01 | |||
| 25a999978b | |||
| de86b45848 | |||
| 770124207c | |||
| dbe6010510 |
+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:
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
comment:
|
|
||||||
layout: "flags, files"
|
|
||||||
behavior: default
|
|
||||||
require_changes: true # only comment on changes in coverage
|
|
||||||
require_base: yes # [yes :: must have a base report to post]
|
|
||||||
require_head: yes # [yes :: must have a head report to post]
|
|
||||||
after_n_builds: 3 # wait for all OS test coverage builds to post comment
|
|
||||||
branches: # branch names that can post comment
|
|
||||||
- "master"
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
status:
|
|
||||||
project: off
|
|
||||||
patch: off
|
|
||||||
|
|
||||||
ignore: # ignore hcl2spec generated code for coverage and mocks
|
|
||||||
- "**/*.hcl2spec.go"
|
|
||||||
- "**/*_mock.go"
|
|
||||||
@@ -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:
|
||||||
|
|||||||
+2
-16
@@ -13,15 +13,12 @@
|
|||||||
/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
|
/examples/jdcloud/ @XiaohanLiang @remrain
|
||||||
/builder/jdcloud/ @XiaohanLiang @remrain
|
/builder/jdcloud/ @XiaohanLiang @remrain
|
||||||
/website/pages/docs/builders/jdcloud* @XiaohanLiang @remrain
|
/website/pages/docs/builders/jdcloud* @XiaohanLiang @remrain
|
||||||
|
|
||||||
/builder/linode/ @displague @ctreatma @stvnjacobs @charliekenney23 @phillc
|
/builder/linode/ @stvnjacobs @charliekenney23 @phillc
|
||||||
/website/pages/docs/builders/linode* @displague @ctreatma @stvnjacobs @charliekenney23 @phillc
|
/website/pages/docs/builders/linode* @stvnjacobs @charliekenney23 @phillc
|
||||||
|
|
||||||
/builder/lxc/ @ChrisLundquist
|
/builder/lxc/ @ChrisLundquist
|
||||||
/website/pages/docs/builders/lxc* @ChrisLundquist
|
/website/pages/docs/builders/lxc* @ChrisLundquist
|
||||||
@@ -52,16 +49,6 @@
|
|||||||
/builder/hcloud/ @LKaemmerling
|
/builder/hcloud/ @LKaemmerling
|
||||||
/website/pages/docs/builders/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
|
||||||
|
|
||||||
@@ -83,4 +70,3 @@
|
|||||||
/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/vsphere-template/ nelson@bennu.cl
|
||||||
/post-processor/ucloud-import/ @shawnmssu
|
|
||||||
|
|||||||
@@ -146,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:
|
||||||
@@ -154,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.
|
||||||
|
|||||||
@@ -1,226 +0,0 @@
|
|||||||
//go:generate packer-sdc 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,270 +0,0 @@
|
|||||||
//go:generate packer-sdc 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,
|
|
||||||
RamRoleName: b.config.RamRoleName,
|
|
||||||
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,275 +0,0 @@
|
|||||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; 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"`
|
|
||||||
RamRoleName *string `mapstructure:"ram_role_name" required:"false" cty:"ram_role_name" hcl:"ram_role_name"`
|
|
||||||
SecurityGroupId *string `mapstructure:"security_group_id" required:"false" cty:"security_group_id" hcl:"security_group_id"`
|
|
||||||
SecurityGroupName *string `mapstructure:"security_group_name" required:"false" cty:"security_group_name" hcl:"security_group_name"`
|
|
||||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
|
||||||
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},
|
|
||||||
"ram_role_name": &hcldec.AttrSpec{Name: "ram_role_name", Type: cty.String, Required: false},
|
|
||||||
"security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false},
|
|
||||||
"security_group_name": &hcldec.AttrSpec{Name: "security_group_name", Type: cty.String, Required: false},
|
|
||||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
|
||||||
"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"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
builderT "github.com/hashicorp/packer/acctest"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 packer-sdc 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,156 +0,0 @@
|
|||||||
//go:generate packer-sdc 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"`
|
|
||||||
// Ram Role to apply when launching the instance.
|
|
||||||
RamRoleName string `mapstructure:"ram_role_name" required:"false"`
|
|
||||||
// ID of the security group to which a newly
|
|
||||||
// created instance belongs. Mutual access is allowed between instances in one
|
|
||||||
// security group. If not specified, the newly created instance will be added
|
|
||||||
// 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,210 +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
|
|
||||||
RamRoleName 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.RamRoleName = s.RamRoleName
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
@@ -302,6 +302,10 @@ type Config struct {
|
|||||||
// group and VM name allows one to execute commands to update the VM during a
|
// group and VM name allows one to execute commands to update the VM during a
|
||||||
// Packer build, e.g. attach a resource disk to the VM.
|
// Packer build, e.g. attach a resource disk to the VM.
|
||||||
TempComputeName string `mapstructure:"temp_compute_name" required:"false"`
|
TempComputeName string `mapstructure:"temp_compute_name" required:"false"`
|
||||||
|
// temporary name assigned to the Nic. If this
|
||||||
|
// value is not set, a random value will be assigned. Being able to assign a custom
|
||||||
|
// nicname could ease deployment if naming conventions are used.
|
||||||
|
TempNicName string `mapstructure:"temp_nic_name" required:"false"`
|
||||||
// name assigned to the temporary resource group created during the build.
|
// name assigned to the temporary resource group created during the build.
|
||||||
// If this value is not set, a random value will be assigned. This resource
|
// If this value is not set, a random value will be assigned. This resource
|
||||||
// group is deleted at the end of the build.
|
// group is deleted at the end of the build.
|
||||||
@@ -395,6 +399,10 @@ type Config struct {
|
|||||||
// machine. For Linux this configures an SSH authorized key. For Windows
|
// machine. For Linux this configures an SSH authorized key. For Windows
|
||||||
// this configures a WinRM certificate.
|
// this configures a WinRM certificate.
|
||||||
OSType string `mapstructure:"os_type" required:"false"`
|
OSType string `mapstructure:"os_type" required:"false"`
|
||||||
|
// temporary name assigned to the OSDisk. If this
|
||||||
|
// value is not set, a random value will be assigned. Being able to assign a custom
|
||||||
|
// osDiskName could ease deployment if naming conventions are used.
|
||||||
|
TempOSDiskName string `mapstructure:"temp_os_disk_name" required:"false"`
|
||||||
// Specify the size of the OS disk in GB
|
// Specify the size of the OS disk in GB
|
||||||
// (gigabytes). Values of zero or less than zero are ignored.
|
// (gigabytes). Values of zero or less than zero are ignored.
|
||||||
OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb" required:"false"`
|
OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb" required:"false"`
|
||||||
@@ -704,9 +712,18 @@ func setRuntimeValues(c *Config) {
|
|||||||
} else if c.TempResourceGroupName != "" && c.BuildResourceGroupName == "" {
|
} else if c.TempResourceGroupName != "" && c.BuildResourceGroupName == "" {
|
||||||
c.tmpResourceGroupName = c.TempResourceGroupName
|
c.tmpResourceGroupName = c.TempResourceGroupName
|
||||||
}
|
}
|
||||||
|
if c.TempNicName == "" {
|
||||||
|
c.tmpNicName = tempName.NicName
|
||||||
|
} else {
|
||||||
|
c.tmpNicName = c.TempNicName
|
||||||
|
}
|
||||||
c.tmpNicName = tempName.NicName
|
c.tmpNicName = tempName.NicName
|
||||||
c.tmpPublicIPAddressName = tempName.PublicIPAddressName
|
c.tmpPublicIPAddressName = tempName.PublicIPAddressName
|
||||||
c.tmpOSDiskName = tempName.OSDiskName
|
if c.TempOSDiskName == "" {
|
||||||
|
c.tmpOSDiskName = tempName.OSDiskName
|
||||||
|
} else {
|
||||||
|
c.tmpOSDiskName = c.TempOSDiskName
|
||||||
|
}
|
||||||
c.tmpDataDiskName = tempName.DataDiskName
|
c.tmpDataDiskName = tempName.DataDiskName
|
||||||
c.tmpSubnetName = tempName.SubnetName
|
c.tmpSubnetName = tempName.SubnetName
|
||||||
c.tmpVirtualNetworkName = tempName.VirtualNetworkName
|
c.tmpVirtualNetworkName = tempName.VirtualNetworkName
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ type FlatConfig struct {
|
|||||||
ResourceGroupName *string `mapstructure:"resource_group_name" cty:"resource_group_name" hcl:"resource_group_name"`
|
ResourceGroupName *string `mapstructure:"resource_group_name" cty:"resource_group_name" hcl:"resource_group_name"`
|
||||||
StorageAccount *string `mapstructure:"storage_account" cty:"storage_account" hcl:"storage_account"`
|
StorageAccount *string `mapstructure:"storage_account" cty:"storage_account" hcl:"storage_account"`
|
||||||
TempComputeName *string `mapstructure:"temp_compute_name" required:"false" cty:"temp_compute_name" hcl:"temp_compute_name"`
|
TempComputeName *string `mapstructure:"temp_compute_name" required:"false" cty:"temp_compute_name" hcl:"temp_compute_name"`
|
||||||
|
TempNicName *string `mapstructure:"temp_nic_name" required:"false" cty:"temp_nic_name" hcl:"temp_nic_name"`
|
||||||
TempResourceGroupName *string `mapstructure:"temp_resource_group_name" cty:"temp_resource_group_name" hcl:"temp_resource_group_name"`
|
TempResourceGroupName *string `mapstructure:"temp_resource_group_name" cty:"temp_resource_group_name" hcl:"temp_resource_group_name"`
|
||||||
BuildResourceGroupName *string `mapstructure:"build_resource_group_name" cty:"build_resource_group_name" hcl:"build_resource_group_name"`
|
BuildResourceGroupName *string `mapstructure:"build_resource_group_name" cty:"build_resource_group_name" hcl:"build_resource_group_name"`
|
||||||
BuildKeyVaultName *string `mapstructure:"build_key_vault_name" cty:"build_key_vault_name" hcl:"build_key_vault_name"`
|
BuildKeyVaultName *string `mapstructure:"build_key_vault_name" cty:"build_key_vault_name" hcl:"build_key_vault_name"`
|
||||||
@@ -70,6 +71,7 @@ type FlatConfig struct {
|
|||||||
PlanInfo *FlatPlanInformation `mapstructure:"plan_info" required:"false" cty:"plan_info" hcl:"plan_info"`
|
PlanInfo *FlatPlanInformation `mapstructure:"plan_info" required:"false" cty:"plan_info" hcl:"plan_info"`
|
||||||
PollingDurationTimeout *string `mapstructure:"polling_duration_timeout" required:"false" cty:"polling_duration_timeout" hcl:"polling_duration_timeout"`
|
PollingDurationTimeout *string `mapstructure:"polling_duration_timeout" required:"false" cty:"polling_duration_timeout" hcl:"polling_duration_timeout"`
|
||||||
OSType *string `mapstructure:"os_type" required:"false" cty:"os_type" hcl:"os_type"`
|
OSType *string `mapstructure:"os_type" required:"false" cty:"os_type" hcl:"os_type"`
|
||||||
|
TempOSDiskName *string `mapstructure:"temp_os_disk_name" required:"false" cty:"temp_os_disk_name" hcl:"temp_os_disk_name"`
|
||||||
OSDiskSizeGB *int32 `mapstructure:"os_disk_size_gb" required:"false" cty:"os_disk_size_gb" hcl:"os_disk_size_gb"`
|
OSDiskSizeGB *int32 `mapstructure:"os_disk_size_gb" required:"false" cty:"os_disk_size_gb" hcl:"os_disk_size_gb"`
|
||||||
AdditionalDiskSize []int32 `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
|
AdditionalDiskSize []int32 `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
|
||||||
DiskCachingType *string `mapstructure:"disk_caching_type" required:"false" cty:"disk_caching_type" hcl:"disk_caching_type"`
|
DiskCachingType *string `mapstructure:"disk_caching_type" required:"false" cty:"disk_caching_type" hcl:"disk_caching_type"`
|
||||||
@@ -187,6 +189,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"resource_group_name": &hcldec.AttrSpec{Name: "resource_group_name", Type: cty.String, Required: false},
|
"resource_group_name": &hcldec.AttrSpec{Name: "resource_group_name", Type: cty.String, Required: false},
|
||||||
"storage_account": &hcldec.AttrSpec{Name: "storage_account", Type: cty.String, Required: false},
|
"storage_account": &hcldec.AttrSpec{Name: "storage_account", Type: cty.String, Required: false},
|
||||||
"temp_compute_name": &hcldec.AttrSpec{Name: "temp_compute_name", Type: cty.String, Required: false},
|
"temp_compute_name": &hcldec.AttrSpec{Name: "temp_compute_name", Type: cty.String, Required: false},
|
||||||
|
"temp_nic_name": &hcldec.AttrSpec{Name: "temp_nic_name", Type: cty.String, Required: false},
|
||||||
"temp_resource_group_name": &hcldec.AttrSpec{Name: "temp_resource_group_name", Type: cty.String, Required: false},
|
"temp_resource_group_name": &hcldec.AttrSpec{Name: "temp_resource_group_name", Type: cty.String, Required: false},
|
||||||
"build_resource_group_name": &hcldec.AttrSpec{Name: "build_resource_group_name", Type: cty.String, Required: false},
|
"build_resource_group_name": &hcldec.AttrSpec{Name: "build_resource_group_name", Type: cty.String, Required: false},
|
||||||
"build_key_vault_name": &hcldec.AttrSpec{Name: "build_key_vault_name", Type: cty.String, Required: false},
|
"build_key_vault_name": &hcldec.AttrSpec{Name: "build_key_vault_name", Type: cty.String, Required: false},
|
||||||
@@ -199,6 +202,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"plan_info": &hcldec.BlockSpec{TypeName: "plan_info", Nested: hcldec.ObjectSpec((*FlatPlanInformation)(nil).HCL2Spec())},
|
"plan_info": &hcldec.BlockSpec{TypeName: "plan_info", Nested: hcldec.ObjectSpec((*FlatPlanInformation)(nil).HCL2Spec())},
|
||||||
"polling_duration_timeout": &hcldec.AttrSpec{Name: "polling_duration_timeout", Type: cty.String, Required: false},
|
"polling_duration_timeout": &hcldec.AttrSpec{Name: "polling_duration_timeout", Type: cty.String, Required: false},
|
||||||
"os_type": &hcldec.AttrSpec{Name: "os_type", Type: cty.String, Required: false},
|
"os_type": &hcldec.AttrSpec{Name: "os_type", Type: cty.String, Required: false},
|
||||||
|
"temp_os_disk_name": &hcldec.AttrSpec{Name: "temp_os_disk_name", Type: cty.String, Required: false},
|
||||||
"os_disk_size_gb": &hcldec.AttrSpec{Name: "os_disk_size_gb", Type: cty.Number, Required: false},
|
"os_disk_size_gb": &hcldec.AttrSpec{Name: "os_disk_size_gb", Type: cty.Number, Required: false},
|
||||||
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.Number), Required: false},
|
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.Number), Required: false},
|
||||||
"disk_caching_type": &hcldec.AttrSpec{Name: "disk_caching_type", Type: cty.String, Required: false},
|
"disk_caching_type": &hcldec.AttrSpec{Name: "disk_caching_type", Type: cty.String, Required: false},
|
||||||
|
|||||||
@@ -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 packer-sdc struct-markdown
|
|
||||||
//go:generate packer-sdc 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 "packer-sdc mapstructure-to-hcl2"; 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)
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Artifact struct {
|
|
||||||
// The name of the snapshot
|
|
||||||
SnapshotName string
|
|
||||||
|
|
||||||
// The ID of the image
|
|
||||||
SnapshotId int
|
|
||||||
|
|
||||||
// The name of the region
|
|
||||||
RegionNames []string
|
|
||||||
|
|
||||||
// The client for making API calls
|
|
||||||
Client *godo.Client
|
|
||||||
|
|
||||||
// StateData should store data such as GeneratedData
|
|
||||||
// to be shared with post-processors
|
|
||||||
StateData map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Artifact) BuilderId() string {
|
|
||||||
return BuilderId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Artifact) Files() []string {
|
|
||||||
// No files with DigitalOcean
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Id() string {
|
|
||||||
return fmt.Sprintf("%s:%s", strings.Join(a.RegionNames[:], ","), strconv.FormatUint(uint64(a.SnapshotId), 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) String() string {
|
|
||||||
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v) in regions '%v'", a.SnapshotName, a.SnapshotId, strings.Join(a.RegionNames[:], ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
|
||||||
return a.StateData[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Destroy() error {
|
|
||||||
log.Printf("Destroying image: %d (%s)", a.SnapshotId, a.SnapshotName)
|
|
||||||
_, err := a.Client.Images.Delete(context.TODO(), a.SnapshotId)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func generatedData() map[string]interface{} {
|
|
||||||
return make(map[string]interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifact_Impl(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &Artifact{}
|
|
||||||
if _, ok := raw.(packersdk.Artifact); !ok {
|
|
||||||
t.Fatalf("Artifact should be artifact")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactId(t *testing.T) {
|
|
||||||
a := &Artifact{"packer-foobar", 42, []string{"sfo", "tor1"}, nil, generatedData()}
|
|
||||||
expected := "sfo,tor1:42"
|
|
||||||
|
|
||||||
if a.Id() != expected {
|
|
||||||
t.Fatalf("artifact ID should match: %v", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactIdWithoutMultipleRegions(t *testing.T) {
|
|
||||||
a := &Artifact{"packer-foobar", 42, []string{"sfo"}, nil, generatedData()}
|
|
||||||
expected := "sfo:42"
|
|
||||||
|
|
||||||
if a.Id() != expected {
|
|
||||||
t.Fatalf("artifact ID should match: %v", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactString(t *testing.T) {
|
|
||||||
a := &Artifact{"packer-foobar", 42, []string{"sfo", "tor1"}, nil, generatedData()}
|
|
||||||
expected := "A snapshot was created: 'packer-foobar' (ID: 42) in regions 'sfo,tor1'"
|
|
||||||
|
|
||||||
if a.String() != expected {
|
|
||||||
t.Fatalf("artifact string should match: %v", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactStringWithoutMultipleRegions(t *testing.T) {
|
|
||||||
a := &Artifact{"packer-foobar", 42, []string{"sfo"}, nil, generatedData()}
|
|
||||||
expected := "A snapshot was created: 'packer-foobar' (ID: 42) in regions 'sfo'"
|
|
||||||
|
|
||||||
if a.String() != expected {
|
|
||||||
t.Fatalf("artifact string should match: %v", 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,135 +0,0 @@
|
|||||||
// The digitalocean package contains a packersdk.Builder implementation
|
|
||||||
// that builds DigitalOcean images (snapshots).
|
|
||||||
|
|
||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"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"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The unique id for the builder
|
|
||||||
const BuilderId = "pearkes.digitalocean"
|
|
||||||
|
|
||||||
type Builder struct {
|
|
||||||
config Config
|
|
||||||
runner multistep.Runner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
||||||
warnings, errs := b.config.Prepare(raws...)
|
|
||||||
if errs != nil {
|
|
||||||
return nil, warnings, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
|
||||||
client := godo.NewClient(oauth2.NewClient(context.TODO(), &apiTokenSource{
|
|
||||||
AccessToken: b.config.APIToken,
|
|
||||||
}))
|
|
||||||
if b.config.APIURL != "" {
|
|
||||||
u, err := url.Parse(b.config.APIURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("DigitalOcean: Invalid API URL, %s.", err)
|
|
||||||
}
|
|
||||||
client.BaseURL = u
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b.config.SnapshotRegions) > 0 {
|
|
||||||
opt := &godo.ListOptions{
|
|
||||||
Page: 1,
|
|
||||||
PerPage: 200,
|
|
||||||
}
|
|
||||||
regions, _, err := client.Regions.List(context.TODO(), opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("DigitalOcean: Unable to get regions, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
validRegions := make(map[string]struct{})
|
|
||||||
for _, val := range regions {
|
|
||||||
validRegions[val.Slug] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, region := range append(b.config.SnapshotRegions, b.config.Region) {
|
|
||||||
if _, ok := validRegions[region]; !ok {
|
|
||||||
return nil, fmt.Errorf("DigitalOcean: Invalid region, %s", region)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up the state
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("config", &b.config)
|
|
||||||
state.Put("client", client)
|
|
||||||
state.Put("hook", hook)
|
|
||||||
state.Put("ui", ui)
|
|
||||||
|
|
||||||
// Build the steps
|
|
||||||
steps := []multistep.Step{
|
|
||||||
&communicator.StepSSHKeyGen{
|
|
||||||
CommConf: &b.config.Comm,
|
|
||||||
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(stepDropletInfo),
|
|
||||||
&communicator.StepConnect{
|
|
||||||
Config: &b.config.Comm,
|
|
||||||
Host: communicator.CommHost(b.config.Comm.Host(), "droplet_ip"),
|
|
||||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
|
||||||
},
|
|
||||||
new(commonsteps.StepProvision),
|
|
||||||
&commonsteps.StepCleanupTempKeys{
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
},
|
|
||||||
new(stepShutdown),
|
|
||||||
new(stepPowerOff),
|
|
||||||
&stepSnapshot{
|
|
||||||
snapshotTimeout: b.config.SnapshotTimeout,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
return nil, rawErr.(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("snapshot_name"); !ok {
|
|
||||||
log.Println("Failed to find snapshot_name in state. Bug?")
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
artifact := &Artifact{
|
|
||||||
SnapshotName: state.Get("snapshot_name").(string),
|
|
||||||
SnapshotId: state.Get("snapshot_image_id").(int),
|
|
||||||
RegionNames: state.Get("regions").([]string),
|
|
||||||
Client: client,
|
|
||||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
builderT "github.com/hashicorp/packer/acctest"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: fmt.Sprintf(testBuilderAccBasic, "ubuntu-20-04-x64"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderAcc_imageId(t *testing.T) {
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: makeTemplateWithImageId(t),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAccPreCheck(t *testing.T) {
|
|
||||||
if v := os.Getenv("DIGITALOCEAN_API_TOKEN"); v == "" {
|
|
||||||
t.Fatal("DIGITALOCEAN_API_TOKEN must be set for acceptance tests")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTemplateWithImageId(t *testing.T) string {
|
|
||||||
if os.Getenv(builderT.TestEnvVar) != "" {
|
|
||||||
token := os.Getenv("DIGITALOCEAN_API_TOKEN")
|
|
||||||
client := godo.NewClient(oauth2.NewClient(context.TODO(), &apiTokenSource{
|
|
||||||
AccessToken: token,
|
|
||||||
}))
|
|
||||||
image, _, err := client.Images.GetBySlug(context.TODO(), "ubuntu-20-04-x64")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to retrieve image ID: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(testBuilderAccBasic, image.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccBasic = `
|
|
||||||
{
|
|
||||||
"builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "nyc2",
|
|
||||||
"size": "s-1vcpu-1gb",
|
|
||||||
"image": "%v",
|
|
||||||
"ssh_username": "root",
|
|
||||||
"user_data": "",
|
|
||||||
"user_data_file": ""
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@@ -1,416 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testConfig() map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"api_token": "bar",
|
|
||||||
"region": "nyc2",
|
|
||||||
"size": "512mb",
|
|
||||||
"ssh_username": "root",
|
|
||||||
"image": "foo",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &Builder{}
|
|
||||||
if _, ok := raw.(packersdk.Builder); !ok {
|
|
||||||
t.Fatalf("Builder should be a builder")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilder_Prepare_BadType(t *testing.T) {
|
|
||||||
b := &Builder{}
|
|
||||||
c := map[string]interface{}{
|
|
||||||
"api_key": []string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, warnings, err := b.Prepare(c)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("prepare should fail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Add a random key
|
|
||||||
config["i_should_not_be_valid"] = true
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_Region(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
delete(config, "region")
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("should error")
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := "sfo1"
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["region"] = expected
|
|
||||||
b = Builder{}
|
|
||||||
_, 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.Region != expected {
|
|
||||||
t.Errorf("found %s, expected %s", b.config.Region, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_Size(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
delete(config, "size")
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("should error")
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := "1024mb"
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["size"] = expected
|
|
||||||
b = Builder{}
|
|
||||||
_, 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.Size != expected {
|
|
||||||
t.Errorf("found %s, expected %s", b.config.Size, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_Image(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
delete(config, "image")
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should error")
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := "ubuntu-14-04-x64"
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["image"] = expected
|
|
||||||
b = Builder{}
|
|
||||||
_, 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.Image != expected {
|
|
||||||
t.Errorf("found %s, expected %s", b.config.Image, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_StateTimeout(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
_, 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.StateTimeout != 6*time.Minute {
|
|
||||||
t.Errorf("invalid: %s", b.config.StateTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["state_timeout"] = "5m"
|
|
||||||
b = Builder{}
|
|
||||||
_, 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["state_timeout"] = "tubes"
|
|
||||||
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_SnapshotTimeout(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
_, 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.SnapshotTimeout != 60*time.Minute {
|
|
||||||
t.Errorf("invalid: %s", b.config.SnapshotTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["snapshot_timeout"] = "15m"
|
|
||||||
b = Builder{}
|
|
||||||
_, 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["snapshot_timeout"] = "badstring"
|
|
||||||
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_PrivateNetworking(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
_, 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.PrivateNetworking != false {
|
|
||||||
t.Errorf("invalid: %t", b.config.PrivateNetworking)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["private_networking"] = true
|
|
||||||
b = Builder{}
|
|
||||||
_, 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.PrivateNetworking != true {
|
|
||||||
t.Errorf("invalid: %t", b.config.PrivateNetworking)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_SnapshotName(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
_, 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.SnapshotName == "" {
|
|
||||||
t.Errorf("invalid: %s", b.config.SnapshotName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["snapshot_name"] = "foobarbaz"
|
|
||||||
b = Builder{}
|
|
||||||
_, 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 set with template
|
|
||||||
config["snapshot_name"] = "{{timestamp}}"
|
|
||||||
b = Builder{}
|
|
||||||
_, 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = strconv.ParseInt(b.config.SnapshotName, 0, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to parse int in template: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_DropletName(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
_, 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.DropletName == "" {
|
|
||||||
t.Errorf("invalid: %s", b.config.DropletName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test normal set
|
|
||||||
config["droplet_name"] = "foobar"
|
|
||||||
b = Builder{}
|
|
||||||
_, 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 with template
|
|
||||||
config["droplet_name"] = "foobar-{{timestamp}}"
|
|
||||||
b = Builder{}
|
|
||||||
_, 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 with bad template
|
|
||||||
config["droplet_name"] = "foobar-{{"
|
|
||||||
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_VPCUUID(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test with the case vpc_uuid is defined but private_networking is not enabled
|
|
||||||
config["vpc_uuid"] = "554c41b3-425f-5403-8860-7f24fb108098"
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("should have error: 'private networking should be enabled to use vpc_uuid'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with the case both vpc_uuid and private_networking are defined/enabled
|
|
||||||
config["private_networking"] = true
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("should not have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_ConnectWithPrivateIP(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test with the case connect_with_private_ip is defined but private_networking is not enabled
|
|
||||||
config["connect_with_private_ip"] = true
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("should have error: 'private networking should be enabled to use connect_with_private_ip'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with the case both connect_with_private_ip and private_networking are enabled
|
|
||||||
config["private_networking"] = true
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("should not have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
//go:generate packer-sdc struct-markdown
|
|
||||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
|
||||||
|
|
||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
|
||||||
// The client TOKEN to use to access your account. It
|
|
||||||
// can also be specified via environment variable DIGITALOCEAN_API_TOKEN, if
|
|
||||||
// set.
|
|
||||||
APIToken string `mapstructure:"api_token" required:"true"`
|
|
||||||
// Non standard api endpoint URL. Set this if you are
|
|
||||||
// using a DigitalOcean API compatible service. It can also be specified via
|
|
||||||
// environment variable DIGITALOCEAN_API_URL.
|
|
||||||
APIURL string `mapstructure:"api_url" required:"false"`
|
|
||||||
// The name (or slug) of the region to launch the droplet
|
|
||||||
// in. Consequently, this is the region where the snapshot will be available.
|
|
||||||
// See
|
|
||||||
// https://developers.digitalocean.com/documentation/v2/#list-all-regions
|
|
||||||
// for the accepted region names/slugs.
|
|
||||||
Region string `mapstructure:"region" required:"true"`
|
|
||||||
// The name (or slug) of the droplet size to use. See
|
|
||||||
// https://developers.digitalocean.com/documentation/v2/#list-all-sizes
|
|
||||||
// for the accepted size names/slugs.
|
|
||||||
Size string `mapstructure:"size" required:"true"`
|
|
||||||
// The name (or slug) of the base image to use. This is the
|
|
||||||
// image that will be used to launch a new droplet and provision it. See
|
|
||||||
// https://developers.digitalocean.com/documentation/v2/#list-all-images
|
|
||||||
// for details on how to get a list of the accepted image names/slugs.
|
|
||||||
Image string `mapstructure:"image" required:"true"`
|
|
||||||
// Set to true to enable private networking
|
|
||||||
// for the droplet being created. This defaults to false, or not enabled.
|
|
||||||
PrivateNetworking bool `mapstructure:"private_networking" required:"false"`
|
|
||||||
// Set to true to enable monitoring for the droplet
|
|
||||||
// being created. This defaults to false, or not enabled.
|
|
||||||
Monitoring bool `mapstructure:"monitoring" required:"false"`
|
|
||||||
// Set to true to enable ipv6 for the droplet being
|
|
||||||
// created. This defaults to false, or not enabled.
|
|
||||||
IPv6 bool `mapstructure:"ipv6" required:"false"`
|
|
||||||
// The name of the resulting snapshot that will
|
|
||||||
// appear in your account. Defaults to `packer-{{timestamp}}` (see
|
|
||||||
// configuration templates for more info).
|
|
||||||
SnapshotName string `mapstructure:"snapshot_name" required:"false"`
|
|
||||||
// The regions of the resulting
|
|
||||||
// snapshot that will appear in your account.
|
|
||||||
SnapshotRegions []string `mapstructure:"snapshot_regions" required:"false"`
|
|
||||||
// The time to wait, as a duration string, for a
|
|
||||||
// droplet to enter a desired state (such as "active") before timing out. The
|
|
||||||
// default state timeout is "6m".
|
|
||||||
StateTimeout time.Duration `mapstructure:"state_timeout" required:"false"`
|
|
||||||
// How long to wait for an image to be published to the shared image
|
|
||||||
// gallery before timing out. If your Packer build is failing on the
|
|
||||||
// Publishing to Shared Image Gallery step with the error `Original Error:
|
|
||||||
// context deadline exceeded`, but the image is present when you check your
|
|
||||||
// Azure dashboard, then you probably need to increase this timeout from
|
|
||||||
// its default of "60m" (valid time units include `s` for seconds, `m` for
|
|
||||||
// minutes, and `h` for hours.)
|
|
||||||
SnapshotTimeout time.Duration `mapstructure:"snapshot_timeout" required:"false"`
|
|
||||||
// The name assigned to the droplet. DigitalOcean
|
|
||||||
// sets the hostname of the machine to this value.
|
|
||||||
DropletName string `mapstructure:"droplet_name" required:"false"`
|
|
||||||
// User data to launch with the Droplet. 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 Droplet.
|
|
||||||
UserDataFile string `mapstructure:"user_data_file" required:"false"`
|
|
||||||
// Tags to apply to the droplet when it is created
|
|
||||||
Tags []string `mapstructure:"tags" required:"false"`
|
|
||||||
// UUID of the VPC which the droplet will be created in. Before using this,
|
|
||||||
// private_networking should be enabled.
|
|
||||||
VPCUUID string `mapstructure:"vpc_uuid" required:"false"`
|
|
||||||
// Wheter the communicators should use private IP or not (public IP in that case).
|
|
||||||
// If the droplet is or going to be accessible only from the local network because
|
|
||||||
// it is at behind a firewall, then communicators should use the private IP
|
|
||||||
// instead of the public IP. Before using this, private_networking should be enabled.
|
|
||||||
ConnectWithPrivateIP bool `mapstructure:"connect_with_private_ip" required:"false"`
|
|
||||||
|
|
||||||
ctx interpolate.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|
||||||
|
|
||||||
var md mapstructure.Metadata
|
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
|
||||||
Metadata: &md,
|
|
||||||
Interpolate: true,
|
|
||||||
InterpolateContext: &c.ctx,
|
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
|
||||||
Exclude: []string{
|
|
||||||
"run_command",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, raws...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defaults
|
|
||||||
if c.APIToken == "" {
|
|
||||||
// Default to environment variable for api_token, if it exists
|
|
||||||
c.APIToken = os.Getenv("DIGITALOCEAN_API_TOKEN")
|
|
||||||
}
|
|
||||||
if c.APIURL == "" {
|
|
||||||
c.APIURL = os.Getenv("DIGITALOCEAN_API_URL")
|
|
||||||
}
|
|
||||||
if c.SnapshotName == "" {
|
|
||||||
def, err := interpolate.Render("packer-{{timestamp}}", nil)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to packer-{{ unix timestamp (utc) }}
|
|
||||||
c.SnapshotName = def
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.DropletName == "" {
|
|
||||||
// Default to packer-[time-ordered-uuid]
|
|
||||||
c.DropletName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.StateTimeout == 0 {
|
|
||||||
// Default to 6 minute timeouts waiting for
|
|
||||||
// desired state. i.e waiting for droplet to become active
|
|
||||||
c.StateTimeout = 6 * time.Minute
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SnapshotTimeout == 0 {
|
|
||||||
// Default to 60 minutes timeout, waiting for snapshot action to finish
|
|
||||||
c.SnapshotTimeout = 60 * time.Minute
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
|
|
||||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, es...)
|
|
||||||
}
|
|
||||||
if c.APIToken == "" {
|
|
||||||
// Required configurations that will display errors if not set
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("api_token for auth must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Region == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("region is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Size == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("size is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Image == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("image is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.UserData != "" && c.UserDataFile != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("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 = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New(fmt.Sprintf("user_data_file not found: %s", c.UserDataFile)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Tags == nil {
|
|
||||||
c.Tags = make([]string, 0)
|
|
||||||
}
|
|
||||||
tagRe := regexp.MustCompile("^[[:alnum:]:_-]{1,255}$")
|
|
||||||
|
|
||||||
for _, t := range c.Tags {
|
|
||||||
if !tagRe.MatchString(t) {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("invalid tag: %s", t))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the PrivateNetworking is enabled by user before use VPC UUID
|
|
||||||
if c.VPCUUID != "" {
|
|
||||||
if c.PrivateNetworking != true {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, errors.New("private networking should be enabled to use vpc_uuid"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the PrivateNetworking is enabled by user before use ConnectWithPrivateIP
|
|
||||||
if c.ConnectWithPrivateIP == true {
|
|
||||||
if c.PrivateNetworking != true {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, errors.New("private networking should be enabled to use connect_with_private_ip"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return nil, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
packersdk.LogSecretFilter.Set(c.APIToken)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatConfig struct {
|
|
||||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
|
||||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
|
||||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
|
||||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
|
||||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
|
||||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
|
||||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
|
||||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
|
||||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
|
||||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
|
||||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
|
||||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
|
||||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
|
||||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
|
||||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
|
||||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
|
||||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
|
||||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
|
||||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
|
||||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
|
||||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
|
||||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
|
||||||
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
|
||||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
|
||||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
|
||||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
|
||||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
|
||||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
|
||||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
|
||||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
|
||||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
|
||||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
|
||||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
|
||||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
|
||||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
|
||||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
|
||||||
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
|
||||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
|
||||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
|
||||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
|
||||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
|
||||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
|
||||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
|
||||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
|
||||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
|
||||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
|
||||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
|
||||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
|
||||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
|
||||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
|
||||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
|
||||||
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
|
||||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
|
||||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
|
||||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
|
||||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
|
||||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
|
||||||
APIToken *string `mapstructure:"api_token" required:"true" cty:"api_token" hcl:"api_token"`
|
|
||||||
APIURL *string `mapstructure:"api_url" required:"false" cty:"api_url" hcl:"api_url"`
|
|
||||||
Region *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"`
|
|
||||||
Size *string `mapstructure:"size" required:"true" cty:"size" hcl:"size"`
|
|
||||||
Image *string `mapstructure:"image" required:"true" cty:"image" hcl:"image"`
|
|
||||||
PrivateNetworking *bool `mapstructure:"private_networking" required:"false" cty:"private_networking" hcl:"private_networking"`
|
|
||||||
Monitoring *bool `mapstructure:"monitoring" required:"false" cty:"monitoring" hcl:"monitoring"`
|
|
||||||
IPv6 *bool `mapstructure:"ipv6" required:"false" cty:"ipv6" hcl:"ipv6"`
|
|
||||||
SnapshotName *string `mapstructure:"snapshot_name" required:"false" cty:"snapshot_name" hcl:"snapshot_name"`
|
|
||||||
SnapshotRegions []string `mapstructure:"snapshot_regions" required:"false" cty:"snapshot_regions" hcl:"snapshot_regions"`
|
|
||||||
StateTimeout *string `mapstructure:"state_timeout" required:"false" cty:"state_timeout" hcl:"state_timeout"`
|
|
||||||
SnapshotTimeout *string `mapstructure:"snapshot_timeout" required:"false" cty:"snapshot_timeout" hcl:"snapshot_timeout"`
|
|
||||||
DropletName *string `mapstructure:"droplet_name" required:"false" cty:"droplet_name" hcl:"droplet_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"`
|
|
||||||
Tags []string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"`
|
|
||||||
VPCUUID *string `mapstructure:"vpc_uuid" required:"false" cty:"vpc_uuid" hcl:"vpc_uuid"`
|
|
||||||
ConnectWithPrivateIP *bool `mapstructure:"connect_with_private_ip" required:"false" cty:"connect_with_private_ip" hcl:"connect_with_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},
|
|
||||||
"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_token": &hcldec.AttrSpec{Name: "api_token", Type: cty.String, Required: false},
|
|
||||||
"api_url": &hcldec.AttrSpec{Name: "api_url", Type: cty.String, Required: false},
|
|
||||||
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
|
|
||||||
"size": &hcldec.AttrSpec{Name: "size", Type: cty.String, Required: false},
|
|
||||||
"image": &hcldec.AttrSpec{Name: "image", Type: cty.String, Required: false},
|
|
||||||
"private_networking": &hcldec.AttrSpec{Name: "private_networking", Type: cty.Bool, Required: false},
|
|
||||||
"monitoring": &hcldec.AttrSpec{Name: "monitoring", Type: cty.Bool, Required: false},
|
|
||||||
"ipv6": &hcldec.AttrSpec{Name: "ipv6", Type: cty.Bool, Required: false},
|
|
||||||
"snapshot_name": &hcldec.AttrSpec{Name: "snapshot_name", Type: cty.String, Required: false},
|
|
||||||
"snapshot_regions": &hcldec.AttrSpec{Name: "snapshot_regions", Type: cty.List(cty.String), Required: false},
|
|
||||||
"state_timeout": &hcldec.AttrSpec{Name: "state_timeout", Type: cty.String, Required: false},
|
|
||||||
"snapshot_timeout": &hcldec.AttrSpec{Name: "snapshot_timeout", Type: cty.String, Required: false},
|
|
||||||
"droplet_name": &hcldec.AttrSpec{Name: "droplet_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},
|
|
||||||
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.List(cty.String), Required: false},
|
|
||||||
"vpc_uuid": &hcldec.AttrSpec{Name: "vpc_uuid", Type: cty.String, Required: false},
|
|
||||||
"connect_with_private_ip": &hcldec.AttrSpec{Name: "connect_with_private_ip", Type: cty.Bool, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateDroplet struct {
|
|
||||||
dropletId int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateDroplet) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
sshKeyId := state.Get("ssh_key_id").(int)
|
|
||||||
|
|
||||||
// Create the droplet based on configuration
|
|
||||||
ui.Say("Creating droplet...")
|
|
||||||
|
|
||||||
userData := c.UserData
|
|
||||||
if c.UserDataFile != "" {
|
|
||||||
contents, err := ioutil.ReadFile(c.UserDataFile)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
userData = string(contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
createImage := getImageType(c.Image)
|
|
||||||
|
|
||||||
dropletCreateReq := &godo.DropletCreateRequest{
|
|
||||||
Name: c.DropletName,
|
|
||||||
Region: c.Region,
|
|
||||||
Size: c.Size,
|
|
||||||
Image: createImage,
|
|
||||||
SSHKeys: []godo.DropletCreateSSHKey{
|
|
||||||
{ID: sshKeyId},
|
|
||||||
},
|
|
||||||
PrivateNetworking: c.PrivateNetworking,
|
|
||||||
Monitoring: c.Monitoring,
|
|
||||||
IPv6: c.IPv6,
|
|
||||||
UserData: userData,
|
|
||||||
Tags: c.Tags,
|
|
||||||
VPCUUID: c.VPCUUID,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[DEBUG] Droplet create paramaters: %s", godo.Stringify(dropletCreateReq))
|
|
||||||
|
|
||||||
droplet, _, err := client.Droplets.Create(context.TODO(), dropletCreateReq)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use this in cleanup
|
|
||||||
s.dropletId = droplet.ID
|
|
||||||
|
|
||||||
// Store the droplet id for later
|
|
||||||
state.Put("droplet_id", droplet.ID)
|
|
||||||
// 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", droplet.ID)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) {
|
|
||||||
// If the dropletid isn't there, we probably never created it
|
|
||||||
if s.dropletId == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
// Destroy the droplet we just created
|
|
||||||
ui.Say("Destroying droplet...")
|
|
||||||
_, err := client.Droplets.Delete(context.TODO(), s.dropletId)
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf(
|
|
||||||
"Error destroying droplet. Please destroy it manually: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getImageType(image string) godo.DropletCreateImage {
|
|
||||||
createImage := godo.DropletCreateImage{Slug: image}
|
|
||||||
|
|
||||||
imageId, err := strconv.Atoi(image)
|
|
||||||
if err == nil {
|
|
||||||
createImage = godo.DropletCreateImage{ID: imageId}
|
|
||||||
}
|
|
||||||
|
|
||||||
return createImage
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuilder_GetImageType(t *testing.T) {
|
|
||||||
imageTypeTests := []struct {
|
|
||||||
in string
|
|
||||||
out godo.DropletCreateImage
|
|
||||||
}{
|
|
||||||
{"ubuntu-20-04-x64", godo.DropletCreateImage{Slug: "ubuntu-20-04-x64"}},
|
|
||||||
{"123456", godo.DropletCreateImage{ID: 123456}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range imageTypeTests {
|
|
||||||
t.Run(tt.in, func(t *testing.T) {
|
|
||||||
i := getImageType(tt.in)
|
|
||||||
if i != tt.out {
|
|
||||||
t.Errorf("got %q, want %q", godo.Stringify(i), godo.Stringify(tt.out))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateSSHKey struct {
|
|
||||||
keyId int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
if c.Comm.SSHPublicKey == nil {
|
|
||||||
ui.Say("No public SSH key found; skipping SSH public key import...")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Importing SSH public key...")
|
|
||||||
|
|
||||||
// The name of the public key on DO
|
|
||||||
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
|
||||||
|
|
||||||
// Create the key!
|
|
||||||
key, _, err := client.Keys.Create(context.TODO(), &godo.KeyCreateRequest{
|
|
||||||
Name: name,
|
|
||||||
PublicKey: string(c.Comm.SSHPublicKey),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use this to check cleanup
|
|
||||||
s.keyId = key.ID
|
|
||||||
|
|
||||||
log.Printf("temporary ssh key name: %s", name)
|
|
||||||
|
|
||||||
// Remember some state for the future
|
|
||||||
state.Put("ssh_key_id", key.ID)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
|
|
||||||
// If no key name is set, then we never created it, so just return
|
|
||||||
if s.keyId == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say("Deleting temporary ssh key...")
|
|
||||||
_, err := client.Keys.DeleteByID(context.TODO(), s.keyId)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error cleaning up ssh key: %s", err)
|
|
||||||
ui.Error(fmt.Sprintf(
|
|
||||||
"Error cleaning up ssh key. Please delete the key manually: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepDropletInfo struct{}
|
|
||||||
|
|
||||||
func (s *stepDropletInfo) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
dropletID := state.Get("droplet_id").(int)
|
|
||||||
|
|
||||||
ui.Say("Waiting for droplet to become active...")
|
|
||||||
|
|
||||||
err := waitForDropletState("active", dropletID, client, c.StateTimeout)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error waiting for droplet to become active: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the IP on the state for later
|
|
||||||
droplet, _, err := client.Droplets.Get(context.TODO(), dropletID)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error retrieving droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify we have an IPv4 address
|
|
||||||
invalid := droplet.Networks == nil ||
|
|
||||||
len(droplet.Networks.V4) == 0
|
|
||||||
if invalid {
|
|
||||||
err := fmt.Errorf("IPv4 address not found for droplet")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the ip address which will be used by communicator
|
|
||||||
foundNetwork := false
|
|
||||||
for _, network := range droplet.Networks.V4 {
|
|
||||||
if (c.ConnectWithPrivateIP && network.Type == "private") ||
|
|
||||||
(!(c.ConnectWithPrivateIP) && network.Type == "public") {
|
|
||||||
state.Put("droplet_ip", network.IPAddress)
|
|
||||||
foundNetwork = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !foundNetwork {
|
|
||||||
err := fmt.Errorf("Count not find a public IPv4 address for this droplet")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepDropletInfo) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepPowerOff struct{}
|
|
||||||
|
|
||||||
func (s *stepPowerOff) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
dropletId := state.Get("droplet_id").(int)
|
|
||||||
|
|
||||||
droplet, _, err := client.Droplets.Get(context.TODO(), dropletId)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error checking droplet state: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
if droplet.Status == "off" {
|
|
||||||
// Droplet is already off, don't do anything
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull the plug on the Droplet
|
|
||||||
ui.Say("Forcefully shutting down Droplet...")
|
|
||||||
_, _, err = client.DropletActions.PowerOff(context.TODO(), dropletId)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error powering off droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Waiting for poweroff event to complete...")
|
|
||||||
err = waitForDropletState("off", dropletId, client, c.StateTimeout)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the droplet to become unlocked for future steps
|
|
||||||
if err := waitForDropletUnlocked(client, dropletId, c.StateTimeout); err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error powering off droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepPowerOff) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepShutdown struct{}
|
|
||||||
|
|
||||||
func (s *stepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
dropletId := state.Get("droplet_id").(int)
|
|
||||||
|
|
||||||
// Gracefully power off the droplet. We have to retry this a number
|
|
||||||
// of times because sometimes it says it completed when it actually
|
|
||||||
// did absolutely nothing (*ALAKAZAM!* magic!). We give up after
|
|
||||||
// a pretty arbitrary amount of time.
|
|
||||||
ui.Say("Gracefully shutting down droplet...")
|
|
||||||
_, _, err := client.DropletActions.Shutdown(context.TODO(), dropletId)
|
|
||||||
if err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error shutting down droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// A channel we use as a flag to end our goroutines
|
|
||||||
done := make(chan struct{})
|
|
||||||
shutdownRetryDone := make(chan struct{})
|
|
||||||
|
|
||||||
// Make sure we wait for the shutdown retry goroutine to end
|
|
||||||
// before moving on.
|
|
||||||
defer func() {
|
|
||||||
close(done)
|
|
||||||
<-shutdownRetryDone
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Start a goroutine that just keeps trying to shut down the
|
|
||||||
// droplet.
|
|
||||||
go func() {
|
|
||||||
defer close(shutdownRetryDone)
|
|
||||||
|
|
||||||
for attempts := 2; attempts > 0; attempts++ {
|
|
||||||
log.Printf("ShutdownDroplet attempt #%d...", attempts)
|
|
||||||
_, _, err := client.DropletActions.Shutdown(context.TODO(), dropletId)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Shutdown retry error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
case <-time.After(20 * time.Second):
|
|
||||||
// Retry!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = waitForDropletState("off", dropletId, client, c.StateTimeout)
|
|
||||||
if err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error shutting down droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := waitForDropletUnlocked(client, dropletId, c.StateTimeout); err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error shutting down droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepShutdown) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepSnapshot struct {
|
|
||||||
snapshotTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
dropletId := state.Get("droplet_id").(int)
|
|
||||||
var snapshotRegions []string
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName))
|
|
||||||
action, _, err := client.DropletActions.Snapshot(context.TODO(), dropletId, c.SnapshotName)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the pending state over, verify that we're in the active state
|
|
||||||
// because action can take a long time and may depend on the size of the final snapshot,
|
|
||||||
// the timeout is parameterized
|
|
||||||
ui.Say("Waiting for snapshot to complete...")
|
|
||||||
if err := waitForActionState(godo.ActionCompleted, dropletId, action.ID,
|
|
||||||
client, s.snapshotTimeout); err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error waiting for snapshot: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the droplet to become unlocked first. For snapshots
|
|
||||||
// this can end up taking quite a long time, so we hardcode this to
|
|
||||||
// 20 minutes.
|
|
||||||
if err := waitForDropletUnlocked(client, dropletId, 20*time.Minute); err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error shutting down droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Looking up snapshot ID for snapshot: %s", c.SnapshotName)
|
|
||||||
images, _, err := client.Droplets.Snapshots(context.TODO(), dropletId, nil)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error looking up snapshot ID: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.SnapshotRegions) > 0 {
|
|
||||||
regionSet := make(map[string]struct{})
|
|
||||||
regions := make([]string, 0, len(c.SnapshotRegions))
|
|
||||||
regionSet[c.Region] = struct{}{}
|
|
||||||
for _, region := range c.SnapshotRegions {
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
snapshotRegions = regions
|
|
||||||
|
|
||||||
for transfer := range snapshotRegions {
|
|
||||||
transferRequest := &godo.ActionRequest{
|
|
||||||
"type": "transfer",
|
|
||||||
"region": snapshotRegions[transfer],
|
|
||||||
}
|
|
||||||
imageTransfer, _, err := client.ImageActions.Transfer(context.TODO(), images[0].ID, transferRequest)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error transferring snapshot: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
ui.Say(fmt.Sprintf("transferring Snapshot ID: %d", imageTransfer.ID))
|
|
||||||
if err := WaitForImageState(godo.ActionCompleted, imageTransfer.ID, action.ID,
|
|
||||||
client, 20*time.Minute); err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error waiting for snapshot transfer: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var imageId int
|
|
||||||
if len(images) == 1 {
|
|
||||||
imageId = images[0].ID
|
|
||||||
} else {
|
|
||||||
err := errors.New("Couldn't find snapshot to get the image ID. Bug?")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
snapshotRegions = append(snapshotRegions, c.Region)
|
|
||||||
|
|
||||||
log.Printf("Snapshot image ID: %d", imageId)
|
|
||||||
state.Put("snapshot_image_id", imageId)
|
|
||||||
state.Put("snapshot_name", c.SnapshotName)
|
|
||||||
state.Put("regions", snapshotRegions)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepSnapshot) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type apiTokenSource struct {
|
|
||||||
AccessToken string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *apiTokenSource) Token() (*oauth2.Token, error) {
|
|
||||||
return &oauth2.Token{
|
|
||||||
AccessToken: t.AccessToken,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
|
||||||
packerVersion "github.com/hashicorp/packer/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var DockerPluginVersion *version.PluginVersion
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
DockerPluginVersion = version.InitializePluginVersion(
|
|
||||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// waitForDropletUnlocked waits for the Droplet to be unlocked to
|
|
||||||
// avoid "pending" errors when making state changes.
|
|
||||||
func waitForDropletUnlocked(
|
|
||||||
client *godo.Client, dropletId int, timeout time.Duration) error {
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
result := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
attempts := 0
|
|
||||||
for {
|
|
||||||
attempts += 1
|
|
||||||
|
|
||||||
log.Printf("[DEBUG] Checking droplet lock state... (attempt: %d)", attempts)
|
|
||||||
droplet, _, err := client.Droplets.Get(context.TODO(), dropletId)
|
|
||||||
if err != nil {
|
|
||||||
result <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !droplet.Locked {
|
|
||||||
result <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait 3 seconds in between
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
|
|
||||||
// Verify we shouldn't exit
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
// We finished, so just exit the goroutine
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// Keep going
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Printf("[DEBUG] Waiting for up to %d seconds for droplet to unlock", timeout/time.Second)
|
|
||||||
select {
|
|
||||||
case err := <-result:
|
|
||||||
return err
|
|
||||||
case <-time.After(timeout):
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Timeout while waiting to for droplet to unlock")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitForDropletState simply blocks until the droplet is in
|
|
||||||
// a state we expect, while eventually timing out.
|
|
||||||
func waitForDropletState(
|
|
||||||
desiredState string, dropletId int,
|
|
||||||
client *godo.Client, timeout time.Duration) error {
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
result := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
attempts := 0
|
|
||||||
for {
|
|
||||||
attempts += 1
|
|
||||||
|
|
||||||
log.Printf("Checking droplet status... (attempt: %d)", attempts)
|
|
||||||
droplet, _, err := client.Droplets.Get(context.TODO(), dropletId)
|
|
||||||
if err != nil {
|
|
||||||
result <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if droplet.Status == desiredState {
|
|
||||||
result <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait 3 seconds in between
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
|
|
||||||
// Verify we shouldn't exit
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
// We finished, so just exit the goroutine
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// Keep going
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Printf("Waiting for up to %d seconds for droplet to become %s", timeout/time.Second, desiredState)
|
|
||||||
select {
|
|
||||||
case err := <-result:
|
|
||||||
return err
|
|
||||||
case <-time.After(timeout):
|
|
||||||
err := fmt.Errorf("Timeout while waiting to for droplet to become '%s'", desiredState)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitForActionState simply blocks until the droplet action is in
|
|
||||||
// a state we expect, while eventually timing out.
|
|
||||||
func waitForActionState(
|
|
||||||
desiredState string, dropletId, actionId int,
|
|
||||||
client *godo.Client, timeout time.Duration) error {
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
result := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
attempts := 0
|
|
||||||
for {
|
|
||||||
attempts += 1
|
|
||||||
|
|
||||||
log.Printf("Checking action status... (attempt: %d)", attempts)
|
|
||||||
action, _, err := client.DropletActions.Get(context.TODO(), dropletId, actionId)
|
|
||||||
if err != nil {
|
|
||||||
result <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if action.Status == desiredState {
|
|
||||||
result <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait 3 seconds in between
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
|
|
||||||
// Verify we shouldn't exit
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
// We finished, so just exit the goroutine
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// Keep going
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Printf("Waiting for up to %d seconds for action to become %s", timeout/time.Second, desiredState)
|
|
||||||
select {
|
|
||||||
case err := <-result:
|
|
||||||
return err
|
|
||||||
case <-time.After(timeout):
|
|
||||||
err := fmt.Errorf("Timeout while waiting to for action to become '%s'", desiredState)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForImageState simply blocks until the image action is in
|
|
||||||
// a state we expect, while eventually timing out.
|
|
||||||
func WaitForImageState(
|
|
||||||
desiredState string, imageId, actionId int,
|
|
||||||
client *godo.Client, timeout time.Duration) error {
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
result := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
attempts := 0
|
|
||||||
for {
|
|
||||||
attempts += 1
|
|
||||||
|
|
||||||
log.Printf("Checking action status... (attempt: %d)", attempts)
|
|
||||||
action, _, err := client.ImageActions.Get(context.TODO(), imageId, actionId)
|
|
||||||
if err != nil {
|
|
||||||
result <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if action.Status == desiredState {
|
|
||||||
result <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait 3 seconds in between
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
|
|
||||||
// Verify we shouldn't exit
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
// We finished, so just exit the goroutine
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// Keep going
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Printf("Waiting for up to %d seconds for image transfer to become %s", timeout/time.Second, desiredState)
|
|
||||||
select {
|
|
||||||
case err := <-result:
|
|
||||||
return err
|
|
||||||
case <-time.After(timeout):
|
|
||||||
err := fmt.Errorf("Timeout while waiting to for image transfer to become '%s'", desiredState)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Artifact struct {
|
|
||||||
// The name of the snapshot
|
|
||||||
snapshotName string
|
|
||||||
|
|
||||||
// The ID of the image
|
|
||||||
snapshotId int
|
|
||||||
|
|
||||||
// The hcloudClient for making API calls
|
|
||||||
hcloudClient *hcloud.Client
|
|
||||||
|
|
||||||
// StateData should store data such as GeneratedData
|
|
||||||
// to be shared with post-processors
|
|
||||||
StateData map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Artifact) BuilderId() string {
|
|
||||||
return BuilderId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Artifact) Files() []string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Id() string {
|
|
||||||
return strconv.Itoa(a.snapshotId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) String() string {
|
|
||||||
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v)", a.snapshotName, a.snapshotId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
|
||||||
return a.StateData[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Destroy() error {
|
|
||||||
log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName)
|
|
||||||
_, err := a.hcloudClient.Image.Delete(context.TODO(), &hcloud.Image{ID: a.snapshotId})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestArtifact_Impl(t *testing.T) {
|
|
||||||
var _ packersdk.Artifact = (*Artifact)(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactId(t *testing.T) {
|
|
||||||
generatedData := make(map[string]interface{})
|
|
||||||
a := &Artifact{"packer-foobar", 42, nil, generatedData}
|
|
||||||
expected := "42"
|
|
||||||
|
|
||||||
if a.Id() != expected {
|
|
||||||
t.Fatalf("artifact ID should match: %v", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactString(t *testing.T) {
|
|
||||||
generatedData := make(map[string]interface{})
|
|
||||||
a := &Artifact{"packer-foobar", 42, nil, generatedData}
|
|
||||||
expected := "A snapshot was created: 'packer-foobar' (ID: 42)"
|
|
||||||
|
|
||||||
if a.String() != expected {
|
|
||||||
t.Fatalf("artifact string should match: %v", 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,91 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
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/hetznercloud/hcloud-go/hcloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The unique id for the builder
|
|
||||||
const BuilderId = "hcloud.builder"
|
|
||||||
|
|
||||||
type Builder struct {
|
|
||||||
config Config
|
|
||||||
runner multistep.Runner
|
|
||||||
hcloudClient *hcloud.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
var pluginVersion = "1.0.0"
|
|
||||||
|
|
||||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
||||||
warnings, errs := b.config.Prepare(raws...)
|
|
||||||
if errs != nil {
|
|
||||||
return nil, warnings, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
|
||||||
opts := []hcloud.ClientOption{
|
|
||||||
hcloud.WithToken(b.config.HCloudToken),
|
|
||||||
hcloud.WithEndpoint(b.config.Endpoint),
|
|
||||||
hcloud.WithPollInterval(b.config.PollInterval),
|
|
||||||
hcloud.WithApplication("hcloud-packer", pluginVersion),
|
|
||||||
}
|
|
||||||
b.hcloudClient = hcloud.NewClient(opts...)
|
|
||||||
// Set up the state
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("config", &b.config)
|
|
||||||
state.Put("hcloudClient", b.hcloudClient)
|
|
||||||
state.Put("hook", hook)
|
|
||||||
state.Put("ui", ui)
|
|
||||||
|
|
||||||
// Build the steps
|
|
||||||
steps := []multistep.Step{
|
|
||||||
&stepCreateSSHKey{
|
|
||||||
Debug: b.config.PackerDebug,
|
|
||||||
DebugKeyPath: fmt.Sprintf("ssh_key_%s.pem", b.config.PackerBuildName),
|
|
||||||
},
|
|
||||||
&stepCreateServer{},
|
|
||||||
&communicator.StepConnect{
|
|
||||||
Config: &b.config.Comm,
|
|
||||||
Host: getServerIP,
|
|
||||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
|
||||||
},
|
|
||||||
&commonsteps.StepProvision{},
|
|
||||||
&commonsteps.StepCleanupTempKeys{
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
},
|
|
||||||
&stepShutdownServer{},
|
|
||||||
&stepCreateSnapshot{},
|
|
||||||
}
|
|
||||||
// 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 {
|
|
||||||
return nil, rawErr.(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("snapshot_name"); !ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
artifact := &Artifact{
|
|
||||||
snapshotName: state.Get("snapshot_name").(string),
|
|
||||||
snapshotId: state.Get("snapshot_id").(int),
|
|
||||||
hcloudClient: b.hcloudClient,
|
|
||||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer/acctest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: testBuilderAccBasic,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAccPreCheck(t *testing.T) {
|
|
||||||
if v := os.Getenv("HCLOUD_TOKEN"); v == "" {
|
|
||||||
t.Fatal("HCLOUD_TOKEN must be set for acceptance tests")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccBasic = `
|
|
||||||
{
|
|
||||||
"builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"location": "nbg1",
|
|
||||||
"server_type": "cx11",
|
|
||||||
"image": "ubuntu-18.04",
|
|
||||||
"user_data": "",
|
|
||||||
"user_data_file": ""
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,imageFilter
|
|
||||||
|
|
||||||
package hcloud
|
|
||||||
|
|
||||||
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"
|
|
||||||
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"
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
|
||||||
|
|
||||||
HCloudToken string `mapstructure:"token"`
|
|
||||||
Endpoint string `mapstructure:"endpoint"`
|
|
||||||
|
|
||||||
PollInterval time.Duration `mapstructure:"poll_interval"`
|
|
||||||
|
|
||||||
ServerName string `mapstructure:"server_name"`
|
|
||||||
Location string `mapstructure:"location"`
|
|
||||||
ServerType string `mapstructure:"server_type"`
|
|
||||||
Image string `mapstructure:"image"`
|
|
||||||
ImageFilter *imageFilter `mapstructure:"image_filter"`
|
|
||||||
|
|
||||||
SnapshotName string `mapstructure:"snapshot_name"`
|
|
||||||
SnapshotLabels map[string]string `mapstructure:"snapshot_labels"`
|
|
||||||
UserData string `mapstructure:"user_data"`
|
|
||||||
UserDataFile string `mapstructure:"user_data_file"`
|
|
||||||
SSHKeys []string `mapstructure:"ssh_keys"`
|
|
||||||
|
|
||||||
RescueMode string `mapstructure:"rescue"`
|
|
||||||
|
|
||||||
ctx interpolate.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
type imageFilter struct {
|
|
||||||
WithSelector []string `mapstructure:"with_selector"`
|
|
||||||
MostRecent bool `mapstructure:"most_recent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|
||||||
var md mapstructure.Metadata
|
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
|
||||||
Metadata: &md,
|
|
||||||
Interpolate: true,
|
|
||||||
InterpolateContext: &c.ctx,
|
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
|
||||||
Exclude: []string{
|
|
||||||
"run_command",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, raws...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defaults
|
|
||||||
if c.HCloudToken == "" {
|
|
||||||
c.HCloudToken = os.Getenv("HCLOUD_TOKEN")
|
|
||||||
}
|
|
||||||
if c.Endpoint == "" {
|
|
||||||
if os.Getenv("HCLOUD_ENDPOINT") != "" {
|
|
||||||
c.Endpoint = os.Getenv("HCLOUD_ENDPOINT")
|
|
||||||
} else {
|
|
||||||
c.Endpoint = hcloud.Endpoint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.PollInterval == 0 {
|
|
||||||
c.PollInterval = 500 * time.Millisecond
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SnapshotName == "" {
|
|
||||||
def, err := interpolate.Render("packer-{{timestamp}}", nil)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// Default to packer-{{ unix timestamp (utc) }}
|
|
||||||
c.SnapshotName = def
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.ServerName == "" {
|
|
||||||
// Default to packer-[time-ordered-uuid]
|
|
||||||
c.ServerName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, es...)
|
|
||||||
}
|
|
||||||
if c.HCloudToken == "" {
|
|
||||||
// Required configurations that will display errors if not set
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("token for auth must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Location == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("location is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.ServerType == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("server type is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Image == "" && c.ImageFilter == nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("image or image_filter is required"))
|
|
||||||
}
|
|
||||||
if c.ImageFilter != nil {
|
|
||||||
if len(c.ImageFilter.WithSelector) == 0 {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("image_filter.with_selector is required when specifying filter"))
|
|
||||||
} else if c.Image != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("only one of image or image_filter can 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"))
|
|
||||||
} else if c.UserDataFile != "" {
|
|
||||||
if _, err := os.Stat(c.UserDataFile); err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New(fmt.Sprintf("user_data_file not found: %s", c.UserDataFile)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return nil, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
packersdk.LogSecretFilter.Set(c.HCloudToken)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getServerIP(state multistep.StateBag) (string, error) {
|
|
||||||
return state.Get("server_ip").(string), nil
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatConfig struct {
|
|
||||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
|
||||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
|
||||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
|
||||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
|
||||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
|
||||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
|
||||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
|
||||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
|
||||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
|
||||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
|
||||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
|
||||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
|
||||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
|
||||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
|
||||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
|
||||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
|
||||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
|
||||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
|
||||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
|
||||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
|
||||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
|
||||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
|
||||||
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
|
||||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
|
||||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
|
||||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
|
||||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
|
||||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
|
||||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
|
||||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
|
||||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
|
||||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
|
||||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
|
||||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
|
||||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
|
||||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
|
||||||
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
|
||||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
|
||||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
|
||||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
|
||||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
|
||||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
|
||||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
|
||||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
|
||||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
|
||||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
|
||||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
|
||||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
|
||||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
|
||||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
|
||||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
|
||||||
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
|
||||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
|
||||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
|
||||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
|
||||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
|
||||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
|
||||||
HCloudToken *string `mapstructure:"token" cty:"token" hcl:"token"`
|
|
||||||
Endpoint *string `mapstructure:"endpoint" cty:"endpoint" hcl:"endpoint"`
|
|
||||||
PollInterval *string `mapstructure:"poll_interval" cty:"poll_interval" hcl:"poll_interval"`
|
|
||||||
ServerName *string `mapstructure:"server_name" cty:"server_name" hcl:"server_name"`
|
|
||||||
Location *string `mapstructure:"location" cty:"location" hcl:"location"`
|
|
||||||
ServerType *string `mapstructure:"server_type" cty:"server_type" hcl:"server_type"`
|
|
||||||
Image *string `mapstructure:"image" cty:"image" hcl:"image"`
|
|
||||||
ImageFilter *FlatimageFilter `mapstructure:"image_filter" cty:"image_filter" hcl:"image_filter"`
|
|
||||||
SnapshotName *string `mapstructure:"snapshot_name" cty:"snapshot_name" hcl:"snapshot_name"`
|
|
||||||
SnapshotLabels map[string]string `mapstructure:"snapshot_labels" cty:"snapshot_labels" hcl:"snapshot_labels"`
|
|
||||||
UserData *string `mapstructure:"user_data" cty:"user_data" hcl:"user_data"`
|
|
||||||
UserDataFile *string `mapstructure:"user_data_file" cty:"user_data_file" hcl:"user_data_file"`
|
|
||||||
SSHKeys []string `mapstructure:"ssh_keys" cty:"ssh_keys" hcl:"ssh_keys"`
|
|
||||||
RescueMode *string `mapstructure:"rescue" cty:"rescue" hcl:"rescue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatConfig.
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a Config.
|
|
||||||
// This spec is used by HCL to read the fields of Config.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
|
||||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
|
||||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
|
||||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
|
||||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
|
||||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
|
||||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
|
||||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
|
||||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
|
||||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
|
||||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
|
||||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
|
||||||
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
|
||||||
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
|
||||||
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
|
||||||
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
|
||||||
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
|
||||||
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
|
||||||
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
|
||||||
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
|
||||||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
|
||||||
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
|
||||||
"endpoint": &hcldec.AttrSpec{Name: "endpoint", Type: cty.String, Required: false},
|
|
||||||
"poll_interval": &hcldec.AttrSpec{Name: "poll_interval", Type: cty.String, Required: false},
|
|
||||||
"server_name": &hcldec.AttrSpec{Name: "server_name", Type: cty.String, Required: false},
|
|
||||||
"location": &hcldec.AttrSpec{Name: "location", Type: cty.String, Required: false},
|
|
||||||
"server_type": &hcldec.AttrSpec{Name: "server_type", Type: cty.String, Required: false},
|
|
||||||
"image": &hcldec.AttrSpec{Name: "image", Type: cty.String, Required: false},
|
|
||||||
"image_filter": &hcldec.BlockSpec{TypeName: "image_filter", Nested: hcldec.ObjectSpec((*FlatimageFilter)(nil).HCL2Spec())},
|
|
||||||
"snapshot_name": &hcldec.AttrSpec{Name: "snapshot_name", Type: cty.String, Required: false},
|
|
||||||
"snapshot_labels": &hcldec.AttrSpec{Name: "snapshot_labels", Type: cty.Map(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},
|
|
||||||
"ssh_keys": &hcldec.AttrSpec{Name: "ssh_keys", Type: cty.List(cty.String), Required: false},
|
|
||||||
"rescue": &hcldec.AttrSpec{Name: "rescue", Type: cty.String, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatimageFilter is an auto-generated flat version of imageFilter.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatimageFilter struct {
|
|
||||||
WithSelector []string `mapstructure:"with_selector" cty:"with_selector" hcl:"with_selector"`
|
|
||||||
MostRecent *bool `mapstructure:"most_recent" cty:"most_recent" hcl:"most_recent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatimageFilter.
|
|
||||||
// FlatimageFilter is an auto-generated flat version of imageFilter.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*imageFilter) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatimageFilter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a imageFilter.
|
|
||||||
// This spec is used by HCL to read the fields of imageFilter.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatimageFilter.
|
|
||||||
func (*FlatimageFilter) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"with_selector": &hcldec.AttrSpec{Name: "with_selector", Type: cty.List(cty.String), Required: false},
|
|
||||||
"most_recent": &hcldec.AttrSpec{Name: "most_recent", Type: cty.Bool, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateServer struct {
|
|
||||||
serverId int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
sshKeyId := state.Get("ssh_key_id").(int)
|
|
||||||
|
|
||||||
// Create the server based on configuration
|
|
||||||
ui.Say("Creating server...")
|
|
||||||
|
|
||||||
userData := c.UserData
|
|
||||||
if c.UserDataFile != "" {
|
|
||||||
contents, err := ioutil.ReadFile(c.UserDataFile)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
userData = string(contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
sshKeys := []*hcloud.SSHKey{{ID: sshKeyId}}
|
|
||||||
for _, k := range c.SSHKeys {
|
|
||||||
sshKey, _, err := client.SSHKey.Get(ctx, k)
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", fmt.Errorf("Error fetching SSH key: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
if sshKey == nil {
|
|
||||||
state.Put("error", fmt.Errorf("Could not find key: %s", k))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
sshKeys = append(sshKeys, sshKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
var image *hcloud.Image
|
|
||||||
if c.Image != "" {
|
|
||||||
image = &hcloud.Image{Name: c.Image}
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
image, err = getImageWithSelectors(ctx, client, c)
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
ui.Message(fmt.Sprintf("Using image %s with ID %d", image.Description, image.ID))
|
|
||||||
}
|
|
||||||
|
|
||||||
serverCreateResult, _, err := client.Server.Create(ctx, hcloud.ServerCreateOpts{
|
|
||||||
Name: c.ServerName,
|
|
||||||
ServerType: &hcloud.ServerType{Name: c.ServerType},
|
|
||||||
Image: image,
|
|
||||||
SSHKeys: sshKeys,
|
|
||||||
Location: &hcloud.Location{Name: c.Location},
|
|
||||||
UserData: userData,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put("server_ip", serverCreateResult.Server.PublicNet.IPv4.IP.String())
|
|
||||||
// We use this in cleanup
|
|
||||||
s.serverId = serverCreateResult.Server.ID
|
|
||||||
|
|
||||||
// Store the server id for later
|
|
||||||
state.Put("server_id", serverCreateResult.Server.ID)
|
|
||||||
// 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", serverCreateResult.Server.ID)
|
|
||||||
|
|
||||||
if err := waitForAction(ctx, client, serverCreateResult.Action); err != nil {
|
|
||||||
err := fmt.Errorf("Error creating server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
for _, nextAction := range serverCreateResult.NextActions {
|
|
||||||
if err := waitForAction(ctx, client, nextAction); err != nil {
|
|
||||||
err := fmt.Errorf("Error creating server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.RescueMode != "" {
|
|
||||||
ui.Say("Enabling Rescue Mode...")
|
|
||||||
rootPassword, err := setRescue(ctx, client, serverCreateResult.Server, c.RescueMode, sshKeys)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error enabling rescue mode: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
ui.Say("Reboot server...")
|
|
||||||
action, _, err := client.Server.Reset(ctx, serverCreateResult.Server)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error rebooting server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
if err := waitForAction(ctx, client, action); err != nil {
|
|
||||||
err := fmt.Errorf("Error rebooting server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
if c.RescueMode == "freebsd64" {
|
|
||||||
// We will set this only on freebsd
|
|
||||||
ui.Say("Using Root Password instead of SSH Keys...")
|
|
||||||
c.Comm.SSHPassword = rootPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
|
|
||||||
// If the serverID isn't there, we probably never created it
|
|
||||||
if s.serverId == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
// Destroy the server we just created
|
|
||||||
ui.Say("Destroying server...")
|
|
||||||
_, err := client.Server.Delete(context.TODO(), &hcloud.Server{ID: s.serverId})
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf(
|
|
||||||
"Error destroying server. Please destroy it manually: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setRescue(ctx context.Context, client *hcloud.Client, server *hcloud.Server, rescue string, sshKeys []*hcloud.SSHKey) (string, error) {
|
|
||||||
rescueChanged := false
|
|
||||||
if server.RescueEnabled {
|
|
||||||
rescueChanged = true
|
|
||||||
action, _, err := client.Server.DisableRescue(ctx, server)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if err := waitForAction(ctx, client, action); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rescue != "" {
|
|
||||||
rescueChanged = true
|
|
||||||
if rescue == "freebsd64" {
|
|
||||||
sshKeys = nil // freebsd64 doesn't allow ssh keys so we will remove them here
|
|
||||||
}
|
|
||||||
res, _, err := client.Server.EnableRescue(ctx, server, hcloud.ServerEnableRescueOpts{
|
|
||||||
Type: hcloud.ServerRescueType(rescue),
|
|
||||||
SSHKeys: sshKeys,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if err := waitForAction(ctx, client, res.Action); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return res.RootPassword, nil
|
|
||||||
}
|
|
||||||
if rescueChanged {
|
|
||||||
action, _, err := client.Server.Reset(ctx, server)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if err := waitForAction(ctx, client, action); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForAction(ctx context.Context, client *hcloud.Client, action *hcloud.Action) error {
|
|
||||||
_, errCh := client.Action.WatchProgress(ctx, action)
|
|
||||||
if err := <-errCh; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getImageWithSelectors(ctx context.Context, client *hcloud.Client, c *Config) (*hcloud.Image, error) {
|
|
||||||
var allImages []*hcloud.Image
|
|
||||||
|
|
||||||
var selector = strings.Join(c.ImageFilter.WithSelector, ",")
|
|
||||||
opts := hcloud.ImageListOpts{
|
|
||||||
ListOpts: hcloud.ListOpts{LabelSelector: selector},
|
|
||||||
Status: []hcloud.ImageStatus{hcloud.ImageStatusAvailable},
|
|
||||||
}
|
|
||||||
|
|
||||||
allImages, err := client.Image.AllWithOpts(ctx, opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(allImages) == 0 {
|
|
||||||
return nil, fmt.Errorf("no image found for selector %q", selector)
|
|
||||||
}
|
|
||||||
if len(allImages) > 1 {
|
|
||||||
if !c.ImageFilter.MostRecent {
|
|
||||||
return nil, fmt.Errorf("more than one image found for selector %q", selector)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(allImages, func(i, j int) bool {
|
|
||||||
return allImages[i].Created.After(allImages[j].Created)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return allImages[0], nil
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateSnapshot struct{}
|
|
||||||
|
|
||||||
func (s *stepCreateSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
serverID := state.Get("server_id").(int)
|
|
||||||
|
|
||||||
ui.Say("Creating snapshot ...")
|
|
||||||
ui.Say("This can take some time")
|
|
||||||
result, _, err := client.Server.CreateImage(ctx, &hcloud.Server{ID: serverID}, &hcloud.ServerCreateImageOpts{
|
|
||||||
Type: hcloud.ImageTypeSnapshot,
|
|
||||||
Labels: c.SnapshotLabels,
|
|
||||||
Description: hcloud.String(c.SnapshotName),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put("snapshot_id", result.Image.ID)
|
|
||||||
state.Put("snapshot_name", c.SnapshotName)
|
|
||||||
_, errCh := client.Action.WatchProgress(ctx, result.Action)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case err1 := <-errCh:
|
|
||||||
if err1 == nil {
|
|
||||||
return multistep.ActionContinue
|
|
||||||
} else {
|
|
||||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateSnapshot) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateSSHKey struct {
|
|
||||||
Debug bool
|
|
||||||
DebugKeyPath string
|
|
||||||
|
|
||||||
keyId int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
ui.Say("Creating temporary ssh key for server...")
|
|
||||||
|
|
||||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Error generating RSA key: %s", err))
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
// ASN.1 DER encoded form
|
|
||||||
privDER := x509.MarshalPKCS1PrivateKey(priv)
|
|
||||||
privBLK := pem.Block{
|
|
||||||
Type: "RSA PRIVATE KEY",
|
|
||||||
Headers: nil,
|
|
||||||
Bytes: privDER,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the private key in the config for later
|
|
||||||
c.Comm.SSHPrivateKey = pem.EncodeToMemory(&privBLK)
|
|
||||||
|
|
||||||
// Marshal the public key into SSH compatible format
|
|
||||||
pub, err := ssh.NewPublicKey(&priv.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Error generating public key: %s", err))
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
pubSSHFormat := string(ssh.MarshalAuthorizedKey(pub))
|
|
||||||
|
|
||||||
// The name of the public key on the Hetzner Cloud
|
|
||||||
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
|
||||||
|
|
||||||
// Create the key!
|
|
||||||
key, _, err := client.SSHKey.Create(ctx, hcloud.SSHKeyCreateOpts{
|
|
||||||
Name: name,
|
|
||||||
PublicKey: pubSSHFormat,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use this to check cleanup
|
|
||||||
s.keyId = key.ID
|
|
||||||
|
|
||||||
log.Printf("temporary ssh key name: %s", name)
|
|
||||||
|
|
||||||
// Remember some state for the future
|
|
||||||
state.Put("ssh_key_id", key.ID)
|
|
||||||
|
|
||||||
// If we're in debug mode, output the private key to the working directory.
|
|
||||||
if s.Debug {
|
|
||||||
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
|
||||||
f, err := os.Create(s.DebugKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
// Write the key out
|
|
||||||
if _, err := f.Write(pem.EncodeToMemory(&privBLK)); 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 *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
|
|
||||||
// If no key id is set, then we never created it, so just return
|
|
||||||
if s.keyId == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say("Deleting temporary ssh key...")
|
|
||||||
_, err := client.SSHKey.Delete(context.TODO(), &hcloud.SSHKey{ID: s.keyId})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error cleaning up ssh key: %s", err)
|
|
||||||
ui.Error(fmt.Sprintf(
|
|
||||||
"Error cleaning up ssh key. Please delete the key manually: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepShutdownServer struct{}
|
|
||||||
|
|
||||||
func (s *stepShutdownServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
serverID := state.Get("server_id").(int)
|
|
||||||
|
|
||||||
ui.Say("Shutting down server...")
|
|
||||||
|
|
||||||
action, _, err := client.Server.Shutdown(ctx, &hcloud.Server{ID: serverID})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error stopping server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
_, errCh := client.Action.WatchProgress(ctx, action)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case err1 := <-errCh:
|
|
||||||
if err1 == nil {
|
|
||||||
return multistep.ActionContinue
|
|
||||||
} else {
|
|
||||||
err := fmt.Errorf("Error stopping server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepShutdownServer) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
|
||||||
packerVersion "github.com/hashicorp/packer/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var HcloudPluginVersion *version.PluginVersion
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
HcloudPluginVersion = version.InitializePluginVersion(
|
|
||||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user