Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 371667c3a7 | |||
| f570852816 |
@@ -20,7 +20,6 @@ async function checkPluginDocs() {
|
||||
console.log(`\n${COLOR_BLUE}${repo}${COLOR_RESET} | ${title}`);
|
||||
console.log(`Fetching docs from release "${version}" …`);
|
||||
try {
|
||||
// Validate that all required properties are present
|
||||
const undefinedProps = ["title", "repo", "version", "path"].filter(
|
||||
(key) => typeof pluginEntry[key] == "undefined"
|
||||
);
|
||||
@@ -35,22 +34,6 @@ async function checkPluginDocs() {
|
||||
)} are defined. Additional information on this configuration can be found in "website/README.md".`
|
||||
);
|
||||
}
|
||||
// Validate pluginTier property
|
||||
const { pluginTier } = pluginEntry;
|
||||
if (typeof pluginTier !== "undefined") {
|
||||
const validPluginTiers = ["official", "community"];
|
||||
const isValid = validPluginTiers.indexOf(pluginTier) !== -1;
|
||||
if (!isValid) {
|
||||
throw new Error(
|
||||
`Failed to validate plugin docs config. Invalid pluginTier "${pluginTier}" found for "${
|
||||
title || pluginEntry.path || repo
|
||||
}". In "website/data/docs-remote-plugins.json", the optional pluginTier property must be one of ${JSON.stringify(
|
||||
validPluginTiers
|
||||
)}. The pluginTier property can also be omitted, in which case it will be determined from the plugin repository owner.`
|
||||
);
|
||||
}
|
||||
}
|
||||
// Attempt to fetch plugin docs files
|
||||
const docsMdxFiles = await fetchPluginDocs({ repo, tag: version });
|
||||
const mdxFilesByComponent = docsMdxFiles.reduce((acc, mdxFile) => {
|
||||
const componentType = mdxFile.filePath.split("/")[1];
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
/builder/triton/ @sean-
|
||||
/website/pages/docs/builders/triton* @sean-
|
||||
|
||||
/builder/ncloud/ @YuSungDuk
|
||||
/website/pages/docs/builders/ncloud* @YuSungDuk
|
||||
|
||||
/builder/proxmox/ @carlpett
|
||||
/website/pages/docs/builders/proxmox* @carlpett
|
||||
|
||||
@@ -65,10 +68,15 @@
|
||||
/builder/yandex/ @GennadySpb @alexanderKhaustov @seukyaso
|
||||
/website/pages/docs/builders/yandex* @GennadySpb @alexanderKhaustov @seukyaso
|
||||
|
||||
/builder/osc/ @marinsalinas @Hakujou
|
||||
/website/pages/docs/builders/osc* @marinsalinas @Hakujou
|
||||
|
||||
/examples/tencentcloud/ @likexian
|
||||
/builder/tencentcloud/ @likexian
|
||||
/website/pages/docs/builders/tencentcloud* @likexian
|
||||
|
||||
|
||||
|
||||
# provisioners
|
||||
|
||||
/examples/ansible/ @bhcleek
|
||||
|
||||
@@ -58,7 +58,8 @@ install-gen-deps: ## Install dependencies for code generation
|
||||
# install` seems to install the last tagged version and we want to install
|
||||
# master.
|
||||
@(cd $(TEMPDIR) && GO111MODULE=on go get github.com/alvaroloes/enumer@master)
|
||||
@go install github.com/hashicorp/packer-plugin-sdk/cmd/packer-sdc@latest
|
||||
@go install ./cmd/struct-markdown
|
||||
@go install ./cmd/mapstructure-to-hcl2
|
||||
|
||||
install-lint-deps: ## Install linter dependencies
|
||||
# Pinning golangci-lint at v1.23.8 as --new-from-rev seems to work properly; the latest 1.24.0 has caused issues with memory consumption
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ecs
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,AlicloudDiskDevice
|
||||
//go:generate mapstructure-to-hcl2 -type Config,AlicloudDiskDevice
|
||||
|
||||
// The alicloud contains a packersdk.Builder implementation that
|
||||
// builds ecs images for alicloud.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,AlicloudDiskDevice"; DO NOT EDIT.
|
||||
|
||||
package ecs
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ecs
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ecs
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation
|
||||
|
||||
package arm
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation"; DO NOT EDIT.
|
||||
|
||||
package arm
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
// Package chroot is able to create an Azure managed image without requiring the
|
||||
// launch of a new virtual machine for every build. It does this by attaching and
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package chroot
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type SharedImageGalleryDestination,TargetRegion
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type SharedImageGalleryDestination,TargetRegion
|
||||
|
||||
package chroot
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type SharedImageGalleryDestination,TargetRegion"; DO NOT EDIT.
|
||||
|
||||
package chroot
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate struct-markdown
|
||||
|
||||
package client
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,DtlArtifact,ArtifactParameter
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,DtlArtifact,ArtifactParameter
|
||||
|
||||
package dtl
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,DtlArtifact,ArtifactParameter"; DO NOT EDIT.
|
||||
|
||||
package dtl
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package cloudstack
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package cloudstack
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package digitalocean
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package digitalocean
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package file
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package file
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_impl(t *testing.T) {
|
||||
var _ packersdk.Artifact = new(Artifact)
|
||||
}
|
||||
|
||||
func TestArtifactState_StateData(t *testing.T) {
|
||||
expectedData := "this is the data"
|
||||
artifact := &Artifact{
|
||||
StateData: map[string]interface{}{"state_data": expectedData},
|
||||
}
|
||||
|
||||
// Valid state
|
||||
result := artifact.State("state_data")
|
||||
if result != expectedData {
|
||||
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
|
||||
}
|
||||
|
||||
// Invalid state
|
||||
result = artifact.State("invalid_key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for invalid state data name")
|
||||
}
|
||||
|
||||
// Nil StateData should not fail and should return nil
|
||||
artifact = &Artifact{}
|
||||
result = artifact.State("key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for nil StateData")
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,CustomerEncryptionKey
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config,CustomerEncryptionKey
|
||||
|
||||
package googlecompute
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,CustomerEncryptionKey"; DO NOT EDIT.
|
||||
|
||||
package googlecompute
|
||||
|
||||
@@ -0,0 +1,692 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||
)
|
||||
|
||||
func TestConfigPrepare(t *testing.T) {
|
||||
cases := []struct {
|
||||
Key string
|
||||
Value interface{}
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
"unknown_key",
|
||||
"bad",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"private_key_file",
|
||||
"/tmp/i/should/not/exist",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"project_id",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"project_id",
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"source_image",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"source_image",
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"source_image_family",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"source_image_family",
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"zone",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"zone",
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"ssh_timeout",
|
||||
"SO BAD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ssh_timeout",
|
||||
"5s",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"wait_to_add_ssh_keys",
|
||||
"SO BAD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"wait_to_add_ssh_keys",
|
||||
"5s",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"state_timeout",
|
||||
"SO BAD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"state_timeout",
|
||||
"5s",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"use_internal_ip",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"use_internal_ip",
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"use_internal_ip",
|
||||
"SO VERY BAD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"on_host_maintenance",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"on_host_maintenance",
|
||||
"TERMINATE",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"on_host_maintenance",
|
||||
"SO VERY BAD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"preemptible",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"preemptible",
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"preemptible",
|
||||
"SO VERY BAD",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"image_family",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"image_family",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"image_family",
|
||||
"foo-bar",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"image_family",
|
||||
"foo bar",
|
||||
true,
|
||||
},
|
||||
{
|
||||
// underscore is not allowed
|
||||
"image_name",
|
||||
"foo_bar",
|
||||
true,
|
||||
},
|
||||
{
|
||||
// too long
|
||||
"image_name",
|
||||
"foobar123xyz_abc-456-one-two_three_five_nine_seventeen_eleventy-seven",
|
||||
true,
|
||||
},
|
||||
{
|
||||
// starts with non-alphabetic character
|
||||
"image_name",
|
||||
"1boohoo",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"image_encryption_key",
|
||||
map[string]string{"kmsKeyName": "foo"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"image_encryption_key",
|
||||
map[string]string{"No such key": "foo"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"image_encryption_key",
|
||||
map[string]string{"kmsKeyName": "foo", "RawKey": "foo"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"scopes",
|
||||
[]string{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"scopes",
|
||||
[]string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control", "https://www.googleapis.com/auth/sqlservice.admin"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"scopes",
|
||||
[]string{"https://www.googleapis.com/auth/cloud-platform"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"disable_default_service_account",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"disable_default_service_account",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"disable_default_service_account",
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"disable_default_service_account",
|
||||
true,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"disable_default_service_account",
|
||||
"NOT A BOOL",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
if tc.Value == nil {
|
||||
delete(raw, tc.Key)
|
||||
} else {
|
||||
raw[tc.Key] = tc.Value
|
||||
}
|
||||
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
|
||||
if tc.Err {
|
||||
testConfigErr(t, warns, errs, tc.Key)
|
||||
} else {
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepareAccelerator(t *testing.T) {
|
||||
cases := []struct {
|
||||
Keys []string
|
||||
Values []interface{}
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
[]string{"accelerator_count", "on_host_maintenance", "accelerator_type"},
|
||||
[]interface{}{1, "MIGRATE", "something_valid"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"accelerator_count", "on_host_maintenance", "accelerator_type"},
|
||||
[]interface{}{1, "TERMINATE", "something_valid"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]string{"accelerator_count", "on_host_maintenance", "accelerator_type"},
|
||||
[]interface{}{1, "TERMINATE", nil},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"accelerator_count", "on_host_maintenance", "accelerator_type"},
|
||||
[]interface{}{1, "TERMINATE", ""},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"accelerator_count", "on_host_maintenance", "accelerator_type"},
|
||||
[]interface{}{1, "TERMINATE", "something_valid"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
errStr := ""
|
||||
for k := range tc.Keys {
|
||||
|
||||
// Create the string for error reporting
|
||||
// convert value to string if it can be converted
|
||||
errStr += fmt.Sprintf("%s:%v, ", tc.Keys[k], tc.Values[k])
|
||||
if tc.Values[k] == nil {
|
||||
delete(raw, tc.Keys[k])
|
||||
} else {
|
||||
raw[tc.Keys[k]] = tc.Values[k]
|
||||
}
|
||||
}
|
||||
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
|
||||
if tc.Err {
|
||||
testConfigErr(t, warns, errs, strings.TrimRight(errStr, ", "))
|
||||
} else {
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepareServiceAccount(t *testing.T) {
|
||||
cases := []struct {
|
||||
Keys []string
|
||||
Values []interface{}
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
[]string{"disable_default_service_account", "service_account_email"},
|
||||
[]interface{}{true, "service@account.email.com"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"disable_default_service_account", "service_account_email"},
|
||||
[]interface{}{false, "service@account.email.com"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]string{"disable_default_service_account", "service_account_email"},
|
||||
[]interface{}{true, ""},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
errStr := ""
|
||||
for k := range tc.Keys {
|
||||
|
||||
// Create the string for error reporting
|
||||
// convert value to string if it can be converted
|
||||
errStr += fmt.Sprintf("%s:%v, ", tc.Keys[k], tc.Values[k])
|
||||
if tc.Values[k] == nil {
|
||||
delete(raw, tc.Keys[k])
|
||||
} else {
|
||||
raw[tc.Keys[k]] = tc.Values[k]
|
||||
}
|
||||
}
|
||||
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
|
||||
if tc.Err {
|
||||
testConfigErr(t, warns, errs, strings.TrimRight(errStr, ", "))
|
||||
} else {
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepareStartupScriptFile(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"project_id": "project",
|
||||
"source_image": "foo",
|
||||
"ssh_username": "packer",
|
||||
"startup_script_file": "no-such-file",
|
||||
"zone": "us-central1-a",
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, errs := c.Prepare(config)
|
||||
|
||||
if errs == nil || !strings.Contains(errs.Error(), "startup_script_file") {
|
||||
t.Fatalf("should error: startup_script_file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepareIAP_SSH(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"project_id": "project",
|
||||
"source_image": "foo",
|
||||
"ssh_username": "packer",
|
||||
"zone": "us-central1-a",
|
||||
"communicator": "ssh",
|
||||
"use_iap": true,
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have errors. Err = %s", err)
|
||||
}
|
||||
if c.Comm.SSHHost != "localhost" {
|
||||
t.Fatalf("Should have set SSHHost")
|
||||
}
|
||||
|
||||
testIAPScript(t, &c)
|
||||
}
|
||||
|
||||
func TestConfigPrepareIAP_WinRM(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"project_id": "project",
|
||||
"source_image": "foo",
|
||||
"winrm_username": "packer",
|
||||
"zone": "us-central1-a",
|
||||
"communicator": "winrm",
|
||||
"use_iap": true,
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have errors. Err = %s", err)
|
||||
}
|
||||
if c.Comm.WinRMHost != "localhost" {
|
||||
t.Fatalf("Should have set WinRMHost")
|
||||
}
|
||||
|
||||
testIAPScript(t, &c)
|
||||
}
|
||||
|
||||
func TestConfigPrepareIAP_failures(t *testing.T) {
|
||||
config := map[string]interface{}{
|
||||
"project_id": "project",
|
||||
"source_image": "foo",
|
||||
"winrm_username": "packer",
|
||||
"zone": "us-central1-a",
|
||||
"communicator": "none",
|
||||
"iap_hashbang": "/bin/bash",
|
||||
"iap_ext": ".ps1",
|
||||
"use_iap": true,
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, errs := c.Prepare(config)
|
||||
if errs == nil {
|
||||
t.Fatalf("Should have errored because we're using none.")
|
||||
}
|
||||
if c.IAPHashBang != "/bin/bash" {
|
||||
t.Fatalf("IAP hashbang defaulted even though set.")
|
||||
}
|
||||
if c.IAPExt != ".ps1" {
|
||||
t.Fatalf("IAP tempfile defaulted even though set.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigDefaults(t *testing.T) {
|
||||
cases := []struct {
|
||||
Read func(c *Config) interface{}
|
||||
Value interface{}
|
||||
}{
|
||||
{
|
||||
func(c *Config) interface{} { return c.Comm.Type },
|
||||
"ssh",
|
||||
},
|
||||
|
||||
{
|
||||
func(c *Config) interface{} { return c.Comm.SSHPort },
|
||||
22,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
|
||||
actual := tc.Read(&c)
|
||||
if actual != tc.Value {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageName(t *testing.T) {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
var c Config
|
||||
c.Prepare(raw)
|
||||
if !strings.HasPrefix(c.ImageName, "packer-") {
|
||||
t.Fatalf("ImageName should have 'packer-' prefix, found %s", c.ImageName)
|
||||
}
|
||||
if strings.Contains(c.ImageName, "{{timestamp}}") {
|
||||
t.Errorf("ImageName should be interpolated; found %s", c.ImageName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegion(t *testing.T) {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
var c Config
|
||||
c.Prepare(raw)
|
||||
if c.Region != "us-east1" {
|
||||
t.Fatalf("Region should be 'us-east1' given Zone of 'us-east1-a', but is %s", c.Region)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyIAPTunnel_SSH(t *testing.T) {
|
||||
c := &communicator.Config{
|
||||
Type: "ssh",
|
||||
SSH: communicator.SSH{
|
||||
SSHHost: "example",
|
||||
SSHPort: 1234,
|
||||
},
|
||||
}
|
||||
|
||||
err := ApplyIAPTunnel(c, 8447)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have errors")
|
||||
}
|
||||
if c.SSHPort != 8447 {
|
||||
t.Fatalf("Should have set SSHPort")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyIAPTunnel_WinRM(t *testing.T) {
|
||||
c := &communicator.Config{
|
||||
Type: "winrm",
|
||||
WinRM: communicator.WinRM{
|
||||
WinRMHost: "example",
|
||||
WinRMPort: 1234,
|
||||
},
|
||||
}
|
||||
|
||||
err := ApplyIAPTunnel(c, 8447)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have errors")
|
||||
}
|
||||
if c.WinRMPort != 8447 {
|
||||
t.Fatalf("Should have set WinRMPort")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyIAPTunnel_none(t *testing.T) {
|
||||
c := &communicator.Config{
|
||||
Type: "none",
|
||||
}
|
||||
|
||||
err := ApplyIAPTunnel(c, 8447)
|
||||
if err == nil {
|
||||
t.Fatalf("Should have errors, none is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
// Helper stuff below
|
||||
|
||||
func testConfig(t *testing.T) (config map[string]interface{}, tempAccountFile string) {
|
||||
tempAccountFile = testAccountFile(t)
|
||||
|
||||
config = map[string]interface{}{
|
||||
"account_file": tempAccountFile,
|
||||
"project_id": "hashicorp",
|
||||
"source_image": "foo",
|
||||
"ssh_username": "root",
|
||||
"image_family": "bar",
|
||||
"image_labels": map[string]string{
|
||||
"label-1": "value-1",
|
||||
"label-2": "value-2",
|
||||
},
|
||||
"image_licenses": []string{
|
||||
"test-license",
|
||||
},
|
||||
"image_storage_locations": []string{
|
||||
"us-east1",
|
||||
},
|
||||
"metadata_files": map[string]string{},
|
||||
"zone": "us-east1-a",
|
||||
}
|
||||
|
||||
return config, tempAccountFile
|
||||
}
|
||||
|
||||
func testConfigStruct(t *testing.T) *Config {
|
||||
raw, tempfile := testConfig(t)
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
var c Config
|
||||
warns, errs := c.Prepare(raw)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", len(warns))
|
||||
}
|
||||
if errs != nil {
|
||||
t.Fatalf("bad: %#v", errs)
|
||||
}
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
func testConfigErr(t *testing.T, warns []string, err error, extra string) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("should error: %s", extra)
|
||||
}
|
||||
}
|
||||
|
||||
func testConfigOk(t *testing.T, warns []string, err error) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccountFile(t *testing.T) string {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer tf.Close()
|
||||
|
||||
if _, err := tf.Write([]byte(testAccountContent)); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return tf.Name()
|
||||
}
|
||||
|
||||
func testIAPScript(t *testing.T, c *Config) {
|
||||
if runtime.GOOS == "windows" {
|
||||
if c.IAPExt != ".cmd" {
|
||||
t.Fatalf("IAP tempfile extension didn't default correctly to .cmd")
|
||||
}
|
||||
if c.IAPHashBang != "" {
|
||||
t.Fatalf("IAP hashbang didn't default correctly to nothing.")
|
||||
}
|
||||
} else {
|
||||
if c.IAPExt != "" {
|
||||
t.Fatalf("IAP tempfile extension should default to empty on unix mahcines")
|
||||
}
|
||||
if c.IAPHashBang != "/bin/sh" {
|
||||
t.Fatalf("IAP hashbang didn't default correctly to /bin/sh.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const testMetadataFileContent = `testMetadata`
|
||||
|
||||
func testMetadataFile(t *testing.T) string {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer tf.Close()
|
||||
if _, err := tf.Write([]byte(testMetadataFileContent)); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return tf.Name()
|
||||
}
|
||||
|
||||
// This is just some dummy data that doesn't actually work
|
||||
const testAccountContent = `{
|
||||
"type": "service_account",
|
||||
"project_id": "test-project-123456789",
|
||||
"private_key_id": "bananaphone",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nring_ring_ring_ring_ring_ring_ring_BANANAPHONE\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "raffi-compute@developer.gserviceaccount.com",
|
||||
"client_id": "1234567890",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://accounts.google.com/o/oauth2/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/12345-compute%40developer.gserviceaccount.com"
|
||||
}`
|
||||
+1
-1
@@ -17,10 +17,10 @@ import (
|
||||
"google.golang.org/api/option"
|
||||
oslogin "google.golang.org/api/oslogin/v1"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-googlecompute/builder/googlecompute/version"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/retry"
|
||||
"github.com/hashicorp/packer-plugin-sdk/useragent"
|
||||
"github.com/hashicorp/packer/builder/googlecompute/version"
|
||||
vaultapi "github.com/hashicorp/vault/api"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
@@ -0,0 +1,26 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func StubImage(name, project string, licenses []string, sizeGb int64) *Image {
|
||||
return &Image{
|
||||
Licenses: licenses,
|
||||
Name: name,
|
||||
ProjectId: project,
|
||||
SelfLink: fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/images/%s", project, name),
|
||||
SizeGb: sizeGb,
|
||||
}
|
||||
}
|
||||
|
||||
func TestImage_IsWindows(t *testing.T) {
|
||||
i := StubImage("foo", "foo-project", []string{"license-foo", "license-bar"}, 100)
|
||||
assert.False(t, i.IsWindows())
|
||||
|
||||
i = StubImage("foo", "foo-project", []string{"license-foo", "windows-license"}, 100)
|
||||
assert.True(t, i.IsWindows())
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetNetworking(t *testing.T) {
|
||||
cases := []struct {
|
||||
c *InstanceConfig
|
||||
expectedNetwork string
|
||||
expectedSubnetwork string
|
||||
error bool
|
||||
}{
|
||||
{
|
||||
c: &InstanceConfig{
|
||||
Network: "default",
|
||||
Subnetwork: "",
|
||||
NetworkProjectId: "project-id",
|
||||
Region: "region-id",
|
||||
},
|
||||
expectedNetwork: "global/networks/default",
|
||||
expectedSubnetwork: "",
|
||||
error: false,
|
||||
},
|
||||
{
|
||||
c: &InstanceConfig{
|
||||
Network: "",
|
||||
Subnetwork: "",
|
||||
NetworkProjectId: "project-id",
|
||||
Region: "region-id",
|
||||
},
|
||||
expectedNetwork: "",
|
||||
expectedSubnetwork: "",
|
||||
error: true,
|
||||
},
|
||||
{
|
||||
c: &InstanceConfig{
|
||||
Network: "some/network/path",
|
||||
Subnetwork: "some/subnetwork/path",
|
||||
NetworkProjectId: "project-id",
|
||||
Region: "region-id",
|
||||
},
|
||||
expectedNetwork: "some/network/path",
|
||||
expectedSubnetwork: "some/subnetwork/path",
|
||||
error: false,
|
||||
},
|
||||
{
|
||||
c: &InstanceConfig{
|
||||
Network: "network-value",
|
||||
Subnetwork: "subnetwork-value",
|
||||
NetworkProjectId: "project-id",
|
||||
Region: "region-id",
|
||||
},
|
||||
expectedNetwork: "projects/project-id/global/networks/network-value",
|
||||
expectedSubnetwork: "projects/project-id/regions/region-id/subnetworks/subnetwork-value",
|
||||
error: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
n, sn, err := getNetworking(tc.c)
|
||||
if n != tc.expectedNetwork {
|
||||
t.Errorf("Expected network %q but got network %q", tc.expectedNetwork, n)
|
||||
}
|
||||
if sn != tc.expectedSubnetwork {
|
||||
t.Errorf("Expected subnetwork %q but got subnetwork %q", tc.expectedSubnetwork, sn)
|
||||
}
|
||||
if !tc.error && err != nil {
|
||||
t.Errorf("Did not expect an error but got: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepCheckExistingImage_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepCheckExistingImage)
|
||||
}
|
||||
|
||||
func TestStepCheckExistingImage(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCheckExistingImage)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("instance_name", "foo")
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.ImageExistsResult = true
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if driver.ImageExistsName != config.ImageName {
|
||||
t.Fatalf("bad: %#v", driver.ImageExistsName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStepCreateImage_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepCreateImage)
|
||||
}
|
||||
|
||||
func TestStepCreateImage(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateImage)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
c := state.Get("config").(*Config)
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
|
||||
// These are the values of the image the driver will return.
|
||||
d.CreateImageResultProjectId = "test-project"
|
||||
d.CreateImageResultSizeGb = 100
|
||||
|
||||
// run the step
|
||||
action := step.Run(context.Background(), state)
|
||||
assert.Equal(t, action, multistep.ActionContinue, "Step did not pass.")
|
||||
|
||||
uncastImage, ok := state.GetOk("image")
|
||||
assert.True(t, ok, "State does not have resulting image.")
|
||||
image, ok := uncastImage.(*Image)
|
||||
assert.True(t, ok, "Image in state is not an Image.")
|
||||
|
||||
// Verify created Image results.
|
||||
assert.Equal(t, image.Name, c.ImageName, "Created image does not match config name.")
|
||||
assert.Equal(t, image.ProjectId, d.CreateImageResultProjectId, "Created image project does not match driver project.")
|
||||
assert.Equal(t, image.SizeGb, d.CreateImageResultSizeGb, "Created image size does not match the size returned by the driver.")
|
||||
|
||||
// Verify proper args passed to driver.CreateImage.
|
||||
assert.Equal(t, d.CreateImageName, c.ImageName, "Incorrect image name passed to driver.")
|
||||
assert.Equal(t, d.CreateImageDesc, c.ImageDescription, "Incorrect image description passed to driver.")
|
||||
assert.Equal(t, d.CreateImageFamily, c.ImageFamily, "Incorrect image family passed to driver.")
|
||||
assert.Equal(t, d.CreateImageZone, c.Zone, "Incorrect image zone passed to driver.")
|
||||
assert.Equal(t, d.CreateImageDisk, c.DiskName, "Incorrect disk passed to driver.")
|
||||
assert.Equal(t, d.CreateImageLabels, c.ImageLabels, "Incorrect image_labels passed to driver.")
|
||||
assert.Equal(t, d.CreateImageLicenses, c.ImageLicenses, "Incorrect image_licenses passed to driver.")
|
||||
assert.Equal(t, d.CreateImageEncryptionKey, c.ImageEncryptionKey.ComputeType(), "Incorrect image_encryption_key passed to driver.")
|
||||
assert.Equal(t, d.CreateImageStorageLocations, c.ImageStorageLocations, "Incorrect image_storage_locations passed to driver.")
|
||||
}
|
||||
|
||||
func TestStepCreateImage_errorOnChannel(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateImage)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
errCh <- errors.New("error")
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.CreateImageErrCh = errCh
|
||||
|
||||
// run the step
|
||||
action := step.Run(context.Background(), state)
|
||||
assert.Equal(t, action, multistep.ActionHalt, "Step should not have passed.")
|
||||
_, ok := state.GetOk("error")
|
||||
assert.True(t, ok, "State should have an error.")
|
||||
_, ok = state.GetOk("image_name")
|
||||
assert.False(t, ok, "State should not have a resulting image.")
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStepCreateInstance_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepCreateInstance)
|
||||
}
|
||||
|
||||
func TestStepCreateInstance(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
c := state.Get("config").(*Config)
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||
|
||||
// run the step
|
||||
assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.")
|
||||
|
||||
// Verify state
|
||||
nameRaw, ok := state.GetOk("instance_name")
|
||||
assert.True(t, ok, "State should have an instance name.")
|
||||
|
||||
// cleanup
|
||||
step.Cleanup(state)
|
||||
|
||||
// Check args passed to the driver.
|
||||
assert.Equal(t, d.DeleteInstanceName, nameRaw.(string), "Incorrect instance name passed to driver.")
|
||||
assert.Equal(t, d.DeleteInstanceZone, c.Zone, "Incorrect instance zone passed to driver.")
|
||||
assert.Equal(t, d.DeleteDiskName, c.InstanceName, "Incorrect disk name passed to driver.")
|
||||
assert.Equal(t, d.DeleteDiskZone, c.Zone, "Incorrect disk zone passed to driver.")
|
||||
}
|
||||
|
||||
func TestStepCreateInstance_fromFamily(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Family string
|
||||
Expect bool
|
||||
}{
|
||||
{"test-image", "", false},
|
||||
{"test-image", "test-family", false}, // name trumps family
|
||||
{"", "test-family", true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
c := state.Get("config").(*Config)
|
||||
c.SourceImage = tc.Name
|
||||
c.SourceImageFamily = tc.Family
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||
|
||||
// run the step
|
||||
assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.")
|
||||
|
||||
// cleanup
|
||||
step.Cleanup(state)
|
||||
|
||||
// Check args passed to the driver.
|
||||
if tc.Expect {
|
||||
assert.True(t, d.GetImageFromFamily, "Driver wasn't instructed to use an image family")
|
||||
} else {
|
||||
assert.False(t, d.GetImageFromFamily, "Driver was unexpectedly instructed to use an image family")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateInstance_windowsNeedsPassword(t *testing.T) {
|
||||
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
c := state.Get("config").(*Config)
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
d.GetImageResult = StubImage("test-image", "test-project", []string{"windows"}, 100)
|
||||
c.Comm.Type = "winrm"
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
nameRaw, ok := state.GetOk("instance_name")
|
||||
if !ok {
|
||||
t.Fatal("should have instance name")
|
||||
}
|
||||
|
||||
createPassword, ok := state.GetOk("create_windows_password")
|
||||
|
||||
if !ok || !createPassword.(bool) {
|
||||
t.Fatal("should need to create a windows password")
|
||||
}
|
||||
|
||||
// cleanup
|
||||
step.Cleanup(state)
|
||||
|
||||
if d.DeleteInstanceName != nameRaw.(string) {
|
||||
t.Fatal("should've deleted instance")
|
||||
}
|
||||
if d.DeleteInstanceZone != c.Zone {
|
||||
t.Fatalf("bad instance zone: %#v", d.DeleteInstanceZone)
|
||||
}
|
||||
|
||||
if d.DeleteDiskName != c.InstanceName {
|
||||
t.Fatal("should've deleted disk")
|
||||
}
|
||||
if d.DeleteDiskZone != c.Zone {
|
||||
t.Fatalf("bad disk zone: %#v", d.DeleteDiskZone)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateInstance_windowsPasswordSet(t *testing.T) {
|
||||
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.GetImageResult = StubImage("test-image", "test-project", []string{"windows"}, 100)
|
||||
config.Comm.Type = "winrm"
|
||||
config.Comm.WinRMPassword = "password"
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
nameRaw, ok := state.GetOk("instance_name")
|
||||
if !ok {
|
||||
t.Fatal("should have instance name")
|
||||
}
|
||||
|
||||
_, ok = state.GetOk("create_windows_password")
|
||||
|
||||
if ok {
|
||||
t.Fatal("should not need to create windows password")
|
||||
}
|
||||
|
||||
// cleanup
|
||||
step.Cleanup(state)
|
||||
|
||||
if driver.DeleteInstanceName != nameRaw.(string) {
|
||||
t.Fatal("should've deleted instance")
|
||||
}
|
||||
if driver.DeleteInstanceZone != config.Zone {
|
||||
t.Fatalf("bad instance zone: %#v", driver.DeleteInstanceZone)
|
||||
}
|
||||
|
||||
if driver.DeleteDiskName != config.InstanceName {
|
||||
t.Fatal("should've deleted disk")
|
||||
}
|
||||
if driver.DeleteDiskZone != config.Zone {
|
||||
t.Fatalf("bad disk zone: %#v", driver.DeleteDiskZone)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateInstance_error(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
d.RunInstanceErr = errors.New("error")
|
||||
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||
|
||||
// run the step
|
||||
assert.Equal(t, step.Run(context.Background(), state), multistep.ActionHalt, "Step should have failed and halted.")
|
||||
|
||||
// Verify state
|
||||
_, ok := state.GetOk("error")
|
||||
assert.True(t, ok, "State should have an error.")
|
||||
_, ok = state.GetOk("instance_name")
|
||||
assert.False(t, ok, "State should not have an instance name.")
|
||||
}
|
||||
|
||||
func TestStepCreateInstance_errorOnChannel(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
errCh <- errors.New("error")
|
||||
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
d.RunInstanceErrCh = errCh
|
||||
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||
|
||||
// run the step
|
||||
assert.Equal(t, step.Run(context.Background(), state), multistep.ActionHalt, "Step should have failed and halted.")
|
||||
|
||||
// Verify state
|
||||
_, ok := state.GetOk("error")
|
||||
assert.True(t, ok, "State should have an error.")
|
||||
_, ok = state.GetOk("instance_name")
|
||||
assert.False(t, ok, "State should not have an instance name.")
|
||||
}
|
||||
|
||||
func TestStepCreateInstance_errorTimeout(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.StateTimeout = 1 * time.Millisecond
|
||||
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
d.RunInstanceErrCh = errCh
|
||||
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||
|
||||
// run the step
|
||||
assert.Equal(t, step.Run(context.Background(), state), multistep.ActionHalt, "Step should have failed and halted.")
|
||||
|
||||
// Verify state
|
||||
_, ok := state.GetOk("error")
|
||||
assert.True(t, ok, "State should have an error.")
|
||||
_, ok = state.GetOk("instance_name")
|
||||
assert.False(t, ok, "State should not have an instance name.")
|
||||
}
|
||||
|
||||
func TestStepCreateInstance_noServiceAccount(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
c := state.Get("config").(*Config)
|
||||
c.DisableDefaultServiceAccount = true
|
||||
c.ServiceAccountEmail = ""
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||
|
||||
// run the step
|
||||
assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.")
|
||||
|
||||
// cleanup
|
||||
step.Cleanup(state)
|
||||
|
||||
// Check args passed to the driver.
|
||||
assert.Equal(t, d.RunInstanceConfig.DisableDefaultServiceAccount, c.DisableDefaultServiceAccount, "Incorrect value for DisableDefaultServiceAccount passed to driver.")
|
||||
assert.Equal(t, d.RunInstanceConfig.ServiceAccountEmail, c.ServiceAccountEmail, "Incorrect value for ServiceAccountEmail passed to driver.")
|
||||
}
|
||||
|
||||
func TestStepCreateInstance_customServiceAccount(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
c := state.Get("config").(*Config)
|
||||
c.DisableDefaultServiceAccount = true
|
||||
c.ServiceAccountEmail = "custom-service-account"
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||
|
||||
// run the step
|
||||
assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.")
|
||||
|
||||
// cleanup
|
||||
step.Cleanup(state)
|
||||
|
||||
// Check args passed to the driver.
|
||||
assert.Equal(t, d.RunInstanceConfig.DisableDefaultServiceAccount, c.DisableDefaultServiceAccount, "Incorrect value for DisableDefaultServiceAccount passed to driver.")
|
||||
assert.Equal(t, d.RunInstanceConfig.ServiceAccountEmail, c.ServiceAccountEmail, "Incorrect value for ServiceAccountEmail passed to driver.")
|
||||
}
|
||||
|
||||
func TestCreateInstanceMetadata(t *testing.T) {
|
||||
state := testState(t)
|
||||
c := state.Get("config").(*Config)
|
||||
image := StubImage("test-image", "test-project", []string{}, 100)
|
||||
key := "abcdefgh12345678"
|
||||
|
||||
// create our metadata
|
||||
_, metadataSSHKeys, err := c.createInstanceMetadata(image, key)
|
||||
|
||||
assert.True(t, err == nil, "Metadata creation should have succeeded.")
|
||||
|
||||
// ensure our key is listed
|
||||
assert.True(t, strings.Contains(metadataSSHKeys["ssh-keys"], key), "Instance metadata should contain provided key")
|
||||
}
|
||||
|
||||
func TestCreateInstanceMetadata_noPublicKey(t *testing.T) {
|
||||
state := testState(t)
|
||||
c := state.Get("config").(*Config)
|
||||
image := StubImage("test-image", "test-project", []string{}, 100)
|
||||
sshKeys := c.Metadata["ssh-keys"]
|
||||
|
||||
// create our metadata
|
||||
_, metadataSSHKeys, err := c.createInstanceMetadata(image, "")
|
||||
|
||||
assert.True(t, err == nil, "Metadata creation should have succeeded.")
|
||||
|
||||
// ensure the ssh metadata hasn't changed
|
||||
assert.Equal(t, metadataSSHKeys["ssh-keys"], sshKeys, "Instance metadata should not have been modified")
|
||||
}
|
||||
|
||||
func TestCreateInstanceMetadata_metadataFile(t *testing.T) {
|
||||
state := testState(t)
|
||||
c := state.Get("config").(*Config)
|
||||
image := StubImage("test-image", "test-project", []string{}, 100)
|
||||
content := testMetadataFileContent
|
||||
fileName := testMetadataFile(t)
|
||||
c.MetadataFiles["user-data"] = fileName
|
||||
|
||||
// create our metadata
|
||||
metadataNoSSHKeys, _, err := c.createInstanceMetadata(image, "")
|
||||
|
||||
assert.True(t, err == nil, "Metadata creation should have succeeded.")
|
||||
|
||||
// ensure the user-data key in metadata is updated with file content
|
||||
assert.Equal(t, metadataNoSSHKeys["user-data"], content, "user-data field of the instance metadata should have been updated.")
|
||||
}
|
||||
|
||||
func TestCreateInstanceMetadata_withWrapStartupScript(t *testing.T) {
|
||||
tt := []struct {
|
||||
WrapStartupScript config.Trilean
|
||||
StartupScriptContents string
|
||||
WrappedStartupScriptContents string
|
||||
WrappedStartupScriptStatus string
|
||||
}{
|
||||
{
|
||||
WrapStartupScript: config.TriUnset,
|
||||
StartupScriptContents: testMetadataFileContent,
|
||||
},
|
||||
{
|
||||
WrapStartupScript: config.TriFalse,
|
||||
StartupScriptContents: testMetadataFileContent,
|
||||
},
|
||||
{
|
||||
WrapStartupScript: config.TriTrue,
|
||||
StartupScriptContents: StartupScriptLinux,
|
||||
WrappedStartupScriptContents: testMetadataFileContent,
|
||||
WrappedStartupScriptStatus: StartupScriptStatusNotDone,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
state := testState(t)
|
||||
image := StubImage("test-image", "test-project", []string{}, 100)
|
||||
c := state.Get("config").(*Config)
|
||||
c.StartupScriptFile = testMetadataFile(t)
|
||||
c.WrapStartupScriptFile = tc.WrapStartupScript
|
||||
|
||||
// create our metadata
|
||||
metadataNoSSHKeys, _, err := c.createInstanceMetadata(image, "")
|
||||
|
||||
assert.True(t, err == nil, "Metadata creation should have succeeded.")
|
||||
assert.Equal(t, tc.StartupScriptContents, metadataNoSSHKeys[StartupScriptKey], fmt.Sprintf("Instance metadata for startup script should be %q.", tc.StartupScriptContents))
|
||||
assert.Equal(t, tc.WrappedStartupScriptContents, metadataNoSSHKeys[StartupWrappedScriptKey], fmt.Sprintf("Instance metadata for wrapped startup script should be %q.", tc.WrappedStartupScriptContents))
|
||||
assert.Equal(t, tc.WrappedStartupScriptStatus, metadataNoSSHKeys[StartupScriptStatusKey], fmt.Sprintf("Instance metadata startup script status should be %q.", tc.WrappedStartupScriptStatus))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateInstanceMetadataWaitToAddSSHKeys(t *testing.T) {
|
||||
state := testState(t)
|
||||
c := state.Get("config").(*Config)
|
||||
image := StubImage("test-image", "test-project", []string{}, 100)
|
||||
key := "abcdefgh12345678"
|
||||
|
||||
var waitTime int = 4
|
||||
c.WaitToAddSSHKeys = time.Duration(waitTime) * time.Second
|
||||
c.Metadata = map[string]string{
|
||||
"metadatakey1": "xyz",
|
||||
"metadatakey2": "123",
|
||||
}
|
||||
|
||||
// create our metadata
|
||||
metadataNoSSHKeys, metadataSSHKeys, err := c.createInstanceMetadata(image, key)
|
||||
|
||||
assert.True(t, err == nil, "Metadata creation should have succeeded.")
|
||||
|
||||
// ensure our metadata is listed
|
||||
assert.True(t, strings.Contains(metadataSSHKeys["ssh-keys"], key), "Instance metadata should contain provided SSH key")
|
||||
assert.True(t, strings.Contains(metadataNoSSHKeys["metadatakey1"], "xyz"), "Instance metadata should contain provided key: metadatakey1")
|
||||
assert.True(t, strings.Contains(metadataNoSSHKeys["metadatakey2"], "123"), "Instance metadata should contain provided key: metadatakey2")
|
||||
}
|
||||
|
||||
func TestStepCreateInstanceWaitToAddSSHKeys(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
c := state.Get("config").(*Config)
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||
|
||||
key := "abcdefgh12345678"
|
||||
|
||||
var waitTime int = 5
|
||||
c.WaitToAddSSHKeys = time.Duration(waitTime) * time.Second
|
||||
c.Comm.SSHPublicKey = []byte(key)
|
||||
|
||||
c.Metadata = map[string]string{
|
||||
"metadatakey1": "xyz",
|
||||
"metadatakey2": "123",
|
||||
}
|
||||
|
||||
// run the step
|
||||
assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.")
|
||||
|
||||
// Verify state
|
||||
_, ok := state.GetOk("instance_name")
|
||||
assert.True(t, ok, "State should have an instance name.")
|
||||
|
||||
// cleanup
|
||||
step.Cleanup(state)
|
||||
}
|
||||
|
||||
func TestStepCreateInstanceNoWaitToAddSSHKeys(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
c := state.Get("config").(*Config)
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
d.GetImageResult = StubImage("test-image", "test-project", []string{}, 100)
|
||||
|
||||
key := "abcdefgh12345678"
|
||||
|
||||
c.Comm.SSHPublicKey = []byte(key)
|
||||
|
||||
c.Metadata = map[string]string{
|
||||
"metadatakey1": "xyz",
|
||||
"metadatakey2": "123",
|
||||
}
|
||||
|
||||
// run the step
|
||||
assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.")
|
||||
|
||||
// Verify state
|
||||
_, ok := state.GetOk("instance_name")
|
||||
assert.True(t, ok, "State should have an instance name.")
|
||||
|
||||
// cleanup
|
||||
step.Cleanup(state)
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStepCreateOrResetWindowsPassword(t *testing.T) {
|
||||
state := testState(t)
|
||||
|
||||
// Step is run after the instance is created so we will have an instance name set
|
||||
state.Put("instance_name", "mock_instance")
|
||||
state.Put("create_windows_password", true)
|
||||
|
||||
step := new(StepCreateWindowsPassword)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if password, ok := state.GetOk("winrm_password"); !ok || password.(string) != "MOCK_PASSWORD" {
|
||||
t.Fatal("should have a password", password, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateOrResetWindowsPassword_passwordSet(t *testing.T) {
|
||||
state := testState(t)
|
||||
|
||||
// Step is run after the instance is created so we will have an instance name set
|
||||
state.Put("instance_name", "mock_instance")
|
||||
|
||||
c := state.Get("config").(*Config)
|
||||
|
||||
c.Comm.WinRMPassword = "password"
|
||||
|
||||
step := new(StepCreateWindowsPassword)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if password, ok := state.GetOk("winrm_password"); !ok || password.(string) != "password" {
|
||||
t.Fatal("should have used existing password", password, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateOrResetWindowsPassword_dontNeedPassword(t *testing.T) {
|
||||
state := testState(t)
|
||||
|
||||
// Step is run after the instance is created so we will have an instance name set
|
||||
state.Put("instance_name", "mock_instance")
|
||||
|
||||
step := new(StepCreateWindowsPassword)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStepCreateOrResetWindowsPassword_debug(t *testing.T) {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
tf.Close()
|
||||
|
||||
state := testState(t)
|
||||
// Step is run after the instance is created so we will have an instance name set
|
||||
state.Put("instance_name", "mock_instance")
|
||||
state.Put("create_windows_password", true)
|
||||
|
||||
step := new(StepCreateWindowsPassword)
|
||||
|
||||
step.Debug = true
|
||||
step.DebugKeyPath = tf.Name()
|
||||
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if password, ok := state.GetOk("winrm_password"); !ok || password.(string) != "MOCK_PASSWORD" {
|
||||
t.Fatal("should have a password", password, ok)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(tf.Name()); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateOrResetWindowsPassword_error(t *testing.T) {
|
||||
state := testState(t)
|
||||
|
||||
// Step is run after the instance is created so we will have an instance name set
|
||||
state.Put("instance_name", "mock_instance")
|
||||
state.Put("create_windows_password", true)
|
||||
|
||||
step := new(StepCreateWindowsPassword)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.CreateOrResetWindowsPasswordErr = errors.New("error")
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("winrm_password"); ok {
|
||||
t.Fatal("should NOT have instance name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateOrResetWindowsPassword_errorOnChannel(t *testing.T) {
|
||||
state := testState(t)
|
||||
|
||||
// Step is run after the instance is created so we will have an instance name set
|
||||
state.Put("instance_name", "mock_instance")
|
||||
state.Put("create_windows_password", true)
|
||||
|
||||
step := new(StepCreateWindowsPassword)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
errCh <- errors.New("error")
|
||||
|
||||
driver.CreateOrResetWindowsPasswordErrCh = errCh
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if _, ok := state.GetOk("winrm_password"); ok {
|
||||
t.Fatal("should NOT have instance name")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"google.golang.org/api/oauth2/v2"
|
||||
)
|
||||
|
||||
func TestStepImportOSLoginSSHKey_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepImportOSLoginSSHKey)
|
||||
}
|
||||
|
||||
func TestStepImportOSLoginSSHKey(t *testing.T) {
|
||||
tt := []struct {
|
||||
Name string
|
||||
UseOSLogin bool
|
||||
ExpectedEmail string
|
||||
ExpectedAction multistep.StepAction
|
||||
PubKeyExpected bool
|
||||
}{
|
||||
{
|
||||
Name: "UseOSLoginDisabled",
|
||||
ExpectedAction: multistep.ActionContinue,
|
||||
},
|
||||
{
|
||||
Name: "UseOSLoginWithAccountFile",
|
||||
UseOSLogin: true,
|
||||
ExpectedAction: multistep.ActionContinue,
|
||||
ExpectedEmail: "raffi-compute@developer.gserviceaccount.com",
|
||||
PubKeyExpected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
state := testState(t)
|
||||
step := new(StepImportOSLoginSSHKey)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.UseOSLogin = tc.UseOSLogin
|
||||
|
||||
if tc.PubKeyExpected {
|
||||
config.Comm.SSHPublicKey = []byte{'k', 'e', 'y'}
|
||||
}
|
||||
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if step.accountEmail != tc.ExpectedEmail {
|
||||
t.Fatalf("expected accountEmail to be %q but got %q", tc.ExpectedEmail, step.accountEmail)
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("ssh_key_public_sha256"); !ok && tc.PubKeyExpected {
|
||||
t.Fatal("expected to see a public key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepImportOSLoginSSHKey_withAccountFile(t *testing.T) {
|
||||
// default teststate contains an account file
|
||||
state := testState(t)
|
||||
step := new(StepImportOSLoginSSHKey)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.UseOSLogin = true
|
||||
config.Comm.SSHPublicKey = []byte{'k', 'e', 'y'}
|
||||
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
fakeAccountEmail := "raffi-compute@developer.gserviceaccount.com"
|
||||
if step.accountEmail != fakeAccountEmail {
|
||||
t.Fatalf("expected accountEmail to be %q but got %q", fakeAccountEmail, step.accountEmail)
|
||||
}
|
||||
|
||||
pubKey, ok := state.GetOk("ssh_key_public_sha256")
|
||||
if !ok {
|
||||
t.Fatal("expected to see a public key")
|
||||
}
|
||||
|
||||
sha256sum := sha256.Sum256(config.Comm.SSHPublicKey)
|
||||
if pubKey != hex.EncodeToString(sha256sum[:]) {
|
||||
t.Errorf("expected to see a matching public key, but got %q", pubKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepImportOSLoginSSHKey_withNoAccountFile(t *testing.T) {
|
||||
state := testState(t)
|
||||
fakeAccountEmail := "testing@packer.io"
|
||||
step := &StepImportOSLoginSSHKey{
|
||||
TokeninfoFunc: func(ctx context.Context) (*oauth2.Tokeninfo, error) {
|
||||
return &oauth2.Tokeninfo{Email: fakeAccountEmail}, nil
|
||||
},
|
||||
}
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.account = nil
|
||||
config.UseOSLogin = true
|
||||
config.Comm.SSHPublicKey = []byte{'k', 'e', 'y'}
|
||||
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if step.accountEmail != fakeAccountEmail {
|
||||
t.Fatalf("expected accountEmail to be %q but got %q", fakeAccountEmail, step.accountEmail)
|
||||
}
|
||||
|
||||
pubKey, ok := state.GetOk("ssh_key_public_sha256")
|
||||
if !ok {
|
||||
t.Fatal("expected to see a public key")
|
||||
}
|
||||
|
||||
sha256sum := sha256.Sum256(config.Comm.SSHPublicKey)
|
||||
if pubKey != hex.EncodeToString(sha256sum[:]) {
|
||||
t.Errorf("expected to see a matching public key, but got %q", pubKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepImportOSLoginSSHKey_withPrivateSSHKey(t *testing.T) {
|
||||
// default teststate contains an account file
|
||||
state := testState(t)
|
||||
step := new(StepImportOSLoginSSHKey)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.UseOSLogin = true
|
||||
config.Comm.SSHPrivateKey = []byte{'k', 'e', 'y'}
|
||||
config.Comm.SSHPublicKey = nil
|
||||
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if step.accountEmail != "" {
|
||||
t.Fatalf("expected accountEmail to be unset but got %q", step.accountEmail)
|
||||
}
|
||||
|
||||
pubKey, ok := state.GetOk("ssh_key_public_sha256")
|
||||
if ok {
|
||||
t.Errorf("expected to not see a public key when using a dedicated private key, but got %q", pubKey)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepInstanceInfo_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepInstanceInfo)
|
||||
}
|
||||
|
||||
func TestStepInstanceInfo(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepInstanceInfo)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("instance_name", "foo")
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.GetNatIPResult = "1.2.3.4"
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if driver.WaitForInstanceState != "RUNNING" {
|
||||
t.Fatalf("bad: %#v", driver.WaitForInstanceState)
|
||||
}
|
||||
if driver.WaitForInstanceZone != config.Zone {
|
||||
t.Fatalf("bad: %#v", driver.WaitForInstanceZone)
|
||||
}
|
||||
if driver.WaitForInstanceName != "foo" {
|
||||
t.Fatalf("bad: %#v", driver.WaitForInstanceName)
|
||||
}
|
||||
|
||||
ipRaw, ok := state.GetOk("instance_ip")
|
||||
if !ok {
|
||||
t.Fatal("should have ip")
|
||||
}
|
||||
if ip, ok := ipRaw.(string); !ok {
|
||||
t.Fatal("ip is not a string")
|
||||
} else if ip != "1.2.3.4" {
|
||||
t.Fatalf("bad ip: %s", ip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepInstanceInfo_InternalIP(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepInstanceInfo)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("instance_name", "foo")
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.UseInternalIP = true
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.GetNatIPResult = "1.2.3.4"
|
||||
driver.GetInternalIPResult = "5.6.7.8"
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if driver.WaitForInstanceState != "RUNNING" {
|
||||
t.Fatalf("bad: %#v", driver.WaitForInstanceState)
|
||||
}
|
||||
if driver.WaitForInstanceZone != config.Zone {
|
||||
t.Fatalf("bad: %#v", driver.WaitForInstanceZone)
|
||||
}
|
||||
if driver.WaitForInstanceName != "foo" {
|
||||
t.Fatalf("bad: %#v", driver.WaitForInstanceName)
|
||||
}
|
||||
|
||||
ipRaw, ok := state.GetOk("instance_ip")
|
||||
if !ok {
|
||||
t.Fatal("should have ip")
|
||||
}
|
||||
if ip, ok := ipRaw.(string); !ok {
|
||||
t.Fatal("ip is not a string")
|
||||
} else if ip != "5.6.7.8" {
|
||||
t.Fatalf("bad ip: %s", ip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepInstanceInfo_getNatIPError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepInstanceInfo)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("instance_name", "foo")
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.GetNatIPErr = errors.New("error")
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if _, ok := state.GetOk("instance_ip"); ok {
|
||||
t.Fatal("should NOT have instance IP")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepInstanceInfo_waitError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepInstanceInfo)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("instance_name", "foo")
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
errCh <- errors.New("error")
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.WaitForInstanceErrCh = errCh
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if _, ok := state.GetOk("instance_ip"); ok {
|
||||
t.Fatal("should NOT have instance IP")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepInstanceInfo_errorTimeout(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepInstanceInfo)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
<-time.After(50 * time.Millisecond)
|
||||
errCh <- nil
|
||||
}()
|
||||
|
||||
state.Put("instance_name", "foo")
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.StateTimeout = 1 * time.Millisecond
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.WaitForInstanceErrCh = errCh
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if _, ok := state.GetOk("instance_ip"); ok {
|
||||
t.Fatal("should NOT have instance IP")
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type IAPConfig
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type IAPConfig
|
||||
|
||||
package googlecompute
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type IAPConfig"; DO NOT EDIT.
|
||||
|
||||
package googlecompute
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||
)
|
||||
|
||||
type MockTunnelDriver struct {
|
||||
StopTunnelCalled bool
|
||||
StartTunnelCalled bool
|
||||
StartTunnelTimeout int
|
||||
}
|
||||
|
||||
func (m *MockTunnelDriver) StopTunnel() {
|
||||
m.StopTunnelCalled = true
|
||||
}
|
||||
|
||||
func (m *MockTunnelDriver) StartTunnel(_ context.Context, _ string, timeout int) error {
|
||||
m.StartTunnelCalled = true
|
||||
m.StartTunnelTimeout = timeout
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTestStepStartTunnel() *StepStartTunnel {
|
||||
return &StepStartTunnel{
|
||||
IAPConf: &IAPConfig{
|
||||
IAP: true,
|
||||
IAPLocalhostPort: 0,
|
||||
IAPHashBang: "/bin/bash",
|
||||
IAPExt: "",
|
||||
},
|
||||
CommConf: &communicator.Config{
|
||||
SSH: communicator.SSH{
|
||||
SSHPort: 1234,
|
||||
},
|
||||
},
|
||||
AccountFile: "/path/to/account_file.json",
|
||||
ProjectId: "fake-project-123",
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepStartTunnel_CreateTempScript(t *testing.T) {
|
||||
s := getTestStepStartTunnel()
|
||||
|
||||
args := []string{"compute", "start-iap-tunnel", "fakeinstance-12345",
|
||||
"1234", "--local-host-port=localhost:8774", "--zone", "us-central-b",
|
||||
"--project", "fake-project-123"}
|
||||
|
||||
scriptPath, err := s.createTempGcloudScript(args)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have error building script file.")
|
||||
}
|
||||
defer os.Remove(scriptPath)
|
||||
|
||||
f, err := ioutil.ReadFile(scriptPath)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't read created inventoryfile: %s", err)
|
||||
}
|
||||
|
||||
expected := `#!/bin/bash
|
||||
|
||||
gcloud auth activate-service-account --key-file='/path/to/account_file.json'
|
||||
gcloud compute start-iap-tunnel fakeinstance-12345 1234 --local-host-port=localhost:8774 --zone us-central-b --project fake-project-123
|
||||
`
|
||||
if runtime.GOOS == "windows" {
|
||||
// in real life you'd not be passing a HashBang here, but GIGO.
|
||||
expected = `#!/bin/bash
|
||||
|
||||
call gcloud auth activate-service-account --key-file "/path/to/account_file.json"
|
||||
call gcloud compute start-iap-tunnel fakeinstance-12345 1234 --local-host-port=localhost:8774 --zone us-central-b --project fake-project-123
|
||||
`
|
||||
}
|
||||
if fmt.Sprintf("%s", f) != expected {
|
||||
t.Fatalf("script didn't match expected:\n\n expected: \n%s\n; recieved: \n%s\n", expected, f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepStartTunnel_Cleanup(t *testing.T) {
|
||||
// Check IAP true
|
||||
s := getTestStepStartTunnel()
|
||||
td := &MockTunnelDriver{}
|
||||
s.tunnelDriver = td
|
||||
|
||||
state := testState(t)
|
||||
s.Cleanup(state)
|
||||
|
||||
if !td.StopTunnelCalled {
|
||||
t.Fatalf("Should have called StopTunnel, since IAP is true")
|
||||
}
|
||||
|
||||
// Check IAP false
|
||||
s = getTestStepStartTunnel()
|
||||
td = &MockTunnelDriver{}
|
||||
s.tunnelDriver = td
|
||||
|
||||
s.IAPConf.IAP = false
|
||||
|
||||
s.Cleanup(state)
|
||||
|
||||
if td.StopTunnelCalled {
|
||||
t.Fatalf("Should not have called StopTunnel, since IAP is false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepStartTunnel_ConfigurePort_port_set_by_user(t *testing.T) {
|
||||
s := getTestStepStartTunnel()
|
||||
s.IAPConf.IAPLocalhostPort = 8447
|
||||
|
||||
ctx := context.TODO()
|
||||
err := s.ConfigureLocalHostPort(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have error detecting port")
|
||||
}
|
||||
if s.IAPConf.IAPLocalhostPort != 8447 {
|
||||
t.Fatalf("Shouldn't have found new port; one was configured.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepStartTunnel_ConfigurePort_port_not_set_by_user(t *testing.T) {
|
||||
s := getTestStepStartTunnel()
|
||||
s.IAPConf.IAPLocalhostPort = 0
|
||||
|
||||
ctx := context.TODO()
|
||||
err := s.ConfigureLocalHostPort(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have error detecting port")
|
||||
}
|
||||
if s.IAPConf.IAPLocalhostPort == 0 {
|
||||
t.Fatalf("Should have found new port; none was configured.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepTeardownInstance_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepTeardownInstance)
|
||||
}
|
||||
|
||||
func TestStepTeardownInstance(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepTeardownInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if driver.DeleteInstanceName != config.InstanceName {
|
||||
t.Fatal("should've deleted instance")
|
||||
}
|
||||
if driver.DeleteInstanceZone != config.Zone {
|
||||
t.Fatalf("bad zone: %#v", driver.DeleteInstanceZone)
|
||||
}
|
||||
|
||||
// cleanup
|
||||
step.Cleanup(state)
|
||||
|
||||
if driver.DeleteDiskName != config.InstanceName {
|
||||
t.Fatal("should've deleted disk")
|
||||
}
|
||||
if driver.DeleteDiskZone != config.Zone {
|
||||
t.Fatalf("bad zone: %#v", driver.DeleteDiskZone)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func testState(t *testing.T) multistep.StateBag {
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", testConfigStruct(t))
|
||||
state.Put("driver", &DriverMock{})
|
||||
state.Put("hook", &packersdk.MockHook{})
|
||||
state.Put("ui", &packersdk.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
})
|
||||
return state
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStepWaitStartupScript(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepWaitStartupScript)
|
||||
c := state.Get("config").(*Config)
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
|
||||
testZone := "test-zone"
|
||||
testInstanceName := "test-instance-name"
|
||||
|
||||
c.Zone = testZone
|
||||
state.Put("instance_name", testInstanceName)
|
||||
|
||||
// This step stops when it gets Done back from the metadata.
|
||||
d.GetInstanceMetadataResult = StartupScriptStatusDone
|
||||
|
||||
// Run the step.
|
||||
assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have passed and continued.")
|
||||
|
||||
// Check that GetInstanceMetadata was called properly.
|
||||
assert.Equal(t, d.GetInstanceMetadataZone, testZone, "Incorrect zone passed to GetInstanceMetadata.")
|
||||
assert.Equal(t, d.GetInstanceMetadataName, testInstanceName, "Incorrect instance name passed to GetInstanceMetadata.")
|
||||
}
|
||||
|
||||
func TestStepWaitStartupScript_withWrapStartupScript(t *testing.T) {
|
||||
tt := []struct {
|
||||
WrapStartup config.Trilean
|
||||
Result, Zone, MetadataName string
|
||||
}{
|
||||
{WrapStartup: config.TriTrue, Result: StartupScriptStatusDone, Zone: "test-zone", MetadataName: "test-instance-name"},
|
||||
{WrapStartup: config.TriFalse},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
state := testState(t)
|
||||
step := new(StepWaitStartupScript)
|
||||
c := state.Get("config").(*Config)
|
||||
d := state.Get("driver").(*DriverMock)
|
||||
|
||||
c.StartupScriptFile = "startup.sh"
|
||||
c.WrapStartupScriptFile = tc.WrapStartup
|
||||
c.Zone = "test-zone"
|
||||
state.Put("instance_name", "test-instance-name")
|
||||
|
||||
// This step stops when it gets Done back from the metadata.
|
||||
d.GetInstanceMetadataResult = tc.Result
|
||||
|
||||
// Run the step.
|
||||
assert.Equal(t, step.Run(context.Background(), state), multistep.ActionContinue, "Step should have continued.")
|
||||
|
||||
assert.Equal(t, d.GetInstanceMetadataResult, tc.Result, "MetadataResult was not the expected value.")
|
||||
assert.Equal(t, d.GetInstanceMetadataZone, tc.Zone, "Zone was not the expected value.")
|
||||
assert.Equal(t, d.GetInstanceMetadataName, tc.MetadataName, "Instance name was not the expected value.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package googlecompute
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_templateCleanImageName(t *testing.T) {
|
||||
vals := []struct {
|
||||
origName string
|
||||
expected string
|
||||
}{
|
||||
// test that valid name is unchanged
|
||||
{
|
||||
origName: "abcde-012345xyz",
|
||||
expected: "abcde-012345xyz",
|
||||
},
|
||||
|
||||
//test that capital letters are converted to lowercase
|
||||
{
|
||||
origName: "ABCDE-012345xyz",
|
||||
expected: "abcde-012345xyz",
|
||||
},
|
||||
// test that periods and colons are converted to hyphens
|
||||
{
|
||||
origName: "abcde-012345v1.0:0",
|
||||
expected: "abcde-012345v1-0-0",
|
||||
},
|
||||
// Name starting with number is not valid, but not in scope of this
|
||||
// function to correct
|
||||
{
|
||||
origName: "012345v1.0:0",
|
||||
expected: "012345v1-0-0",
|
||||
},
|
||||
// Name over 64 chars is not valid, but not corrected by this function.
|
||||
{
|
||||
origName: "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
|
||||
expected: "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
|
||||
},
|
||||
}
|
||||
|
||||
for _, v := range vals {
|
||||
name := templateCleanImageName(v.origName)
|
||||
if name != v.expected {
|
||||
t.Fatalf("template names do not match: expected %s got %s\n", v.expected, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,imageFilter
|
||||
//go:generate mapstructure-to-hcl2 -type Config,imageFilter
|
||||
|
||||
package hcloud
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,imageFilter"; DO NOT EDIT.
|
||||
|
||||
package hcloud
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package hyperone
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package hyperone
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate struct-markdown
|
||||
|
||||
package common
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type OutputConfig
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type OutputConfig
|
||||
|
||||
package common
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type OutputConfig"; DO NOT EDIT.
|
||||
|
||||
package common
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package iso
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package iso
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package vmcx
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package vmcx
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package jdcloud
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package jdcloud
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package linode
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package linode
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package lxc
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package lxc
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package lxd
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package lxd
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
package ncloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server"
|
||||
"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"
|
||||
)
|
||||
|
||||
// Builder assume this implements packersdk.Builder
|
||||
type Builder struct {
|
||||
config Config
|
||||
stateBag multistep.StateBag
|
||||
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
|
||||
}
|
||||
|
||||
b.stateBag = new(multistep.BasicStateBag)
|
||||
|
||||
return nil, warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
||||
ui.Message("Creating Naver Cloud Platform Connection ...")
|
||||
config := Config{
|
||||
AccessKey: b.config.AccessKey,
|
||||
SecretKey: b.config.SecretKey,
|
||||
}
|
||||
|
||||
conn, err := config.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.stateBag.Put("hook", hook)
|
||||
b.stateBag.Put("ui", ui)
|
||||
|
||||
var steps []multistep.Step
|
||||
|
||||
steps = []multistep.Step{}
|
||||
|
||||
if b.config.Comm.Type == "ssh" {
|
||||
steps = []multistep.Step{
|
||||
NewStepValidateTemplate(conn, ui, &b.config),
|
||||
NewStepCreateLoginKey(conn, ui),
|
||||
NewStepCreateServerInstance(conn, ui, &b.config),
|
||||
NewStepCreateBlockStorageInstance(conn, ui, &b.config),
|
||||
NewStepGetRootPassword(conn, ui, &b.config),
|
||||
NewStepCreatePublicIPInstance(conn, ui, &b.config),
|
||||
&communicator.StepConnectSSH{
|
||||
Config: &b.config.Comm,
|
||||
Host: func(stateBag multistep.StateBag) (string, error) {
|
||||
return stateBag.Get("PublicIP").(string), nil
|
||||
},
|
||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
||||
},
|
||||
&commonsteps.StepProvision{},
|
||||
&commonsteps.StepCleanupTempKeys{
|
||||
Comm: &b.config.Comm,
|
||||
},
|
||||
NewStepStopServerInstance(conn, ui),
|
||||
NewStepCreateServerImage(conn, ui, &b.config),
|
||||
NewStepDeleteBlockStorageInstance(conn, ui, &b.config),
|
||||
NewStepTerminateServerInstance(conn, ui),
|
||||
}
|
||||
} else if b.config.Comm.Type == "winrm" {
|
||||
steps = []multistep.Step{
|
||||
NewStepValidateTemplate(conn, ui, &b.config),
|
||||
NewStepCreateLoginKey(conn, ui),
|
||||
NewStepCreateServerInstance(conn, ui, &b.config),
|
||||
NewStepCreateBlockStorageInstance(conn, ui, &b.config),
|
||||
NewStepGetRootPassword(conn, ui, &b.config),
|
||||
NewStepCreatePublicIPInstance(conn, ui, &b.config),
|
||||
&communicator.StepConnectWinRM{
|
||||
Config: &b.config.Comm,
|
||||
Host: func(stateBag multistep.StateBag) (string, error) {
|
||||
return stateBag.Get("PublicIP").(string), nil
|
||||
},
|
||||
WinRMConfig: func(state multistep.StateBag) (*communicator.WinRMConfig, error) {
|
||||
return &communicator.WinRMConfig{
|
||||
Username: b.config.Comm.WinRMUser,
|
||||
Password: b.config.Comm.WinRMPassword,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
&commonsteps.StepProvision{},
|
||||
NewStepStopServerInstance(conn, ui),
|
||||
NewStepCreateServerImage(conn, ui, &b.config),
|
||||
NewStepDeleteBlockStorageInstance(conn, ui, &b.config),
|
||||
NewStepTerminateServerInstance(conn, ui),
|
||||
}
|
||||
}
|
||||
|
||||
// Run!
|
||||
b.runner = commonsteps.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, b.stateBag)
|
||||
b.runner.Run(ctx, b.stateBag)
|
||||
|
||||
// If there was an error, return that
|
||||
if rawErr, ok := b.stateBag.GetOk("Error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
// Build the artifact and return it
|
||||
artifact := &Artifact{
|
||||
StateData: map[string]interface{}{"generated_data": b.stateBag.Get("generated_data")},
|
||||
}
|
||||
|
||||
if serverImage, ok := b.stateBag.GetOk("memberServerImage"); ok {
|
||||
artifact.MemberServerImage = serverImage.(*server.MemberServerImage)
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
+24
-49
@@ -1,5 +1,5 @@
|
||||
//go:generate packer-sdc struct-markdown
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package ncloud
|
||||
|
||||
@@ -8,9 +8,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vpc"
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver"
|
||||
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/ncloud"
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server"
|
||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||
@@ -53,22 +50,12 @@ type Config struct {
|
||||
BlockStorageSize int `mapstructure:"block_storage_size" required:"false"`
|
||||
// Name of the region where you want to create an image.
|
||||
// (default: Korea)
|
||||
Region string `mapstructure:"region" required:"false"`
|
||||
RegionCode string `mapstructure:"region_code" required:"false"`
|
||||
// Deprecated
|
||||
AccessControlGroupConfigurationNo string `mapstructure:"access_control_group_configuration_no" required:"false"`
|
||||
Region string `mapstructure:"region" required:"false"`
|
||||
// This is used to allow
|
||||
// winrm access when you create a Windows server. An ACG that specifies an
|
||||
// access source (0.0.0.0/0) and allowed port (5985) must be created in
|
||||
// advance if you use CLASSIC env. If this field is left blank,
|
||||
// Packer will create temporary ACG for automatically in VPC environment.
|
||||
AccessControlGroupNo string `mapstructure:"access_control_group_no" required:"false"`
|
||||
SupportVPC bool `mapstructure:"support_vpc" required:"false"`
|
||||
// The ID of the Subnet where you want to place the Server Instance. If this field is left blank, Packer will try to get the Public Subnet ID from the `vpc_no`.
|
||||
SubnetNo string `mapstructure:"subnet_no" required:"false"`
|
||||
// The ID of the VPC where you want to place the Server Instance. If this field is left blank, Packer will try to get the VPC ID from the `subnet_no`.
|
||||
// (You are required to least one between two parameters if u want using VPC environment: `vpc_no` or `subnet_no`)
|
||||
VpcNo string `mapstructure:"vpc_no" required:"false"`
|
||||
// advance.
|
||||
AccessControlGroupConfigurationNo string `mapstructure:"access_control_group_configuration_no" required:"false"`
|
||||
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
ctx *interpolate.Context
|
||||
@@ -76,7 +63,7 @@ type Config struct {
|
||||
|
||||
// NewConfig checks parameters
|
||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
var warnings []string
|
||||
warnings := []string{}
|
||||
|
||||
err := config.Decode(c, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
@@ -94,67 +81,59 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
|
||||
if c.AccessKey == "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("`access_key` is required"))
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("access_key is required"))
|
||||
}
|
||||
|
||||
if c.SecretKey == "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("`secret_key` is required"))
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("secret_key is required"))
|
||||
}
|
||||
|
||||
if c.MemberServerImageNo == "" && c.ServerImageProductCode == "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("`server_image_product_code` or `member_server_image_no` is required"))
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("server_image_product_code or member_server_image_no is required"))
|
||||
}
|
||||
|
||||
if c.MemberServerImageNo != "" && c.ServerImageProductCode != "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("only one of `server_image_product_code` and `member_server_image_no` can be set"))
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("Only one of server_image_product_code and member_server_image_no can be set"))
|
||||
}
|
||||
|
||||
if c.ServerImageProductCode != "" && len(c.ServerImageProductCode) > 50 {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("if `server_image_product_code` field is set, length of `server_image_product_code` should be max 50"))
|
||||
if c.ServerImageProductCode != "" && len(c.ServerImageProductCode) > 20 {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("If server_image_product_code field is set, length of server_image_product_code should be max 20"))
|
||||
}
|
||||
|
||||
if c.ServerProductCode != "" && len(c.ServerProductCode) > 50 {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("if `server_product_code` field is set, length of `server_product_code` should be max 50"))
|
||||
if c.ServerProductCode != "" && len(c.ServerProductCode) > 20 {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("If server_product_code field is set, length of server_product_code should be max 20"))
|
||||
}
|
||||
|
||||
if c.ServerImageName != "" && (len(c.ServerImageName) < 3 || len(c.ServerImageName) > 30) {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("if `server_image_name` field is set, length of `server_image_name` should be min 3 and max 30"))
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("If server_image_name field is set, length of server_image_name should be min 3 and max 20"))
|
||||
}
|
||||
|
||||
if c.ServerImageDescription != "" && len(c.ServerImageDescription) > 1000 {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("if `server_image_description` field is set, length of `server_image_description` should be max 1000"))
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("If server_image_description field is set, length of server_image_description should be max 1000"))
|
||||
}
|
||||
|
||||
if c.BlockStorageSize != 0 {
|
||||
if c.BlockStorageSize < 10 || c.BlockStorageSize > 2000 {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("the size of `block_storage_size` is at least 10 GB and up to 2000GB"))
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("The size of BlockStorageSize is at least 10 GB and up to 2000GB"))
|
||||
} else if int(c.BlockStorageSize/10)*10 != c.BlockStorageSize {
|
||||
return nil, errors.New("BlockStorageSize must be a multiple of 10 GB")
|
||||
}
|
||||
}
|
||||
|
||||
if c.UserData != "" && c.UserDataFile != "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("only one of `user_data` or `user_data_file` can be specified."))
|
||||
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, fmt.Errorf("`user_data_file` not found: %s", c.UserDataFile))
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
|
||||
}
|
||||
}
|
||||
|
||||
if c.UserData != "" && len(c.UserData) > 21847 {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("if `user_data` field is set, length of UserData should be max 21847"))
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("If user_data field is set, length of UserData should be max 21847"))
|
||||
}
|
||||
|
||||
if c.AccessControlGroupConfigurationNo != "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("`access_control_group_configuration_no` is deprecated, please use `access_control_group_no` instead"))
|
||||
}
|
||||
|
||||
if c.VpcNo != "" || c.SubnetNo != "" {
|
||||
c.SupportVPC = true
|
||||
}
|
||||
|
||||
if c.Comm.Type == "winrm" && c.AccessControlGroupNo == "" && !c.SupportVPC {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("if Communicator is winrm, `access_control_group_no` (allow 5986 port) is required in `CLASSIC` environment"))
|
||||
if c.Comm.Type == "winrm" && c.AccessControlGroupConfigurationNo == "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("If Communicator is winrm, access_control_group_configuration_no is required"))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
@@ -165,9 +144,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
|
||||
type NcloudAPIClient struct {
|
||||
server *server.APIClient
|
||||
vserver *vserver.APIClient
|
||||
vpc *vpc.APIClient
|
||||
server *server.APIClient
|
||||
}
|
||||
|
||||
func (c *Config) Client() (*NcloudAPIClient, error) {
|
||||
@@ -176,8 +153,6 @@ func (c *Config) Client() (*NcloudAPIClient, error) {
|
||||
SecretKey: c.SecretKey,
|
||||
}
|
||||
return &NcloudAPIClient{
|
||||
server: server.NewAPIClient(server.NewConfiguration(apiKey)),
|
||||
vserver: vserver.NewAPIClient(vserver.NewConfiguration(apiKey)),
|
||||
vpc: vpc.NewAPIClient(vpc.NewConfiguration(apiKey)),
|
||||
server: server.NewAPIClient(server.NewConfiguration(apiKey)),
|
||||
}, nil
|
||||
}
|
||||
+1
-11
@@ -1,4 +1,4 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package ncloud
|
||||
|
||||
@@ -29,12 +29,7 @@ type FlatConfig struct {
|
||||
UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file" hcl:"user_data_file"`
|
||||
BlockStorageSize *int `mapstructure:"block_storage_size" required:"false" cty:"block_storage_size" hcl:"block_storage_size"`
|
||||
Region *string `mapstructure:"region" required:"false" cty:"region" hcl:"region"`
|
||||
RegionCode *string `mapstructure:"region_code" required:"false" cty:"region_code" hcl:"region_code"`
|
||||
AccessControlGroupConfigurationNo *string `mapstructure:"access_control_group_configuration_no" required:"false" cty:"access_control_group_configuration_no" hcl:"access_control_group_configuration_no"`
|
||||
AccessControlGroupNo *string `mapstructure:"access_control_group_no" required:"false" cty:"access_control_group_no" hcl:"access_control_group_no"`
|
||||
SupportVPC *bool `mapstructure:"support_vpc" required:"false" cty:"support_vpc" hcl:"support_vpc"`
|
||||
SubnetNo *string `mapstructure:"subnet_no" required:"false" cty:"subnet_no" hcl:"subnet_no"`
|
||||
VpcNo *string `mapstructure:"vpc_no" required:"false" cty:"vpc_no" hcl:"vpc_no"`
|
||||
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"`
|
||||
@@ -117,12 +112,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
|
||||
"block_storage_size": &hcldec.AttrSpec{Name: "block_storage_size", Type: cty.Number, Required: false},
|
||||
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
|
||||
"region_code": &hcldec.AttrSpec{Name: "region_code", Type: cty.String, Required: false},
|
||||
"access_control_group_configuration_no": &hcldec.AttrSpec{Name: "access_control_group_configuration_no", Type: cty.String, Required: false},
|
||||
"access_control_group_no": &hcldec.AttrSpec{Name: "access_control_group_no", Type: cty.String, Required: false},
|
||||
"support_vpc": &hcldec.AttrSpec{Name: "support_vpc", Type: cty.Bool, Required: false},
|
||||
"subnet_no": &hcldec.AttrSpec{Name: "subnet_no", Type: cty.String, Required: false},
|
||||
"vpc_no": &hcldec.AttrSpec{Name: "vpc_no", 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},
|
||||
@@ -0,0 +1,150 @@
|
||||
package ncloud
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"access_key": "access_key",
|
||||
"secret_key": "secret_key",
|
||||
"server_image_product_code": "SPSW0WINNT000016",
|
||||
"server_product_code": "SPSVRSSD00000011",
|
||||
"server_image_name": "packer-test {{timestamp}}",
|
||||
"server_image_description": "server description",
|
||||
"block_storage_size": 100,
|
||||
"user_data": "#!/bin/sh\nyum install -y httpd\ntouch /var/www/html/index.html\nchkconfig --level 2345 httpd on",
|
||||
"region": "Korea",
|
||||
"access_control_group_configuration_no": "33",
|
||||
"communicator": "ssh",
|
||||
"ssh_username": "root",
|
||||
}
|
||||
}
|
||||
|
||||
func testConfigForMemberServerImage() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"access_key": "access_key",
|
||||
"secret_key": "secret_key",
|
||||
"server_product_code": "SPSVRSSD00000011",
|
||||
"member_server_image_no": "2440",
|
||||
"server_image_name": "packer-test {{timestamp}}",
|
||||
"server_image_description": "server description",
|
||||
"block_storage_size": 100,
|
||||
"user_data": "#!/bin/sh\nyum install -y httpd\ntouch /var/www/html/index.html\nchkconfig --level 2345 httpd on",
|
||||
"region": "Korea",
|
||||
"access_control_group_configuration_no": "33",
|
||||
"communicator": "ssh",
|
||||
"ssh_username": "root",
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigWithServerImageProductCode(t *testing.T) {
|
||||
raw := testConfig()
|
||||
|
||||
var c Config
|
||||
c.Prepare(raw)
|
||||
|
||||
if c.AccessKey != "access_key" {
|
||||
t.Errorf("Expected 'access_key' to be set to '%s', but got '%s'.", raw["access_key"], c.AccessKey)
|
||||
}
|
||||
|
||||
if c.SecretKey != "secret_key" {
|
||||
t.Errorf("Expected 'secret_key' to be set to '%s', but got '%s'.", raw["secret_key"], c.SecretKey)
|
||||
}
|
||||
|
||||
if c.ServerImageProductCode != "SPSW0WINNT000016" {
|
||||
t.Errorf("Expected 'server_image_product_code' to be set to '%s', but got '%s'.", raw["server_image_product_code"], c.ServerImageProductCode)
|
||||
}
|
||||
|
||||
if c.ServerProductCode != "SPSVRSSD00000011" {
|
||||
t.Errorf("Expected 'server_product_code' to be set to '%s', but got '%s'.", raw["server_product_code"], c.ServerProductCode)
|
||||
}
|
||||
|
||||
if c.BlockStorageSize != 100 {
|
||||
t.Errorf("Expected 'block_storage_size' to be set to '%d', but got '%d'.", raw["block_storage_size"], c.BlockStorageSize)
|
||||
}
|
||||
|
||||
if c.ServerImageDescription != "server description" {
|
||||
t.Errorf("Expected 'server_image_description_key' to be set to '%s', but got '%s'.", raw["server_image_description"], c.ServerImageDescription)
|
||||
}
|
||||
|
||||
if c.Region != "Korea" {
|
||||
t.Errorf("Expected 'region' to be set to '%s', but got '%s'.", raw["server_image_description"], c.Region)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigWithMemberServerImageCode(t *testing.T) {
|
||||
raw := testConfigForMemberServerImage()
|
||||
|
||||
var c Config
|
||||
c.Prepare(raw)
|
||||
|
||||
if c.AccessKey != "access_key" {
|
||||
t.Errorf("Expected 'access_key' to be set to '%s', but got '%s'.", raw["access_key"], c.AccessKey)
|
||||
}
|
||||
|
||||
if c.SecretKey != "secret_key" {
|
||||
t.Errorf("Expected 'secret_key' to be set to '%s', but got '%s'.", raw["secret_key"], c.SecretKey)
|
||||
}
|
||||
|
||||
if c.MemberServerImageNo != "2440" {
|
||||
t.Errorf("Expected 'member_server_image_no' to be set to '%s', but got '%s'.", raw["member_server_image_no"], c.MemberServerImageNo)
|
||||
}
|
||||
|
||||
if c.ServerProductCode != "SPSVRSSD00000011" {
|
||||
t.Errorf("Expected 'server_product_code' to be set to '%s', but got '%s'.", raw["server_product_code"], c.ServerProductCode)
|
||||
}
|
||||
|
||||
if c.BlockStorageSize != 100 {
|
||||
t.Errorf("Expected 'block_storage_size' to be set to '%d', but got '%d'.", raw["block_storage_size"], c.BlockStorageSize)
|
||||
}
|
||||
|
||||
if c.ServerImageDescription != "server description" {
|
||||
t.Errorf("Expected 'server_image_description_key' to be set to '%s', but got '%s'.", raw["server_image_description"], c.ServerImageDescription)
|
||||
}
|
||||
|
||||
if c.Region != "Korea" {
|
||||
t.Errorf("Expected 'region' to be set to '%s', but got '%s'.", raw["server_image_description"], c.Region)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyConfig(t *testing.T) {
|
||||
raw := new(map[string]interface{})
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(raw)
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected Config to require 'access_key', 'secret_key' and some mandatory fields, but it did not")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "access_key is required") {
|
||||
t.Error("Expected Config to require 'access_key', but it did not")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "secret_key is required") {
|
||||
t.Error("Expected Config to require 'secret_key', but it did not")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "server_image_product_code or member_server_image_no is required") {
|
||||
t.Error("Expected Config to require 'server_image_product_code' or 'member_server_image_no', but it did not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExistsBothServerImageProductCodeAndMemberServerImageNoConfig(t *testing.T) {
|
||||
raw := map[string]interface{}{
|
||||
"access_key": "access_key",
|
||||
"secret_key": "secret_key",
|
||||
"server_image_product_code": "SPSW0WINNT000016",
|
||||
"server_product_code": "SPSVRSSD00000011",
|
||||
"member_server_image_no": "2440",
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(raw)
|
||||
|
||||
if !strings.Contains(err.Error(), "Only one of server_image_product_code and member_server_image_no can be set") {
|
||||
t.Error("Expected Config to require Only one of 'server_image_product_code' and 'member_server_image_no' can be set, but it did not")
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
func processStepResult(err error, sayError func(error), state multistep.StateBag) multistep.StepAction {
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
state.Put("Error", err)
|
||||
sayError(err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
@@ -0,0 +1,106 @@
|
||||
package ncloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/ncloud"
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
// StepCreateBlockStorageInstance struct is for making extra block storage
|
||||
type StepCreateBlockStorageInstance struct {
|
||||
Conn *NcloudAPIClient
|
||||
CreateBlockStorageInstance func(serverInstanceNo string) (*string, error)
|
||||
Say func(message string)
|
||||
Error func(e error)
|
||||
Config *Config
|
||||
}
|
||||
|
||||
// NewStepCreateBlockStorageInstance make StepCreateBlockStorage struct to make extra block storage
|
||||
func NewStepCreateBlockStorageInstance(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreateBlockStorageInstance {
|
||||
var step = &StepCreateBlockStorageInstance{
|
||||
Conn: conn,
|
||||
Say: func(message string) { ui.Say(message) },
|
||||
Error: func(e error) { ui.Error(e.Error()) },
|
||||
Config: config,
|
||||
}
|
||||
|
||||
step.CreateBlockStorageInstance = step.createBlockStorageInstance
|
||||
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepCreateBlockStorageInstance) createBlockStorageInstance(serverInstanceNo string) (*string, error) {
|
||||
|
||||
reqParams := new(server.CreateBlockStorageInstanceRequest)
|
||||
reqParams.BlockStorageSize = ncloud.Int64(int64(s.Config.BlockStorageSize))
|
||||
reqParams.ServerInstanceNo = &serverInstanceNo
|
||||
|
||||
resp, err := s.Conn.server.V2Api.CreateBlockStorageInstance(reqParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockStorageInstance := resp.BlockStorageInstanceList[0]
|
||||
log.Println("Block Storage Instance information : ", blockStorageInstance.BlockStorageInstanceNo)
|
||||
|
||||
if err := waiterBlockStorageInstanceStatus(s.Conn, blockStorageInstance.BlockStorageInstanceNo, "ATTAC", 10*time.Minute); err != nil {
|
||||
return nil, errors.New("TIMEOUT : Block Storage instance status is not attached")
|
||||
}
|
||||
|
||||
return blockStorageInstance.BlockStorageInstanceNo, nil
|
||||
}
|
||||
|
||||
func (s *StepCreateBlockStorageInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
if s.Config.BlockStorageSize == 0 {
|
||||
return processStepResult(nil, s.Error, state)
|
||||
}
|
||||
|
||||
s.Say("Create extra block storage instance")
|
||||
|
||||
serverInstanceNo := state.Get("InstanceNo").(string)
|
||||
|
||||
blockStorageInstanceNo, err := s.CreateBlockStorageInstance(serverInstanceNo)
|
||||
if err == nil {
|
||||
state.Put("BlockStorageInstanceNo", *blockStorageInstanceNo)
|
||||
}
|
||||
|
||||
return processStepResult(err, s.Error, state)
|
||||
}
|
||||
|
||||
func (s *StepCreateBlockStorageInstance) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
if !cancelled && !halted {
|
||||
return
|
||||
}
|
||||
|
||||
if s.Config.BlockStorageSize == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if blockStorageInstanceNo, ok := state.GetOk("BlockStorageInstanceNo"); ok {
|
||||
s.Say("Clean up Block Storage Instance")
|
||||
reqParams := server.DeleteBlockStorageInstancesRequest{
|
||||
BlockStorageInstanceNoList: []*string{blockStorageInstanceNo.(*string)},
|
||||
}
|
||||
blockStorageInstanceList, err := s.Conn.server.V2Api.DeleteBlockStorageInstances(&reqParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.Say(fmt.Sprintf("Block Storage Instance is deleted. Block Storage InstanceNo is %s", blockStorageInstanceNo.(string)))
|
||||
log.Println("Block Storage Instance information : ", blockStorageInstanceList.BlockStorageInstanceList[0])
|
||||
|
||||
if err := waiterBlockStorageInstanceStatus(s.Conn, blockStorageInstanceNo.(*string), "DETAC", time.Minute); err != nil {
|
||||
s.Say("TIMEOUT : Block Storage instance status is not deattached")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package ncloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepCreateBlockStorageInstanceShouldFailIfOperationCreateBlockStorageInstanceFails(t *testing.T) {
|
||||
|
||||
var testSubject = &StepCreateBlockStorageInstance{
|
||||
CreateBlockStorageInstance: func(serverInstanceNo string) (*string, error) { return nil, fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
Say: func(message string) {},
|
||||
Error: func(e error) {},
|
||||
Config: new(Config),
|
||||
}
|
||||
|
||||
testSubject.Config.BlockStorageSize = 10
|
||||
|
||||
stateBag := createTestStateBagStepCreateBlockStorageInstance()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk("Error"); ok == false {
|
||||
t.Fatal("Expected the step to set stateBag['Error'], but it was not.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateBlockStorageInstanceShouldPassIfOperationCreateBlockStorageInstancePasses(t *testing.T) {
|
||||
var instanceNo = "a"
|
||||
var testSubject = &StepCreateBlockStorageInstance{
|
||||
CreateBlockStorageInstance: func(serverInstanceNo string) (*string, error) { return &instanceNo, nil },
|
||||
Say: func(message string) {},
|
||||
Error: func(e error) {},
|
||||
Config: new(Config),
|
||||
}
|
||||
|
||||
testSubject.Config.BlockStorageSize = 10
|
||||
|
||||
stateBag := createTestStateBagStepCreateBlockStorageInstance()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk("Error"); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['Error'], but it was.")
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepCreateBlockStorageInstance() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put("InstanceNo", "a")
|
||||
|
||||
return stateBag
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package ncloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type LoginKey struct {
|
||||
KeyName string
|
||||
PrivateKey string
|
||||
}
|
||||
|
||||
type StepCreateLoginKey struct {
|
||||
Conn *NcloudAPIClient
|
||||
CreateLoginKey func() (*LoginKey, error)
|
||||
Say func(message string)
|
||||
Error func(e error)
|
||||
}
|
||||
|
||||
func NewStepCreateLoginKey(conn *NcloudAPIClient, ui packersdk.Ui) *StepCreateLoginKey {
|
||||
var step = &StepCreateLoginKey{
|
||||
Conn: conn,
|
||||
Say: func(message string) { ui.Say(message) },
|
||||
Error: func(e error) { ui.Error(e.Error()) },
|
||||
}
|
||||
|
||||
step.CreateLoginKey = step.createLoginKey
|
||||
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepCreateLoginKey) createLoginKey() (*LoginKey, error) {
|
||||
keyName := fmt.Sprintf("packer-%d", time.Now().Unix())
|
||||
reqParams := &server.CreateLoginKeyRequest{KeyName: &keyName}
|
||||
|
||||
privateKey, err := s.Conn.server.V2Api.CreateLoginKey(reqParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LoginKey{keyName, *privateKey.PrivateKey}, nil
|
||||
}
|
||||
|
||||
func (s *StepCreateLoginKey) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.Say("Create Login Key")
|
||||
|
||||
loginKey, err := s.CreateLoginKey()
|
||||
if err == nil {
|
||||
state.Put("LoginKey", loginKey)
|
||||
s.Say(fmt.Sprintf("Login Key[%s] is created", loginKey.KeyName))
|
||||
}
|
||||
|
||||
return processStepResult(err, s.Error, state)
|
||||
}
|
||||
|
||||
func (s *StepCreateLoginKey) Cleanup(state multistep.StateBag) {
|
||||
if loginKey, ok := state.GetOk("LoginKey"); ok {
|
||||
s.Say("Clean up login key")
|
||||
reqParams := &server.DeleteLoginKeyRequest{KeyName: &loginKey.(*LoginKey).KeyName}
|
||||
s.Conn.server.V2Api.DeleteLoginKey(reqParams)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package ncloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepCreateLoginKeyShouldFailIfOperationCreateLoginKeyFails(t *testing.T) {
|
||||
var testSubject = &StepCreateLoginKey{
|
||||
CreateLoginKey: func() (*LoginKey, error) { return nil, fmt.Errorf("!! Unit Test FAIL !!") },
|
||||
Say: func(message string) {},
|
||||
Error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateLoginKey()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk("Error"); ok == false {
|
||||
t.Fatal("Expected the step to set stateBag['Error'], but it was not.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateLoginKeyShouldPassIfOperationCreateLoginKeyPasses(t *testing.T) {
|
||||
var testSubject = &StepCreateLoginKey{
|
||||
CreateLoginKey: func() (*LoginKey, error) { return &LoginKey{"a", "b"}, nil },
|
||||
Say: func(message string) {},
|
||||
Error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateLoginKey()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk("Error"); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['Error'], but it was.")
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepCreateLoginKey() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
return stateBag
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package ncloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/ncloud"
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepCreatePublicIPInstance struct {
|
||||
Conn *NcloudAPIClient
|
||||
CreatePublicIPInstance func(serverInstanceNo string) (*server.PublicIpInstance, error)
|
||||
WaiterAssociatePublicIPToServerInstance func(serverInstanceNo string, publicIP string) error
|
||||
Say func(message string)
|
||||
Error func(e error)
|
||||
Config *Config
|
||||
}
|
||||
|
||||
func NewStepCreatePublicIPInstance(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreatePublicIPInstance {
|
||||
var step = &StepCreatePublicIPInstance{
|
||||
Conn: conn,
|
||||
Say: func(message string) { ui.Say(message) },
|
||||
Error: func(e error) { ui.Error(e.Error()) },
|
||||
Config: config,
|
||||
}
|
||||
|
||||
step.CreatePublicIPInstance = step.createPublicIPInstance
|
||||
step.WaiterAssociatePublicIPToServerInstance = step.waiterAssociatePublicIPToServerInstance
|
||||
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepCreatePublicIPInstance) waiterAssociatePublicIPToServerInstance(serverInstanceNo string, publicIP string) error {
|
||||
reqParams := new(server.GetServerInstanceListRequest)
|
||||
reqParams.ServerInstanceNoList = []*string{&serverInstanceNo}
|
||||
|
||||
c1 := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
serverInstanceList, err := s.Conn.server.V2Api.GetServerInstanceList(reqParams)
|
||||
|
||||
if err != nil {
|
||||
c1 <- err
|
||||
return
|
||||
}
|
||||
|
||||
if publicIP == *serverInstanceList.ServerInstanceList[0].PublicIp {
|
||||
c1 <- nil
|
||||
return
|
||||
}
|
||||
|
||||
s.Say("Wait to associate public ip serverInstance")
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case res := <-c1:
|
||||
return res
|
||||
case <-time.After(time.Second * 60):
|
||||
return fmt.Errorf("TIMEOUT : association public ip[%s] to server instance[%s] Failed", publicIP, serverInstanceNo)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepCreatePublicIPInstance) createPublicIPInstance(serverInstanceNo string) (*server.PublicIpInstance, error) {
|
||||
reqParams := new(server.CreatePublicIpInstanceRequest)
|
||||
reqParams.ServerInstanceNo = &serverInstanceNo
|
||||
|
||||
publicIPInstanceList, err := s.Conn.server.V2Api.CreatePublicIpInstance(reqParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicIPInstance := publicIPInstanceList.PublicIpInstanceList[0]
|
||||
publicIP := publicIPInstance.PublicIp
|
||||
s.Say(fmt.Sprintf("Public IP Instance [%s:%s] is created", *publicIPInstance.PublicIpInstanceNo, *publicIP))
|
||||
|
||||
err = s.waiterAssociatePublicIPToServerInstance(serverInstanceNo, *publicIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return publicIPInstance, nil
|
||||
}
|
||||
|
||||
func (s *StepCreatePublicIPInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.Say("Create Public IP Instance")
|
||||
|
||||
serverInstanceNo := state.Get("InstanceNo").(string)
|
||||
|
||||
publicIPInstance, err := s.CreatePublicIPInstance(serverInstanceNo)
|
||||
if err == nil {
|
||||
state.Put("PublicIP", *publicIPInstance.PublicIp)
|
||||
state.Put("PublicIPInstance", publicIPInstance)
|
||||
// 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", *publicIPInstance)
|
||||
}
|
||||
|
||||
return processStepResult(err, s.Error, state)
|
||||
}
|
||||
|
||||
func (s *StepCreatePublicIPInstance) Cleanup(state multistep.StateBag) {
|
||||
publicIPInstance, ok := state.GetOk("PublicIPInstance")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
s.Say("Clean up Public IP Instance")
|
||||
publicIPInstanceNo := publicIPInstance.(*server.PublicIpInstance).PublicIpInstanceNo
|
||||
s.waitPublicIPInstanceStatus(publicIPInstanceNo, "USED")
|
||||
|
||||
log.Println("Disassociate Public IP Instance ", publicIPInstanceNo)
|
||||
reqParams := &server.DisassociatePublicIpFromServerInstanceRequest{PublicIpInstanceNo: publicIPInstanceNo}
|
||||
s.Conn.server.V2Api.DisassociatePublicIpFromServerInstance(reqParams)
|
||||
|
||||
s.waitPublicIPInstanceStatus(publicIPInstanceNo, "CREAT")
|
||||
|
||||
reqDeleteParams := &server.DeletePublicIpInstancesRequest{
|
||||
PublicIpInstanceNoList: ncloud.StringList([]string{*publicIPInstanceNo}),
|
||||
}
|
||||
|
||||
log.Println("Delete Public IP Instance ", publicIPInstanceNo)
|
||||
s.Conn.server.V2Api.DeletePublicIpInstances(reqDeleteParams)
|
||||
}
|
||||
|
||||
func (s *StepCreatePublicIPInstance) waitPublicIPInstanceStatus(publicIPInstanceNo *string, status string) {
|
||||
c1 := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
reqParams := new(server.GetPublicIpInstanceListRequest)
|
||||
reqParams.PublicIpInstanceNoList = []*string{publicIPInstanceNo}
|
||||
|
||||
for {
|
||||
resp, err := s.Conn.server.V2Api.GetPublicIpInstanceList(reqParams)
|
||||
if err != nil {
|
||||
log.Printf(err.Error())
|
||||
c1 <- err
|
||||
return
|
||||
}
|
||||
|
||||
if *resp.TotalRows == 0 {
|
||||
c1 <- nil
|
||||
return
|
||||
}
|
||||
|
||||
instance := resp.PublicIpInstanceList[0]
|
||||
if *instance.PublicIpInstanceStatus.Code == status && *instance.PublicIpInstanceOperation.Code == "NULL" {
|
||||
c1 <- nil
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 2)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-c1:
|
||||
return
|
||||
case <-time.After(time.Second * 60):
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package ncloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/ncloud"
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepCreatePublicIPInstanceShouldFailIfOperationCreatePublicIPInstanceFails(t *testing.T) {
|
||||
var testSubject = &StepCreatePublicIPInstance{
|
||||
CreatePublicIPInstance: func(serverInstanceNo string) (*server.PublicIpInstance, error) {
|
||||
return nil, fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
Say: func(message string) {},
|
||||
Error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateServerImage()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk("Error"); ok == false {
|
||||
t.Fatal("Expected the step to set stateBag['Error'], but it was not.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreatePublicIPInstanceShouldPassIfOperationCreatePublicIPInstancePasses(t *testing.T) {
|
||||
c := new(Config)
|
||||
c.Comm.Prepare(nil)
|
||||
c.Comm.Type = "ssh"
|
||||
|
||||
var testSubject = &StepCreatePublicIPInstance{
|
||||
CreatePublicIPInstance: func(serverInstanceNo string) (*server.PublicIpInstance, error) {
|
||||
return &server.PublicIpInstance{PublicIpInstanceNo: ncloud.String("a"), PublicIp: ncloud.String("b")}, nil
|
||||
},
|
||||
Say: func(message string) {},
|
||||
Error: func(e error) {},
|
||||
Config: c,
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreatePublicIPInstance()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk("Error"); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['Error'], but it was.")
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepCreatePublicIPInstance() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put("InstanceNo", "a")
|
||||
|
||||
return stateBag
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package ncloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepCreateServerImage struct {
|
||||
Conn *NcloudAPIClient
|
||||
CreateServerImage func(serverInstanceNo string) (*server.MemberServerImage, error)
|
||||
Say func(message string)
|
||||
Error func(e error)
|
||||
Config *Config
|
||||
}
|
||||
|
||||
func NewStepCreateServerImage(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreateServerImage {
|
||||
var step = &StepCreateServerImage{
|
||||
Conn: conn,
|
||||
Say: func(message string) { ui.Say(message) },
|
||||
Error: func(e error) { ui.Error(e.Error()) },
|
||||
Config: config,
|
||||
}
|
||||
|
||||
step.CreateServerImage = step.createServerImage
|
||||
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepCreateServerImage) createServerImage(serverInstanceNo string) (*server.MemberServerImage, error) {
|
||||
// Can't create server image when status of server instance is stopping (not stopped)
|
||||
if err := waiterServerInstanceStatus(s.Conn, serverInstanceNo, "NSTOP", 1*time.Minute); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqParams := new(server.CreateMemberServerImageRequest)
|
||||
reqParams.MemberServerImageName = &s.Config.ServerImageName
|
||||
reqParams.MemberServerImageDescription = &s.Config.ServerImageDescription
|
||||
reqParams.ServerInstanceNo = &serverInstanceNo
|
||||
|
||||
memberServerImageList, err := s.Conn.server.V2Api.CreateMemberServerImage(reqParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverImage := memberServerImageList.MemberServerImageList[0]
|
||||
|
||||
s.Say(fmt.Sprintf("Server Image[%s:%s] is creating...", *serverImage.MemberServerImageName, *serverImage.MemberServerImageNo))
|
||||
|
||||
if err := waiterMemberServerImageStatus(s.Conn, *serverImage.MemberServerImageNo, "CREAT", 6*time.Hour); err != nil {
|
||||
return nil, errors.New("TIMEOUT : Server Image is not created")
|
||||
}
|
||||
|
||||
s.Say(fmt.Sprintf("Server Image[%s:%s] is created", *serverImage.MemberServerImageName, *serverImage.MemberServerImageNo))
|
||||
|
||||
return serverImage, nil
|
||||
}
|
||||
|
||||
func (s *StepCreateServerImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.Say("Create Server Image")
|
||||
|
||||
serverInstanceNo := state.Get("InstanceNo").(string)
|
||||
|
||||
serverImage, err := s.CreateServerImage(serverInstanceNo)
|
||||
if err == nil {
|
||||
state.Put("memberServerImage", serverImage)
|
||||
}
|
||||
|
||||
return processStepResult(err, s.Error, state)
|
||||
}
|
||||
|
||||
func (*StepCreateServerImage) Cleanup(multistep.StateBag) {
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package ncloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepCreateServerImageShouldFailIfOperationCreateServerImageFails(t *testing.T) {
|
||||
var testSubject = &StepCreateServerImage{
|
||||
CreateServerImage: func(serverInstanceNo string) (*server.MemberServerImage, error) {
|
||||
return nil, fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
Say: func(message string) {},
|
||||
Error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateServerImage()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk("Error"); ok == false {
|
||||
t.Fatal("Expected the step to set stateBag['Error'], but it was not.")
|
||||
}
|
||||
}
|
||||
func TestStepCreateServerImageShouldPassIfOperationCreateServerImagePasses(t *testing.T) {
|
||||
var testSubject = &StepCreateServerImage{
|
||||
CreateServerImage: func(serverInstanceNo string) (*server.MemberServerImage, error) { return nil, nil },
|
||||
Say: func(message string) {},
|
||||
Error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateServerImage()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk("Error"); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['Error'], but it was.")
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepCreateServerImage() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put("InstanceNo", "a")
|
||||
|
||||
return stateBag
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package ncloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/ncloud"
|
||||
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepCreateServerInstance struct {
|
||||
Conn *NcloudAPIClient
|
||||
CreateServerInstance func(loginKeyName string, zoneNo string, feeSystemTypeCode string) (string, error)
|
||||
CheckServerInstanceStatusIsRunning func(serverInstanceNo string) error
|
||||
Say func(message string)
|
||||
Error func(e error)
|
||||
Config *Config
|
||||
serverInstanceNo string
|
||||
}
|
||||
|
||||
func NewStepCreateServerInstance(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreateServerInstance {
|
||||
var step = &StepCreateServerInstance{
|
||||
Conn: conn,
|
||||
Say: func(message string) { ui.Say(message) },
|
||||
Error: func(e error) { ui.Error(e.Error()) },
|
||||
Config: config,
|
||||
}
|
||||
|
||||
step.CreateServerInstance = step.createServerInstance
|
||||
|
||||
return step
|
||||
}
|
||||
|
||||
func (s *StepCreateServerInstance) createServerInstance(loginKeyName string, zoneNo string, feeSystemTypeCode string) (string, error) {
|
||||
reqParams := new(server.CreateServerInstancesRequest)
|
||||
reqParams.ServerProductCode = &s.Config.ServerProductCode
|
||||
reqParams.MemberServerImageNo = &s.Config.MemberServerImageNo
|
||||
if s.Config.MemberServerImageNo == "" {
|
||||
reqParams.ServerImageProductCode = &s.Config.ServerImageProductCode
|
||||
}
|
||||
reqParams.LoginKeyName = &loginKeyName
|
||||
reqParams.ZoneNo = &zoneNo
|
||||
reqParams.FeeSystemTypeCode = &feeSystemTypeCode
|
||||
|
||||
if s.Config.UserData != "" {
|
||||
reqParams.UserData = &s.Config.UserData
|
||||
}
|
||||
|
||||
if s.Config.UserDataFile != "" {
|
||||
contents, err := ioutil.ReadFile(s.Config.UserDataFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Problem reading user data file: %s", err)
|
||||
}
|
||||
|
||||
reqParams.UserData = ncloud.String(string(contents))
|
||||
}
|
||||
|
||||
if s.Config.AccessControlGroupConfigurationNo != "" {
|
||||
reqParams.AccessControlGroupConfigurationNoList = []*string{&s.Config.AccessControlGroupConfigurationNo}
|
||||
}
|
||||
|
||||
serverInstanceList, err := s.Conn.server.V2Api.CreateServerInstances(reqParams)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.serverInstanceNo = *serverInstanceList.ServerInstanceList[0].ServerInstanceNo
|
||||
s.Say(fmt.Sprintf("Server Instance is creating. Server InstanceNo is %s", s.serverInstanceNo))
|
||||
log.Println("Server Instance information : ", serverInstanceList.ServerInstanceList[0])
|
||||
|
||||
if err := waiterServerInstanceStatus(s.Conn, s.serverInstanceNo, "RUN", 30*time.Minute); err != nil {
|
||||
return "", errors.New("TIMEOUT : server instance status is not running")
|
||||
}
|
||||
|
||||
s.Say(fmt.Sprintf("Server Instance is created. Server InstanceNo is %s", s.serverInstanceNo))
|
||||
|
||||
return s.serverInstanceNo, nil
|
||||
}
|
||||
|
||||
func (s *StepCreateServerInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
s.Say("Create Server Instance")
|
||||
|
||||
var loginKey = state.Get("LoginKey").(*LoginKey)
|
||||
var zoneNo = state.Get("ZoneNo").(string)
|
||||
|
||||
feeSystemTypeCode := "MTRAT"
|
||||
if _, ok := state.GetOk("FeeSystemTypeCode"); ok {
|
||||
feeSystemTypeCode = state.Get("FeeSystemTypeCode").(string)
|
||||
}
|
||||
|
||||
serverInstanceNo, err := s.CreateServerInstance(loginKey.KeyName, zoneNo, feeSystemTypeCode)
|
||||
if err == nil {
|
||||
state.Put("InstanceNo", serverInstanceNo)
|
||||
// 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", serverInstanceNo)
|
||||
}
|
||||
|
||||
return processStepResult(err, s.Error, state)
|
||||
}
|
||||
|
||||
func (s *StepCreateServerInstance) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
if !cancelled && !halted {
|
||||
return
|
||||
}
|
||||
|
||||
if s.serverInstanceNo == "" {
|
||||
return
|
||||
}
|
||||
|
||||
reqParams := new(server.GetServerInstanceListRequest)
|
||||
reqParams.ServerInstanceNoList = []*string{&s.serverInstanceNo}
|
||||
|
||||
serverInstanceList, err := s.Conn.server.V2Api.GetServerInstanceList(reqParams)
|
||||
if err != nil || *serverInstanceList.TotalRows == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
s.Say("Clean up Server Instance")
|
||||
|
||||
serverInstance := serverInstanceList.ServerInstanceList[0]
|
||||
// stop server instance
|
||||
if *serverInstance.ServerInstanceStatus.Code != "NSTOP" && *serverInstance.ServerInstanceStatus.Code != "TERMT" {
|
||||
reqParams := new(server.StopServerInstancesRequest)
|
||||
reqParams.ServerInstanceNoList = []*string{&s.serverInstanceNo}
|
||||
|
||||
log.Println("Stop Server Instance")
|
||||
s.Conn.server.V2Api.StopServerInstances(reqParams)
|
||||
waiterServerInstanceStatus(s.Conn, s.serverInstanceNo, "NSTOP", time.Minute)
|
||||
}
|
||||
|
||||
// terminate server instance
|
||||
if *serverInstance.ServerInstanceStatus.Code != "TERMT" {
|
||||
reqParams := new(server.TerminateServerInstancesRequest)
|
||||
reqParams.ServerInstanceNoList = []*string{&s.serverInstanceNo}
|
||||
|
||||
log.Println("Terminate Server Instance")
|
||||
s.Conn.server.V2Api.TerminateServerInstances(reqParams)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package ncloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepCreateServerInstanceShouldFailIfOperationCreateFails(t *testing.T) {
|
||||
var testSubject = &StepCreateServerInstance{
|
||||
CreateServerInstance: func(loginKeyName string, zoneNo string, feeSystemTypeCode string) (string, error) {
|
||||
return "", fmt.Errorf("!! Unit Test FAIL !!")
|
||||
},
|
||||
Say: func(message string) {},
|
||||
Error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateServerInstance()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionHalt {
|
||||
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk("Error"); ok == false {
|
||||
t.Fatal("Expected the step to set stateBag['Error'], but it was not.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateServerInstanceShouldPassIfOperationCreatePasses(t *testing.T) {
|
||||
var testSubject = &StepCreateServerInstance{
|
||||
CreateServerInstance: func(loginKeyName string, zoneNo string, feeSystemTypeCode string) (string, error) { return "", nil },
|
||||
Say: func(message string) {},
|
||||
Error: func(e error) {},
|
||||
}
|
||||
|
||||
stateBag := createTestStateBagStepCreateServerInstance()
|
||||
|
||||
var result = testSubject.Run(context.Background(), stateBag)
|
||||
|
||||
if result != multistep.ActionContinue {
|
||||
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||
}
|
||||
|
||||
if _, ok := stateBag.GetOk("Error"); ok == true {
|
||||
t.Fatalf("Expected the step to not set stateBag['Error'], but it was.")
|
||||
}
|
||||
}
|
||||
|
||||
func createTestStateBagStepCreateServerInstance() multistep.StateBag {
|
||||
stateBag := new(multistep.BasicStateBag)
|
||||
|
||||
stateBag.Put("LoginKey", &LoginKey{"a", "b"})
|
||||
stateBag.Put("ZoneNo", "1")
|
||||
|
||||
return stateBag
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user