Compare commits

..

2 Commits

Author SHA1 Message Date
Megan Marsh 371667c3a7 vendor 2021-04-12 15:38:36 -07:00
Megan Marsh f570852816 extract Vagrant 2021-04-12 15:38:23 -07:00
2521 changed files with 140163 additions and 99411 deletions
-17
View File
@@ -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];
+8
View File
@@ -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
+2 -1
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
//go:generate packer-sdc struct-markdown
//go:generate struct-markdown
package ecs
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
//go:generate packer-sdc struct-markdown
//go:generate struct-markdown
package ecs
+1 -1
View File
@@ -1,4 +1,4 @@
//go:generate packer-sdc struct-markdown
//go:generate struct-markdown
package ecs
+2 -2
View File
@@ -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 -1
View File
@@ -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
+2 -2
View File
@@ -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 -1
View 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 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 -1
View File
@@ -1,4 +1,4 @@
//go:generate packer-sdc struct-markdown
//go:generate struct-markdown
package client
+2 -2
View File
@@ -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 -1
View File
@@ -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
+2 -2
View File
@@ -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 -1
View 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 cloudstack
+2 -2
View File
@@ -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 -1
View 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 digitalocean
+1 -1
View File
@@ -1,4 +1,4 @@
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
//go:generate mapstructure-to-hcl2 -type Config
package file
+1 -1
View 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
+37
View 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")
}
}
@@ -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,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
+692
View File
@@ -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"
}`
@@ -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"
+26
View File
@@ -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())
}
+72
View File
@@ -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")
}
}
@@ -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,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)
}
}
+21
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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
+2 -2
View File
@@ -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 -1
View 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 hyperone
+1 -1
View File
@@ -1,4 +1,4 @@
//go:generate packer-sdc struct-markdown
//go:generate struct-markdown
package common
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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 -1
View 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 iso
+2 -2
View File
@@ -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 -1
View 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 vmcx
+1 -1
View File
@@ -1,4 +1,4 @@
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
//go:generate mapstructure-to-hcl2 -type Config
package jdcloud
+1 -1
View 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 jdcloud
+1 -1
View File
@@ -1,4 +1,4 @@
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
//go:generate mapstructure-to-hcl2 -type Config
package linode
+1 -1
View 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 linode
+2 -2
View File
@@ -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 -1
View 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 lxc
+2 -2
View File
@@ -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 -1
View 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 lxd
+124
View File
@@ -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
}
@@ -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,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},
+150
View File
@@ -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")
}
}
@@ -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
}
+67
View File
@@ -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