Compare commits

..

2 Commits

Author SHA1 Message Date
Wilken Rivera fd3cc596c7 The original approach of running version, but that seems to bypass wrappedMain entirely 2021-04-05 16:06:41 -04:00
Wilken Rivera 708f9cdfbd Add example test to validate correct output 2021-04-05 16:01:01 -04:00
3151 changed files with 141514 additions and 202796 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];
+1 -15
View File
@@ -1,18 +1,4 @@
## 1.7.3 (Upcoming)
## 1.7.2 (April 05, 2021)
### IMPROVEMENTS:
* builder/alicloud: Add `ramrole` configuration to ECS instance. [GH-10845]
### BUG FIXES:
* builder/proxmox: Update Proxmox Go API to ensure only the first non-loopback
IPv4 address gets returned. [GH-10858]
* builder/vsphere: Fix primary disk resize on clone. [GH-10848]
* core: Fix bug where call to "packer version" sent output to stderr instead of
stdout. [GH-10850]
## 1.7.2 (Upcoming)
## 1.7.1 (March 31, 2021)
+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
-71
View File
@@ -1,71 +0,0 @@
// component_acc_test.go should contain acceptance tests for plugin components
// to make sure all component types can be discovered and started.
package plugin
import (
_ "embed"
"fmt"
"io/ioutil"
"os"
"os/exec"
"testing"
amazonacc "github.com/hashicorp/packer-plugin-amazon/builder/ebs/acceptance"
"github.com/hashicorp/packer-plugin-sdk/acctest"
"github.com/hashicorp/packer/hcl2template/addrs"
)
//go:embed test-fixtures/basic-amazon-ami-datasource.pkr.hcl
var basicAmazonAmiDatasourceHCL2Template string
func TestAccInitAndBuildBasicAmazonAmiDatasource(t *testing.T) {
plugin := addrs.Plugin{
Hostname: "github.com",
Namespace: "hashicorp",
Type: "amazon",
}
testCase := &acctest.PluginTestCase{
Name: "amazon-ami_basic_datasource_test",
Setup: func() error {
return cleanupPluginInstallation(plugin)
},
Teardown: func() error {
helper := amazonacc.AWSHelper{
Region: "us-west-2",
AMIName: "packer-amazon-ami-test",
}
return helper.CleanUpAmi()
},
Template: basicAmazonAmiDatasourceHCL2Template,
Type: "amazon-ami",
Init: true,
CheckInit: func(initCommand *exec.Cmd, logfile string) error {
if initCommand.ProcessState != nil {
if initCommand.ProcessState.ExitCode() != 0 {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}
}
logs, err := os.Open(logfile)
if err != nil {
return fmt.Errorf("Unable find %s", logfile)
}
defer logs.Close()
logsBytes, err := ioutil.ReadAll(logs)
if err != nil {
return fmt.Errorf("Unable to read %s", logfile)
}
initOutput := string(logsBytes)
return checkPluginInstallation(initOutput, plugin)
},
Check: func(buildCommand *exec.Cmd, logfile string) error {
if buildCommand.ProcessState != nil {
if buildCommand.ProcessState.ExitCode() != 0 {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}
}
return nil
},
}
acctest.TestPlugin(t, testCase)
}
-112
View File
@@ -1,112 +0,0 @@
// plugin_acc_test.go should contain acceptance tests for features related to
// installing, discovering and running plugins.
package plugin
import (
_ "embed"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"testing"
amazonacc "github.com/hashicorp/packer-plugin-amazon/builder/ebs/acceptance"
"github.com/hashicorp/packer-plugin-sdk/acctest"
"github.com/hashicorp/packer-plugin-sdk/acctest/testutils"
"github.com/hashicorp/packer/hcl2template/addrs"
"github.com/mitchellh/go-homedir"
)
//go:embed test-fixtures/basic-amazon-ebs.pkr.hcl
var basicAmazonEbsHCL2Template string
func TestAccInitAndBuildBasicAmazonEbs(t *testing.T) {
plugin := addrs.Plugin{
Hostname: "github.com",
Namespace: "hashicorp",
Type: "amazon",
}
testCase := &acctest.PluginTestCase{
Name: "amazon-ebs_basic_plugin_init_and_build_test",
Setup: func() error {
return cleanupPluginInstallation(plugin)
},
Teardown: func() error {
helper := amazonacc.AWSHelper{
Region: "us-east-1",
AMIName: "packer-plugin-amazon-ebs-test",
}
return helper.CleanUpAmi()
},
Template: basicAmazonEbsHCL2Template,
Type: "amazon-ebs",
Init: true,
CheckInit: func(initCommand *exec.Cmd, logfile string) error {
if initCommand.ProcessState != nil {
if initCommand.ProcessState.ExitCode() != 0 {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}
}
logs, err := os.Open(logfile)
if err != nil {
return fmt.Errorf("Unable find %s", logfile)
}
defer logs.Close()
logsBytes, err := ioutil.ReadAll(logs)
if err != nil {
return fmt.Errorf("Unable to read %s", logfile)
}
initOutput := string(logsBytes)
return checkPluginInstallation(initOutput, plugin)
},
Check: func(buildCommand *exec.Cmd, logfile string) error {
if buildCommand.ProcessState != nil {
if buildCommand.ProcessState.ExitCode() != 0 {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}
}
return nil
},
}
acctest.TestPlugin(t, testCase)
}
func cleanupPluginInstallation(plugin addrs.Plugin) error {
home, err := homedir.Dir()
if err != nil {
return err
}
pluginPath := filepath.Join(home,
".packer.d",
"plugins",
plugin.Hostname,
plugin.Namespace,
plugin.Type)
testutils.CleanupFiles(pluginPath)
return nil
}
func checkPluginInstallation(initOutput string, plugin addrs.Plugin) error {
expectedInitLog := "Installed plugin " + plugin.String()
if matched, _ := regexp.MatchString(expectedInitLog+".*", initOutput); !matched {
return fmt.Errorf("logs doesn't contain expected foo value %q", initOutput)
}
home, err := homedir.Dir()
if err != nil {
return err
}
pluginPath := filepath.Join(home,
".packer.d",
"plugins",
plugin.Hostname,
plugin.Namespace,
plugin.Type)
if !testutils.FileExists(pluginPath) {
return fmt.Errorf("%s plugin installation not found", plugin.String())
}
return nil
}
@@ -1,33 +0,0 @@
packer {
required_plugins {
amazon = {
version = ">= 0.0.1"
source = "github.com/hashicorp/amazon"
}
}
}
data "amazon-ami" "test" {
filters = {
name = "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
source "amazon-ebs" "basic-example" {
region = "us-west-2"
source_ami = data.amazon-ami.test.id
ami_name = "packer-amazon-ami-test"
communicator = "ssh"
instance_type = "t2.micro"
ssh_username = "ubuntu"
}
build {
sources = [
"source.amazon-ebs.basic-example"
]
}
@@ -1,20 +0,0 @@
packer {
required_plugins {
amazon = {
version = ">= 0.0.1"
source = "github.com/hashicorp/amazon"
}
}
}
source "amazon-ebs" "basic-test" {
region = "us-east-1"
instance_type = "m3.medium"
source_ami = "ami-76b2a71e"
ssh_username = "ubuntu"
ami_name = "packer-plugin-amazon-ebs-test"
}
build {
sources = ["source.amazon-ebs.basic-test"]
}
-72
View File
@@ -1,72 +0,0 @@
package acctest
import (
"os"
"testing"
)
func init() {
testTesting = true
if err := os.Setenv(TestEnvVar, "1"); err != nil {
panic(err)
}
}
func TestTest_noEnv(t *testing.T) {
// Unset the variable
if err := os.Setenv(TestEnvVar, ""); err != nil {
t.Fatalf("err: %s", err)
}
defer os.Setenv(TestEnvVar, "1")
mt := new(mockT)
Test(mt, TestCase{})
if !mt.SkipCalled {
t.Fatal("skip not called")
}
}
func TestTest_preCheck(t *testing.T) {
called := false
mt := new(mockT)
Test(mt, TestCase{
PreCheck: func() { called = true },
})
if !called {
t.Fatal("precheck should be called")
}
}
// mockT implements TestT for testing
type mockT struct {
ErrorCalled bool
ErrorArgs []interface{}
FatalCalled bool
FatalArgs []interface{}
SkipCalled bool
SkipArgs []interface{}
f bool
}
func (t *mockT) Error(args ...interface{}) {
t.ErrorCalled = true
t.ErrorArgs = args
t.f = true
}
func (t *mockT) Fatal(args ...interface{}) {
t.FatalCalled = true
t.FatalArgs = args
t.f = true
}
func (t *mockT) Skip(args ...interface{}) {
t.SkipCalled = true
t.SkipArgs = args
t.f = true
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:generate packer-sdc struct-markdown
//go:generate struct-markdown
package ecs
+1 -2
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.
@@ -135,7 +135,6 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
RamRoleName: b.config.RamRoleName,
RegionId: b.config.AlicloudRegion,
InternetChargeType: b.config.InternetChargeType,
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
+1 -3
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
@@ -88,7 +88,6 @@ type FlatConfig struct {
AlicloudSourceImage *string `mapstructure:"source_image" required:"true" cty:"source_image" hcl:"source_image"`
ForceStopInstance *bool `mapstructure:"force_stop_instance" required:"false" cty:"force_stop_instance" hcl:"force_stop_instance"`
DisableStopInstance *bool `mapstructure:"disable_stop_instance" required:"false" cty:"disable_stop_instance" hcl:"disable_stop_instance"`
RamRoleName *string `mapstructure:"ram_role_name" required:"false" cty:"ram_role_name" hcl:"ram_role_name"`
SecurityGroupId *string `mapstructure:"security_group_id" required:"false" cty:"security_group_id" hcl:"security_group_id"`
SecurityGroupName *string `mapstructure:"security_group_name" required:"false" cty:"security_group_name" hcl:"security_group_name"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
@@ -206,7 +205,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"source_image": &hcldec.AttrSpec{Name: "source_image", Type: cty.String, Required: false},
"force_stop_instance": &hcldec.AttrSpec{Name: "force_stop_instance", Type: cty.Bool, Required: false},
"disable_stop_instance": &hcldec.AttrSpec{Name: "disable_stop_instance", Type: cty.Bool, Required: false},
"ram_role_name": &hcldec.AttrSpec{Name: "ram_role_name", Type: cty.String, Required: false},
"security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false},
"security_group_name": &hcldec.AttrSpec{Name: "security_group_name", Type: cty.String, Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
+1 -1
View File
@@ -8,8 +8,8 @@ import (
"testing"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
builderT "github.com/hashicorp/packer/acctest"
)
const defaultTestRegion = "cn-beijing"
+1 -1
View File
@@ -1,4 +1,4 @@
//go:generate packer-sdc struct-markdown
//go:generate struct-markdown
package ecs
+1 -3
View File
@@ -1,4 +1,4 @@
//go:generate packer-sdc struct-markdown
//go:generate struct-markdown
package ecs
@@ -47,8 +47,6 @@ type RunConfig struct {
// E.g., Sysprep a windows which may shutdown the instance within its command.
// The default value is false.
DisableStopInstance bool `mapstructure:"disable_stop_instance" required:"false"`
// Ram Role to apply when launching the instance.
RamRoleName string `mapstructure:"ram_role_name" required:"false"`
// ID of the security group to which a newly
// created instance belongs. Mutual access is allowed between instances in one
// security group. If not specified, the newly created instance will be added
@@ -23,7 +23,6 @@ type stepCreateAlicloudInstance struct {
UserData string
UserDataFile string
instanceId string
RamRoleName string
RegionId string
InternetChargeType string
InternetMaxBandwidthOut int
@@ -116,7 +115,6 @@ func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep.
request.RegionId = s.RegionId
request.InstanceType = s.InstanceType
request.InstanceName = s.InstanceName
request.RamRoleName = s.RamRoleName
request.ZoneId = s.ZoneId
sourceImage := state.Get("source_image").(*ecs.Image)
+1 -1
View File
@@ -30,8 +30,8 @@ import (
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure/auth"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
builderT "github.com/hashicorp/packer/acctest"
)
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
+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
+1
View File
@@ -181,6 +181,7 @@ func NewAzureClient(subscriptionID, resourceGroupName string,
azureClient.GalleryImageVersionsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.GalleryImageVersionsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.GalleryImageVersionsClient.UserAgent)
azureClient.GalleryImageVersionsClient.Client.PollingDuration = SharedGalleryTimeout
azureClient.GalleryImageVersionsClient.Client.PollingDuration = PollingDuration
azureClient.GalleryImagesClient = newCompute.NewGalleryImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.GalleryImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
+1 -1
View File
@@ -28,7 +28,7 @@ package dtl
import (
"testing"
builderT "github.com/hashicorp/packer/acctest"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
)
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
+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
+3 -10
View File
@@ -80,17 +80,10 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
// Build the steps
steps := []multistep.Step{
&communicator.StepSSHKeyGen{
CommConf: &b.config.Comm,
SSHTemporaryKeyPair: b.config.Comm.SSH.SSHTemporaryKeyPair,
&stepCreateSSHKey{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("do_%s.pem", b.config.PackerBuildName),
},
multistep.If(b.config.PackerDebug && b.config.Comm.SSHPrivateKeyFile == "",
&communicator.StepDumpSSHKey{
Path: fmt.Sprintf("do_%s.pem", b.config.PackerBuildName),
SSH: &b.config.Comm.SSH,
},
),
&stepCreateSSHKey{},
new(stepCreateDroplet),
new(stepDropletInfo),
&communicator.StepConnect{
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"testing"
"github.com/digitalocean/godo"
builderT "github.com/hashicorp/packer/acctest"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
"golang.org/x/oauth2"
)
+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
+59 -5
View File
@@ -2,16 +2,26 @@ package digitalocean
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"log"
"os"
"runtime"
"github.com/digitalocean/godo"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/uuid"
"golang.org/x/crypto/ssh"
)
type stepCreateSSHKey struct {
Debug bool
DebugKeyPath string
keyId int
}
@@ -20,12 +30,31 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
ui := state.Get("ui").(packersdk.Ui)
c := state.Get("config").(*Config)
if c.Comm.SSHPublicKey == nil {
ui.Say("No public SSH key found; skipping SSH public key import...")
return multistep.ActionContinue
ui.Say("Creating temporary ssh key for droplet...")
priv, err := rsa.GenerateKey(rand.Reader, 2014)
if err != nil {
err := fmt.Errorf("error generating RSA key: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Importing SSH public key...")
// ASN.1 DER encoded form
priv_der := x509.MarshalPKCS1PrivateKey(priv)
priv_blk := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: priv_der,
}
// Set the private key in the config for later
c.Comm.SSHPrivateKey = pem.EncodeToMemory(&priv_blk)
// Marshal the public key into SSH compatible format
// TODO properly handle the public key error
pub, _ := ssh.NewPublicKey(&priv.PublicKey)
pub_sshformat := string(ssh.MarshalAuthorizedKey(pub))
// The name of the public key on DO
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
@@ -33,7 +62,7 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
// Create the key!
key, _, err := client.Keys.Create(context.TODO(), &godo.KeyCreateRequest{
Name: name,
PublicKey: string(c.Comm.SSHPublicKey),
PublicKey: pub_sshformat,
})
if err != nil {
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
@@ -50,6 +79,31 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
// Remember some state for the future
state.Put("ssh_key_id", key.ID)
// If we're in debug mode, output the private key to the working directory.
if s.Debug {
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
f, err := os.Create(s.DebugKeyPath)
if err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
defer f.Close()
// Write the key out
if _, err := f.Write(pem.EncodeToMemory(&priv_blk)); err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
// Chmod it so that it is SSH ready
if runtime.GOOS != "windows" {
if err := f.Chmod(0600); err != nil {
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
+1 -1
View File
@@ -5,8 +5,8 @@ import (
"io/ioutil"
"testing"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
builderT "github.com/hashicorp/packer/acctest"
)
func TestBuilder_implBuilder(t *testing.T) {
+1 -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
@@ -4,7 +4,7 @@ import (
"os"
"testing"
builderT "github.com/hashicorp/packer/acctest"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
)
func TestBuilderAcc_basic(t *testing.T) {
+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
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"os"
"testing"
builderT "github.com/hashicorp/packer/acctest"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
)
func TestBuilderAcc_basic(t *testing.T) {
+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
@@ -4,7 +4,7 @@ import (
"os"
"testing"
builderT "github.com/hashicorp/packer/acctest"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
)
func TestBuilderAcc_basic(t *testing.T) {
+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

Some files were not shown because too many files have changed in this diff Show More