Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a091b7bd3 | |||
| c56677d399 | |||
| 8a3912c54b | |||
| 38837848f9 | |||
| e681669c70 | |||
| 58bf783a2f | |||
| bcb25f1916 | |||
| ef612c0eb1 | |||
| 972497589e | |||
| 2cd296874e | |||
| f161f2bed2 | |||
| 6b59525408 | |||
| d0a15f9a15 | |||
| af37f53439 | |||
| d0588580fa | |||
| bb511e9592 | |||
| b3ba270f2d | |||
| ed4ca8e6dc | |||
| de86b45848 | |||
| 770124207c | |||
| dbe6010510 |
@@ -1,18 +0,0 @@
|
|||||||
comment:
|
|
||||||
layout: "flags, files"
|
|
||||||
behavior: default
|
|
||||||
require_changes: true # only comment on changes in coverage
|
|
||||||
require_base: yes # [yes :: must have a base report to post]
|
|
||||||
require_head: yes # [yes :: must have a head report to post]
|
|
||||||
after_n_builds: 3 # wait for all OS test coverage builds to post comment
|
|
||||||
branches: # branch names that can post comment
|
|
||||||
- "master"
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
status:
|
|
||||||
project: off
|
|
||||||
patch: off
|
|
||||||
|
|
||||||
ignore: # ignore hcl2spec generated code for coverage and mocks
|
|
||||||
- "**/*.hcl2spec.go"
|
|
||||||
- "**/*_mock.go"
|
|
||||||
@@ -302,6 +302,10 @@ type Config struct {
|
|||||||
// group and VM name allows one to execute commands to update the VM during a
|
// group and VM name allows one to execute commands to update the VM during a
|
||||||
// Packer build, e.g. attach a resource disk to the VM.
|
// Packer build, e.g. attach a resource disk to the VM.
|
||||||
TempComputeName string `mapstructure:"temp_compute_name" required:"false"`
|
TempComputeName string `mapstructure:"temp_compute_name" required:"false"`
|
||||||
|
// temporary name assigned to the Nic. If this
|
||||||
|
// value is not set, a random value will be assigned. Being able to assign a custom
|
||||||
|
// nicname could ease deployment if naming conventions are used.
|
||||||
|
TempNicName string `mapstructure:"temp_nic_name" required:"false"`
|
||||||
// name assigned to the temporary resource group created during the build.
|
// name assigned to the temporary resource group created during the build.
|
||||||
// If this value is not set, a random value will be assigned. This resource
|
// If this value is not set, a random value will be assigned. This resource
|
||||||
// group is deleted at the end of the build.
|
// group is deleted at the end of the build.
|
||||||
@@ -395,6 +399,10 @@ type Config struct {
|
|||||||
// machine. For Linux this configures an SSH authorized key. For Windows
|
// machine. For Linux this configures an SSH authorized key. For Windows
|
||||||
// this configures a WinRM certificate.
|
// this configures a WinRM certificate.
|
||||||
OSType string `mapstructure:"os_type" required:"false"`
|
OSType string `mapstructure:"os_type" required:"false"`
|
||||||
|
// temporary name assigned to the OSDisk. If this
|
||||||
|
// value is not set, a random value will be assigned. Being able to assign a custom
|
||||||
|
// osDiskName could ease deployment if naming conventions are used.
|
||||||
|
TempOSDiskName string `mapstructure:"temp_os_disk_name" required:"false"`
|
||||||
// Specify the size of the OS disk in GB
|
// Specify the size of the OS disk in GB
|
||||||
// (gigabytes). Values of zero or less than zero are ignored.
|
// (gigabytes). Values of zero or less than zero are ignored.
|
||||||
OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb" required:"false"`
|
OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb" required:"false"`
|
||||||
@@ -704,9 +712,18 @@ func setRuntimeValues(c *Config) {
|
|||||||
} else if c.TempResourceGroupName != "" && c.BuildResourceGroupName == "" {
|
} else if c.TempResourceGroupName != "" && c.BuildResourceGroupName == "" {
|
||||||
c.tmpResourceGroupName = c.TempResourceGroupName
|
c.tmpResourceGroupName = c.TempResourceGroupName
|
||||||
}
|
}
|
||||||
|
if c.TempNicName == "" {
|
||||||
|
c.tmpNicName = tempName.NicName
|
||||||
|
} else {
|
||||||
|
c.tmpNicName = c.TempNicName
|
||||||
|
}
|
||||||
c.tmpNicName = tempName.NicName
|
c.tmpNicName = tempName.NicName
|
||||||
c.tmpPublicIPAddressName = tempName.PublicIPAddressName
|
c.tmpPublicIPAddressName = tempName.PublicIPAddressName
|
||||||
c.tmpOSDiskName = tempName.OSDiskName
|
if c.TempOSDiskName == "" {
|
||||||
|
c.tmpOSDiskName = tempName.OSDiskName
|
||||||
|
} else {
|
||||||
|
c.tmpOSDiskName = c.TempOSDiskName
|
||||||
|
}
|
||||||
c.tmpDataDiskName = tempName.DataDiskName
|
c.tmpDataDiskName = tempName.DataDiskName
|
||||||
c.tmpSubnetName = tempName.SubnetName
|
c.tmpSubnetName = tempName.SubnetName
|
||||||
c.tmpVirtualNetworkName = tempName.VirtualNetworkName
|
c.tmpVirtualNetworkName = tempName.VirtualNetworkName
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ type FlatConfig struct {
|
|||||||
ResourceGroupName *string `mapstructure:"resource_group_name" cty:"resource_group_name" hcl:"resource_group_name"`
|
ResourceGroupName *string `mapstructure:"resource_group_name" cty:"resource_group_name" hcl:"resource_group_name"`
|
||||||
StorageAccount *string `mapstructure:"storage_account" cty:"storage_account" hcl:"storage_account"`
|
StorageAccount *string `mapstructure:"storage_account" cty:"storage_account" hcl:"storage_account"`
|
||||||
TempComputeName *string `mapstructure:"temp_compute_name" required:"false" cty:"temp_compute_name" hcl:"temp_compute_name"`
|
TempComputeName *string `mapstructure:"temp_compute_name" required:"false" cty:"temp_compute_name" hcl:"temp_compute_name"`
|
||||||
|
TempNicName *string `mapstructure:"temp_nic_name" required:"false" cty:"temp_nic_name" hcl:"temp_nic_name"`
|
||||||
TempResourceGroupName *string `mapstructure:"temp_resource_group_name" cty:"temp_resource_group_name" hcl:"temp_resource_group_name"`
|
TempResourceGroupName *string `mapstructure:"temp_resource_group_name" cty:"temp_resource_group_name" hcl:"temp_resource_group_name"`
|
||||||
BuildResourceGroupName *string `mapstructure:"build_resource_group_name" cty:"build_resource_group_name" hcl:"build_resource_group_name"`
|
BuildResourceGroupName *string `mapstructure:"build_resource_group_name" cty:"build_resource_group_name" hcl:"build_resource_group_name"`
|
||||||
BuildKeyVaultName *string `mapstructure:"build_key_vault_name" cty:"build_key_vault_name" hcl:"build_key_vault_name"`
|
BuildKeyVaultName *string `mapstructure:"build_key_vault_name" cty:"build_key_vault_name" hcl:"build_key_vault_name"`
|
||||||
@@ -70,6 +71,7 @@ type FlatConfig struct {
|
|||||||
PlanInfo *FlatPlanInformation `mapstructure:"plan_info" required:"false" cty:"plan_info" hcl:"plan_info"`
|
PlanInfo *FlatPlanInformation `mapstructure:"plan_info" required:"false" cty:"plan_info" hcl:"plan_info"`
|
||||||
PollingDurationTimeout *string `mapstructure:"polling_duration_timeout" required:"false" cty:"polling_duration_timeout" hcl:"polling_duration_timeout"`
|
PollingDurationTimeout *string `mapstructure:"polling_duration_timeout" required:"false" cty:"polling_duration_timeout" hcl:"polling_duration_timeout"`
|
||||||
OSType *string `mapstructure:"os_type" required:"false" cty:"os_type" hcl:"os_type"`
|
OSType *string `mapstructure:"os_type" required:"false" cty:"os_type" hcl:"os_type"`
|
||||||
|
TempOSDiskName *string `mapstructure:"temp_os_disk_name" required:"false" cty:"temp_os_disk_name" hcl:"temp_os_disk_name"`
|
||||||
OSDiskSizeGB *int32 `mapstructure:"os_disk_size_gb" required:"false" cty:"os_disk_size_gb" hcl:"os_disk_size_gb"`
|
OSDiskSizeGB *int32 `mapstructure:"os_disk_size_gb" required:"false" cty:"os_disk_size_gb" hcl:"os_disk_size_gb"`
|
||||||
AdditionalDiskSize []int32 `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
|
AdditionalDiskSize []int32 `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
|
||||||
DiskCachingType *string `mapstructure:"disk_caching_type" required:"false" cty:"disk_caching_type" hcl:"disk_caching_type"`
|
DiskCachingType *string `mapstructure:"disk_caching_type" required:"false" cty:"disk_caching_type" hcl:"disk_caching_type"`
|
||||||
@@ -187,6 +189,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"resource_group_name": &hcldec.AttrSpec{Name: "resource_group_name", Type: cty.String, Required: false},
|
"resource_group_name": &hcldec.AttrSpec{Name: "resource_group_name", Type: cty.String, Required: false},
|
||||||
"storage_account": &hcldec.AttrSpec{Name: "storage_account", Type: cty.String, Required: false},
|
"storage_account": &hcldec.AttrSpec{Name: "storage_account", Type: cty.String, Required: false},
|
||||||
"temp_compute_name": &hcldec.AttrSpec{Name: "temp_compute_name", Type: cty.String, Required: false},
|
"temp_compute_name": &hcldec.AttrSpec{Name: "temp_compute_name", Type: cty.String, Required: false},
|
||||||
|
"temp_nic_name": &hcldec.AttrSpec{Name: "temp_nic_name", Type: cty.String, Required: false},
|
||||||
"temp_resource_group_name": &hcldec.AttrSpec{Name: "temp_resource_group_name", Type: cty.String, Required: false},
|
"temp_resource_group_name": &hcldec.AttrSpec{Name: "temp_resource_group_name", Type: cty.String, Required: false},
|
||||||
"build_resource_group_name": &hcldec.AttrSpec{Name: "build_resource_group_name", Type: cty.String, Required: false},
|
"build_resource_group_name": &hcldec.AttrSpec{Name: "build_resource_group_name", Type: cty.String, Required: false},
|
||||||
"build_key_vault_name": &hcldec.AttrSpec{Name: "build_key_vault_name", Type: cty.String, Required: false},
|
"build_key_vault_name": &hcldec.AttrSpec{Name: "build_key_vault_name", Type: cty.String, Required: false},
|
||||||
@@ -199,6 +202,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"plan_info": &hcldec.BlockSpec{TypeName: "plan_info", Nested: hcldec.ObjectSpec((*FlatPlanInformation)(nil).HCL2Spec())},
|
"plan_info": &hcldec.BlockSpec{TypeName: "plan_info", Nested: hcldec.ObjectSpec((*FlatPlanInformation)(nil).HCL2Spec())},
|
||||||
"polling_duration_timeout": &hcldec.AttrSpec{Name: "polling_duration_timeout", Type: cty.String, Required: false},
|
"polling_duration_timeout": &hcldec.AttrSpec{Name: "polling_duration_timeout", Type: cty.String, Required: false},
|
||||||
"os_type": &hcldec.AttrSpec{Name: "os_type", Type: cty.String, Required: false},
|
"os_type": &hcldec.AttrSpec{Name: "os_type", Type: cty.String, Required: false},
|
||||||
|
"temp_os_disk_name": &hcldec.AttrSpec{Name: "temp_os_disk_name", Type: cty.String, Required: false},
|
||||||
"os_disk_size_gb": &hcldec.AttrSpec{Name: "os_disk_size_gb", Type: cty.Number, Required: false},
|
"os_disk_size_gb": &hcldec.AttrSpec{Name: "os_disk_size_gb", Type: cty.Number, Required: false},
|
||||||
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.Number), Required: false},
|
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.Number), Required: false},
|
||||||
"disk_caching_type": &hcldec.AttrSpec{Name: "disk_caching_type", Type: cty.String, Required: false},
|
"disk_caching_type": &hcldec.AttrSpec{Name: "disk_caching_type", Type: cty.String, Required: false},
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Artifact struct {
|
|
||||||
// The name of the snapshot
|
|
||||||
SnapshotName string
|
|
||||||
|
|
||||||
// The ID of the image
|
|
||||||
SnapshotId int
|
|
||||||
|
|
||||||
// The name of the region
|
|
||||||
RegionNames []string
|
|
||||||
|
|
||||||
// The client for making API calls
|
|
||||||
Client *godo.Client
|
|
||||||
|
|
||||||
// StateData should store data such as GeneratedData
|
|
||||||
// to be shared with post-processors
|
|
||||||
StateData map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Artifact) BuilderId() string {
|
|
||||||
return BuilderId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Artifact) Files() []string {
|
|
||||||
// No files with DigitalOcean
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Id() string {
|
|
||||||
return fmt.Sprintf("%s:%s", strings.Join(a.RegionNames[:], ","), strconv.FormatUint(uint64(a.SnapshotId), 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) String() string {
|
|
||||||
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v) in regions '%v'", a.SnapshotName, a.SnapshotId, strings.Join(a.RegionNames[:], ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
|
||||||
return a.StateData[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Destroy() error {
|
|
||||||
log.Printf("Destroying image: %d (%s)", a.SnapshotId, a.SnapshotName)
|
|
||||||
_, err := a.Client.Images.Delete(context.TODO(), a.SnapshotId)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func generatedData() map[string]interface{} {
|
|
||||||
return make(map[string]interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifact_Impl(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &Artifact{}
|
|
||||||
if _, ok := raw.(packersdk.Artifact); !ok {
|
|
||||||
t.Fatalf("Artifact should be artifact")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactId(t *testing.T) {
|
|
||||||
a := &Artifact{"packer-foobar", 42, []string{"sfo", "tor1"}, nil, generatedData()}
|
|
||||||
expected := "sfo,tor1:42"
|
|
||||||
|
|
||||||
if a.Id() != expected {
|
|
||||||
t.Fatalf("artifact ID should match: %v", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactIdWithoutMultipleRegions(t *testing.T) {
|
|
||||||
a := &Artifact{"packer-foobar", 42, []string{"sfo"}, nil, generatedData()}
|
|
||||||
expected := "sfo:42"
|
|
||||||
|
|
||||||
if a.Id() != expected {
|
|
||||||
t.Fatalf("artifact ID should match: %v", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactString(t *testing.T) {
|
|
||||||
a := &Artifact{"packer-foobar", 42, []string{"sfo", "tor1"}, nil, generatedData()}
|
|
||||||
expected := "A snapshot was created: 'packer-foobar' (ID: 42) in regions 'sfo,tor1'"
|
|
||||||
|
|
||||||
if a.String() != expected {
|
|
||||||
t.Fatalf("artifact string should match: %v", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactStringWithoutMultipleRegions(t *testing.T) {
|
|
||||||
a := &Artifact{"packer-foobar", 42, []string{"sfo"}, nil, generatedData()}
|
|
||||||
expected := "A snapshot was created: 'packer-foobar' (ID: 42) in regions 'sfo'"
|
|
||||||
|
|
||||||
if a.String() != expected {
|
|
||||||
t.Fatalf("artifact string should match: %v", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactState_StateData(t *testing.T) {
|
|
||||||
expectedData := "this is the data"
|
|
||||||
artifact := &Artifact{
|
|
||||||
StateData: map[string]interface{}{"state_data": expectedData},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid state
|
|
||||||
result := artifact.State("state_data")
|
|
||||||
if result != expectedData {
|
|
||||||
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalid state
|
|
||||||
result = artifact.State("invalid_key")
|
|
||||||
if result != nil {
|
|
||||||
t.Fatalf("Bad: State should be nil for invalid state data name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nil StateData should not fail and should return nil
|
|
||||||
artifact = &Artifact{}
|
|
||||||
result = artifact.State("key")
|
|
||||||
if result != nil {
|
|
||||||
t.Fatalf("Bad: State should be nil for nil StateData")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
// The digitalocean package contains a packersdk.Builder implementation
|
|
||||||
// that builds DigitalOcean images (snapshots).
|
|
||||||
|
|
||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The unique id for the builder
|
|
||||||
const BuilderId = "pearkes.digitalocean"
|
|
||||||
|
|
||||||
type Builder struct {
|
|
||||||
config Config
|
|
||||||
runner multistep.Runner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
||||||
warnings, errs := b.config.Prepare(raws...)
|
|
||||||
if errs != nil {
|
|
||||||
return nil, warnings, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
|
||||||
client := godo.NewClient(oauth2.NewClient(context.TODO(), &apiTokenSource{
|
|
||||||
AccessToken: b.config.APIToken,
|
|
||||||
}))
|
|
||||||
if b.config.APIURL != "" {
|
|
||||||
u, err := url.Parse(b.config.APIURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("DigitalOcean: Invalid API URL, %s.", err)
|
|
||||||
}
|
|
||||||
client.BaseURL = u
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b.config.SnapshotRegions) > 0 {
|
|
||||||
opt := &godo.ListOptions{
|
|
||||||
Page: 1,
|
|
||||||
PerPage: 200,
|
|
||||||
}
|
|
||||||
regions, _, err := client.Regions.List(context.TODO(), opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("DigitalOcean: Unable to get regions, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
validRegions := make(map[string]struct{})
|
|
||||||
for _, val := range regions {
|
|
||||||
validRegions[val.Slug] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, region := range append(b.config.SnapshotRegions, b.config.Region) {
|
|
||||||
if _, ok := validRegions[region]; !ok {
|
|
||||||
return nil, fmt.Errorf("DigitalOcean: Invalid region, %s", region)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up the state
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("config", &b.config)
|
|
||||||
state.Put("client", client)
|
|
||||||
state.Put("hook", hook)
|
|
||||||
state.Put("ui", ui)
|
|
||||||
|
|
||||||
// Build the steps
|
|
||||||
steps := []multistep.Step{
|
|
||||||
&communicator.StepSSHKeyGen{
|
|
||||||
CommConf: &b.config.Comm,
|
|
||||||
SSHTemporaryKeyPair: b.config.Comm.SSH.SSHTemporaryKeyPair,
|
|
||||||
},
|
|
||||||
multistep.If(b.config.PackerDebug && b.config.Comm.SSHPrivateKeyFile == "",
|
|
||||||
&communicator.StepDumpSSHKey{
|
|
||||||
Path: fmt.Sprintf("do_%s.pem", b.config.PackerBuildName),
|
|
||||||
SSH: &b.config.Comm.SSH,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
&stepCreateSSHKey{},
|
|
||||||
new(stepCreateDroplet),
|
|
||||||
new(stepDropletInfo),
|
|
||||||
&communicator.StepConnect{
|
|
||||||
Config: &b.config.Comm,
|
|
||||||
Host: communicator.CommHost(b.config.Comm.Host(), "droplet_ip"),
|
|
||||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
|
||||||
},
|
|
||||||
new(commonsteps.StepProvision),
|
|
||||||
&commonsteps.StepCleanupTempKeys{
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
},
|
|
||||||
new(stepShutdown),
|
|
||||||
new(stepPowerOff),
|
|
||||||
&stepSnapshot{
|
|
||||||
snapshotTimeout: b.config.SnapshotTimeout,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the steps
|
|
||||||
b.runner = commonsteps.NewRunner(steps, b.config.PackerConfig, ui)
|
|
||||||
b.runner.Run(ctx, state)
|
|
||||||
|
|
||||||
// If there was an error, return that
|
|
||||||
if rawErr, ok := state.GetOk("error"); ok {
|
|
||||||
return nil, rawErr.(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("snapshot_name"); !ok {
|
|
||||||
log.Println("Failed to find snapshot_name in state. Bug?")
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
artifact := &Artifact{
|
|
||||||
SnapshotName: state.Get("snapshot_name").(string),
|
|
||||||
SnapshotId: state.Get("snapshot_image_id").(int),
|
|
||||||
RegionNames: state.Get("regions").([]string),
|
|
||||||
Client: client,
|
|
||||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
builderT "github.com/hashicorp/packer/acctest"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: fmt.Sprintf(testBuilderAccBasic, "ubuntu-20-04-x64"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderAcc_imageId(t *testing.T) {
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: makeTemplateWithImageId(t),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAccPreCheck(t *testing.T) {
|
|
||||||
if v := os.Getenv("DIGITALOCEAN_API_TOKEN"); v == "" {
|
|
||||||
t.Fatal("DIGITALOCEAN_API_TOKEN must be set for acceptance tests")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTemplateWithImageId(t *testing.T) string {
|
|
||||||
if os.Getenv(builderT.TestEnvVar) != "" {
|
|
||||||
token := os.Getenv("DIGITALOCEAN_API_TOKEN")
|
|
||||||
client := godo.NewClient(oauth2.NewClient(context.TODO(), &apiTokenSource{
|
|
||||||
AccessToken: token,
|
|
||||||
}))
|
|
||||||
image, _, err := client.Images.GetBySlug(context.TODO(), "ubuntu-20-04-x64")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to retrieve image ID: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(testBuilderAccBasic, image.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccBasic = `
|
|
||||||
{
|
|
||||||
"builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"region": "nyc2",
|
|
||||||
"size": "s-1vcpu-1gb",
|
|
||||||
"image": "%v",
|
|
||||||
"ssh_username": "root",
|
|
||||||
"user_data": "",
|
|
||||||
"user_data_file": ""
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@@ -1,416 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testConfig() map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"api_token": "bar",
|
|
||||||
"region": "nyc2",
|
|
||||||
"size": "512mb",
|
|
||||||
"ssh_username": "root",
|
|
||||||
"image": "foo",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &Builder{}
|
|
||||||
if _, ok := raw.(packersdk.Builder); !ok {
|
|
||||||
t.Fatalf("Builder should be a builder")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilder_Prepare_BadType(t *testing.T) {
|
|
||||||
b := &Builder{}
|
|
||||||
c := map[string]interface{}{
|
|
||||||
"api_key": []string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, warnings, err := b.Prepare(c)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("prepare should fail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Add a random key
|
|
||||||
config["i_should_not_be_valid"] = true
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_Region(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
delete(config, "region")
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("should error")
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := "sfo1"
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["region"] = expected
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.Region != expected {
|
|
||||||
t.Errorf("found %s, expected %s", b.config.Region, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_Size(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
delete(config, "size")
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("should error")
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := "1024mb"
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["size"] = expected
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.Size != expected {
|
|
||||||
t.Errorf("found %s, expected %s", b.config.Size, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_Image(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
delete(config, "image")
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should error")
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := "ubuntu-14-04-x64"
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["image"] = expected
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.Image != expected {
|
|
||||||
t.Errorf("found %s, expected %s", b.config.Image, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_StateTimeout(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.StateTimeout != 6*time.Minute {
|
|
||||||
t.Errorf("invalid: %s", b.config.StateTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["state_timeout"] = "5m"
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test bad
|
|
||||||
config["state_timeout"] = "tubes"
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_SnapshotTimeout(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.SnapshotTimeout != 60*time.Minute {
|
|
||||||
t.Errorf("invalid: %s", b.config.SnapshotTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["snapshot_timeout"] = "15m"
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test bad
|
|
||||||
config["snapshot_timeout"] = "badstring"
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_PrivateNetworking(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.PrivateNetworking != false {
|
|
||||||
t.Errorf("invalid: %t", b.config.PrivateNetworking)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["private_networking"] = true
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.PrivateNetworking != true {
|
|
||||||
t.Errorf("invalid: %t", b.config.PrivateNetworking)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_SnapshotName(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.SnapshotName == "" {
|
|
||||||
t.Errorf("invalid: %s", b.config.SnapshotName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test set
|
|
||||||
config["snapshot_name"] = "foobarbaz"
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test set with template
|
|
||||||
config["snapshot_name"] = "{{timestamp}}"
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = strconv.ParseInt(b.config.SnapshotName, 0, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to parse int in template: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_DropletName(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test default
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.DropletName == "" {
|
|
||||||
t.Errorf("invalid: %s", b.config.DropletName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test normal set
|
|
||||||
config["droplet_name"] = "foobar"
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with template
|
|
||||||
config["droplet_name"] = "foobar-{{timestamp}}"
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with bad template
|
|
||||||
config["droplet_name"] = "foobar-{{"
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_VPCUUID(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test with the case vpc_uuid is defined but private_networking is not enabled
|
|
||||||
config["vpc_uuid"] = "554c41b3-425f-5403-8860-7f24fb108098"
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("should have error: 'private networking should be enabled to use vpc_uuid'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with the case both vpc_uuid and private_networking are defined/enabled
|
|
||||||
config["private_networking"] = true
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("should not have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_ConnectWithPrivateIP(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test with the case connect_with_private_ip is defined but private_networking is not enabled
|
|
||||||
config["connect_with_private_ip"] = true
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("should have error: 'private networking should be enabled to use connect_with_private_ip'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with the case both connect_with_private_ip and private_networking are enabled
|
|
||||||
config["private_networking"] = true
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("should not have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
//go:generate packer-sdc struct-markdown
|
|
||||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
|
||||||
|
|
||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
|
||||||
// The client TOKEN to use to access your account. It
|
|
||||||
// can also be specified via environment variable DIGITALOCEAN_API_TOKEN, if
|
|
||||||
// set.
|
|
||||||
APIToken string `mapstructure:"api_token" required:"true"`
|
|
||||||
// Non standard api endpoint URL. Set this if you are
|
|
||||||
// using a DigitalOcean API compatible service. It can also be specified via
|
|
||||||
// environment variable DIGITALOCEAN_API_URL.
|
|
||||||
APIURL string `mapstructure:"api_url" required:"false"`
|
|
||||||
// The name (or slug) of the region to launch the droplet
|
|
||||||
// in. Consequently, this is the region where the snapshot will be available.
|
|
||||||
// See
|
|
||||||
// https://developers.digitalocean.com/documentation/v2/#list-all-regions
|
|
||||||
// for the accepted region names/slugs.
|
|
||||||
Region string `mapstructure:"region" required:"true"`
|
|
||||||
// The name (or slug) of the droplet size to use. See
|
|
||||||
// https://developers.digitalocean.com/documentation/v2/#list-all-sizes
|
|
||||||
// for the accepted size names/slugs.
|
|
||||||
Size string `mapstructure:"size" required:"true"`
|
|
||||||
// The name (or slug) of the base image to use. This is the
|
|
||||||
// image that will be used to launch a new droplet and provision it. See
|
|
||||||
// https://developers.digitalocean.com/documentation/v2/#list-all-images
|
|
||||||
// for details on how to get a list of the accepted image names/slugs.
|
|
||||||
Image string `mapstructure:"image" required:"true"`
|
|
||||||
// Set to true to enable private networking
|
|
||||||
// for the droplet being created. This defaults to false, or not enabled.
|
|
||||||
PrivateNetworking bool `mapstructure:"private_networking" required:"false"`
|
|
||||||
// Set to true to enable monitoring for the droplet
|
|
||||||
// being created. This defaults to false, or not enabled.
|
|
||||||
Monitoring bool `mapstructure:"monitoring" required:"false"`
|
|
||||||
// Set to true to enable ipv6 for the droplet being
|
|
||||||
// created. This defaults to false, or not enabled.
|
|
||||||
IPv6 bool `mapstructure:"ipv6" required:"false"`
|
|
||||||
// The name of the resulting snapshot that will
|
|
||||||
// appear in your account. Defaults to `packer-{{timestamp}}` (see
|
|
||||||
// configuration templates for more info).
|
|
||||||
SnapshotName string `mapstructure:"snapshot_name" required:"false"`
|
|
||||||
// The regions of the resulting
|
|
||||||
// snapshot that will appear in your account.
|
|
||||||
SnapshotRegions []string `mapstructure:"snapshot_regions" required:"false"`
|
|
||||||
// The time to wait, as a duration string, for a
|
|
||||||
// droplet to enter a desired state (such as "active") before timing out. The
|
|
||||||
// default state timeout is "6m".
|
|
||||||
StateTimeout time.Duration `mapstructure:"state_timeout" required:"false"`
|
|
||||||
// How long to wait for an image to be published to the shared image
|
|
||||||
// gallery before timing out. If your Packer build is failing on the
|
|
||||||
// Publishing to Shared Image Gallery step with the error `Original Error:
|
|
||||||
// context deadline exceeded`, but the image is present when you check your
|
|
||||||
// Azure dashboard, then you probably need to increase this timeout from
|
|
||||||
// its default of "60m" (valid time units include `s` for seconds, `m` for
|
|
||||||
// minutes, and `h` for hours.)
|
|
||||||
SnapshotTimeout time.Duration `mapstructure:"snapshot_timeout" required:"false"`
|
|
||||||
// The name assigned to the droplet. DigitalOcean
|
|
||||||
// sets the hostname of the machine to this value.
|
|
||||||
DropletName string `mapstructure:"droplet_name" required:"false"`
|
|
||||||
// User data to launch with the Droplet. Packer will
|
|
||||||
// not automatically wait for a user script to finish before shutting down the
|
|
||||||
// instance this must be handled in a provisioner.
|
|
||||||
UserData string `mapstructure:"user_data" required:"false"`
|
|
||||||
// Path to a file that will be used for the user
|
|
||||||
// data when launching the Droplet.
|
|
||||||
UserDataFile string `mapstructure:"user_data_file" required:"false"`
|
|
||||||
// Tags to apply to the droplet when it is created
|
|
||||||
Tags []string `mapstructure:"tags" required:"false"`
|
|
||||||
// UUID of the VPC which the droplet will be created in. Before using this,
|
|
||||||
// private_networking should be enabled.
|
|
||||||
VPCUUID string `mapstructure:"vpc_uuid" required:"false"`
|
|
||||||
// Wheter the communicators should use private IP or not (public IP in that case).
|
|
||||||
// If the droplet is or going to be accessible only from the local network because
|
|
||||||
// it is at behind a firewall, then communicators should use the private IP
|
|
||||||
// instead of the public IP. Before using this, private_networking should be enabled.
|
|
||||||
ConnectWithPrivateIP bool `mapstructure:"connect_with_private_ip" required:"false"`
|
|
||||||
|
|
||||||
ctx interpolate.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|
||||||
|
|
||||||
var md mapstructure.Metadata
|
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
|
||||||
Metadata: &md,
|
|
||||||
Interpolate: true,
|
|
||||||
InterpolateContext: &c.ctx,
|
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
|
||||||
Exclude: []string{
|
|
||||||
"run_command",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, raws...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defaults
|
|
||||||
if c.APIToken == "" {
|
|
||||||
// Default to environment variable for api_token, if it exists
|
|
||||||
c.APIToken = os.Getenv("DIGITALOCEAN_API_TOKEN")
|
|
||||||
}
|
|
||||||
if c.APIURL == "" {
|
|
||||||
c.APIURL = os.Getenv("DIGITALOCEAN_API_URL")
|
|
||||||
}
|
|
||||||
if c.SnapshotName == "" {
|
|
||||||
def, err := interpolate.Render("packer-{{timestamp}}", nil)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to packer-{{ unix timestamp (utc) }}
|
|
||||||
c.SnapshotName = def
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.DropletName == "" {
|
|
||||||
// Default to packer-[time-ordered-uuid]
|
|
||||||
c.DropletName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.StateTimeout == 0 {
|
|
||||||
// Default to 6 minute timeouts waiting for
|
|
||||||
// desired state. i.e waiting for droplet to become active
|
|
||||||
c.StateTimeout = 6 * time.Minute
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SnapshotTimeout == 0 {
|
|
||||||
// Default to 60 minutes timeout, waiting for snapshot action to finish
|
|
||||||
c.SnapshotTimeout = 60 * time.Minute
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
|
|
||||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, es...)
|
|
||||||
}
|
|
||||||
if c.APIToken == "" {
|
|
||||||
// Required configurations that will display errors if not set
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("api_token for auth must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Region == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("region is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Size == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("size is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Image == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("image is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.UserData != "" && c.UserDataFile != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("only one of user_data or user_data_file can be specified"))
|
|
||||||
} else if c.UserDataFile != "" {
|
|
||||||
if _, err := os.Stat(c.UserDataFile); err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New(fmt.Sprintf("user_data_file not found: %s", c.UserDataFile)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Tags == nil {
|
|
||||||
c.Tags = make([]string, 0)
|
|
||||||
}
|
|
||||||
tagRe := regexp.MustCompile("^[[:alnum:]:_-]{1,255}$")
|
|
||||||
|
|
||||||
for _, t := range c.Tags {
|
|
||||||
if !tagRe.MatchString(t) {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("invalid tag: %s", t))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the PrivateNetworking is enabled by user before use VPC UUID
|
|
||||||
if c.VPCUUID != "" {
|
|
||||||
if c.PrivateNetworking != true {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, errors.New("private networking should be enabled to use vpc_uuid"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the PrivateNetworking is enabled by user before use ConnectWithPrivateIP
|
|
||||||
if c.ConnectWithPrivateIP == true {
|
|
||||||
if c.PrivateNetworking != true {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, errors.New("private networking should be enabled to use connect_with_private_ip"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return nil, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
packersdk.LogSecretFilter.Set(c.APIToken)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatConfig struct {
|
|
||||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
|
||||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
|
||||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
|
||||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
|
||||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
|
||||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
|
||||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
|
||||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
|
||||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
|
||||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
|
||||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
|
||||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
|
||||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
|
||||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
|
||||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
|
||||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
|
||||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
|
||||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
|
||||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
|
||||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
|
||||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
|
||||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
|
||||||
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
|
||||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
|
||||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
|
||||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
|
||||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
|
||||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
|
||||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
|
||||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
|
||||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
|
||||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
|
||||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
|
||||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
|
||||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
|
||||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
|
||||||
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
|
||||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
|
||||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
|
||||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
|
||||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
|
||||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
|
||||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
|
||||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
|
||||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
|
||||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
|
||||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
|
||||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
|
||||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
|
||||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
|
||||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
|
||||||
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
|
||||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
|
||||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
|
||||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
|
||||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
|
||||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
|
||||||
APIToken *string `mapstructure:"api_token" required:"true" cty:"api_token" hcl:"api_token"`
|
|
||||||
APIURL *string `mapstructure:"api_url" required:"false" cty:"api_url" hcl:"api_url"`
|
|
||||||
Region *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"`
|
|
||||||
Size *string `mapstructure:"size" required:"true" cty:"size" hcl:"size"`
|
|
||||||
Image *string `mapstructure:"image" required:"true" cty:"image" hcl:"image"`
|
|
||||||
PrivateNetworking *bool `mapstructure:"private_networking" required:"false" cty:"private_networking" hcl:"private_networking"`
|
|
||||||
Monitoring *bool `mapstructure:"monitoring" required:"false" cty:"monitoring" hcl:"monitoring"`
|
|
||||||
IPv6 *bool `mapstructure:"ipv6" required:"false" cty:"ipv6" hcl:"ipv6"`
|
|
||||||
SnapshotName *string `mapstructure:"snapshot_name" required:"false" cty:"snapshot_name" hcl:"snapshot_name"`
|
|
||||||
SnapshotRegions []string `mapstructure:"snapshot_regions" required:"false" cty:"snapshot_regions" hcl:"snapshot_regions"`
|
|
||||||
StateTimeout *string `mapstructure:"state_timeout" required:"false" cty:"state_timeout" hcl:"state_timeout"`
|
|
||||||
SnapshotTimeout *string `mapstructure:"snapshot_timeout" required:"false" cty:"snapshot_timeout" hcl:"snapshot_timeout"`
|
|
||||||
DropletName *string `mapstructure:"droplet_name" required:"false" cty:"droplet_name" hcl:"droplet_name"`
|
|
||||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
|
||||||
UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file" hcl:"user_data_file"`
|
|
||||||
Tags []string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"`
|
|
||||||
VPCUUID *string `mapstructure:"vpc_uuid" required:"false" cty:"vpc_uuid" hcl:"vpc_uuid"`
|
|
||||||
ConnectWithPrivateIP *bool `mapstructure:"connect_with_private_ip" required:"false" cty:"connect_with_private_ip" hcl:"connect_with_private_ip"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatConfig.
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a Config.
|
|
||||||
// This spec is used by HCL to read the fields of Config.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
|
||||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
|
||||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
|
||||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
|
||||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
|
||||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
|
||||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
|
||||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
|
||||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
|
||||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
|
||||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
|
||||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
|
||||||
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
|
||||||
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
|
||||||
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
|
||||||
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
|
||||||
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
|
||||||
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
|
||||||
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
|
||||||
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
|
||||||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
|
||||||
"api_token": &hcldec.AttrSpec{Name: "api_token", Type: cty.String, Required: false},
|
|
||||||
"api_url": &hcldec.AttrSpec{Name: "api_url", Type: cty.String, Required: false},
|
|
||||||
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
|
|
||||||
"size": &hcldec.AttrSpec{Name: "size", Type: cty.String, Required: false},
|
|
||||||
"image": &hcldec.AttrSpec{Name: "image", Type: cty.String, Required: false},
|
|
||||||
"private_networking": &hcldec.AttrSpec{Name: "private_networking", Type: cty.Bool, Required: false},
|
|
||||||
"monitoring": &hcldec.AttrSpec{Name: "monitoring", Type: cty.Bool, Required: false},
|
|
||||||
"ipv6": &hcldec.AttrSpec{Name: "ipv6", Type: cty.Bool, Required: false},
|
|
||||||
"snapshot_name": &hcldec.AttrSpec{Name: "snapshot_name", Type: cty.String, Required: false},
|
|
||||||
"snapshot_regions": &hcldec.AttrSpec{Name: "snapshot_regions", Type: cty.List(cty.String), Required: false},
|
|
||||||
"state_timeout": &hcldec.AttrSpec{Name: "state_timeout", Type: cty.String, Required: false},
|
|
||||||
"snapshot_timeout": &hcldec.AttrSpec{Name: "snapshot_timeout", Type: cty.String, Required: false},
|
|
||||||
"droplet_name": &hcldec.AttrSpec{Name: "droplet_name", Type: cty.String, Required: false},
|
|
||||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
|
||||||
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
|
|
||||||
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.List(cty.String), Required: false},
|
|
||||||
"vpc_uuid": &hcldec.AttrSpec{Name: "vpc_uuid", Type: cty.String, Required: false},
|
|
||||||
"connect_with_private_ip": &hcldec.AttrSpec{Name: "connect_with_private_ip", Type: cty.Bool, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateDroplet struct {
|
|
||||||
dropletId int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateDroplet) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
sshKeyId := state.Get("ssh_key_id").(int)
|
|
||||||
|
|
||||||
// Create the droplet based on configuration
|
|
||||||
ui.Say("Creating droplet...")
|
|
||||||
|
|
||||||
userData := c.UserData
|
|
||||||
if c.UserDataFile != "" {
|
|
||||||
contents, err := ioutil.ReadFile(c.UserDataFile)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
userData = string(contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
createImage := getImageType(c.Image)
|
|
||||||
|
|
||||||
dropletCreateReq := &godo.DropletCreateRequest{
|
|
||||||
Name: c.DropletName,
|
|
||||||
Region: c.Region,
|
|
||||||
Size: c.Size,
|
|
||||||
Image: createImage,
|
|
||||||
SSHKeys: []godo.DropletCreateSSHKey{
|
|
||||||
{ID: sshKeyId},
|
|
||||||
},
|
|
||||||
PrivateNetworking: c.PrivateNetworking,
|
|
||||||
Monitoring: c.Monitoring,
|
|
||||||
IPv6: c.IPv6,
|
|
||||||
UserData: userData,
|
|
||||||
Tags: c.Tags,
|
|
||||||
VPCUUID: c.VPCUUID,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[DEBUG] Droplet create paramaters: %s", godo.Stringify(dropletCreateReq))
|
|
||||||
|
|
||||||
droplet, _, err := client.Droplets.Create(context.TODO(), dropletCreateReq)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use this in cleanup
|
|
||||||
s.dropletId = droplet.ID
|
|
||||||
|
|
||||||
// Store the droplet id for later
|
|
||||||
state.Put("droplet_id", droplet.ID)
|
|
||||||
// instance_id is the generic term used so that users can have access to the
|
|
||||||
// instance id inside of the provisioners, used in step_provision.
|
|
||||||
state.Put("instance_id", droplet.ID)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) {
|
|
||||||
// If the dropletid isn't there, we probably never created it
|
|
||||||
if s.dropletId == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
// Destroy the droplet we just created
|
|
||||||
ui.Say("Destroying droplet...")
|
|
||||||
_, err := client.Droplets.Delete(context.TODO(), s.dropletId)
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf(
|
|
||||||
"Error destroying droplet. Please destroy it manually: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getImageType(image string) godo.DropletCreateImage {
|
|
||||||
createImage := godo.DropletCreateImage{Slug: image}
|
|
||||||
|
|
||||||
imageId, err := strconv.Atoi(image)
|
|
||||||
if err == nil {
|
|
||||||
createImage = godo.DropletCreateImage{ID: imageId}
|
|
||||||
}
|
|
||||||
|
|
||||||
return createImage
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuilder_GetImageType(t *testing.T) {
|
|
||||||
imageTypeTests := []struct {
|
|
||||||
in string
|
|
||||||
out godo.DropletCreateImage
|
|
||||||
}{
|
|
||||||
{"ubuntu-20-04-x64", godo.DropletCreateImage{Slug: "ubuntu-20-04-x64"}},
|
|
||||||
{"123456", godo.DropletCreateImage{ID: 123456}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range imageTypeTests {
|
|
||||||
t.Run(tt.in, func(t *testing.T) {
|
|
||||||
i := getImageType(tt.in)
|
|
||||||
if i != tt.out {
|
|
||||||
t.Errorf("got %q, want %q", godo.Stringify(i), godo.Stringify(tt.out))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateSSHKey struct {
|
|
||||||
keyId int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
if c.Comm.SSHPublicKey == nil {
|
|
||||||
ui.Say("No public SSH key found; skipping SSH public key import...")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Importing SSH public key...")
|
|
||||||
|
|
||||||
// The name of the public key on DO
|
|
||||||
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
|
||||||
|
|
||||||
// Create the key!
|
|
||||||
key, _, err := client.Keys.Create(context.TODO(), &godo.KeyCreateRequest{
|
|
||||||
Name: name,
|
|
||||||
PublicKey: string(c.Comm.SSHPublicKey),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use this to check cleanup
|
|
||||||
s.keyId = key.ID
|
|
||||||
|
|
||||||
log.Printf("temporary ssh key name: %s", name)
|
|
||||||
|
|
||||||
// Remember some state for the future
|
|
||||||
state.Put("ssh_key_id", key.ID)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
|
|
||||||
// If no key name is set, then we never created it, so just return
|
|
||||||
if s.keyId == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say("Deleting temporary ssh key...")
|
|
||||||
_, err := client.Keys.DeleteByID(context.TODO(), s.keyId)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error cleaning up ssh key: %s", err)
|
|
||||||
ui.Error(fmt.Sprintf(
|
|
||||||
"Error cleaning up ssh key. Please delete the key manually: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepDropletInfo struct{}
|
|
||||||
|
|
||||||
func (s *stepDropletInfo) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
dropletID := state.Get("droplet_id").(int)
|
|
||||||
|
|
||||||
ui.Say("Waiting for droplet to become active...")
|
|
||||||
|
|
||||||
err := waitForDropletState("active", dropletID, client, c.StateTimeout)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error waiting for droplet to become active: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the IP on the state for later
|
|
||||||
droplet, _, err := client.Droplets.Get(context.TODO(), dropletID)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error retrieving droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify we have an IPv4 address
|
|
||||||
invalid := droplet.Networks == nil ||
|
|
||||||
len(droplet.Networks.V4) == 0
|
|
||||||
if invalid {
|
|
||||||
err := fmt.Errorf("IPv4 address not found for droplet")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the ip address which will be used by communicator
|
|
||||||
foundNetwork := false
|
|
||||||
for _, network := range droplet.Networks.V4 {
|
|
||||||
if (c.ConnectWithPrivateIP && network.Type == "private") ||
|
|
||||||
(!(c.ConnectWithPrivateIP) && network.Type == "public") {
|
|
||||||
state.Put("droplet_ip", network.IPAddress)
|
|
||||||
foundNetwork = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !foundNetwork {
|
|
||||||
err := fmt.Errorf("Count not find a public IPv4 address for this droplet")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepDropletInfo) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepPowerOff struct{}
|
|
||||||
|
|
||||||
func (s *stepPowerOff) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
dropletId := state.Get("droplet_id").(int)
|
|
||||||
|
|
||||||
droplet, _, err := client.Droplets.Get(context.TODO(), dropletId)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error checking droplet state: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
if droplet.Status == "off" {
|
|
||||||
// Droplet is already off, don't do anything
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull the plug on the Droplet
|
|
||||||
ui.Say("Forcefully shutting down Droplet...")
|
|
||||||
_, _, err = client.DropletActions.PowerOff(context.TODO(), dropletId)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error powering off droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Waiting for poweroff event to complete...")
|
|
||||||
err = waitForDropletState("off", dropletId, client, c.StateTimeout)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the droplet to become unlocked for future steps
|
|
||||||
if err := waitForDropletUnlocked(client, dropletId, c.StateTimeout); err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error powering off droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepPowerOff) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepShutdown struct{}
|
|
||||||
|
|
||||||
func (s *stepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
dropletId := state.Get("droplet_id").(int)
|
|
||||||
|
|
||||||
// Gracefully power off the droplet. We have to retry this a number
|
|
||||||
// of times because sometimes it says it completed when it actually
|
|
||||||
// did absolutely nothing (*ALAKAZAM!* magic!). We give up after
|
|
||||||
// a pretty arbitrary amount of time.
|
|
||||||
ui.Say("Gracefully shutting down droplet...")
|
|
||||||
_, _, err := client.DropletActions.Shutdown(context.TODO(), dropletId)
|
|
||||||
if err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error shutting down droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// A channel we use as a flag to end our goroutines
|
|
||||||
done := make(chan struct{})
|
|
||||||
shutdownRetryDone := make(chan struct{})
|
|
||||||
|
|
||||||
// Make sure we wait for the shutdown retry goroutine to end
|
|
||||||
// before moving on.
|
|
||||||
defer func() {
|
|
||||||
close(done)
|
|
||||||
<-shutdownRetryDone
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Start a goroutine that just keeps trying to shut down the
|
|
||||||
// droplet.
|
|
||||||
go func() {
|
|
||||||
defer close(shutdownRetryDone)
|
|
||||||
|
|
||||||
for attempts := 2; attempts > 0; attempts++ {
|
|
||||||
log.Printf("ShutdownDroplet attempt #%d...", attempts)
|
|
||||||
_, _, err := client.DropletActions.Shutdown(context.TODO(), dropletId)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Shutdown retry error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
case <-time.After(20 * time.Second):
|
|
||||||
// Retry!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = waitForDropletState("off", dropletId, client, c.StateTimeout)
|
|
||||||
if err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error shutting down droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := waitForDropletUnlocked(client, dropletId, c.StateTimeout); err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error shutting down droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepShutdown) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepSnapshot struct {
|
|
||||||
snapshotTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*godo.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
dropletId := state.Get("droplet_id").(int)
|
|
||||||
var snapshotRegions []string
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName))
|
|
||||||
action, _, err := client.DropletActions.Snapshot(context.TODO(), dropletId, c.SnapshotName)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the pending state over, verify that we're in the active state
|
|
||||||
// because action can take a long time and may depend on the size of the final snapshot,
|
|
||||||
// the timeout is parameterized
|
|
||||||
ui.Say("Waiting for snapshot to complete...")
|
|
||||||
if err := waitForActionState(godo.ActionCompleted, dropletId, action.ID,
|
|
||||||
client, s.snapshotTimeout); err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error waiting for snapshot: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the droplet to become unlocked first. For snapshots
|
|
||||||
// this can end up taking quite a long time, so we hardcode this to
|
|
||||||
// 20 minutes.
|
|
||||||
if err := waitForDropletUnlocked(client, dropletId, 20*time.Minute); err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error shutting down droplet: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Looking up snapshot ID for snapshot: %s", c.SnapshotName)
|
|
||||||
images, _, err := client.Droplets.Snapshots(context.TODO(), dropletId, nil)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error looking up snapshot ID: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.SnapshotRegions) > 0 {
|
|
||||||
regionSet := make(map[string]struct{})
|
|
||||||
regions := make([]string, 0, len(c.SnapshotRegions))
|
|
||||||
regionSet[c.Region] = struct{}{}
|
|
||||||
for _, region := range c.SnapshotRegions {
|
|
||||||
// If we already saw the region, then don't look again
|
|
||||||
if _, ok := regionSet[region]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark that we saw the region
|
|
||||||
regionSet[region] = struct{}{}
|
|
||||||
|
|
||||||
regions = append(regions, region)
|
|
||||||
}
|
|
||||||
snapshotRegions = regions
|
|
||||||
|
|
||||||
for transfer := range snapshotRegions {
|
|
||||||
transferRequest := &godo.ActionRequest{
|
|
||||||
"type": "transfer",
|
|
||||||
"region": snapshotRegions[transfer],
|
|
||||||
}
|
|
||||||
imageTransfer, _, err := client.ImageActions.Transfer(context.TODO(), images[0].ID, transferRequest)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error transferring snapshot: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
ui.Say(fmt.Sprintf("transferring Snapshot ID: %d", imageTransfer.ID))
|
|
||||||
if err := WaitForImageState(godo.ActionCompleted, imageTransfer.ID, action.ID,
|
|
||||||
client, 20*time.Minute); err != nil {
|
|
||||||
// If we get an error the first time, actually report it
|
|
||||||
err := fmt.Errorf("Error waiting for snapshot transfer: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var imageId int
|
|
||||||
if len(images) == 1 {
|
|
||||||
imageId = images[0].ID
|
|
||||||
} else {
|
|
||||||
err := errors.New("Couldn't find snapshot to get the image ID. Bug?")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
snapshotRegions = append(snapshotRegions, c.Region)
|
|
||||||
|
|
||||||
log.Printf("Snapshot image ID: %d", imageId)
|
|
||||||
state.Put("snapshot_image_id", imageId)
|
|
||||||
state.Put("snapshot_name", c.SnapshotName)
|
|
||||||
state.Put("regions", snapshotRegions)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepSnapshot) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type apiTokenSource struct {
|
|
||||||
AccessToken string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *apiTokenSource) Token() (*oauth2.Token, error) {
|
|
||||||
return &oauth2.Token{
|
|
||||||
AccessToken: t.AccessToken,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
|
||||||
packerVersion "github.com/hashicorp/packer/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var DockerPluginVersion *version.PluginVersion
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
DockerPluginVersion = version.InitializePluginVersion(
|
|
||||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// waitForDropletUnlocked waits for the Droplet to be unlocked to
|
|
||||||
// avoid "pending" errors when making state changes.
|
|
||||||
func waitForDropletUnlocked(
|
|
||||||
client *godo.Client, dropletId int, timeout time.Duration) error {
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
result := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
attempts := 0
|
|
||||||
for {
|
|
||||||
attempts += 1
|
|
||||||
|
|
||||||
log.Printf("[DEBUG] Checking droplet lock state... (attempt: %d)", attempts)
|
|
||||||
droplet, _, err := client.Droplets.Get(context.TODO(), dropletId)
|
|
||||||
if err != nil {
|
|
||||||
result <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !droplet.Locked {
|
|
||||||
result <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait 3 seconds in between
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
|
|
||||||
// Verify we shouldn't exit
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
// We finished, so just exit the goroutine
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// Keep going
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Printf("[DEBUG] Waiting for up to %d seconds for droplet to unlock", timeout/time.Second)
|
|
||||||
select {
|
|
||||||
case err := <-result:
|
|
||||||
return err
|
|
||||||
case <-time.After(timeout):
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Timeout while waiting to for droplet to unlock")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitForDropletState simply blocks until the droplet is in
|
|
||||||
// a state we expect, while eventually timing out.
|
|
||||||
func waitForDropletState(
|
|
||||||
desiredState string, dropletId int,
|
|
||||||
client *godo.Client, timeout time.Duration) error {
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
result := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
attempts := 0
|
|
||||||
for {
|
|
||||||
attempts += 1
|
|
||||||
|
|
||||||
log.Printf("Checking droplet status... (attempt: %d)", attempts)
|
|
||||||
droplet, _, err := client.Droplets.Get(context.TODO(), dropletId)
|
|
||||||
if err != nil {
|
|
||||||
result <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if droplet.Status == desiredState {
|
|
||||||
result <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait 3 seconds in between
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
|
|
||||||
// Verify we shouldn't exit
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
// We finished, so just exit the goroutine
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// Keep going
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Printf("Waiting for up to %d seconds for droplet to become %s", timeout/time.Second, desiredState)
|
|
||||||
select {
|
|
||||||
case err := <-result:
|
|
||||||
return err
|
|
||||||
case <-time.After(timeout):
|
|
||||||
err := fmt.Errorf("Timeout while waiting to for droplet to become '%s'", desiredState)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitForActionState simply blocks until the droplet action is in
|
|
||||||
// a state we expect, while eventually timing out.
|
|
||||||
func waitForActionState(
|
|
||||||
desiredState string, dropletId, actionId int,
|
|
||||||
client *godo.Client, timeout time.Duration) error {
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
result := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
attempts := 0
|
|
||||||
for {
|
|
||||||
attempts += 1
|
|
||||||
|
|
||||||
log.Printf("Checking action status... (attempt: %d)", attempts)
|
|
||||||
action, _, err := client.DropletActions.Get(context.TODO(), dropletId, actionId)
|
|
||||||
if err != nil {
|
|
||||||
result <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if action.Status == desiredState {
|
|
||||||
result <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait 3 seconds in between
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
|
|
||||||
// Verify we shouldn't exit
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
// We finished, so just exit the goroutine
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// Keep going
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Printf("Waiting for up to %d seconds for action to become %s", timeout/time.Second, desiredState)
|
|
||||||
select {
|
|
||||||
case err := <-result:
|
|
||||||
return err
|
|
||||||
case <-time.After(timeout):
|
|
||||||
err := fmt.Errorf("Timeout while waiting to for action to become '%s'", desiredState)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForImageState simply blocks until the image action is in
|
|
||||||
// a state we expect, while eventually timing out.
|
|
||||||
func WaitForImageState(
|
|
||||||
desiredState string, imageId, actionId int,
|
|
||||||
client *godo.Client, timeout time.Duration) error {
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
result := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
attempts := 0
|
|
||||||
for {
|
|
||||||
attempts += 1
|
|
||||||
|
|
||||||
log.Printf("Checking action status... (attempt: %d)", attempts)
|
|
||||||
action, _, err := client.ImageActions.Get(context.TODO(), imageId, actionId)
|
|
||||||
if err != nil {
|
|
||||||
result <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if action.Status == desiredState {
|
|
||||||
result <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait 3 seconds in between
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
|
|
||||||
// Verify we shouldn't exit
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
// We finished, so just exit the goroutine
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// Keep going
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Printf("Waiting for up to %d seconds for image transfer to become %s", timeout/time.Second, desiredState)
|
|
||||||
select {
|
|
||||||
case err := <-result:
|
|
||||||
return err
|
|
||||||
case <-time.After(timeout):
|
|
||||||
err := fmt.Errorf("Timeout while waiting to for image transfer to become '%s'", desiredState)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Artifact struct {
|
|
||||||
// The name of the snapshot
|
|
||||||
snapshotName string
|
|
||||||
|
|
||||||
// The ID of the image
|
|
||||||
snapshotId int
|
|
||||||
|
|
||||||
// The hcloudClient for making API calls
|
|
||||||
hcloudClient *hcloud.Client
|
|
||||||
|
|
||||||
// StateData should store data such as GeneratedData
|
|
||||||
// to be shared with post-processors
|
|
||||||
StateData map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Artifact) BuilderId() string {
|
|
||||||
return BuilderId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Artifact) Files() []string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Id() string {
|
|
||||||
return strconv.Itoa(a.snapshotId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) String() string {
|
|
||||||
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v)", a.snapshotName, a.snapshotId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
|
||||||
return a.StateData[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Destroy() error {
|
|
||||||
log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName)
|
|
||||||
_, err := a.hcloudClient.Image.Delete(context.TODO(), &hcloud.Image{ID: a.snapshotId})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestArtifact_Impl(t *testing.T) {
|
|
||||||
var _ packersdk.Artifact = (*Artifact)(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactId(t *testing.T) {
|
|
||||||
generatedData := make(map[string]interface{})
|
|
||||||
a := &Artifact{"packer-foobar", 42, nil, generatedData}
|
|
||||||
expected := "42"
|
|
||||||
|
|
||||||
if a.Id() != expected {
|
|
||||||
t.Fatalf("artifact ID should match: %v", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactString(t *testing.T) {
|
|
||||||
generatedData := make(map[string]interface{})
|
|
||||||
a := &Artifact{"packer-foobar", 42, nil, generatedData}
|
|
||||||
expected := "A snapshot was created: 'packer-foobar' (ID: 42)"
|
|
||||||
|
|
||||||
if a.String() != expected {
|
|
||||||
t.Fatalf("artifact string should match: %v", expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArtifactState_StateData(t *testing.T) {
|
|
||||||
expectedData := "this is the data"
|
|
||||||
artifact := &Artifact{
|
|
||||||
StateData: map[string]interface{}{"state_data": expectedData},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid state
|
|
||||||
result := artifact.State("state_data")
|
|
||||||
if result != expectedData {
|
|
||||||
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalid state
|
|
||||||
result = artifact.State("invalid_key")
|
|
||||||
if result != nil {
|
|
||||||
t.Fatalf("Bad: State should be nil for invalid state data name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nil StateData should not fail and should return nil
|
|
||||||
artifact = &Artifact{}
|
|
||||||
result = artifact.State("key")
|
|
||||||
if result != nil {
|
|
||||||
t.Fatalf("Bad: State should be nil for nil StateData")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The unique id for the builder
|
|
||||||
const BuilderId = "hcloud.builder"
|
|
||||||
|
|
||||||
type Builder struct {
|
|
||||||
config Config
|
|
||||||
runner multistep.Runner
|
|
||||||
hcloudClient *hcloud.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
var pluginVersion = "1.0.0"
|
|
||||||
|
|
||||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
||||||
warnings, errs := b.config.Prepare(raws...)
|
|
||||||
if errs != nil {
|
|
||||||
return nil, warnings, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
|
||||||
opts := []hcloud.ClientOption{
|
|
||||||
hcloud.WithToken(b.config.HCloudToken),
|
|
||||||
hcloud.WithEndpoint(b.config.Endpoint),
|
|
||||||
hcloud.WithPollInterval(b.config.PollInterval),
|
|
||||||
hcloud.WithApplication("hcloud-packer", pluginVersion),
|
|
||||||
}
|
|
||||||
b.hcloudClient = hcloud.NewClient(opts...)
|
|
||||||
// Set up the state
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("config", &b.config)
|
|
||||||
state.Put("hcloudClient", b.hcloudClient)
|
|
||||||
state.Put("hook", hook)
|
|
||||||
state.Put("ui", ui)
|
|
||||||
|
|
||||||
// Build the steps
|
|
||||||
steps := []multistep.Step{
|
|
||||||
&stepCreateSSHKey{
|
|
||||||
Debug: b.config.PackerDebug,
|
|
||||||
DebugKeyPath: fmt.Sprintf("ssh_key_%s.pem", b.config.PackerBuildName),
|
|
||||||
},
|
|
||||||
&stepCreateServer{},
|
|
||||||
&communicator.StepConnect{
|
|
||||||
Config: &b.config.Comm,
|
|
||||||
Host: getServerIP,
|
|
||||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
|
||||||
},
|
|
||||||
&commonsteps.StepProvision{},
|
|
||||||
&commonsteps.StepCleanupTempKeys{
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
},
|
|
||||||
&stepShutdownServer{},
|
|
||||||
&stepCreateSnapshot{},
|
|
||||||
}
|
|
||||||
// Run the steps
|
|
||||||
b.runner = commonsteps.NewRunner(steps, b.config.PackerConfig, ui)
|
|
||||||
b.runner.Run(ctx, state)
|
|
||||||
// If there was an error, return that
|
|
||||||
if rawErr, ok := state.GetOk("error"); ok {
|
|
||||||
return nil, rawErr.(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("snapshot_name"); !ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
artifact := &Artifact{
|
|
||||||
snapshotName: state.Get("snapshot_name").(string),
|
|
||||||
snapshotId: state.Get("snapshot_id").(int),
|
|
||||||
hcloudClient: b.hcloudClient,
|
|
||||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer/acctest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
Builder: &Builder{},
|
|
||||||
Template: testBuilderAccBasic,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAccPreCheck(t *testing.T) {
|
|
||||||
if v := os.Getenv("HCLOUD_TOKEN"); v == "" {
|
|
||||||
t.Fatal("HCLOUD_TOKEN must be set for acceptance tests")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const testBuilderAccBasic = `
|
|
||||||
{
|
|
||||||
"builders": [{
|
|
||||||
"type": "test",
|
|
||||||
"location": "nbg1",
|
|
||||||
"server_type": "cx11",
|
|
||||||
"image": "ubuntu-18.04",
|
|
||||||
"user_data": "",
|
|
||||||
"user_data_file": ""
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,imageFilter
|
|
||||||
|
|
||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
|
||||||
|
|
||||||
HCloudToken string `mapstructure:"token"`
|
|
||||||
Endpoint string `mapstructure:"endpoint"`
|
|
||||||
|
|
||||||
PollInterval time.Duration `mapstructure:"poll_interval"`
|
|
||||||
|
|
||||||
ServerName string `mapstructure:"server_name"`
|
|
||||||
Location string `mapstructure:"location"`
|
|
||||||
ServerType string `mapstructure:"server_type"`
|
|
||||||
Image string `mapstructure:"image"`
|
|
||||||
ImageFilter *imageFilter `mapstructure:"image_filter"`
|
|
||||||
|
|
||||||
SnapshotName string `mapstructure:"snapshot_name"`
|
|
||||||
SnapshotLabels map[string]string `mapstructure:"snapshot_labels"`
|
|
||||||
UserData string `mapstructure:"user_data"`
|
|
||||||
UserDataFile string `mapstructure:"user_data_file"`
|
|
||||||
SSHKeys []string `mapstructure:"ssh_keys"`
|
|
||||||
|
|
||||||
RescueMode string `mapstructure:"rescue"`
|
|
||||||
|
|
||||||
ctx interpolate.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
type imageFilter struct {
|
|
||||||
WithSelector []string `mapstructure:"with_selector"`
|
|
||||||
MostRecent bool `mapstructure:"most_recent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|
||||||
var md mapstructure.Metadata
|
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
|
||||||
Metadata: &md,
|
|
||||||
Interpolate: true,
|
|
||||||
InterpolateContext: &c.ctx,
|
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
|
||||||
Exclude: []string{
|
|
||||||
"run_command",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, raws...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defaults
|
|
||||||
if c.HCloudToken == "" {
|
|
||||||
c.HCloudToken = os.Getenv("HCLOUD_TOKEN")
|
|
||||||
}
|
|
||||||
if c.Endpoint == "" {
|
|
||||||
if os.Getenv("HCLOUD_ENDPOINT") != "" {
|
|
||||||
c.Endpoint = os.Getenv("HCLOUD_ENDPOINT")
|
|
||||||
} else {
|
|
||||||
c.Endpoint = hcloud.Endpoint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.PollInterval == 0 {
|
|
||||||
c.PollInterval = 500 * time.Millisecond
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SnapshotName == "" {
|
|
||||||
def, err := interpolate.Render("packer-{{timestamp}}", nil)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// Default to packer-{{ unix timestamp (utc) }}
|
|
||||||
c.SnapshotName = def
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.ServerName == "" {
|
|
||||||
// Default to packer-[time-ordered-uuid]
|
|
||||||
c.ServerName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, es...)
|
|
||||||
}
|
|
||||||
if c.HCloudToken == "" {
|
|
||||||
// Required configurations that will display errors if not set
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("token for auth must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Location == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("location is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.ServerType == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("server type is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Image == "" && c.ImageFilter == nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("image or image_filter is required"))
|
|
||||||
}
|
|
||||||
if c.ImageFilter != nil {
|
|
||||||
if len(c.ImageFilter.WithSelector) == 0 {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("image_filter.with_selector is required when specifying filter"))
|
|
||||||
} else if c.Image != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("only one of image or image_filter can be specified"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.UserData != "" && c.UserDataFile != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("only one of user_data or user_data_file can be specified"))
|
|
||||||
} else if c.UserDataFile != "" {
|
|
||||||
if _, err := os.Stat(c.UserDataFile); err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New(fmt.Sprintf("user_data_file not found: %s", c.UserDataFile)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return nil, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
packersdk.LogSecretFilter.Set(c.HCloudToken)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getServerIP(state multistep.StateBag) (string, error) {
|
|
||||||
return state.Get("server_ip").(string), nil
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatConfig struct {
|
|
||||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
|
||||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
|
||||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
|
||||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
|
||||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
|
||||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
|
||||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
|
||||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
|
||||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
|
||||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
|
||||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
|
||||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
|
||||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
|
||||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
|
||||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
|
||||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
|
||||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
|
||||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
|
||||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
|
||||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
|
||||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
|
||||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
|
||||||
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
|
||||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
|
||||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
|
||||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
|
||||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
|
||||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
|
||||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
|
||||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
|
||||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
|
||||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
|
||||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
|
||||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
|
||||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
|
||||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
|
||||||
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
|
||||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
|
||||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
|
||||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
|
||||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
|
||||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
|
||||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
|
||||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
|
||||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
|
||||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
|
||||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
|
||||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
|
||||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
|
||||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
|
||||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
|
||||||
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
|
||||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
|
||||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
|
||||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
|
||||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
|
||||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
|
||||||
HCloudToken *string `mapstructure:"token" cty:"token" hcl:"token"`
|
|
||||||
Endpoint *string `mapstructure:"endpoint" cty:"endpoint" hcl:"endpoint"`
|
|
||||||
PollInterval *string `mapstructure:"poll_interval" cty:"poll_interval" hcl:"poll_interval"`
|
|
||||||
ServerName *string `mapstructure:"server_name" cty:"server_name" hcl:"server_name"`
|
|
||||||
Location *string `mapstructure:"location" cty:"location" hcl:"location"`
|
|
||||||
ServerType *string `mapstructure:"server_type" cty:"server_type" hcl:"server_type"`
|
|
||||||
Image *string `mapstructure:"image" cty:"image" hcl:"image"`
|
|
||||||
ImageFilter *FlatimageFilter `mapstructure:"image_filter" cty:"image_filter" hcl:"image_filter"`
|
|
||||||
SnapshotName *string `mapstructure:"snapshot_name" cty:"snapshot_name" hcl:"snapshot_name"`
|
|
||||||
SnapshotLabels map[string]string `mapstructure:"snapshot_labels" cty:"snapshot_labels" hcl:"snapshot_labels"`
|
|
||||||
UserData *string `mapstructure:"user_data" cty:"user_data" hcl:"user_data"`
|
|
||||||
UserDataFile *string `mapstructure:"user_data_file" cty:"user_data_file" hcl:"user_data_file"`
|
|
||||||
SSHKeys []string `mapstructure:"ssh_keys" cty:"ssh_keys" hcl:"ssh_keys"`
|
|
||||||
RescueMode *string `mapstructure:"rescue" cty:"rescue" hcl:"rescue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatConfig.
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a Config.
|
|
||||||
// This spec is used by HCL to read the fields of Config.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
|
||||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
|
||||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
|
||||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
|
||||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
|
||||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
|
||||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
|
||||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
|
||||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
|
||||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
|
||||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
|
||||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
|
||||||
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
|
||||||
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
|
||||||
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
|
||||||
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
|
||||||
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
|
||||||
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
|
||||||
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
|
||||||
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
|
||||||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
|
||||||
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
|
||||||
"endpoint": &hcldec.AttrSpec{Name: "endpoint", Type: cty.String, Required: false},
|
|
||||||
"poll_interval": &hcldec.AttrSpec{Name: "poll_interval", Type: cty.String, Required: false},
|
|
||||||
"server_name": &hcldec.AttrSpec{Name: "server_name", Type: cty.String, Required: false},
|
|
||||||
"location": &hcldec.AttrSpec{Name: "location", Type: cty.String, Required: false},
|
|
||||||
"server_type": &hcldec.AttrSpec{Name: "server_type", Type: cty.String, Required: false},
|
|
||||||
"image": &hcldec.AttrSpec{Name: "image", Type: cty.String, Required: false},
|
|
||||||
"image_filter": &hcldec.BlockSpec{TypeName: "image_filter", Nested: hcldec.ObjectSpec((*FlatimageFilter)(nil).HCL2Spec())},
|
|
||||||
"snapshot_name": &hcldec.AttrSpec{Name: "snapshot_name", Type: cty.String, Required: false},
|
|
||||||
"snapshot_labels": &hcldec.AttrSpec{Name: "snapshot_labels", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
|
||||||
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_keys": &hcldec.AttrSpec{Name: "ssh_keys", Type: cty.List(cty.String), Required: false},
|
|
||||||
"rescue": &hcldec.AttrSpec{Name: "rescue", Type: cty.String, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatimageFilter is an auto-generated flat version of imageFilter.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatimageFilter struct {
|
|
||||||
WithSelector []string `mapstructure:"with_selector" cty:"with_selector" hcl:"with_selector"`
|
|
||||||
MostRecent *bool `mapstructure:"most_recent" cty:"most_recent" hcl:"most_recent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatimageFilter.
|
|
||||||
// FlatimageFilter is an auto-generated flat version of imageFilter.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*imageFilter) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatimageFilter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a imageFilter.
|
|
||||||
// This spec is used by HCL to read the fields of imageFilter.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatimageFilter.
|
|
||||||
func (*FlatimageFilter) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"with_selector": &hcldec.AttrSpec{Name: "with_selector", Type: cty.List(cty.String), Required: false},
|
|
||||||
"most_recent": &hcldec.AttrSpec{Name: "most_recent", Type: cty.Bool, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateServer struct {
|
|
||||||
serverId int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
sshKeyId := state.Get("ssh_key_id").(int)
|
|
||||||
|
|
||||||
// Create the server based on configuration
|
|
||||||
ui.Say("Creating server...")
|
|
||||||
|
|
||||||
userData := c.UserData
|
|
||||||
if c.UserDataFile != "" {
|
|
||||||
contents, err := ioutil.ReadFile(c.UserDataFile)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
userData = string(contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
sshKeys := []*hcloud.SSHKey{{ID: sshKeyId}}
|
|
||||||
for _, k := range c.SSHKeys {
|
|
||||||
sshKey, _, err := client.SSHKey.Get(ctx, k)
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", fmt.Errorf("Error fetching SSH key: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
if sshKey == nil {
|
|
||||||
state.Put("error", fmt.Errorf("Could not find key: %s", k))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
sshKeys = append(sshKeys, sshKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
var image *hcloud.Image
|
|
||||||
if c.Image != "" {
|
|
||||||
image = &hcloud.Image{Name: c.Image}
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
image, err = getImageWithSelectors(ctx, client, c)
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
ui.Message(fmt.Sprintf("Using image %s with ID %d", image.Description, image.ID))
|
|
||||||
}
|
|
||||||
|
|
||||||
serverCreateResult, _, err := client.Server.Create(ctx, hcloud.ServerCreateOpts{
|
|
||||||
Name: c.ServerName,
|
|
||||||
ServerType: &hcloud.ServerType{Name: c.ServerType},
|
|
||||||
Image: image,
|
|
||||||
SSHKeys: sshKeys,
|
|
||||||
Location: &hcloud.Location{Name: c.Location},
|
|
||||||
UserData: userData,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put("server_ip", serverCreateResult.Server.PublicNet.IPv4.IP.String())
|
|
||||||
// We use this in cleanup
|
|
||||||
s.serverId = serverCreateResult.Server.ID
|
|
||||||
|
|
||||||
// Store the server id for later
|
|
||||||
state.Put("server_id", serverCreateResult.Server.ID)
|
|
||||||
// instance_id is the generic term used so that users can have access to the
|
|
||||||
// instance id inside of the provisioners, used in step_provision.
|
|
||||||
state.Put("instance_id", serverCreateResult.Server.ID)
|
|
||||||
|
|
||||||
if err := waitForAction(ctx, client, serverCreateResult.Action); err != nil {
|
|
||||||
err := fmt.Errorf("Error creating server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
for _, nextAction := range serverCreateResult.NextActions {
|
|
||||||
if err := waitForAction(ctx, client, nextAction); err != nil {
|
|
||||||
err := fmt.Errorf("Error creating server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.RescueMode != "" {
|
|
||||||
ui.Say("Enabling Rescue Mode...")
|
|
||||||
rootPassword, err := setRescue(ctx, client, serverCreateResult.Server, c.RescueMode, sshKeys)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error enabling rescue mode: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
ui.Say("Reboot server...")
|
|
||||||
action, _, err := client.Server.Reset(ctx, serverCreateResult.Server)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error rebooting server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
if err := waitForAction(ctx, client, action); err != nil {
|
|
||||||
err := fmt.Errorf("Error rebooting server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
if c.RescueMode == "freebsd64" {
|
|
||||||
// We will set this only on freebsd
|
|
||||||
ui.Say("Using Root Password instead of SSH Keys...")
|
|
||||||
c.Comm.SSHPassword = rootPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
|
|
||||||
// If the serverID isn't there, we probably never created it
|
|
||||||
if s.serverId == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
// Destroy the server we just created
|
|
||||||
ui.Say("Destroying server...")
|
|
||||||
_, err := client.Server.Delete(context.TODO(), &hcloud.Server{ID: s.serverId})
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf(
|
|
||||||
"Error destroying server. Please destroy it manually: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setRescue(ctx context.Context, client *hcloud.Client, server *hcloud.Server, rescue string, sshKeys []*hcloud.SSHKey) (string, error) {
|
|
||||||
rescueChanged := false
|
|
||||||
if server.RescueEnabled {
|
|
||||||
rescueChanged = true
|
|
||||||
action, _, err := client.Server.DisableRescue(ctx, server)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if err := waitForAction(ctx, client, action); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rescue != "" {
|
|
||||||
rescueChanged = true
|
|
||||||
if rescue == "freebsd64" {
|
|
||||||
sshKeys = nil // freebsd64 doesn't allow ssh keys so we will remove them here
|
|
||||||
}
|
|
||||||
res, _, err := client.Server.EnableRescue(ctx, server, hcloud.ServerEnableRescueOpts{
|
|
||||||
Type: hcloud.ServerRescueType(rescue),
|
|
||||||
SSHKeys: sshKeys,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if err := waitForAction(ctx, client, res.Action); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return res.RootPassword, nil
|
|
||||||
}
|
|
||||||
if rescueChanged {
|
|
||||||
action, _, err := client.Server.Reset(ctx, server)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if err := waitForAction(ctx, client, action); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForAction(ctx context.Context, client *hcloud.Client, action *hcloud.Action) error {
|
|
||||||
_, errCh := client.Action.WatchProgress(ctx, action)
|
|
||||||
if err := <-errCh; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getImageWithSelectors(ctx context.Context, client *hcloud.Client, c *Config) (*hcloud.Image, error) {
|
|
||||||
var allImages []*hcloud.Image
|
|
||||||
|
|
||||||
var selector = strings.Join(c.ImageFilter.WithSelector, ",")
|
|
||||||
opts := hcloud.ImageListOpts{
|
|
||||||
ListOpts: hcloud.ListOpts{LabelSelector: selector},
|
|
||||||
Status: []hcloud.ImageStatus{hcloud.ImageStatusAvailable},
|
|
||||||
}
|
|
||||||
|
|
||||||
allImages, err := client.Image.AllWithOpts(ctx, opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(allImages) == 0 {
|
|
||||||
return nil, fmt.Errorf("no image found for selector %q", selector)
|
|
||||||
}
|
|
||||||
if len(allImages) > 1 {
|
|
||||||
if !c.ImageFilter.MostRecent {
|
|
||||||
return nil, fmt.Errorf("more than one image found for selector %q", selector)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(allImages, func(i, j int) bool {
|
|
||||||
return allImages[i].Created.After(allImages[j].Created)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return allImages[0], nil
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateSnapshot struct{}
|
|
||||||
|
|
||||||
func (s *stepCreateSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
serverID := state.Get("server_id").(int)
|
|
||||||
|
|
||||||
ui.Say("Creating snapshot ...")
|
|
||||||
ui.Say("This can take some time")
|
|
||||||
result, _, err := client.Server.CreateImage(ctx, &hcloud.Server{ID: serverID}, &hcloud.ServerCreateImageOpts{
|
|
||||||
Type: hcloud.ImageTypeSnapshot,
|
|
||||||
Labels: c.SnapshotLabels,
|
|
||||||
Description: hcloud.String(c.SnapshotName),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put("snapshot_id", result.Image.ID)
|
|
||||||
state.Put("snapshot_name", c.SnapshotName)
|
|
||||||
_, errCh := client.Action.WatchProgress(ctx, result.Action)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case err1 := <-errCh:
|
|
||||||
if err1 == nil {
|
|
||||||
return multistep.ActionContinue
|
|
||||||
} else {
|
|
||||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateSnapshot) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateSSHKey struct {
|
|
||||||
Debug bool
|
|
||||||
DebugKeyPath string
|
|
||||||
|
|
||||||
keyId int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
c := state.Get("config").(*Config)
|
|
||||||
ui.Say("Creating temporary ssh key for server...")
|
|
||||||
|
|
||||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Error generating RSA key: %s", err))
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
// ASN.1 DER encoded form
|
|
||||||
privDER := x509.MarshalPKCS1PrivateKey(priv)
|
|
||||||
privBLK := pem.Block{
|
|
||||||
Type: "RSA PRIVATE KEY",
|
|
||||||
Headers: nil,
|
|
||||||
Bytes: privDER,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the private key in the config for later
|
|
||||||
c.Comm.SSHPrivateKey = pem.EncodeToMemory(&privBLK)
|
|
||||||
|
|
||||||
// Marshal the public key into SSH compatible format
|
|
||||||
pub, err := ssh.NewPublicKey(&priv.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Error generating public key: %s", err))
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
pubSSHFormat := string(ssh.MarshalAuthorizedKey(pub))
|
|
||||||
|
|
||||||
// The name of the public key on the Hetzner Cloud
|
|
||||||
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
|
||||||
|
|
||||||
// Create the key!
|
|
||||||
key, _, err := client.SSHKey.Create(ctx, hcloud.SSHKeyCreateOpts{
|
|
||||||
Name: name,
|
|
||||||
PublicKey: pubSSHFormat,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use this to check cleanup
|
|
||||||
s.keyId = key.ID
|
|
||||||
|
|
||||||
log.Printf("temporary ssh key name: %s", name)
|
|
||||||
|
|
||||||
// Remember some state for the future
|
|
||||||
state.Put("ssh_key_id", key.ID)
|
|
||||||
|
|
||||||
// If we're in debug mode, output the private key to the working directory.
|
|
||||||
if s.Debug {
|
|
||||||
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
|
||||||
f, err := os.Create(s.DebugKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
// Write the key out
|
|
||||||
if _, err := f.Write(pem.EncodeToMemory(&privBLK)); err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chmod it so that it is SSH ready
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
if err := f.Chmod(0600); err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
|
|
||||||
// If no key id is set, then we never created it, so just return
|
|
||||||
if s.keyId == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say("Deleting temporary ssh key...")
|
|
||||||
_, err := client.SSHKey.Delete(context.TODO(), &hcloud.SSHKey{ID: s.keyId})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error cleaning up ssh key: %s", err)
|
|
||||||
ui.Error(fmt.Sprintf(
|
|
||||||
"Error cleaning up ssh key. Please delete the key manually: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package hcloud
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepShutdownServer struct{}
|
|
||||||
|
|
||||||
func (s *stepShutdownServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("hcloudClient").(*hcloud.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
serverID := state.Get("server_id").(int)
|
|
||||||
|
|
||||||
ui.Say("Shutting down server...")
|
|
||||||
|
|
||||||
action, _, err := client.Server.Shutdown(ctx, &hcloud.Server{ID: serverID})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error stopping server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
_, errCh := client.Action.WatchProgress(ctx, action)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case err1 := <-errCh:
|
|
||||||
if err1 == nil {
|
|
||||||
return multistep.ActionContinue
|
|
||||||
} else {
|
|
||||||
err := fmt.Errorf("Error stopping server: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepShutdownServer) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
|
||||||
packerVersion "github.com/hashicorp/packer/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var HcloudPluginVersion *version.PluginVersion
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
HcloudPluginVersion = version.InitializePluginVersion(
|
|
||||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Artifact struct {
|
|
||||||
dir string
|
|
||||||
f []string
|
|
||||||
// StateData should store data such as GeneratedData
|
|
||||||
// to be shared with post-processors
|
|
||||||
StateData map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Artifact) BuilderId() string {
|
|
||||||
return BuilderId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Files() []string {
|
|
||||||
return a.f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Artifact) Id() string {
|
|
||||||
return "VM"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) String() string {
|
|
||||||
return fmt.Sprintf("VM files in directory: %s", a.dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
|
||||||
return a.StateData[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Destroy() error {
|
|
||||||
return os.RemoveAll(a.dir)
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The unique ID for this builder
|
|
||||||
const BuilderId = "ustream.lxc"
|
|
||||||
|
|
||||||
type wrappedCommandTemplate struct {
|
|
||||||
Command string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Builder struct {
|
|
||||||
config Config
|
|
||||||
runner multistep.Runner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
||||||
errs := b.config.Prepare(raws...)
|
|
||||||
if errs != nil {
|
|
||||||
return nil, nil, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
|
||||||
wrappedCommand := func(command string) (string, error) {
|
|
||||||
b.config.ctx.Data = &wrappedCommandTemplate{Command: command}
|
|
||||||
return interpolate.Render(b.config.CommandWrapper, &b.config.ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
steps := []multistep.Step{
|
|
||||||
new(stepPrepareOutputDir),
|
|
||||||
new(stepLxcCreate),
|
|
||||||
&StepWaitInit{
|
|
||||||
WaitTimeout: b.config.InitTimeout,
|
|
||||||
},
|
|
||||||
new(StepProvision),
|
|
||||||
new(stepExport),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the state bag
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("config", &b.config)
|
|
||||||
state.Put("hook", hook)
|
|
||||||
state.Put("ui", ui)
|
|
||||||
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
|
|
||||||
|
|
||||||
// Run
|
|
||||||
b.runner = commonsteps.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
|
||||||
b.runner.Run(ctx, state)
|
|
||||||
|
|
||||||
// If there was an error, return that
|
|
||||||
if rawErr, ok := state.GetOk("error"); ok {
|
|
||||||
return nil, rawErr.(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile the artifact list
|
|
||||||
files := make([]string, 0, 5)
|
|
||||||
visit := func(path string, info os.FileInfo, err error) error {
|
|
||||||
if !info.IsDir() {
|
|
||||||
files = append(files, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := filepath.Walk(b.config.OutputDir, visit); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
artifact := &Artifact{
|
|
||||||
dir: b.config.OutputDir,
|
|
||||||
f: files,
|
|
||||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testConfig() map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"config_file": "builder_test.go",
|
|
||||||
"template_name": "debian",
|
|
||||||
"template_environment_vars": "SUITE=jessie",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilder_Foo(t *testing.T) {
|
|
||||||
if os.Getenv("PACKER_ACC") == "" {
|
|
||||||
t.Skip("This test is only run with PACKER_ACC=1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_ConfigFile(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
// Good
|
|
||||||
config := testConfig()
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bad, missing config file
|
|
||||||
config = testConfig()
|
|
||||||
delete(config, "config_file")
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &Builder{}
|
|
||||||
if _, ok := raw.(packersdk.Builder); !ok {
|
|
||||||
t.Fatalf("Builder should be a builder")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CommandWrapper is a type that given a command, will possibly modify that
|
|
||||||
// command in-flight. This might return an error.
|
|
||||||
type CommandWrapper func(string) (string, error)
|
|
||||||
|
|
||||||
// ShellCommand takes a command string and returns an *exec.Cmd to execute
|
|
||||||
// it within the context of a shell (/bin/sh).
|
|
||||||
func ShellCommand(command string) *exec.Cmd {
|
|
||||||
return exec.Command("/bin/sh", "-c", command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunCommand(args ...string) error {
|
|
||||||
var stdout, stderr bytes.Buffer
|
|
||||||
|
|
||||||
log.Printf("Executing args: %#v", args)
|
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
|
||||||
cmd.Stdout = &stdout
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
err := cmd.Run()
|
|
||||||
|
|
||||||
stdoutString := strings.TrimSpace(stdout.String())
|
|
||||||
stderrString := strings.TrimSpace(stderr.String())
|
|
||||||
|
|
||||||
if _, ok := err.(*exec.ExitError); ok {
|
|
||||||
err = fmt.Errorf("Command error: %s", stderrString)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("stdout: %s", stdoutString)
|
|
||||||
log.Printf("stderr: %s", stderrString)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/tmp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LxcAttachCommunicator struct {
|
|
||||||
RootFs string
|
|
||||||
ContainerName string
|
|
||||||
AttachOptions []string
|
|
||||||
CmdWrapper CommandWrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LxcAttachCommunicator) Start(ctx context.Context, cmd *packersdk.RemoteCmd) error {
|
|
||||||
localCmd, err := c.Execute(cmd.Command)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
localCmd.Stdin = cmd.Stdin
|
|
||||||
localCmd.Stdout = cmd.Stdout
|
|
||||||
localCmd.Stderr = cmd.Stderr
|
|
||||||
if err := localCmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
exitStatus := 0
|
|
||||||
if err := localCmd.Wait(); err != nil {
|
|
||||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
||||||
exitStatus = 1
|
|
||||||
|
|
||||||
// There is no process-independent way to get the REAL
|
|
||||||
// exit status so we just try to go deeper.
|
|
||||||
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
|
||||||
exitStatus = status.ExitStatus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf(
|
|
||||||
"lxc-attach execution exited with '%d': '%s'",
|
|
||||||
exitStatus, cmd.Command)
|
|
||||||
cmd.SetExited(exitStatus)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LxcAttachCommunicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
|
|
||||||
log.Printf("Uploading to rootfs: %s", dst)
|
|
||||||
tf, err := tmp.File("packer-lxc-attach")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error uploading file to rootfs: %s", err)
|
|
||||||
}
|
|
||||||
defer os.Remove(tf.Name())
|
|
||||||
io.Copy(tf, r)
|
|
||||||
|
|
||||||
attachCommand := []string{"cat", "%s", " | ", "lxc-attach"}
|
|
||||||
attachCommand = append(attachCommand, c.AttachOptions...)
|
|
||||||
attachCommand = append(attachCommand, []string{"--name", "%s", "--", "/bin/sh -c \"/bin/cat > %s\""}...)
|
|
||||||
|
|
||||||
cpCmd, err := c.CmdWrapper(fmt.Sprintf(strings.Join(attachCommand, " "), tf.Name(), c.ContainerName, dst))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi != nil {
|
|
||||||
tfDir := filepath.Dir(tf.Name())
|
|
||||||
// rename tempfile to match original file name. This makes sure that if file is being
|
|
||||||
// moved into a directory, the filename is preserved instead of a temp name.
|
|
||||||
adjustedTempName := filepath.Join(tfDir, (*fi).Name())
|
|
||||||
mvCmd, err := c.CmdWrapper(fmt.Sprintf("mv %s %s", tf.Name(), adjustedTempName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.Remove(adjustedTempName)
|
|
||||||
ShellCommand(mvCmd).Run()
|
|
||||||
// change cpCmd to use new file name as source
|
|
||||||
cpCmd, err = c.CmdWrapper(fmt.Sprintf(strings.Join(attachCommand, " "), adjustedTempName, c.ContainerName, dst))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Running copy command: %s", dst)
|
|
||||||
|
|
||||||
return ShellCommand(cpCmd).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LxcAttachCommunicator) UploadDir(dst string, src string, exclude []string) error {
|
|
||||||
// TODO: remove any file copied if it appears in `exclude`
|
|
||||||
dest := filepath.Join(c.RootFs, dst)
|
|
||||||
log.Printf("Uploading directory '%s' to rootfs '%s'", src, dest)
|
|
||||||
cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp -R %s/. %s", src, dest))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ShellCommand(cpCmd).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LxcAttachCommunicator) Download(src string, w io.Writer) error {
|
|
||||||
src = filepath.Join(c.RootFs, src)
|
|
||||||
log.Printf("Downloading from rootfs dir: %s", src)
|
|
||||||
f, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(w, f); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LxcAttachCommunicator) DownloadDir(src string, dst string, exclude []string) error {
|
|
||||||
return fmt.Errorf("DownloadDir is not implemented for lxc")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LxcAttachCommunicator) Execute(commandString string) (*exec.Cmd, error) {
|
|
||||||
log.Printf("Executing with lxc-attach in container: %s %s %s", c.ContainerName, c.RootFs, commandString)
|
|
||||||
|
|
||||||
attachCommand := []string{"lxc-attach"}
|
|
||||||
attachCommand = append(attachCommand, c.AttachOptions...)
|
|
||||||
attachCommand = append(attachCommand, []string{"--name", "%s", "--", "/bin/sh -c \"%s\""}...)
|
|
||||||
|
|
||||||
command, err := c.CmdWrapper(
|
|
||||||
fmt.Sprintf(strings.Join(attachCommand, " "), c.ContainerName, commandString))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
localCmd := ShellCommand(command)
|
|
||||||
log.Printf("Executing lxc-attach: %s %#v", localCmd.Path, localCmd.Args)
|
|
||||||
|
|
||||||
return localCmd, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LxcAttachCommunicator) CheckInit() (string, error) {
|
|
||||||
log.Printf("Debug runlevel exec")
|
|
||||||
localCmd, err := c.Execute("/sbin/runlevel")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
pr, _ := localCmd.StdoutPipe()
|
|
||||||
if err = localCmd.Start(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := ioutil.ReadAll(pr)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = localCmd.Wait()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.TrimSpace(string(output)), nil
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCommunicator_ImplementsCommunicator(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &LxcAttachCommunicator{}
|
|
||||||
if _, ok := raw.(packersdk.Communicator); !ok {
|
|
||||||
t.Fatalf("Communicator should be a communicator")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
//go:generate packer-sdc struct-markdown
|
|
||||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
|
||||||
|
|
||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
|
||||||
// The path to the lxc configuration file.
|
|
||||||
ConfigFile string `mapstructure:"config_file" required:"true"`
|
|
||||||
// The directory in which to save the exported
|
|
||||||
// tar.gz. Defaults to `output-<BuildName>` in the current directory.
|
|
||||||
OutputDir string `mapstructure:"output_directory" required:"false"`
|
|
||||||
// The name of the LXC container. Usually stored
|
|
||||||
// in `/var/lib/lxc/containers/<container_name>`. Defaults to
|
|
||||||
// `packer-<BuildName>`.
|
|
||||||
ContainerName string `mapstructure:"container_name" required:"false"`
|
|
||||||
// Allows you to specify a wrapper command, such
|
|
||||||
// as ssh so you can execute packer builds on a remote host. Defaults to
|
|
||||||
// `{{.Command}}`; i.e. no wrapper.
|
|
||||||
CommandWrapper string `mapstructure:"command_wrapper" required:"false"`
|
|
||||||
// The timeout in seconds to wait for the the
|
|
||||||
// container to start. Defaults to 20 seconds.
|
|
||||||
InitTimeout time.Duration `mapstructure:"init_timeout" required:"false"`
|
|
||||||
|
|
||||||
// Options to pass to lxc-create. For
|
|
||||||
// instance, you can specify a custom LXC container configuration file with
|
|
||||||
// ["-f", "/path/to/lxc.conf"]. Defaults to []. See man 1 lxc-create for
|
|
||||||
// available options.
|
|
||||||
CreateOptions []string `mapstructure:"create_options" required:"false"`
|
|
||||||
// Options to pass to lxc-start. For
|
|
||||||
// instance, you can override parameters from the LXC container configuration
|
|
||||||
// file via ["--define", "KEY=VALUE"]. Defaults to []. See
|
|
||||||
// man 1 lxc-start for available options.
|
|
||||||
StartOptions []string `mapstructure:"start_options" required:"false"`
|
|
||||||
// Options to pass to lxc-attach. For
|
|
||||||
// instance, you can prevent the container from inheriting the host machine's
|
|
||||||
// environment by specifying ["--clear-env"]. Defaults to []. See
|
|
||||||
// man 1 lxc-attach for available options.
|
|
||||||
AttachOptions []string `mapstructure:"attach_options" required:"false"`
|
|
||||||
// The LXC template name to use.
|
|
||||||
Name string `mapstructure:"template_name" required:"true"`
|
|
||||||
// Options to pass to the given
|
|
||||||
// lxc-template command, usually located in
|
|
||||||
// `/usr/share/lxc/templates/lxc-<template_name>`. Note: This gets passed as
|
|
||||||
// ARGV to the template command. Ensure you have an array of strings, as a
|
|
||||||
// single string with spaces probably won't work. Defaults to [].
|
|
||||||
Parameters []string `mapstructure:"template_parameters" required:"false"`
|
|
||||||
// Environmental variables to
|
|
||||||
// use to build the template with.
|
|
||||||
EnvVars []string `mapstructure:"template_environment_vars" required:"true"`
|
|
||||||
// The minimum run level to wait for the
|
|
||||||
// container to reach. Note some distributions (Ubuntu) simulate run levels
|
|
||||||
// and may report 5 rather than 3.
|
|
||||||
TargetRunlevel int `mapstructure:"target_runlevel" required:"false"`
|
|
||||||
|
|
||||||
ctx interpolate.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) error {
|
|
||||||
|
|
||||||
var md mapstructure.Metadata
|
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
|
||||||
Metadata: &md,
|
|
||||||
Interpolate: true,
|
|
||||||
}, raws...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accumulate any errors
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
|
|
||||||
if c.OutputDir == "" {
|
|
||||||
c.OutputDir = fmt.Sprintf("output-%s", c.PackerBuildName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.ContainerName == "" {
|
|
||||||
c.ContainerName = fmt.Sprintf("packer-%s", c.PackerBuildName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.TargetRunlevel == 0 {
|
|
||||||
c.TargetRunlevel = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.CommandWrapper == "" {
|
|
||||||
c.CommandWrapper = "{{.Command}}"
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.InitTimeout == 0 {
|
|
||||||
c.InitTimeout = 20 * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(c.ConfigFile); os.IsNotExist(err) {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("LXC Config file appears to be missing: %s", c.ConfigFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatConfig struct {
|
|
||||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
|
||||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
|
||||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
|
||||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
|
||||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
|
||||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
|
||||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
|
||||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
|
||||||
ConfigFile *string `mapstructure:"config_file" required:"true" cty:"config_file" hcl:"config_file"`
|
|
||||||
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"`
|
|
||||||
ContainerName *string `mapstructure:"container_name" required:"false" cty:"container_name" hcl:"container_name"`
|
|
||||||
CommandWrapper *string `mapstructure:"command_wrapper" required:"false" cty:"command_wrapper" hcl:"command_wrapper"`
|
|
||||||
InitTimeout *string `mapstructure:"init_timeout" required:"false" cty:"init_timeout" hcl:"init_timeout"`
|
|
||||||
CreateOptions []string `mapstructure:"create_options" required:"false" cty:"create_options" hcl:"create_options"`
|
|
||||||
StartOptions []string `mapstructure:"start_options" required:"false" cty:"start_options" hcl:"start_options"`
|
|
||||||
AttachOptions []string `mapstructure:"attach_options" required:"false" cty:"attach_options" hcl:"attach_options"`
|
|
||||||
Name *string `mapstructure:"template_name" required:"true" cty:"template_name" hcl:"template_name"`
|
|
||||||
Parameters []string `mapstructure:"template_parameters" required:"false" cty:"template_parameters" hcl:"template_parameters"`
|
|
||||||
EnvVars []string `mapstructure:"template_environment_vars" required:"true" cty:"template_environment_vars" hcl:"template_environment_vars"`
|
|
||||||
TargetRunlevel *int `mapstructure:"target_runlevel" required:"false" cty:"target_runlevel" hcl:"target_runlevel"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatConfig.
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a Config.
|
|
||||||
// This spec is used by HCL to read the fields of Config.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
|
||||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
|
||||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
|
||||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
|
||||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
|
||||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
|
||||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
|
||||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
|
||||||
"config_file": &hcldec.AttrSpec{Name: "config_file", Type: cty.String, Required: false},
|
|
||||||
"output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false},
|
|
||||||
"container_name": &hcldec.AttrSpec{Name: "container_name", Type: cty.String, Required: false},
|
|
||||||
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
|
|
||||||
"init_timeout": &hcldec.AttrSpec{Name: "init_timeout", Type: cty.String, Required: false},
|
|
||||||
"create_options": &hcldec.AttrSpec{Name: "create_options", Type: cty.List(cty.String), Required: false},
|
|
||||||
"start_options": &hcldec.AttrSpec{Name: "start_options", Type: cty.List(cty.String), Required: false},
|
|
||||||
"attach_options": &hcldec.AttrSpec{Name: "attach_options", Type: cty.List(cty.String), Required: false},
|
|
||||||
"template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false},
|
|
||||||
"template_parameters": &hcldec.AttrSpec{Name: "template_parameters", Type: cty.List(cty.String), Required: false},
|
|
||||||
"template_environment_vars": &hcldec.AttrSpec{Name: "template_environment_vars", Type: cty.List(cty.String), Required: false},
|
|
||||||
"target_runlevel": &hcldec.AttrSpec{Name: "target_runlevel", Type: cty.Number, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepExport struct{}
|
|
||||||
|
|
||||||
func (s *stepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
name := config.ContainerName
|
|
||||||
|
|
||||||
lxc_dir := "/var/lib/lxc"
|
|
||||||
user, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
log.Print("Cannot find current user. Falling back to /var/lib/lxc...")
|
|
||||||
}
|
|
||||||
if user.Uid != "0" && user.HomeDir != "" {
|
|
||||||
lxc_dir = filepath.Join(user.HomeDir, ".local", "share", "lxc")
|
|
||||||
}
|
|
||||||
|
|
||||||
containerDir := filepath.Join(lxc_dir, name)
|
|
||||||
outputPath := filepath.Join(config.OutputDir, "rootfs.tar.gz")
|
|
||||||
configFilePath := filepath.Join(config.OutputDir, "lxc-config")
|
|
||||||
|
|
||||||
configFile, err := os.Create(configFilePath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating config file: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
originalConfigFile, err := os.Open(config.ConfigFile)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error opening config file: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(configFile, originalConfigFile)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("error copying file %s: %v", config.ConfigFile, err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
commands := make([][]string, 3)
|
|
||||||
commands[0] = []string{
|
|
||||||
"lxc-stop", "--name", name,
|
|
||||||
}
|
|
||||||
commands[1] = []string{
|
|
||||||
"tar", "-C", containerDir, "--numeric-owner", "--anchored", "--exclude=./rootfs/dev/log", "-czf", outputPath, "./rootfs",
|
|
||||||
}
|
|
||||||
commands[2] = []string{
|
|
||||||
"chmod", "+x", configFilePath,
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Exporting container...")
|
|
||||||
for _, command := range commands {
|
|
||||||
err := RunCommand(command...)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error exporting container: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepExport) Cleanup(state multistep.StateBag) {}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os/user"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepLxcCreate struct{}
|
|
||||||
|
|
||||||
func (s *stepLxcCreate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
name := config.ContainerName
|
|
||||||
|
|
||||||
// TODO: read from env
|
|
||||||
lxc_dir := "/var/lib/lxc"
|
|
||||||
user, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
log.Print("Cannot find current user. Falling back to /var/lib/lxc...")
|
|
||||||
}
|
|
||||||
if user.Uid != "0" && user.HomeDir != "" {
|
|
||||||
lxc_dir = filepath.Join(user.HomeDir, ".local", "share", "lxc")
|
|
||||||
}
|
|
||||||
rootfs := filepath.Join(lxc_dir, name, "rootfs")
|
|
||||||
|
|
||||||
if config.PackerForce {
|
|
||||||
s.Cleanup(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
commands := make([][]string, 3)
|
|
||||||
commands[0] = append(commands[0], "env")
|
|
||||||
commands[0] = append(commands[0], config.EnvVars...)
|
|
||||||
commands[0] = append(commands[0], "lxc-create")
|
|
||||||
commands[0] = append(commands[0], config.CreateOptions...)
|
|
||||||
commands[0] = append(commands[0], []string{"-n", name, "-t", config.Name, "--"}...)
|
|
||||||
commands[0] = append(commands[0], config.Parameters...)
|
|
||||||
// prevent tmp from being cleaned on boot, we put provisioning scripts there
|
|
||||||
// todo: wait for init to finish before moving on to provisioning instead of this
|
|
||||||
commands[1] = []string{"touch", filepath.Join(rootfs, "tmp", ".tmpfs")}
|
|
||||||
commands[2] = append([]string{"lxc-start"}, config.StartOptions...)
|
|
||||||
commands[2] = append(commands[2], []string{"-d", "--name", name}...)
|
|
||||||
|
|
||||||
ui.Say("Creating container...")
|
|
||||||
for _, command := range commands {
|
|
||||||
err := RunCommand(command...)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating container: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Put("mount_path", rootfs)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepLxcCreate) Cleanup(state multistep.StateBag) {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
command := []string{
|
|
||||||
"lxc-destroy", "-f", "-n", config.ContainerName,
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Unregistering and deleting virtual machine...")
|
|
||||||
if err := RunCommand(command...); err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting virtual machine: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepPrepareOutputDir struct{}
|
|
||||||
|
|
||||||
func (stepPrepareOutputDir) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce {
|
|
||||||
ui.Say("Deleting previous output directory...")
|
|
||||||
os.RemoveAll(config.OutputDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
|
|
||||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
|
||||||
_, halted := state.GetOk(multistep.StateHalted)
|
|
||||||
|
|
||||||
if cancelled || halted {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
ui.Say("Deleting output directory...")
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
err := os.RemoveAll(config.OutputDir)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Error removing output dir: %s", err)
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StepProvision provisions the instance within a chroot.
|
|
||||||
type StepProvision struct{}
|
|
||||||
|
|
||||||
func (s *StepProvision) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
hook := state.Get("hook").(packersdk.Hook)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
mountPath := state.Get("mount_path").(string)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
|
||||||
|
|
||||||
// Create our communicator
|
|
||||||
comm := &LxcAttachCommunicator{
|
|
||||||
ContainerName: config.ContainerName,
|
|
||||||
AttachOptions: config.AttachOptions,
|
|
||||||
RootFs: mountPath,
|
|
||||||
CmdWrapper: wrappedCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loads hook data from builder's state, if it has been set.
|
|
||||||
hookData := commonsteps.PopulateProvisionHookData(state)
|
|
||||||
|
|
||||||
// Update state generated_data with complete hookData
|
|
||||||
// to make them accessible by post-processors
|
|
||||||
state.Put("generated_data", hookData)
|
|
||||||
|
|
||||||
// Provision
|
|
||||||
log.Println("Running the provision hook")
|
|
||||||
if err := hook.Run(ctx, packersdk.HookProvision, ui, comm, hookData); err != nil {
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepProvision) Cleanup(state multistep.StateBag) {}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package lxc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StepWaitInit struct {
|
|
||||||
WaitTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepWaitInit) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
cancel := make(chan struct{})
|
|
||||||
waitDone := make(chan bool, 1)
|
|
||||||
go func() {
|
|
||||||
ui.Say("Waiting for container to finish init...")
|
|
||||||
err = s.waitForInit(state, cancel)
|
|
||||||
waitDone <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Printf("Waiting for container to finish init, up to timeout: %s", s.WaitTimeout)
|
|
||||||
timeout := time.After(s.WaitTimeout)
|
|
||||||
WaitLoop:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-waitDone:
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error waiting for container to finish init: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Container finished init!")
|
|
||||||
break WaitLoop
|
|
||||||
case <-timeout:
|
|
||||||
err := fmt.Errorf("Timeout waiting for container to finish init.")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
close(cancel)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
|
||||||
close(cancel)
|
|
||||||
log.Println("Interrupt detected, quitting waiting for container to finish init.")
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepWaitInit) Cleanup(multistep.StateBag) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepWaitInit) waitForInit(state multistep.StateBag, cancel <-chan struct{}) error {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
mountPath := state.Get("mount_path").(string)
|
|
||||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-cancel:
|
|
||||||
log.Println("Cancelled. Exiting loop.")
|
|
||||||
return errors.New("Wait cancelled")
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
}
|
|
||||||
|
|
||||||
comm := &LxcAttachCommunicator{
|
|
||||||
ContainerName: config.ContainerName,
|
|
||||||
AttachOptions: config.AttachOptions,
|
|
||||||
RootFs: mountPath,
|
|
||||||
CmdWrapper: wrappedCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
runlevel, _ := comm.CheckInit()
|
|
||||||
currentRunlevel := "unknown"
|
|
||||||
if arr := strings.Split(runlevel, " "); len(arr) >= 2 {
|
|
||||||
currentRunlevel = arr[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Current runlevel in container: '%s'", runlevel)
|
|
||||||
|
|
||||||
targetRunlevel := fmt.Sprintf("%d", config.TargetRunlevel)
|
|
||||||
if currentRunlevel == targetRunlevel {
|
|
||||||
log.Printf("Container finished init.")
|
|
||||||
break
|
|
||||||
} else if currentRunlevel > targetRunlevel {
|
|
||||||
log.Printf("Expected Runlevel %s, Got Runlevel %s, continuing", targetRunlevel, currentRunlevel)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
|
||||||
packerVersion "github.com/hashicorp/packer/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var LXCPluginVersion *version.PluginVersion
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
LXCPluginVersion = version.InitializePluginVersion(
|
|
||||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package lxd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Artifact struct {
|
|
||||||
id string
|
|
||||||
|
|
||||||
// StateData should store data such as GeneratedData
|
|
||||||
// to be shared with post-processors
|
|
||||||
StateData map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Artifact) BuilderId() string {
|
|
||||||
return BuilderId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Files() []string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Id() string {
|
|
||||||
return a.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) String() string {
|
|
||||||
return fmt.Sprintf("image: %s", a.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
|
||||||
return a.StateData[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Destroy() error {
|
|
||||||
_, err := LXDCommand("image", "delete", a.id)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package lxd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The unique ID for this builder
|
|
||||||
const BuilderId = "lxd"
|
|
||||||
|
|
||||||
type wrappedCommandTemplate struct {
|
|
||||||
Command string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Builder struct {
|
|
||||||
config Config
|
|
||||||
runner multistep.Runner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
||||||
errs := b.config.Prepare(raws...)
|
|
||||||
if errs != nil {
|
|
||||||
return nil, nil, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
|
||||||
wrappedCommand := func(command string) (string, error) {
|
|
||||||
b.config.ctx.Data = &wrappedCommandTemplate{Command: command}
|
|
||||||
return interpolate.Render(b.config.CommandWrapper, &b.config.ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
steps := []multistep.Step{
|
|
||||||
&stepLxdLaunch{},
|
|
||||||
&StepProvision{},
|
|
||||||
&stepPublish{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the state bag
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("config", &b.config)
|
|
||||||
state.Put("hook", hook)
|
|
||||||
state.Put("ui", ui)
|
|
||||||
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
|
|
||||||
|
|
||||||
// Run
|
|
||||||
b.runner = commonsteps.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
|
||||||
b.runner.Run(ctx, state)
|
|
||||||
|
|
||||||
// If there was an error, return that
|
|
||||||
if rawErr, ok := state.GetOk("error"); ok {
|
|
||||||
return nil, rawErr.(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
artifact := &Artifact{
|
|
||||||
id: state.Get("imageFingerprint").(string),
|
|
||||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package lxd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testConfig() map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"output_image": "foo",
|
|
||||||
"image": "bar",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilder_Foo(t *testing.T) {
|
|
||||||
if os.Getenv("PACKER_ACC") == "" {
|
|
||||||
t.Skip("This test is only run with PACKER_ACC=1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_ConfigFile(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
// Good
|
|
||||||
config := testConfig()
|
|
||||||
_, warnings, err := b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Good, remote image
|
|
||||||
config = testConfig()
|
|
||||||
config["image"] = "remote:bar"
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Good, remote output image
|
|
||||||
config = testConfig()
|
|
||||||
config["output_image"] = "remote:foo"
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bad, missing image name
|
|
||||||
config = testConfig()
|
|
||||||
delete(config, "image")
|
|
||||||
b = Builder{}
|
|
||||||
_, warnings, err = b.Prepare(config)
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warnings)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &Builder{}
|
|
||||||
if _, ok := raw.(packersdk.Builder); !ok {
|
|
||||||
t.Fatalf("Builder should be a builder")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package lxd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CommandWrapper is a type that given a command, will possibly modify that
|
|
||||||
// command in-flight. This might return an error.
|
|
||||||
type CommandWrapper func(string) (string, error)
|
|
||||||
|
|
||||||
// ShellCommand takes a command string and returns an *exec.Cmd to execute
|
|
||||||
// it within the context of a shell (/bin/sh).
|
|
||||||
func ShellCommand(command string) *exec.Cmd {
|
|
||||||
return exec.Command("/bin/sh", "-c", command)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Yeah...LXD calls `lxc` because the command line is different between the
|
|
||||||
// packages. This should also avoid a naming collision between the LXC builder.
|
|
||||||
func LXDCommand(args ...string) (string, error) {
|
|
||||||
var stdout, stderr bytes.Buffer
|
|
||||||
|
|
||||||
log.Printf("Executing lxc command: %#v", args)
|
|
||||||
cmd := exec.Command("lxc", args...)
|
|
||||||
cmd.Stdout = &stdout
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
err := cmd.Run()
|
|
||||||
|
|
||||||
stdoutString := strings.TrimSpace(stdout.String())
|
|
||||||
stderrString := strings.TrimSpace(stderr.String())
|
|
||||||
|
|
||||||
if _, ok := err.(*exec.ExitError); ok {
|
|
||||||
err = fmt.Errorf("LXD command error: %s", stderrString)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("stdout: %s", stdoutString)
|
|
||||||
log.Printf("stderr: %s", stderrString)
|
|
||||||
|
|
||||||
return stdoutString, err
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
package lxd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Communicator struct {
|
|
||||||
ContainerName string
|
|
||||||
CmdWrapper CommandWrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Communicator) Start(ctx context.Context, cmd *packersdk.RemoteCmd) error {
|
|
||||||
localCmd, err := c.Execute(cmd.Command)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
localCmd.Stdin = cmd.Stdin
|
|
||||||
localCmd.Stdout = cmd.Stdout
|
|
||||||
localCmd.Stderr = cmd.Stderr
|
|
||||||
if err := localCmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
exitStatus := 0
|
|
||||||
if err := localCmd.Wait(); err != nil {
|
|
||||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
||||||
exitStatus = 1
|
|
||||||
|
|
||||||
// There is no process-independent way to get the REAL
|
|
||||||
// exit status so we just try to go deeper.
|
|
||||||
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
|
||||||
exitStatus = status.ExitStatus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf(
|
|
||||||
"lxc exec execution exited with '%d': '%s'",
|
|
||||||
exitStatus, cmd.Command)
|
|
||||||
cmd.SetExited(exitStatus)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Communicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
|
|
||||||
ctx := context.TODO()
|
|
||||||
|
|
||||||
fileDestination := filepath.Join(c.ContainerName, dst)
|
|
||||||
// find out if the place we are pushing to is a directory
|
|
||||||
testDirectoryCommand := fmt.Sprintf(`test -d "%s"`, dst)
|
|
||||||
cmd := &packersdk.RemoteCmd{Command: testDirectoryCommand}
|
|
||||||
err := c.Start(ctx, cmd)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to check whether remote path is a dir: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd.Wait()
|
|
||||||
|
|
||||||
if cmd.ExitStatus() == 0 {
|
|
||||||
log.Printf("path is a directory; copying file into directory.")
|
|
||||||
fileDestination = filepath.Join(c.ContainerName, dst, (*fi).Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
cpCmd, err := c.CmdWrapper(fmt.Sprintf("lxc file push - %s", fileDestination))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Running copy command: %s", cpCmd)
|
|
||||||
command := ShellCommand(cpCmd)
|
|
||||||
command.Stdin = r
|
|
||||||
|
|
||||||
return command.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
|
|
||||||
fileDestination := fmt.Sprintf("%s/%s", c.ContainerName, dst)
|
|
||||||
pushCommand := fmt.Sprintf("lxc file push --debug -pr %s %s", src, fileDestination)
|
|
||||||
log.Printf(pushCommand)
|
|
||||||
cp, err := c.CmdWrapper(pushCommand)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error running cp command: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cpCmd := ShellCommand(cp)
|
|
||||||
|
|
||||||
log.Printf("Running cp command: %s", cp)
|
|
||||||
err = cpCmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error running cp command: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Communicator) Download(src string, w io.Writer) error {
|
|
||||||
cpCmd, err := c.CmdWrapper(fmt.Sprintf("lxc file pull %s -", filepath.Join(c.ContainerName, src)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Running copy command: %s", cpCmd)
|
|
||||||
command := ShellCommand(cpCmd)
|
|
||||||
command.Stdout = w
|
|
||||||
|
|
||||||
return command.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Communicator) DownloadDir(src string, dst string, exclude []string) error {
|
|
||||||
// TODO This could probably be "lxc exec <container> -- cd <src> && tar -czf - | tar -xzf - -C <dst>"
|
|
||||||
return fmt.Errorf("DownloadDir is not implemented for lxc")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Communicator) Execute(commandString string) (*exec.Cmd, error) {
|
|
||||||
log.Printf("Executing with lxc exec in container: %s %s", c.ContainerName, commandString)
|
|
||||||
command, err := c.CmdWrapper(
|
|
||||||
fmt.Sprintf("lxc exec %s -- /bin/sh -c \"%s\"", c.ContainerName, commandString))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
localCmd := ShellCommand(command)
|
|
||||||
log.Printf("Executing lxc exec: %s %#v", localCmd.Path, localCmd.Args)
|
|
||||||
|
|
||||||
return localCmd, nil
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package lxd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCommunicator_ImplementsCommunicator(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &Communicator{}
|
|
||||||
if _, ok := raw.(packersdk.Communicator); !ok {
|
|
||||||
t.Fatalf("Communicator should be a communicator")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Acceptance tests
|
|
||||||
// TODO Execute a command
|
|
||||||
// TODO Upload a file
|
|
||||||
// TODO Download a file
|
|
||||||
// TODO Upload a Directory
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
//go:generate packer-sdc struct-markdown
|
|
||||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
|
||||||
|
|
||||||
package lxd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
|
||||||
// The name of the output artifact. Defaults to
|
|
||||||
// name.
|
|
||||||
OutputImage string `mapstructure:"output_image" required:"false"`
|
|
||||||
ContainerName string `mapstructure:"container_name"`
|
|
||||||
// Lets you prefix all builder commands, such as
|
|
||||||
// with ssh for a remote build host. Defaults to `{{.Command}}`; i.e. no
|
|
||||||
// wrapper.
|
|
||||||
CommandWrapper string `mapstructure:"command_wrapper" required:"false"`
|
|
||||||
// The source image to use when creating the build
|
|
||||||
// container. This can be a (local or remote) image (name or fingerprint).
|
|
||||||
// E.G. my-base-image, ubuntu-daily:x, 08fababf6f27, ...
|
|
||||||
Image string `mapstructure:"image" required:"true"`
|
|
||||||
Profile string `mapstructure:"profile"`
|
|
||||||
// The number of seconds to sleep between launching
|
|
||||||
// the LXD instance and provisioning it; defaults to 3 seconds.
|
|
||||||
InitSleep string `mapstructure:"init_sleep" required:"false"`
|
|
||||||
// Pass key values to the publish
|
|
||||||
// step to be set as properties on the output image. This is most helpful to
|
|
||||||
// set the description, but can be used to set anything needed. See
|
|
||||||
// https://stgraber.org/2016/03/30/lxd-2-0-image-management-512/
|
|
||||||
// for more properties.
|
|
||||||
PublishProperties map[string]string `mapstructure:"publish_properties" required:"false"`
|
|
||||||
// List of key/value pairs you wish to
|
|
||||||
// pass to lxc launch via --config. Defaults to empty.
|
|
||||||
LaunchConfig map[string]string `mapstructure:"launch_config" required:"false"`
|
|
||||||
|
|
||||||
ctx interpolate.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) error {
|
|
||||||
|
|
||||||
var md mapstructure.Metadata
|
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
|
||||||
Metadata: &md,
|
|
||||||
Interpolate: true,
|
|
||||||
}, raws...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accumulate any errors
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
|
|
||||||
if c.ContainerName == "" {
|
|
||||||
c.ContainerName = fmt.Sprintf("packer-%s", c.PackerBuildName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.OutputImage == "" {
|
|
||||||
c.OutputImage = c.ContainerName
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.CommandWrapper == "" {
|
|
||||||
c.CommandWrapper = "{{.Command}}"
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Image == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("`image` is a required parameter for LXD. Please specify an image by alias or fingerprint. e.g. `ubuntu-daily:x`"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Profile == "" {
|
|
||||||
c.Profile = "default"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sadly we have to wait a few seconds for /tmp to be intialized and networking
|
|
||||||
// to finish starting. There isn't a great cross platform to check when things are ready.
|
|
||||||
if c.InitSleep == "" {
|
|
||||||
c.InitSleep = "3"
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package lxd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatConfig struct {
|
|
||||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
|
||||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
|
||||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
|
||||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
|
||||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
|
||||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
|
||||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
|
||||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
|
||||||
OutputImage *string `mapstructure:"output_image" required:"false" cty:"output_image" hcl:"output_image"`
|
|
||||||
ContainerName *string `mapstructure:"container_name" cty:"container_name" hcl:"container_name"`
|
|
||||||
CommandWrapper *string `mapstructure:"command_wrapper" required:"false" cty:"command_wrapper" hcl:"command_wrapper"`
|
|
||||||
Image *string `mapstructure:"image" required:"true" cty:"image" hcl:"image"`
|
|
||||||
Profile *string `mapstructure:"profile" cty:"profile" hcl:"profile"`
|
|
||||||
InitSleep *string `mapstructure:"init_sleep" required:"false" cty:"init_sleep" hcl:"init_sleep"`
|
|
||||||
PublishProperties map[string]string `mapstructure:"publish_properties" required:"false" cty:"publish_properties" hcl:"publish_properties"`
|
|
||||||
LaunchConfig map[string]string `mapstructure:"launch_config" required:"false" cty:"launch_config" hcl:"launch_config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatConfig.
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a Config.
|
|
||||||
// This spec is used by HCL to read the fields of Config.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
|
||||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
|
||||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
|
||||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
|
||||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
|
||||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
|
||||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
|
||||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
|
||||||
"output_image": &hcldec.AttrSpec{Name: "output_image", Type: cty.String, Required: false},
|
|
||||||
"container_name": &hcldec.AttrSpec{Name: "container_name", Type: cty.String, Required: false},
|
|
||||||
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
|
|
||||||
"image": &hcldec.AttrSpec{Name: "image", Type: cty.String, Required: false},
|
|
||||||
"profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false},
|
|
||||||
"init_sleep": &hcldec.AttrSpec{Name: "init_sleep", Type: cty.String, Required: false},
|
|
||||||
"publish_properties": &hcldec.AttrSpec{Name: "publish_properties", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"launch_config": &hcldec.AttrSpec{Name: "launch_config", Type: cty.Map(cty.String), Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package lxd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepLxdLaunch struct{}
|
|
||||||
|
|
||||||
func (s *stepLxdLaunch) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
name := config.ContainerName
|
|
||||||
image := config.Image
|
|
||||||
profile := fmt.Sprintf("--profile=%s", config.Profile)
|
|
||||||
|
|
||||||
launch_args := []string{
|
|
||||||
"launch", "--ephemeral=false", profile, image, name,
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range config.LaunchConfig {
|
|
||||||
launch_args = append(launch_args, "--config", fmt.Sprintf("%s=%s", k, v))
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Creating container...")
|
|
||||||
_, err := LXDCommand(launch_args...)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating container: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
sleep_seconds, err := strconv.Atoi(config.InitSleep)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error parsing InitSleep into int: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Should we check `lxc info <container>` for "Running"?
|
|
||||||
// We have to do this so /tmp doesn't get cleared and lose our provisioner scripts.
|
|
||||||
|
|
||||||
time.Sleep(time.Duration(sleep_seconds) * time.Second)
|
|
||||||
log.Printf("Sleeping for %d seconds...", sleep_seconds)
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepLxdLaunch) Cleanup(state multistep.StateBag) {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
cleanup_args := []string{
|
|
||||||
"delete", "--force", config.ContainerName,
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Unregistering and deleting deleting container...")
|
|
||||||
if _, err := LXDCommand(cleanup_args...); err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting container: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package lxd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StepProvision provisions the container
|
|
||||||
type StepProvision struct{}
|
|
||||||
|
|
||||||
func (s *StepProvision) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
hook := state.Get("hook").(packersdk.Hook)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
|
||||||
|
|
||||||
// Create our communicator
|
|
||||||
comm := &Communicator{
|
|
||||||
ContainerName: config.ContainerName,
|
|
||||||
CmdWrapper: wrappedCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loads hook data from builder's state, if it has been set.
|
|
||||||
hookData := commonsteps.PopulateProvisionHookData(state)
|
|
||||||
|
|
||||||
// Update state generated_data with complete hookData
|
|
||||||
// to make them accessible by post-processors
|
|
||||||
state.Put("generated_data", hookData)
|
|
||||||
|
|
||||||
// Provision
|
|
||||||
log.Println("Running the provision hook")
|
|
||||||
if err := hook.Run(ctx, packersdk.HookProvision, ui, comm, hookData); err != nil {
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepProvision) Cleanup(state multistep.StateBag) {}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package lxd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepPublish struct{}
|
|
||||||
|
|
||||||
func (s *stepPublish) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
name := config.ContainerName
|
|
||||||
stop_args := []string{
|
|
||||||
// We created the container with "--ephemeral=false" so we know it is safe to stop.
|
|
||||||
"stop", name,
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Stopping container...")
|
|
||||||
_, err := LXDCommand(stop_args...)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error stopping container: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
publish_args := []string{
|
|
||||||
"publish", name, "--alias", config.OutputImage,
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range config.PublishProperties {
|
|
||||||
publish_args = append(publish_args, fmt.Sprintf("%s=%s", k, v))
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Publishing container...")
|
|
||||||
stdoutString, err := LXDCommand(publish_args...)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error publishing container: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
r := regexp.MustCompile("([0-9a-fA-F]+)$")
|
|
||||||
fingerprint := r.FindAllStringSubmatch(stdoutString, -1)[0][0]
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Created image: %s", fingerprint))
|
|
||||||
|
|
||||||
state.Put("imageFingerprint", fingerprint)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepPublish) Cleanup(state multistep.StateBag) {}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
|
||||||
packerVersion "github.com/hashicorp/packer/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var LXDPluginVersion *version.PluginVersion
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
LXDPluginVersion = version.InitializePluginVersion(
|
|
||||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Artifact is an artifact implementation that contains Image List
|
|
||||||
// and Machine Image info.
|
|
||||||
type Artifact struct {
|
|
||||||
MachineImageName string
|
|
||||||
MachineImageFile string
|
|
||||||
ImageListVersion int
|
|
||||||
|
|
||||||
// StateData should store data such as GeneratedData
|
|
||||||
// to be shared with post-processors
|
|
||||||
StateData map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuilderId uniquely identifies the builder.
|
|
||||||
func (a *Artifact) BuilderId() string {
|
|
||||||
return BuilderId
|
|
||||||
}
|
|
||||||
|
|
||||||
// Files lists the files associated with an artifact. We don't have any files
|
|
||||||
// as the custom image is stored server side.
|
|
||||||
func (a *Artifact) Files() []string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) Id() string {
|
|
||||||
return a.MachineImageName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) String() string {
|
|
||||||
return fmt.Sprintf("An image list entry was created: \n"+
|
|
||||||
"Name: %s\n"+
|
|
||||||
"File: %s\n"+
|
|
||||||
"Version: %d",
|
|
||||||
a.MachineImageName, a.MachineImageFile, a.ImageListVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
|
||||||
return a.StateData[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy deletes the custom image associated with the artifact.
|
|
||||||
func (a *Artifact) Destroy() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
|
||||||
|
|
||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-cleanhttp"
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/opc"
|
|
||||||
"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"
|
|
||||||
ocommon "github.com/hashicorp/packer/builder/oracle/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BuilderId uniquely identifies the builder
|
|
||||||
const BuilderId = "packer.oracle.classic"
|
|
||||||
|
|
||||||
// Builder is a builder implementation that creates Oracle OCI custom images.
|
|
||||||
type Builder struct {
|
|
||||||
config Config
|
|
||||||
runner multistep.Runner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
||||||
err := b.config.Prepare(raws...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, b.config.PVConfig.Prepare(&b.config.ctx))
|
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return nil, nil, errs
|
|
||||||
}
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
|
||||||
loggingEnabled := os.Getenv("PACKER_OCI_CLASSIC_LOGGING") != ""
|
|
||||||
httpClient := cleanhttp.DefaultClient()
|
|
||||||
config := &opc.Config{
|
|
||||||
Username: opc.String(b.config.Username),
|
|
||||||
Password: opc.String(b.config.Password),
|
|
||||||
IdentityDomain: opc.String(b.config.IdentityDomain),
|
|
||||||
APIEndpoint: b.config.apiEndpointURL,
|
|
||||||
LogLevel: opc.LogDebug,
|
|
||||||
Logger: &Logger{loggingEnabled},
|
|
||||||
// Logger: # Leave blank to use the default logger, or provide your own
|
|
||||||
HTTPClient: httpClient,
|
|
||||||
}
|
|
||||||
// Create the Compute Client
|
|
||||||
client, err := compute.NewComputeClient(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error creating OPC Compute Client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
runID := fmt.Sprintf("%s_%s", b.config.ImageName, os.Getenv("PACKER_RUN_UUID"))
|
|
||||||
// Populate the state bag
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("config", &b.config)
|
|
||||||
state.Put("hook", hook)
|
|
||||||
state.Put("ui", ui)
|
|
||||||
state.Put("client", client)
|
|
||||||
state.Put("run_id", runID)
|
|
||||||
|
|
||||||
var steps []multistep.Step
|
|
||||||
if b.config.IsPV() {
|
|
||||||
steps = []multistep.Step{
|
|
||||||
&ocommon.StepKeyPair{
|
|
||||||
Debug: b.config.PackerDebug,
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
DebugKeyPath: fmt.Sprintf("oci_classic_%s.pem", b.config.PackerBuildName),
|
|
||||||
},
|
|
||||||
&stepCreateIPReservation{},
|
|
||||||
&stepAddKeysToAPI{
|
|
||||||
KeyName: fmt.Sprintf("packer-generated-key_%s", runID),
|
|
||||||
},
|
|
||||||
&stepSecurity{
|
|
||||||
CommType: b.config.Comm.Type,
|
|
||||||
SecurityListKey: "security_list_master",
|
|
||||||
},
|
|
||||||
&stepCreatePersistentVolume{
|
|
||||||
VolumeSize: fmt.Sprintf("%d", b.config.PersistentVolumeSize),
|
|
||||||
VolumeName: fmt.Sprintf("master-storage_%s", runID),
|
|
||||||
ImageList: b.config.SourceImageList,
|
|
||||||
ImageListEntry: b.config.SourceImageListEntry,
|
|
||||||
Bootable: true,
|
|
||||||
},
|
|
||||||
&stepCreatePVMaster{
|
|
||||||
Name: fmt.Sprintf("master-instance_%s", runID),
|
|
||||||
VolumeName: fmt.Sprintf("master-storage_%s", runID),
|
|
||||||
SecurityListKey: "security_list_master",
|
|
||||||
},
|
|
||||||
&communicator.StepConnect{
|
|
||||||
Config: &b.config.Comm,
|
|
||||||
Host: communicator.CommHost(b.config.Comm.Host(), "instance_ip"),
|
|
||||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
|
||||||
},
|
|
||||||
&commonsteps.StepProvision{},
|
|
||||||
&stepTerminatePVMaster{},
|
|
||||||
&stepSecurity{
|
|
||||||
SecurityListKey: "security_list_builder",
|
|
||||||
CommType: "ssh",
|
|
||||||
},
|
|
||||||
&stepCreatePersistentVolume{
|
|
||||||
// We double the master volume size because we need room to
|
|
||||||
// tarball the disk image. We also need to chunk the tar ball,
|
|
||||||
// but we can remove the original disk image first.
|
|
||||||
VolumeSize: fmt.Sprintf("%d", b.config.PersistentVolumeSize*2),
|
|
||||||
VolumeName: fmt.Sprintf("builder-storage_%s", runID),
|
|
||||||
},
|
|
||||||
&stepCreatePVBuilder{
|
|
||||||
Name: fmt.Sprintf("builder-instance_%s", runID),
|
|
||||||
BuilderVolumeName: fmt.Sprintf("builder-storage_%s", runID),
|
|
||||||
SecurityListKey: "security_list_builder",
|
|
||||||
},
|
|
||||||
&stepAttachVolume{
|
|
||||||
VolumeName: fmt.Sprintf("master-storage_%s", runID),
|
|
||||||
Index: 2,
|
|
||||||
InstanceInfoKey: "builder_instance_info",
|
|
||||||
},
|
|
||||||
&stepConnectBuilder{
|
|
||||||
KeyName: fmt.Sprintf("packer-generated-key_%s", runID),
|
|
||||||
StepConnectSSH: &communicator.StepConnectSSH{
|
|
||||||
Config: &b.config.BuilderComm,
|
|
||||||
Host: communicator.CommHost(b.config.Comm.Host(), "instance_ip"),
|
|
||||||
SSHConfig: b.config.BuilderComm.SSHConfigFunc(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&stepUploadImage{
|
|
||||||
UploadImageCommand: b.config.BuilderUploadImageCommand,
|
|
||||||
},
|
|
||||||
&stepCreateImage{},
|
|
||||||
&stepListImages{},
|
|
||||||
&commonsteps.StepCleanupTempKeys{
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Build the steps
|
|
||||||
steps = []multistep.Step{
|
|
||||||
&ocommon.StepKeyPair{
|
|
||||||
Debug: b.config.PackerDebug,
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
DebugKeyPath: fmt.Sprintf("oci_classic_%s.pem", b.config.PackerBuildName),
|
|
||||||
},
|
|
||||||
&stepCreateIPReservation{},
|
|
||||||
&stepAddKeysToAPI{
|
|
||||||
Skip: b.config.Comm.Type != "ssh",
|
|
||||||
KeyName: fmt.Sprintf("packer-generated-key_%s", runID),
|
|
||||||
},
|
|
||||||
&stepSecurity{
|
|
||||||
SecurityListKey: "security_list",
|
|
||||||
CommType: b.config.Comm.Type,
|
|
||||||
},
|
|
||||||
&stepCreateInstance{},
|
|
||||||
&communicator.StepConnect{
|
|
||||||
Config: &b.config.Comm,
|
|
||||||
Host: communicator.CommHost(b.config.Comm.Host(), "instance_ip"),
|
|
||||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
|
||||||
},
|
|
||||||
&commonsteps.StepProvision{},
|
|
||||||
&commonsteps.StepCleanupTempKeys{
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
},
|
|
||||||
&stepSnapshot{},
|
|
||||||
&stepListImages{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the steps
|
|
||||||
b.runner = commonsteps.NewRunner(steps, b.config.PackerConfig, ui)
|
|
||||||
b.runner.Run(ctx, state)
|
|
||||||
|
|
||||||
// If there was an error, return that
|
|
||||||
if rawErr, ok := state.GetOk("error"); ok {
|
|
||||||
return nil, rawErr.(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is no snapshot, then just return
|
|
||||||
if _, ok := state.GetOk("machine_image_name"); !ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the artifact and return it
|
|
||||||
artifact := &Artifact{
|
|
||||||
ImageListVersion: state.Get("image_list_version").(int),
|
|
||||||
MachineImageName: state.Get("machine_image_name").(string),
|
|
||||||
MachineImageFile: state.Get("machine_image_file").(string),
|
|
||||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel terminates a running build.
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatConfig struct {
|
|
||||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
|
||||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
|
||||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
|
||||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
|
||||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
|
||||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
|
||||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
|
||||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
|
||||||
PersistentVolumeSize *int `mapstructure:"persistent_volume_size" cty:"persistent_volume_size" hcl:"persistent_volume_size"`
|
|
||||||
BuilderUploadImageCommand *string `mapstructure:"builder_upload_image_command" cty:"builder_upload_image_command" hcl:"builder_upload_image_command"`
|
|
||||||
BuilderShape *string `mapstructure:"builder_shape" cty:"builder_shape" hcl:"builder_shape"`
|
|
||||||
BuilderImageList *string `mapstructure:"builder_image_list" cty:"builder_image_list" hcl:"builder_image_list"`
|
|
||||||
BuilderImageListEntry *int `mapstructure:"builder_image_list_entry" cty:"builder_image_list_entry" hcl:"builder_image_list_entry"`
|
|
||||||
BuilderComm *communicator.FlatConfig `mapstructure:"builder_communicator" cty:"builder_communicator" hcl:"builder_communicator"`
|
|
||||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
|
||||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
|
||||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
|
||||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
|
||||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
|
||||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
|
||||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
|
||||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
|
||||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
|
||||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
|
||||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
|
||||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
|
||||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
|
||||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
|
||||||
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
|
||||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
|
||||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
|
||||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
|
||||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
|
||||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
|
||||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
|
||||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
|
||||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
|
||||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
|
||||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
|
||||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
|
||||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
|
||||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
|
||||||
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
|
||||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
|
||||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
|
||||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
|
||||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
|
||||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
|
||||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
|
||||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
|
||||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
|
||||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
|
||||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
|
||||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
|
||||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
|
||||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
|
||||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
|
||||||
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
|
||||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
|
||||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
|
||||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
|
||||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
|
||||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
|
||||||
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
|
|
||||||
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
|
|
||||||
IdentityDomain *string `mapstructure:"identity_domain" cty:"identity_domain" hcl:"identity_domain"`
|
|
||||||
APIEndpoint *string `mapstructure:"api_endpoint" cty:"api_endpoint" hcl:"api_endpoint"`
|
|
||||||
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
|
||||||
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
|
|
||||||
SourceImageList *string `mapstructure:"source_image_list" cty:"source_image_list" hcl:"source_image_list"`
|
|
||||||
SourceImageListEntry *int `mapstructure:"source_image_list_entry" cty:"source_image_list_entry" hcl:"source_image_list_entry"`
|
|
||||||
SnapshotTimeout *string `mapstructure:"snapshot_timeout" cty:"snapshot_timeout" hcl:"snapshot_timeout"`
|
|
||||||
DestImageList *string `mapstructure:"dest_image_list" cty:"dest_image_list" hcl:"dest_image_list"`
|
|
||||||
Attributes *string `mapstructure:"attributes" cty:"attributes" hcl:"attributes"`
|
|
||||||
AttributesFile *string `mapstructure:"attributes_file" cty:"attributes_file" hcl:"attributes_file"`
|
|
||||||
DestImageListDescription *string `mapstructure:"image_description" cty:"image_description" hcl:"image_description"`
|
|
||||||
SSHSourceList *string `mapstructure:"ssh_source_list" cty:"ssh_source_list" hcl:"ssh_source_list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatConfig.
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a Config.
|
|
||||||
// This spec is used by HCL to read the fields of Config.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
|
||||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
|
||||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
|
||||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
|
||||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
|
||||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
|
||||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
|
||||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
|
||||||
"persistent_volume_size": &hcldec.AttrSpec{Name: "persistent_volume_size", Type: cty.Number, Required: false},
|
|
||||||
"builder_upload_image_command": &hcldec.AttrSpec{Name: "builder_upload_image_command", Type: cty.String, Required: false},
|
|
||||||
"builder_shape": &hcldec.AttrSpec{Name: "builder_shape", Type: cty.String, Required: false},
|
|
||||||
"builder_image_list": &hcldec.AttrSpec{Name: "builder_image_list", Type: cty.String, Required: false},
|
|
||||||
"builder_image_list_entry": &hcldec.AttrSpec{Name: "builder_image_list_entry", Type: cty.Number, Required: false},
|
|
||||||
"builder_communicator": &hcldec.BlockSpec{TypeName: "builder_communicator", Nested: hcldec.ObjectSpec((*communicator.FlatConfig)(nil).HCL2Spec())},
|
|
||||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
|
||||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
|
||||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
|
||||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
|
||||||
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
|
||||||
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
|
||||||
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
|
||||||
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
|
||||||
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
|
||||||
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
|
||||||
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
|
||||||
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
|
||||||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
|
||||||
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
|
|
||||||
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
|
|
||||||
"identity_domain": &hcldec.AttrSpec{Name: "identity_domain", Type: cty.String, Required: false},
|
|
||||||
"api_endpoint": &hcldec.AttrSpec{Name: "api_endpoint", Type: cty.String, Required: false},
|
|
||||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
|
||||||
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
|
|
||||||
"source_image_list": &hcldec.AttrSpec{Name: "source_image_list", Type: cty.String, Required: false},
|
|
||||||
"source_image_list_entry": &hcldec.AttrSpec{Name: "source_image_list_entry", Type: cty.Number, Required: false},
|
|
||||||
"snapshot_timeout": &hcldec.AttrSpec{Name: "snapshot_timeout", Type: cty.String, Required: false},
|
|
||||||
"dest_image_list": &hcldec.AttrSpec{Name: "dest_image_list", Type: cty.String, Required: false},
|
|
||||||
"attributes": &hcldec.AttrSpec{Name: "attributes", Type: cty.String, Required: false},
|
|
||||||
"attributes_file": &hcldec.AttrSpec{Name: "attributes_file", Type: cty.String, Required: false},
|
|
||||||
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
|
|
||||||
"ssh_source_list": &hcldec.AttrSpec{Name: "ssh_source_list", Type: cty.String, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
|
||||||
PVConfig `mapstructure:",squash"`
|
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
|
||||||
attribs map[string]interface{}
|
|
||||||
|
|
||||||
// Access config overrides
|
|
||||||
Username string `mapstructure:"username"`
|
|
||||||
Password string `mapstructure:"password"`
|
|
||||||
IdentityDomain string `mapstructure:"identity_domain"`
|
|
||||||
APIEndpoint string `mapstructure:"api_endpoint"`
|
|
||||||
apiEndpointURL *url.URL
|
|
||||||
|
|
||||||
// Image
|
|
||||||
ImageName string `mapstructure:"image_name"`
|
|
||||||
Shape string `mapstructure:"shape"`
|
|
||||||
SourceImageList string `mapstructure:"source_image_list"`
|
|
||||||
SourceImageListEntry int `mapstructure:"source_image_list_entry"`
|
|
||||||
SnapshotTimeout time.Duration `mapstructure:"snapshot_timeout"`
|
|
||||||
DestImageList string `mapstructure:"dest_image_list"`
|
|
||||||
// Attributes and Attributes file are both optional and mutually exclusive.
|
|
||||||
Attributes string `mapstructure:"attributes"`
|
|
||||||
AttributesFile string `mapstructure:"attributes_file"`
|
|
||||||
// Optional; if you don't enter anything, the image list description
|
|
||||||
// will read "Packer-built image list"
|
|
||||||
DestImageListDescription string `mapstructure:"image_description"`
|
|
||||||
// Optional. Describes what computers are allowed to reach your instance
|
|
||||||
// via SSH. This whitelist must contain the computer you're running Packer
|
|
||||||
// from. It defaults to public-internet, meaning that you can SSH into your
|
|
||||||
// instance from anywhere as long as you have the right keys
|
|
||||||
SSHSourceList string `mapstructure:"ssh_source_list"`
|
|
||||||
|
|
||||||
ctx interpolate.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Identifier(s string) string {
|
|
||||||
return fmt.Sprintf("/Compute-%s/%s/%s", c.IdentityDomain, c.Username, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) error {
|
|
||||||
|
|
||||||
// Decode from template
|
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
|
||||||
Interpolate: true,
|
|
||||||
InterpolateContext: &c.ctx,
|
|
||||||
}, raws...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to mapstructure Config: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.apiEndpointURL, err = url.Parse(c.APIEndpoint)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error parsing API Endpoint: %s", err)
|
|
||||||
}
|
|
||||||
// Set default source list
|
|
||||||
if c.SSHSourceList == "" {
|
|
||||||
c.SSHSourceList = "seciplist:/oracle/public/public-internet"
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SnapshotTimeout == 0 {
|
|
||||||
c.SnapshotTimeout = 20 * time.Minute
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that all required fields are present
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
required := map[string]string{
|
|
||||||
"username": c.Username,
|
|
||||||
"password": c.Password,
|
|
||||||
"api_endpoint": c.APIEndpoint,
|
|
||||||
"identity_domain": c.IdentityDomain,
|
|
||||||
"source_image_list": c.SourceImageList,
|
|
||||||
"dest_image_list": c.DestImageList,
|
|
||||||
"shape": c.Shape,
|
|
||||||
}
|
|
||||||
for k, v := range required {
|
|
||||||
if v == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("You must specify a %s.", k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods
|
|
||||||
reValidObject := regexp.MustCompile("^[a-zA-Z0-9-._/]+$")
|
|
||||||
var objectValidation = []struct {
|
|
||||||
name string
|
|
||||||
value string
|
|
||||||
}{
|
|
||||||
{"dest_image_list", c.DestImageList},
|
|
||||||
{"image_name", c.ImageName},
|
|
||||||
}
|
|
||||||
for _, ov := range objectValidation {
|
|
||||||
if !reValidObject.MatchString(ov.value) {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("%s can contain only alphanumeric characters, hyphens, underscores, and periods.", ov.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Attributes != "" && c.AttributesFile != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
|
|
||||||
} else if c.AttributesFile != "" {
|
|
||||||
if _, err := os.Stat(c.AttributesFile); err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("attributes_file not found: %s", c.AttributesFile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, es...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpack attributes from json into config
|
|
||||||
var data map[string]interface{}
|
|
||||||
|
|
||||||
if c.Attributes != "" {
|
|
||||||
err := json.Unmarshal([]byte(c.Attributes), &data)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem parsing json from attributes: %s", err)
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, err)
|
|
||||||
}
|
|
||||||
c.attribs = data
|
|
||||||
} else if c.AttributesFile != "" {
|
|
||||||
fidata, err := ioutil.ReadFile(c.AttributesFile)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem reading attributes_file: %s", err)
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, err)
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(fidata, &data)
|
|
||||||
c.attribs = data
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem parsing json from attributes_file: %s", err)
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, err)
|
|
||||||
}
|
|
||||||
c.attribs = data
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testConfig() map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"identity_domain": "abc12345",
|
|
||||||
"username": "test@hashicorp.com",
|
|
||||||
"password": "testpassword123",
|
|
||||||
"api_endpoint": "https://api-test.compute.test.oraclecloud.com/",
|
|
||||||
"dest_image_list": "/Config-thing/myuser/myimage",
|
|
||||||
"source_image_list": "/oracle/public/whatever",
|
|
||||||
"shape": "oc3",
|
|
||||||
"image_name": "TestImageName",
|
|
||||||
"ssh_username": "opc",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigAutoFillsSourceList(t *testing.T) {
|
|
||||||
tc := testConfig()
|
|
||||||
var conf Config
|
|
||||||
err := conf.Prepare(tc)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Should not have error: %s", err.Error())
|
|
||||||
}
|
|
||||||
if conf.SSHSourceList != "seciplist:/oracle/public/public-internet" {
|
|
||||||
t.Fatalf("conf.SSHSourceList should have been "+
|
|
||||||
"\"seciplist:/oracle/public/public-internet\" but is \"%s\"",
|
|
||||||
conf.SSHSourceList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigValidationCatchesMissing(t *testing.T) {
|
|
||||||
required := []string{
|
|
||||||
"username",
|
|
||||||
"password",
|
|
||||||
"api_endpoint",
|
|
||||||
"identity_domain",
|
|
||||||
"dest_image_list",
|
|
||||||
"source_image_list",
|
|
||||||
"shape",
|
|
||||||
"ssh_username",
|
|
||||||
}
|
|
||||||
for _, key := range required {
|
|
||||||
tc := testConfig()
|
|
||||||
delete(tc, key)
|
|
||||||
var c Config
|
|
||||||
err := c.Prepare(tc)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Test should have failed when config lacked %s!", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigValidatesObjects(t *testing.T) {
|
|
||||||
var objectTests = []struct {
|
|
||||||
object string
|
|
||||||
valid bool
|
|
||||||
}{
|
|
||||||
{"foo-BAR.0_9", true},
|
|
||||||
{"%", false},
|
|
||||||
{"Matt...?", false},
|
|
||||||
{"/Config-thing/myuser/myimage", true},
|
|
||||||
}
|
|
||||||
for _, s := range []string{"dest_image_list", "image_name"} {
|
|
||||||
for _, tt := range objectTests {
|
|
||||||
tc := testConfig()
|
|
||||||
tc[s] = tt.object
|
|
||||||
var c Config
|
|
||||||
err := c.Prepare(tc)
|
|
||||||
if tt.valid {
|
|
||||||
assert.NoError(t, err, tt.object)
|
|
||||||
} else {
|
|
||||||
assert.Error(t, err, tt.object)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
type Logger struct {
|
|
||||||
Enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Logger) Log(input ...interface{}) {
|
|
||||||
if !l.Enabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Println(input...)
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
)
|
|
||||||
|
|
||||||
const imageListDefault = "/oracle/public/OL_7.2_UEKR4_x86_64"
|
|
||||||
const imageListEntryDefault = 5
|
|
||||||
const usernameDefault = "opc"
|
|
||||||
const shapeDefault = "oc3"
|
|
||||||
const uploadImageCommandDefault = `
|
|
||||||
# https://www.oracle.com/webfolder/technetwork/tutorials/obe/cloud/objectstorage/upload_files_gt_5GB_REST_API/upload_files_gt_5GB_REST_API.html
|
|
||||||
|
|
||||||
# Split diskimage in to 100mb chunks
|
|
||||||
split -b 100m diskimage.tar.gz segment_
|
|
||||||
printf "Split diskimage into %s segments\n" $(ls segment_* | wc -l)
|
|
||||||
|
|
||||||
# Download jq tool
|
|
||||||
curl -OLs https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64
|
|
||||||
mv jq-linux64 jq
|
|
||||||
chmod u+x jq
|
|
||||||
|
|
||||||
# Create manifest file
|
|
||||||
(
|
|
||||||
for i in segment_*; do
|
|
||||||
./jq -n --arg path "{{.SegmentPath}}/$i" \
|
|
||||||
--arg etag $(md5sum $i | cut -f1 -d' ') \
|
|
||||||
--arg size_bytes $(stat --printf "%s" $i) \
|
|
||||||
'{path: $path, etag: $etag, size_bytes: $size_bytes}'
|
|
||||||
done
|
|
||||||
) | ./jq -s . > manifest.json
|
|
||||||
|
|
||||||
# Authenticate
|
|
||||||
curl -D auth-headers -s -X GET \
|
|
||||||
-H "X-Storage-User: Storage-{{.AccountID}}:{{.Username}}" \
|
|
||||||
-H "X-Storage-Pass: {{.Password}}" \
|
|
||||||
https://{{.AccountID}}.storage.oraclecloud.com/auth/v1.0
|
|
||||||
|
|
||||||
export AUTH_TOKEN=$(awk 'BEGIN {FS=": "; RS="\r\n"}/^X-Auth-Token/{print $2}' auth-headers)
|
|
||||||
export STORAGE_URL=$(awk 'BEGIN {FS=": "; RS="\r\n"}/^X-Storage-Url/{print $2}' auth-headers)
|
|
||||||
|
|
||||||
# Upload segments
|
|
||||||
for i in segment_*; do
|
|
||||||
echo "Uploading segment $i"
|
|
||||||
|
|
||||||
curl -s -X PUT -T $i \
|
|
||||||
-H "X-Auth-Token: $AUTH_TOKEN" ${STORAGE_URL}/{{.SegmentPath}}/$i;
|
|
||||||
done
|
|
||||||
|
|
||||||
# Create machine image from manifest
|
|
||||||
curl -s -X PUT \
|
|
||||||
-H "X-Auth-Token: $AUTH_TOKEN" \
|
|
||||||
"${STORAGE_URL}/compute_images/{{.ImageFile}}?multipart-manifest=put" \
|
|
||||||
-T ./manifest.json
|
|
||||||
|
|
||||||
# Get uploaded image description
|
|
||||||
echo "Details of ${STORAGE_URL}/compute_images/{{.ImageFile}}:"
|
|
||||||
curl -I -X HEAD \
|
|
||||||
-H "X-Auth-Token: $AUTH_TOKEN" \
|
|
||||||
"${STORAGE_URL}/compute_images/{{.ImageFile}}"
|
|
||||||
`
|
|
||||||
|
|
||||||
type PVConfig struct {
|
|
||||||
// PersistentVolumeSize lets us control the volume size by using persistent boot storage
|
|
||||||
PersistentVolumeSize int `mapstructure:"persistent_volume_size"`
|
|
||||||
BuilderUploadImageCommand string `mapstructure:"builder_upload_image_command"`
|
|
||||||
|
|
||||||
// Builder Image
|
|
||||||
BuilderShape string `mapstructure:"builder_shape"`
|
|
||||||
BuilderImageList string `mapstructure:"builder_image_list"`
|
|
||||||
BuilderImageListEntry *int `mapstructure:"builder_image_list_entry"`
|
|
||||||
|
|
||||||
BuilderComm communicator.Config `mapstructure:"builder_communicator"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPV tells us if we're using a persistent volume for this build
|
|
||||||
func (c *PVConfig) IsPV() bool {
|
|
||||||
return c.PersistentVolumeSize > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PVConfig) Prepare(ctx *interpolate.Context) (errs *packersdk.MultiError) {
|
|
||||||
if !c.IsPV() {
|
|
||||||
if c.BuilderShape != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs,
|
|
||||||
fmt.Errorf("`builder_shape` has no meaning when `persistent_volume_size` is not set."))
|
|
||||||
}
|
|
||||||
if c.BuilderImageList != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs,
|
|
||||||
fmt.Errorf("`builder_image_list` has no meaning when `persistent_volume_size` is not set."))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.BuilderImageListEntry != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs,
|
|
||||||
fmt.Errorf("`builder_image_list_entry` has no meaning when `persistent_volume_size` is not set."))
|
|
||||||
}
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
c.BuilderComm.SSHPty = true
|
|
||||||
if c.BuilderComm.Type == "winrm" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs,
|
|
||||||
fmt.Errorf("`ssh` is the only valid builder communicator type."))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.BuilderShape == "" {
|
|
||||||
c.BuilderShape = shapeDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.BuilderComm.SSHUsername == "" {
|
|
||||||
c.BuilderComm.SSHUsername = usernameDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.BuilderImageList == "" {
|
|
||||||
c.BuilderImageList = imageListDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set to known working default if this is unset and we're using the
|
|
||||||
// default image list
|
|
||||||
if c.BuilderImageListEntry == nil {
|
|
||||||
var entry int
|
|
||||||
if c.BuilderImageList == imageListDefault {
|
|
||||||
entry = imageListEntryDefault
|
|
||||||
}
|
|
||||||
c.BuilderImageListEntry = &entry
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.BuilderUploadImageCommand == "" {
|
|
||||||
c.BuilderUploadImageCommand = uploadImageCommandDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
if es := c.BuilderComm.Prepare(ctx); len(es) > 0 {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, es...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPVConfigEntry(t *testing.T) {
|
|
||||||
entry := 1
|
|
||||||
var entryTests = []struct {
|
|
||||||
imageList string
|
|
||||||
imageListEntry *int
|
|
||||||
expected int
|
|
||||||
}{
|
|
||||||
{"x", nil, 0},
|
|
||||||
{"x", &entry, 1},
|
|
||||||
{imageListDefault, nil, 5},
|
|
||||||
{imageListDefault, &entry, 1},
|
|
||||||
}
|
|
||||||
for _, tt := range entryTests {
|
|
||||||
tc := &PVConfig{
|
|
||||||
PersistentVolumeSize: 1,
|
|
||||||
BuilderImageList: tt.imageList,
|
|
||||||
BuilderImageListEntry: tt.imageListEntry,
|
|
||||||
}
|
|
||||||
errs := tc.Prepare(nil)
|
|
||||||
assert.Nil(t, errs, "Didn't expect any errors")
|
|
||||||
assert.Equal(t, tt.expected, *tc.BuilderImageListEntry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepAddKeysToAPI struct {
|
|
||||||
Skip bool
|
|
||||||
KeyName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepAddKeysToAPI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
// get variables from state
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
|
|
||||||
if s.Skip {
|
|
||||||
ui.Say("Skipping generating SSH keys...")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
// grab packer-generated key from statebag context.
|
|
||||||
// Always check configured communicator for keys
|
|
||||||
sshPublicKey := bytes.TrimSpace(config.Comm.SSHPublicKey)
|
|
||||||
|
|
||||||
// form API call to add key to compute cloud
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Creating temporary key: %s", s.KeyName))
|
|
||||||
|
|
||||||
sshKeysClient := client.SSHKeys()
|
|
||||||
sshKeysInput := compute.CreateSSHKeyInput{
|
|
||||||
Name: s.KeyName,
|
|
||||||
Key: string(sshPublicKey),
|
|
||||||
Enabled: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the packer-generated SSH key into the Oracle Compute cloud.
|
|
||||||
keyInfo, err := sshKeysClient.CreateSSHKey(&sshKeysInput)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem adding Public SSH key through Oracle's API: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
config.Comm.SSHKeyPairName = keyInfo.Name
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepAddKeysToAPI) Cleanup(state multistep.StateBag) {
|
|
||||||
if s.Skip {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
// Delete the keys we created during this run
|
|
||||||
if len(config.Comm.SSHKeyPairName) == 0 {
|
|
||||||
// No keys were generated; none need to be cleaned up.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
ui.Say("Deleting SSH keys...")
|
|
||||||
deleteInput := compute.DeleteSSHKeyInput{Name: config.Comm.SSHKeyPairName}
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
deleteClient := client.SSHKeys()
|
|
||||||
err := deleteClient.DeleteSSHKey(&deleteInput)
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting SSH keys: %s", err.Error()))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepAttachVolume struct {
|
|
||||||
Index int
|
|
||||||
VolumeName string
|
|
||||||
InstanceInfoKey string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepAttachVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
instanceInfo := state.Get(s.InstanceInfoKey).(*compute.InstanceInfo)
|
|
||||||
|
|
||||||
saClient := client.StorageAttachments()
|
|
||||||
saInput := &compute.CreateStorageAttachmentInput{
|
|
||||||
Index: s.Index,
|
|
||||||
InstanceName: instanceInfo.Name + "/" + instanceInfo.ID,
|
|
||||||
StorageVolumeName: s.VolumeName,
|
|
||||||
}
|
|
||||||
|
|
||||||
sa, err := saClient.CreateStorageAttachment(saInput)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem attaching master volume: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Put(s.InstanceInfoKey+"/attachment", sa)
|
|
||||||
|
|
||||||
ui.Message("Volume attached to instance.")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepAttachVolume) Cleanup(state multistep.StateBag) {
|
|
||||||
sa, ok := state.GetOk(s.InstanceInfoKey + "/attachment")
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
saClient := client.StorageAttachments()
|
|
||||||
saI := &compute.DeleteStorageAttachmentInput{
|
|
||||||
Name: sa.(*compute.StorageAttachmentInfo).Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := saClient.DeleteStorageAttachment(saI); err != nil {
|
|
||||||
err = fmt.Errorf("Problem detaching storage volume: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepConnectBuilder struct {
|
|
||||||
*communicator.StepConnectSSH
|
|
||||||
KeyName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConnectBuilder) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
s.Config.SSHKeyPairName = s.KeyName
|
|
||||||
return s.StepConnectSSH.Run(ctx, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepConnectBuilder) Cleanup(state multistep.StateBag) {
|
|
||||||
s.StepConnectSSH.Cleanup(state)
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateImage struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
imageFile := state.Get("image_file").(string)
|
|
||||||
|
|
||||||
// Image uploaded, let's register it
|
|
||||||
machineImageClient := client.MachineImages()
|
|
||||||
createMI := &compute.CreateMachineImageInput{
|
|
||||||
// Two-part name of the account
|
|
||||||
Account: fmt.Sprintf("/Compute-%s/cloud_storage", config.IdentityDomain),
|
|
||||||
Description: "Packer generated Machine Image.",
|
|
||||||
// The three-part name of the object
|
|
||||||
Name: config.ImageName,
|
|
||||||
// image_file.tar.gz, where image_file is the .tar.gz name of the
|
|
||||||
// machine image file that you have uploaded to Oracle Cloud
|
|
||||||
// Infrastructure Object Storage Classic.
|
|
||||||
File: imageFile,
|
|
||||||
}
|
|
||||||
mi, err := machineImageClient.CreateMachineImage(createMI)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error creating machine image: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
log.Println("Registered machine image.")
|
|
||||||
state.Put("machine_image", mi.Name)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateImage) Cleanup(state multistep.StateBag) {
|
|
||||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
|
||||||
_, halted := state.GetOk(multistep.StateHalted)
|
|
||||||
if !cancelled && !halted {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
ui.Say("Cleaning up Image...")
|
|
||||||
|
|
||||||
machineImageClient := client.MachineImages()
|
|
||||||
deleteMI := &compute.DeleteMachineImageInput{
|
|
||||||
Name: config.ImageName,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := machineImageClient.DeleteMachineImage(deleteMI); err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error cleaning up machine image: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Deleted Image: %s", config.ImageName))
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateInstance struct{}
|
|
||||||
|
|
||||||
func (s *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
// get variables from state
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
ui.Say("Creating Instance...")
|
|
||||||
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
ipAddName := state.Get("ipres_name").(string)
|
|
||||||
secListName := state.Get("security_list").(string)
|
|
||||||
|
|
||||||
netInfo := compute.NetworkingInfo{
|
|
||||||
Nat: []string{ipAddName},
|
|
||||||
SecLists: []string{secListName},
|
|
||||||
}
|
|
||||||
|
|
||||||
// get instances client
|
|
||||||
instanceClient := client.Instances()
|
|
||||||
|
|
||||||
// Instances Input
|
|
||||||
input := &compute.CreateInstanceInput{
|
|
||||||
Name: config.ImageName,
|
|
||||||
Shape: config.Shape,
|
|
||||||
ImageList: config.SourceImageList,
|
|
||||||
Entry: config.SourceImageListEntry,
|
|
||||||
Networking: map[string]compute.NetworkingInfo{"eth0": netInfo},
|
|
||||||
Attributes: config.attribs,
|
|
||||||
}
|
|
||||||
if config.Comm.Type == "ssh" {
|
|
||||||
input.SSHKeys = []string{config.Comm.SSHKeyPairName}
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceInfo, err := instanceClient.CreateInstance(input)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem creating instance: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Put("instance_info", instanceInfo)
|
|
||||||
state.Put("instance_id", instanceInfo.ID)
|
|
||||||
ui.Message(fmt.Sprintf("Created instance: %s.", instanceInfo.ID))
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateInstance) Cleanup(state multistep.StateBag) {
|
|
||||||
instanceID, ok := state.GetOk("instance_id")
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// terminate instance
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
ui.Say("Terminating source instance...")
|
|
||||||
|
|
||||||
instanceClient := client.Instances()
|
|
||||||
input := &compute.DeleteInstanceInput{
|
|
||||||
Name: config.ImageName,
|
|
||||||
ID: instanceID.(string),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := instanceClient.DeleteInstance(input)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem destroying instance: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ui.Say("Terminated instance.")
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateIPReservation struct{}
|
|
||||||
|
|
||||||
func (s *stepCreateIPReservation) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
iprClient := client.IPReservations()
|
|
||||||
// TODO: add optional Name and Tags
|
|
||||||
|
|
||||||
ipresName := fmt.Sprintf("ipres_%s_%s", config.ImageName, uuid.TimeOrderedUUID())
|
|
||||||
ui.Message(fmt.Sprintf("Creating temporary IP reservation: %s", ipresName))
|
|
||||||
|
|
||||||
IPInput := &compute.CreateIPReservationInput{
|
|
||||||
ParentPool: compute.PublicReservationPool,
|
|
||||||
Permanent: true,
|
|
||||||
Name: ipresName,
|
|
||||||
}
|
|
||||||
ipRes, err := iprClient.CreateIPReservation(IPInput)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error creating IP Reservation: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put("instance_ip", ipRes.IP)
|
|
||||||
state.Put("ipres_name", ipresName)
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateIPReservation) Cleanup(state multistep.StateBag) {
|
|
||||||
ipResName, ok := state.GetOk("ipres_name")
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
ui.Say("Cleaning up IP reservations...")
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
|
|
||||||
input := compute.DeleteIPReservationInput{Name: ipResName.(string)}
|
|
||||||
ipClient := client.IPReservations()
|
|
||||||
err := ipClient.DeleteIPReservation(&input)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error deleting IP reservation: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreatePersistentVolume struct {
|
|
||||||
VolumeSize string
|
|
||||||
VolumeName string
|
|
||||||
Bootable bool
|
|
||||||
ImageList string
|
|
||||||
ImageListEntry int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreatePersistentVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
ui.Say("Creating Volume...")
|
|
||||||
|
|
||||||
c := &compute.CreateStorageVolumeInput{
|
|
||||||
Name: s.VolumeName,
|
|
||||||
Size: s.VolumeSize,
|
|
||||||
ImageList: s.ImageList,
|
|
||||||
ImageListEntry: s.ImageListEntry,
|
|
||||||
Properties: []string{"/oracle/public/storage/default"},
|
|
||||||
Bootable: s.Bootable,
|
|
||||||
}
|
|
||||||
|
|
||||||
sc := client.StorageVolumes()
|
|
||||||
cc, err := sc.CreateStorageVolume(c)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error creating persistent storage volume: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Created volume: %s", cc.Name))
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreatePersistentVolume) Cleanup(state multistep.StateBag) {
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
ui.Say("Cleaning up Volume...")
|
|
||||||
|
|
||||||
c := &compute.DeleteStorageVolumeInput{
|
|
||||||
Name: s.VolumeName,
|
|
||||||
}
|
|
||||||
|
|
||||||
sc := client.StorageVolumes()
|
|
||||||
|
|
||||||
if err := sc.DeleteStorageVolume(c); err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error cleaning up persistent storage volume: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Deleted volume: %s", s.VolumeName))
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreatePVBuilder struct {
|
|
||||||
Name string
|
|
||||||
BuilderVolumeName string
|
|
||||||
SecurityListKey string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreatePVBuilder) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
// get variables from state
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
ui.Say("Creating builder instance...")
|
|
||||||
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
ipAddName := state.Get("ipres_name").(string)
|
|
||||||
secListName := state.Get(s.SecurityListKey).(string)
|
|
||||||
|
|
||||||
// get instances client
|
|
||||||
instanceClient := client.Instances()
|
|
||||||
|
|
||||||
// Instances Input
|
|
||||||
input := &compute.CreateInstanceInput{
|
|
||||||
Name: s.Name,
|
|
||||||
Shape: config.BuilderShape,
|
|
||||||
Networking: map[string]compute.NetworkingInfo{
|
|
||||||
"eth0": {
|
|
||||||
Nat: []string{ipAddName},
|
|
||||||
SecLists: []string{secListName},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Storage: []compute.StorageAttachmentInput{
|
|
||||||
{
|
|
||||||
Volume: s.BuilderVolumeName,
|
|
||||||
Index: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ImageList: config.BuilderImageList,
|
|
||||||
SSHKeys: []string{config.Comm.SSHKeyPairName},
|
|
||||||
Entry: *config.BuilderImageListEntry,
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceInfo, err := instanceClient.CreateInstance(input)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem creating instance: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
log.Printf("Created instance %s", instanceInfo.Name)
|
|
||||||
|
|
||||||
state.Put("builder_instance_info", instanceInfo)
|
|
||||||
state.Put("builder_instance_id", instanceInfo.ID)
|
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Created builder instance: %s.", instanceInfo.Name))
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreatePVBuilder) Cleanup(state multistep.StateBag) {
|
|
||||||
instanceID, ok := state.GetOk("builder_instance_id")
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// terminate instance
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
|
|
||||||
ui.Say("Terminating builder instance...")
|
|
||||||
|
|
||||||
instanceClient := client.Instances()
|
|
||||||
input := &compute.DeleteInstanceInput{
|
|
||||||
Name: s.Name,
|
|
||||||
ID: instanceID.(string),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := instanceClient.DeleteInstance(input)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem destroying instance: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ui.Say("Terminated builder instance.")
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreatePVMaster struct {
|
|
||||||
Name string
|
|
||||||
VolumeName string
|
|
||||||
SecurityListKey string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreatePVMaster) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
ui.Say("Creating master instance...")
|
|
||||||
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
ipAddName := state.Get("ipres_name").(string)
|
|
||||||
secListName := state.Get(s.SecurityListKey).(string)
|
|
||||||
|
|
||||||
// get instances client
|
|
||||||
instanceClient := client.Instances()
|
|
||||||
|
|
||||||
// Instances Input
|
|
||||||
input := &compute.CreateInstanceInput{
|
|
||||||
Name: s.Name,
|
|
||||||
Shape: config.Shape,
|
|
||||||
Networking: map[string]compute.NetworkingInfo{
|
|
||||||
"eth0": {
|
|
||||||
Nat: []string{ipAddName},
|
|
||||||
SecLists: []string{secListName},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Storage: []compute.StorageAttachmentInput{
|
|
||||||
{
|
|
||||||
Volume: s.VolumeName,
|
|
||||||
Index: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
BootOrder: []int{1},
|
|
||||||
Attributes: config.attribs,
|
|
||||||
}
|
|
||||||
if config.Comm.Type == "ssh" {
|
|
||||||
input.SSHKeys = []string{config.Comm.SSHKeyPairName}
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceInfo, err := instanceClient.CreateInstance(input)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem creating instance: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Put("master_instance_info", instanceInfo)
|
|
||||||
state.Put("master_instance_id", instanceInfo.ID)
|
|
||||||
ui.Message(fmt.Sprintf("Created master instance: %s.", instanceInfo.Name))
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreatePVMaster) Cleanup(state multistep.StateBag) {
|
|
||||||
if _, deleted := state.GetOk("master_instance_deleted"); deleted {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceID, ok := state.GetOk("master_instance_id")
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// terminate instance
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
|
|
||||||
ui.Say("Terminating builder instance...")
|
|
||||||
|
|
||||||
instanceClient := client.Instances()
|
|
||||||
input := &compute.DeleteInstanceInput{
|
|
||||||
Name: s.Name,
|
|
||||||
ID: instanceID.(string),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := instanceClient.DeleteInstance(input)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem destroying instance: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ui.Say("Terminated master instance.")
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepListImages struct{}
|
|
||||||
|
|
||||||
func (s *stepListImages) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
// get variables from state
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
ui.Say("Adding image to image list...")
|
|
||||||
|
|
||||||
imageListClient := client.ImageList()
|
|
||||||
getInput := compute.GetImageListInput{
|
|
||||||
Name: config.DestImageList,
|
|
||||||
}
|
|
||||||
imList, err := imageListClient.GetImageList(&getInput)
|
|
||||||
if err != nil {
|
|
||||||
// If the list didn't exist, create it.
|
|
||||||
ui.Say(fmt.Sprintf(err.Error()))
|
|
||||||
ui.Say(fmt.Sprintf("Destination image list %s does not exist; Creating it...",
|
|
||||||
config.DestImageList))
|
|
||||||
|
|
||||||
ilInput := compute.CreateImageListInput{
|
|
||||||
Name: config.DestImageList,
|
|
||||||
Description: "Packer-built image list",
|
|
||||||
}
|
|
||||||
|
|
||||||
imList, err = imageListClient.CreateImageList(&ilInput)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem creating image list: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
ui.Message(fmt.Sprintf("Image list %s created!", imList.URI))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now create and image list entry for the image into that list.
|
|
||||||
machineImage := state.Get("machine_image").(string)
|
|
||||||
version := len(imList.Entries) + 1
|
|
||||||
entriesClient := client.ImageListEntries()
|
|
||||||
entriesInput := compute.CreateImageListEntryInput{
|
|
||||||
Name: config.DestImageList,
|
|
||||||
MachineImages: []string{config.Identifier(machineImage)},
|
|
||||||
Version: version,
|
|
||||||
}
|
|
||||||
entryInfo, err := entriesClient.CreateImageListEntry(&entriesInput)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem creating an image list entry: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put("image_list_entry", entryInfo)
|
|
||||||
ui.Message(fmt.Sprintf("created image list entry %s", entryInfo.Name))
|
|
||||||
|
|
||||||
machineImagesClient := client.MachineImages()
|
|
||||||
getImagesInput := compute.GetMachineImageInput{
|
|
||||||
Name: config.ImageName,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update image list default to use latest version
|
|
||||||
updateInput := compute.UpdateImageListInput{
|
|
||||||
Default: version,
|
|
||||||
Description: config.DestImageListDescription,
|
|
||||||
Name: config.DestImageList,
|
|
||||||
}
|
|
||||||
_, err = imageListClient.UpdateImageList(&updateInput)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem updating default image list version: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab info about the machine image to return with the artifact
|
|
||||||
imInfo, err := machineImagesClient.GetMachineImage(&getImagesInput)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem getting machine image info: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put("machine_image_file", imInfo.File)
|
|
||||||
state.Put("machine_image_name", imInfo.Name)
|
|
||||||
state.Put("image_list_version", version)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepListImages) Cleanup(state multistep.StateBag) {
|
|
||||||
// Nothing to do
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepSecurity struct {
|
|
||||||
CommType string
|
|
||||||
SecurityListKey string
|
|
||||||
secListName string
|
|
||||||
secRuleName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepSecurity) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
runID := state.Get("run_id").(string)
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
|
|
||||||
commType := ""
|
|
||||||
if s.CommType == "ssh" {
|
|
||||||
commType = "SSH"
|
|
||||||
} else if s.CommType == "winrm" {
|
|
||||||
commType = "WINRM"
|
|
||||||
}
|
|
||||||
secListName := fmt.Sprintf("Packer_%s_Allow_%s", commType, runID)
|
|
||||||
|
|
||||||
if _, ok := state.GetOk(secListName); ok {
|
|
||||||
log.Println("SecList created in earlier step, continuing")
|
|
||||||
// copy sec list name to proper key
|
|
||||||
state.Put(s.SecurityListKey, secListName)
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Configuring security lists and rules to enable %s access...", commType))
|
|
||||||
log.Println(secListName)
|
|
||||||
|
|
||||||
secListClient := client.SecurityLists()
|
|
||||||
secListInput := compute.CreateSecurityListInput{
|
|
||||||
Description: fmt.Sprintf("Packer-generated security list to give packer %s access", commType),
|
|
||||||
Name: config.Identifier(secListName),
|
|
||||||
}
|
|
||||||
_, err := secListClient.CreateSecurityList(&secListInput)
|
|
||||||
if err != nil {
|
|
||||||
if !strings.Contains(err.Error(), "already exists") {
|
|
||||||
err = fmt.Errorf("Error creating security List to"+
|
|
||||||
" allow Packer to connect to Oracle instance via %s: %s", commType, err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// DOCS NOTE: user must have Compute_Operations role
|
|
||||||
// Create security rule that allows Packer to connect via SSH or winRM
|
|
||||||
var application string
|
|
||||||
if commType == "SSH" {
|
|
||||||
application = "/oracle/public/ssh"
|
|
||||||
} else if commType == "WINRM" {
|
|
||||||
// Check to see whether a winRM security application is already defined
|
|
||||||
applicationClient := client.SecurityApplications()
|
|
||||||
application = fmt.Sprintf("packer_winRM_%s", runID)
|
|
||||||
applicationInput := compute.CreateSecurityApplicationInput{
|
|
||||||
Description: "Allows Packer to connect to instance via winRM",
|
|
||||||
DPort: "5985-5986",
|
|
||||||
Name: application,
|
|
||||||
Protocol: "TCP",
|
|
||||||
}
|
|
||||||
_, err = applicationClient.CreateSecurityApplication(&applicationInput)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error creating security application to"+
|
|
||||||
" allow Packer to connect to Oracle instance via %s: %s", commType, err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put("winrm_application", application)
|
|
||||||
}
|
|
||||||
secRulesClient := client.SecRules()
|
|
||||||
secRuleName := fmt.Sprintf("Packer-allow-%s-Rule_%s", commType, runID)
|
|
||||||
log.Println(secRuleName)
|
|
||||||
secRulesInput := compute.CreateSecRuleInput{
|
|
||||||
Action: "PERMIT",
|
|
||||||
Application: application,
|
|
||||||
Description: "Packer-generated security rule to allow ssh/winrm",
|
|
||||||
DestinationList: "seclist:" + config.Identifier(secListName),
|
|
||||||
Name: config.Identifier(secRuleName),
|
|
||||||
SourceList: config.SSHSourceList,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = secRulesClient.CreateSecRule(&secRulesInput)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error creating security rule to"+
|
|
||||||
" allow Packer to connect to Oracle instance: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put(s.SecurityListKey, secListName)
|
|
||||||
state.Put(secListName, true)
|
|
||||||
s.secListName = secListName
|
|
||||||
s.secRuleName = secRuleName
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepSecurity) Cleanup(state multistep.StateBag) {
|
|
||||||
if s.secListName == "" || s.secRuleName == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
|
|
||||||
ui.Say("Deleting temporary rules and lists...")
|
|
||||||
|
|
||||||
// delete security rules that Packer generated
|
|
||||||
secRulesClient := client.SecRules()
|
|
||||||
ruleInput := compute.DeleteSecRuleInput{
|
|
||||||
Name: config.Identifier(s.secRuleName),
|
|
||||||
}
|
|
||||||
err := secRulesClient.DeleteSecRule(&ruleInput)
|
|
||||||
if err != nil {
|
|
||||||
ui.Say(fmt.Sprintf("Error deleting the packer-generated security rule %s; "+
|
|
||||||
"please delete manually. (error: %s)", s.secRuleName, err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete security list that Packer generated
|
|
||||||
secListClient := client.SecurityLists()
|
|
||||||
input := compute.DeleteSecurityListInput{Name: config.Identifier(s.secListName)}
|
|
||||||
err = secListClient.DeleteSecurityList(&input)
|
|
||||||
if err != nil {
|
|
||||||
ui.Say(fmt.Sprintf("Error deleting the packer-generated security list %s; "+
|
|
||||||
"please delete manually. (error : %s)", s.secListName, err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some extra cleanup if we used the winRM communicator
|
|
||||||
if s.CommType == "winrm" {
|
|
||||||
// Delete the packer-generated application
|
|
||||||
application, ok := state.GetOk("winrm_application")
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
applicationClient := client.SecurityApplications()
|
|
||||||
deleteApplicationInput := compute.DeleteSecurityApplicationInput{
|
|
||||||
Name: config.Identifier(application.(string)),
|
|
||||||
}
|
|
||||||
err = applicationClient.DeleteSecurityApplication(&deleteApplicationInput)
|
|
||||||
if err != nil {
|
|
||||||
ui.Say(fmt.Sprintf("Error deleting the packer-generated winrm security application %s; "+
|
|
||||||
"please delete manually. (error : %s)", application.(string), err.Error()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepSnapshot struct {
|
|
||||||
cleanupSnap bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
// get variables from state
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
ui.Say("Creating Snapshot...")
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
instanceID := state.Get("instance_id").(string)
|
|
||||||
|
|
||||||
// get instances client
|
|
||||||
snapshotClient := client.Snapshots()
|
|
||||||
|
|
||||||
// Instances Input
|
|
||||||
snapshotInput := &compute.CreateSnapshotInput{
|
|
||||||
Instance: fmt.Sprintf("%s/%s", config.ImageName, instanceID),
|
|
||||||
MachineImage: config.ImageName,
|
|
||||||
Timeout: config.SnapshotTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
snap, err := snapshotClient.CreateSnapshot(snapshotInput)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem creating snapshot: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
state.Put("snapshot", snap)
|
|
||||||
state.Put("machine_image", snap.MachineImage)
|
|
||||||
ui.Message(fmt.Sprintf("Created snapshot: %s.", snap.Name))
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepSnapshot) Cleanup(state multistep.StateBag) {
|
|
||||||
// Delete the snapshot
|
|
||||||
var snap *compute.Snapshot
|
|
||||||
if snapshot, ok := state.GetOk("snapshot"); ok {
|
|
||||||
snap = snapshot.(*compute.Snapshot)
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
ui.Say("Deleting Snapshot...")
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
snapClient := client.Snapshots()
|
|
||||||
snapInput := compute.DeleteSnapshotInput{
|
|
||||||
Snapshot: snap.Name,
|
|
||||||
MachineImage: snap.MachineImage,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := snapClient.DeleteSnapshotResourceOnly(&snapInput)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem deleting snapshot: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepTerminatePVMaster struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepTerminatePVMaster) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
// get variables from state
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
ui.Say("Deleting master Instance...")
|
|
||||||
|
|
||||||
client := state.Get("client").(*compute.Client)
|
|
||||||
instanceInfo := state.Get("master_instance_info").(*compute.InstanceInfo)
|
|
||||||
|
|
||||||
// get instances client
|
|
||||||
instanceClient := client.Instances()
|
|
||||||
|
|
||||||
// Instances Input
|
|
||||||
input := &compute.DeleteInstanceInput{
|
|
||||||
Name: instanceInfo.Name,
|
|
||||||
ID: instanceInfo.ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := instanceClient.DeleteInstance(input)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem creating instance: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf("Deleted master instance: %s.", instanceInfo.ID))
|
|
||||||
state.Put("master_instance_deleted", true)
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepTerminatePVMaster) Cleanup(state multistep.StateBag) {
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package classic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepUploadImage struct {
|
|
||||||
UploadImageCommand string
|
|
||||||
segmentPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
type uploadCmdData struct {
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
AccountID string
|
|
||||||
ImageFile string
|
|
||||||
SegmentPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepUploadImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
comm := state.Get("communicator").(packersdk.Communicator)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
runID := state.Get("run_id").(string)
|
|
||||||
|
|
||||||
imageFile := fmt.Sprintf("%s.tar.gz", config.ImageName)
|
|
||||||
state.Put("image_file", imageFile)
|
|
||||||
s.segmentPath = fmt.Sprintf("compute_images_segments/%s/_segment_/%s", imageFile, runID)
|
|
||||||
|
|
||||||
config.ctx.Data = uploadCmdData{
|
|
||||||
Username: config.Username,
|
|
||||||
Password: config.Password,
|
|
||||||
AccountID: config.IdentityDomain,
|
|
||||||
ImageFile: imageFile,
|
|
||||||
SegmentPath: s.segmentPath,
|
|
||||||
}
|
|
||||||
uploadImageCmd, err := interpolate.Render(s.UploadImageCommand, &config.ctx)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error processing image upload command: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
command := fmt.Sprintf(`#!/bin/sh
|
|
||||||
set -e
|
|
||||||
mkdir /builder
|
|
||||||
mkfs -t ext3 /dev/xvdb
|
|
||||||
mount /dev/xvdb /builder
|
|
||||||
chown opc:opc /builder
|
|
||||||
cd /builder
|
|
||||||
dd if=/dev/xvdc bs=8M status=progress | cp --sparse=always /dev/stdin diskimage.raw
|
|
||||||
tar czSf ./diskimage.tar.gz ./diskimage.raw
|
|
||||||
rm diskimage.raw
|
|
||||||
%s`, uploadImageCmd)
|
|
||||||
|
|
||||||
dest := "/tmp/create-packer-diskimage.sh"
|
|
||||||
comm.Upload(dest, strings.NewReader(command), nil)
|
|
||||||
cmd := &packersdk.RemoteCmd{
|
|
||||||
Command: fmt.Sprintf("sudo /bin/sh %s", dest),
|
|
||||||
}
|
|
||||||
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
|
|
||||||
err = fmt.Errorf("Problem creating image`: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.ExitStatus() != 0 {
|
|
||||||
err = fmt.Errorf("Create Disk Image command failed with exit code %d", cmd.ExitStatus())
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Uploaded image to object storage.")
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepUploadImage) Cleanup(state multistep.StateBag) {
|
|
||||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
|
||||||
_, halted := state.GetOk(multistep.StateHalted)
|
|
||||||
if !cancelled && !halted {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("Some segments may need to be manually cleaned at '%s'", s.segmentPath)
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StepKeyPair struct {
|
|
||||||
Debug bool
|
|
||||||
Comm *communicator.Config
|
|
||||||
DebugKeyPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
if s.Comm.SSHPrivateKeyFile != "" {
|
|
||||||
ui.Say("Using existing SSH private key")
|
|
||||||
privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := ssh.ParsePrivateKey(privateKeyBytes)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error parsing 'ssh_private_key_file': %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Comm.SSHPublicKey = ssh.MarshalAuthorizedKey(key.PublicKey())
|
|
||||||
s.Comm.SSHPrivateKey = privateKeyBytes
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Creating temporary ssh key for instance...")
|
|
||||||
|
|
||||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error creating temporary SSH key: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// ASN.1 DER encoded form
|
|
||||||
privDer := x509.MarshalPKCS1PrivateKey(priv)
|
|
||||||
privBlk := pem.Block{Type: "RSA PRIVATE KEY", Headers: nil, Bytes: privDer}
|
|
||||||
|
|
||||||
// Set the private key in the statebag for later
|
|
||||||
state.Put("privateKey", string(pem.EncodeToMemory(&privBlk)))
|
|
||||||
|
|
||||||
// Marshal the public key into SSH compatible format
|
|
||||||
pub, err := ssh.NewPublicKey(&priv.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error marshaling temporary SSH public key: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Comm.SSHPublicKey = ssh.MarshalAuthorizedKey(pub)
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
err = fmt.Errorf("Error saving debug key: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
// Write the key out
|
|
||||||
if _, err := f.Write(pem.EncodeToMemory(&privBlk)); err != nil {
|
|
||||||
err = fmt.Errorf("Error saving debug key: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chmod it so that it is SSH ready
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
if err := f.Chmod(0600); err != nil {
|
|
||||||
err = fmt.Errorf("Error setting permissions of debug key: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
|
||||||
@@ -1,375 +0,0 @@
|
|||||||
Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved.
|
|
||||||
|
|
||||||
Mozilla Public License Version 2.0
|
|
||||||
==================================
|
|
||||||
|
|
||||||
1. Definitions
|
|
||||||
--------------
|
|
||||||
|
|
||||||
1.1. "Contributor"
|
|
||||||
means each individual or legal entity that creates, contributes to
|
|
||||||
the creation of, or owns Covered Software.
|
|
||||||
|
|
||||||
1.2. "Contributor Version"
|
|
||||||
means the combination of the Contributions of others (if any) used
|
|
||||||
by a Contributor and that particular Contributor's Contribution.
|
|
||||||
|
|
||||||
1.3. "Contribution"
|
|
||||||
means Covered Software of a particular Contributor.
|
|
||||||
|
|
||||||
1.4. "Covered Software"
|
|
||||||
means Source Code Form to which the initial Contributor has attached
|
|
||||||
the notice in Exhibit A, the Executable Form of such Source Code
|
|
||||||
Form, and Modifications of such Source Code Form, in each case
|
|
||||||
including portions thereof.
|
|
||||||
|
|
||||||
1.5. "Incompatible With Secondary Licenses"
|
|
||||||
means
|
|
||||||
|
|
||||||
(a) that the initial Contributor has attached the notice described
|
|
||||||
in Exhibit B to the Covered Software; or
|
|
||||||
|
|
||||||
(b) that the Covered Software was made available under the terms of
|
|
||||||
version 1.1 or earlier of the License, but not also under the
|
|
||||||
terms of a Secondary License.
|
|
||||||
|
|
||||||
1.6. "Executable Form"
|
|
||||||
means any form of the work other than Source Code Form.
|
|
||||||
|
|
||||||
1.7. "Larger Work"
|
|
||||||
means a work that combines Covered Software with other material, in
|
|
||||||
a separate file or files, that is not Covered Software.
|
|
||||||
|
|
||||||
1.8. "License"
|
|
||||||
means this document.
|
|
||||||
|
|
||||||
1.9. "Licensable"
|
|
||||||
means having the right to grant, to the maximum extent possible,
|
|
||||||
whether at the time of the initial grant or subsequently, any and
|
|
||||||
all of the rights conveyed by this License.
|
|
||||||
|
|
||||||
1.10. "Modifications"
|
|
||||||
means any of the following:
|
|
||||||
|
|
||||||
(a) any file in Source Code Form that results from an addition to,
|
|
||||||
deletion from, or modification of the contents of Covered
|
|
||||||
Software; or
|
|
||||||
|
|
||||||
(b) any new file in Source Code Form that contains any Covered
|
|
||||||
Software.
|
|
||||||
|
|
||||||
1.11. "Patent Claims" of a Contributor
|
|
||||||
means any patent claim(s), including without limitation, method,
|
|
||||||
process, and apparatus claims, in any patent Licensable by such
|
|
||||||
Contributor that would be infringed, but for the grant of the
|
|
||||||
License, by the making, using, selling, offering for sale, having
|
|
||||||
made, import, or transfer of either its Contributions or its
|
|
||||||
Contributor Version.
|
|
||||||
|
|
||||||
1.12. "Secondary License"
|
|
||||||
means either the GNU General Public License, Version 2.0, the GNU
|
|
||||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
|
||||||
Public License, Version 3.0, or any later versions of those
|
|
||||||
licenses.
|
|
||||||
|
|
||||||
1.13. "Source Code Form"
|
|
||||||
means the form of the work preferred for making modifications.
|
|
||||||
|
|
||||||
1.14. "You" (or "Your")
|
|
||||||
means an individual or a legal entity exercising rights under this
|
|
||||||
License. For legal entities, "You" includes any entity that
|
|
||||||
controls, is controlled by, or is under common control with You. For
|
|
||||||
purposes of this definition, "control" means (a) the power, direct
|
|
||||||
or indirect, to cause the direction or management of such entity,
|
|
||||||
whether by contract or otherwise, or (b) ownership of more than
|
|
||||||
fifty percent (50%) of the outstanding shares or beneficial
|
|
||||||
ownership of such entity.
|
|
||||||
|
|
||||||
2. License Grants and Conditions
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
2.1. Grants
|
|
||||||
|
|
||||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
|
||||||
non-exclusive license:
|
|
||||||
|
|
||||||
(a) under intellectual property rights (other than patent or trademark)
|
|
||||||
Licensable by such Contributor to use, reproduce, make available,
|
|
||||||
modify, display, perform, distribute, and otherwise exploit its
|
|
||||||
Contributions, either on an unmodified basis, with Modifications, or
|
|
||||||
as part of a Larger Work; and
|
|
||||||
|
|
||||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
|
||||||
for sale, have made, import, and otherwise transfer either its
|
|
||||||
Contributions or its Contributor Version.
|
|
||||||
|
|
||||||
2.2. Effective Date
|
|
||||||
|
|
||||||
The licenses granted in Section 2.1 with respect to any Contribution
|
|
||||||
become effective for each Contribution on the date the Contributor first
|
|
||||||
distributes such Contribution.
|
|
||||||
|
|
||||||
2.3. Limitations on Grant Scope
|
|
||||||
|
|
||||||
The licenses granted in this Section 2 are the only rights granted under
|
|
||||||
this License. No additional rights or licenses will be implied from the
|
|
||||||
distribution or licensing of Covered Software under this License.
|
|
||||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
|
||||||
Contributor:
|
|
||||||
|
|
||||||
(a) for any code that a Contributor has removed from Covered Software;
|
|
||||||
or
|
|
||||||
|
|
||||||
(b) for infringements caused by: (i) Your and any other third party's
|
|
||||||
modifications of Covered Software, or (ii) the combination of its
|
|
||||||
Contributions with other software (except as part of its Contributor
|
|
||||||
Version); or
|
|
||||||
|
|
||||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
|
||||||
its Contributions.
|
|
||||||
|
|
||||||
This License does not grant any rights in the trademarks, service marks,
|
|
||||||
or logos of any Contributor (except as may be necessary to comply with
|
|
||||||
the notice requirements in Section 3.4).
|
|
||||||
|
|
||||||
2.4. Subsequent Licenses
|
|
||||||
|
|
||||||
No Contributor makes additional grants as a result of Your choice to
|
|
||||||
distribute the Covered Software under a subsequent version of this
|
|
||||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
|
||||||
permitted under the terms of Section 3.3).
|
|
||||||
|
|
||||||
2.5. Representation
|
|
||||||
|
|
||||||
Each Contributor represents that the Contributor believes its
|
|
||||||
Contributions are its original creation(s) or it has sufficient rights
|
|
||||||
to grant the rights to its Contributions conveyed by this License.
|
|
||||||
|
|
||||||
2.6. Fair Use
|
|
||||||
|
|
||||||
This License is not intended to limit any rights You have under
|
|
||||||
applicable copyright doctrines of fair use, fair dealing, or other
|
|
||||||
equivalents.
|
|
||||||
|
|
||||||
2.7. Conditions
|
|
||||||
|
|
||||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
|
||||||
in Section 2.1.
|
|
||||||
|
|
||||||
3. Responsibilities
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
3.1. Distribution of Source Form
|
|
||||||
|
|
||||||
All distribution of Covered Software in Source Code Form, including any
|
|
||||||
Modifications that You create or to which You contribute, must be under
|
|
||||||
the terms of this License. You must inform recipients that the Source
|
|
||||||
Code Form of the Covered Software is governed by the terms of this
|
|
||||||
License, and how they can obtain a copy of this License. You may not
|
|
||||||
attempt to alter or restrict the recipients' rights in the Source Code
|
|
||||||
Form.
|
|
||||||
|
|
||||||
3.2. Distribution of Executable Form
|
|
||||||
|
|
||||||
If You distribute Covered Software in Executable Form then:
|
|
||||||
|
|
||||||
(a) such Covered Software must also be made available in Source Code
|
|
||||||
Form, as described in Section 3.1, and You must inform recipients of
|
|
||||||
the Executable Form how they can obtain a copy of such Source Code
|
|
||||||
Form by reasonable means in a timely manner, at a charge no more
|
|
||||||
than the cost of distribution to the recipient; and
|
|
||||||
|
|
||||||
(b) You may distribute such Executable Form under the terms of this
|
|
||||||
License, or sublicense it under different terms, provided that the
|
|
||||||
license for the Executable Form does not attempt to limit or alter
|
|
||||||
the recipients' rights in the Source Code Form under this License.
|
|
||||||
|
|
||||||
3.3. Distribution of a Larger Work
|
|
||||||
|
|
||||||
You may create and distribute a Larger Work under terms of Your choice,
|
|
||||||
provided that You also comply with the requirements of this License for
|
|
||||||
the Covered Software. If the Larger Work is a combination of Covered
|
|
||||||
Software with a work governed by one or more Secondary Licenses, and the
|
|
||||||
Covered Software is not Incompatible With Secondary Licenses, this
|
|
||||||
License permits You to additionally distribute such Covered Software
|
|
||||||
under the terms of such Secondary License(s), so that the recipient of
|
|
||||||
the Larger Work may, at their option, further distribute the Covered
|
|
||||||
Software under the terms of either this License or such Secondary
|
|
||||||
License(s).
|
|
||||||
|
|
||||||
3.4. Notices
|
|
||||||
|
|
||||||
You may not remove or alter the substance of any license notices
|
|
||||||
(including copyright notices, patent notices, disclaimers of warranty,
|
|
||||||
or limitations of liability) contained within the Source Code Form of
|
|
||||||
the Covered Software, except that You may alter any license notices to
|
|
||||||
the extent required to remedy known factual inaccuracies.
|
|
||||||
|
|
||||||
3.5. Application of Additional Terms
|
|
||||||
|
|
||||||
You may choose to offer, and to charge a fee for, warranty, support,
|
|
||||||
indemnity or liability obligations to one or more recipients of Covered
|
|
||||||
Software. However, You may do so only on Your own behalf, and not on
|
|
||||||
behalf of any Contributor. You must make it absolutely clear that any
|
|
||||||
such warranty, support, indemnity, or liability obligation is offered by
|
|
||||||
You alone, and You hereby agree to indemnify every Contributor for any
|
|
||||||
liability incurred by such Contributor as a result of warranty, support,
|
|
||||||
indemnity or liability terms You offer. You may include additional
|
|
||||||
disclaimers of warranty and limitations of liability specific to any
|
|
||||||
jurisdiction.
|
|
||||||
|
|
||||||
4. Inability to Comply Due to Statute or Regulation
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
If it is impossible for You to comply with any of the terms of this
|
|
||||||
License with respect to some or all of the Covered Software due to
|
|
||||||
statute, judicial order, or regulation then You must: (a) comply with
|
|
||||||
the terms of this License to the maximum extent possible; and (b)
|
|
||||||
describe the limitations and the code they affect. Such description must
|
|
||||||
be placed in a text file included with all distributions of the Covered
|
|
||||||
Software under this License. Except to the extent prohibited by statute
|
|
||||||
or regulation, such description must be sufficiently detailed for a
|
|
||||||
recipient of ordinary skill to be able to understand it.
|
|
||||||
|
|
||||||
5. Termination
|
|
||||||
--------------
|
|
||||||
|
|
||||||
5.1. The rights granted under this License will terminate automatically
|
|
||||||
if You fail to comply with any of its terms. However, if You become
|
|
||||||
compliant, then the rights granted under this License from a particular
|
|
||||||
Contributor are reinstated (a) provisionally, unless and until such
|
|
||||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
|
||||||
ongoing basis, if such Contributor fails to notify You of the
|
|
||||||
non-compliance by some reasonable means prior to 60 days after You have
|
|
||||||
come back into compliance. Moreover, Your grants from a particular
|
|
||||||
Contributor are reinstated on an ongoing basis if such Contributor
|
|
||||||
notifies You of the non-compliance by some reasonable means, this is the
|
|
||||||
first time You have received notice of non-compliance with this License
|
|
||||||
from such Contributor, and You become compliant prior to 30 days after
|
|
||||||
Your receipt of the notice.
|
|
||||||
|
|
||||||
5.2. If You initiate litigation against any entity by asserting a patent
|
|
||||||
infringement claim (excluding declaratory judgment actions,
|
|
||||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
|
||||||
directly or indirectly infringes any patent, then the rights granted to
|
|
||||||
You by any and all Contributors for the Covered Software under Section
|
|
||||||
2.1 of this License shall terminate.
|
|
||||||
|
|
||||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
|
||||||
end user license agreements (excluding distributors and resellers) which
|
|
||||||
have been validly granted by You or Your distributors under this License
|
|
||||||
prior to termination shall survive termination.
|
|
||||||
|
|
||||||
************************************************************************
|
|
||||||
* *
|
|
||||||
* 6. Disclaimer of Warranty *
|
|
||||||
* ------------------------- *
|
|
||||||
* *
|
|
||||||
* Covered Software is provided under this License on an "as is" *
|
|
||||||
* basis, without warranty of any kind, either expressed, implied, or *
|
|
||||||
* statutory, including, without limitation, warranties that the *
|
|
||||||
* Covered Software is free of defects, merchantable, fit for a *
|
|
||||||
* particular purpose or non-infringing. The entire risk as to the *
|
|
||||||
* quality and performance of the Covered Software is with You. *
|
|
||||||
* Should any Covered Software prove defective in any respect, You *
|
|
||||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
|
||||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
|
||||||
* essential part of this License. No use of any Covered Software is *
|
|
||||||
* authorized under this License except under this disclaimer. *
|
|
||||||
* *
|
|
||||||
************************************************************************
|
|
||||||
|
|
||||||
************************************************************************
|
|
||||||
* *
|
|
||||||
* 7. Limitation of Liability *
|
|
||||||
* -------------------------- *
|
|
||||||
* *
|
|
||||||
* Under no circumstances and under no legal theory, whether tort *
|
|
||||||
* (including negligence), contract, or otherwise, shall any *
|
|
||||||
* Contributor, or anyone who distributes Covered Software as *
|
|
||||||
* permitted above, be liable to You for any direct, indirect, *
|
|
||||||
* special, incidental, or consequential damages of any character *
|
|
||||||
* including, without limitation, damages for lost profits, loss of *
|
|
||||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
|
||||||
* and all other commercial damages or losses, even if such party *
|
|
||||||
* shall have been informed of the possibility of such damages. This *
|
|
||||||
* limitation of liability shall not apply to liability for death or *
|
|
||||||
* personal injury resulting from such party's negligence to the *
|
|
||||||
* extent applicable law prohibits such limitation. Some *
|
|
||||||
* jurisdictions do not allow the exclusion or limitation of *
|
|
||||||
* incidental or consequential damages, so this exclusion and *
|
|
||||||
* limitation may not apply to You. *
|
|
||||||
* *
|
|
||||||
************************************************************************
|
|
||||||
|
|
||||||
8. Litigation
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Any litigation relating to this License may be brought only in the
|
|
||||||
courts of a jurisdiction where the defendant maintains its principal
|
|
||||||
place of business and such litigation shall be governed by laws of that
|
|
||||||
jurisdiction, without reference to its conflict-of-law provisions.
|
|
||||||
Nothing in this Section shall prevent a party's ability to bring
|
|
||||||
cross-claims or counter-claims.
|
|
||||||
|
|
||||||
9. Miscellaneous
|
|
||||||
----------------
|
|
||||||
|
|
||||||
This License represents the complete agreement concerning the subject
|
|
||||||
matter hereof. If any provision of this License is held to be
|
|
||||||
unenforceable, such provision shall be reformed only to the extent
|
|
||||||
necessary to make it enforceable. Any law or regulation which provides
|
|
||||||
that the language of a contract shall be construed against the drafter
|
|
||||||
shall not be used to construe this License against a Contributor.
|
|
||||||
|
|
||||||
10. Versions of the License
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
10.1. New Versions
|
|
||||||
|
|
||||||
Mozilla Foundation is the license steward. Except as provided in Section
|
|
||||||
10.3, no one other than the license steward has the right to modify or
|
|
||||||
publish new versions of this License. Each version will be given a
|
|
||||||
distinguishing version number.
|
|
||||||
|
|
||||||
10.2. Effect of New Versions
|
|
||||||
|
|
||||||
You may distribute the Covered Software under the terms of the version
|
|
||||||
of the License under which You originally received the Covered Software,
|
|
||||||
or under the terms of any subsequent version published by the license
|
|
||||||
steward.
|
|
||||||
|
|
||||||
10.3. Modified Versions
|
|
||||||
|
|
||||||
If you create software not governed by this License, and you want to
|
|
||||||
create a new license for such software, you may create and use a
|
|
||||||
modified version of this License if you rename the license and remove
|
|
||||||
any references to the name of the license steward (except to note that
|
|
||||||
such modified license differs from this License).
|
|
||||||
|
|
||||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
|
||||||
Licenses
|
|
||||||
|
|
||||||
If You choose to distribute Source Code Form that is Incompatible With
|
|
||||||
Secondary Licenses under the terms of this version of the License, the
|
|
||||||
notice described in Exhibit B of this License must be attached.
|
|
||||||
|
|
||||||
Exhibit A - Source Code Form License Notice
|
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
If it is not possible or desirable to put the notice in a particular
|
|
||||||
file, then You may include the notice in a location (such as a LICENSE
|
|
||||||
file in a relevant directory) where a recipient would be likely to look
|
|
||||||
for such a notice.
|
|
||||||
|
|
||||||
You may add additional accurate notices of copyright ownership.
|
|
||||||
|
|
||||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
||||||
defined by the Mozilla Public License, v. 2.0.
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/oracle/oci-go-sdk/v36/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Artifact is an artifact implementation that contains a built Custom Image.
|
|
||||||
type Artifact struct {
|
|
||||||
Image core.Image
|
|
||||||
Region string
|
|
||||||
driver Driver
|
|
||||||
|
|
||||||
// StateData should store data such as GeneratedData
|
|
||||||
// to be shared with post-processors
|
|
||||||
StateData map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuilderId uniquely identifies the builder.
|
|
||||||
func (a *Artifact) BuilderId() string {
|
|
||||||
return BuilderId
|
|
||||||
}
|
|
||||||
|
|
||||||
// Files lists the files associated with an artifact. We don't have any files
|
|
||||||
// as the custom image is stored server side.
|
|
||||||
func (a *Artifact) Files() []string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Id returns the OCID of the associated Image.
|
|
||||||
func (a *Artifact) Id() string {
|
|
||||||
return *a.Image.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Artifact) String() string {
|
|
||||||
var displayName string
|
|
||||||
if a.Image.DisplayName != nil {
|
|
||||||
displayName = *a.Image.DisplayName
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"An image was created: '%v' (OCID: %v) in region '%v'",
|
|
||||||
displayName, *a.Image.Id, a.Region,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// State ...
|
|
||||||
func (a *Artifact) State(name string) interface{} {
|
|
||||||
return a.StateData[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy deletes the custom image associated with the artifact.
|
|
||||||
func (a *Artifact) Destroy() error {
|
|
||||||
return a.driver.DeleteImage(context.TODO(), *a.Image.Id)
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestArtifactImpl(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &Artifact{}
|
|
||||||
if _, ok := raw.(packersdk.Artifact); !ok {
|
|
||||||
t.Fatalf("Artifact should be 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,110 +0,0 @@
|
|||||||
// Package oci contains a packersdk.Builder implementation that builds Oracle
|
|
||||||
// Bare Metal Cloud Services (OCI) images.
|
|
||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
ocommon "github.com/hashicorp/packer/builder/oracle/common"
|
|
||||||
"github.com/oracle/oci-go-sdk/v36/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BuilderId uniquely identifies the builder
|
|
||||||
const BuilderId = "packer.oracle.oci"
|
|
||||||
|
|
||||||
// OCI API version
|
|
||||||
const ociAPIVersion = "20160918"
|
|
||||||
|
|
||||||
// Builder is a builder implementation that creates Oracle OCI custom images.
|
|
||||||
type Builder struct {
|
|
||||||
config Config
|
|
||||||
runner multistep.Runner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
||||||
err := b.config.Prepare(raws...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
|
||||||
driver, err := NewDriverOCI(&b.config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate the state bag
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("config", &b.config)
|
|
||||||
state.Put("driver", driver)
|
|
||||||
state.Put("hook", hook)
|
|
||||||
state.Put("ui", ui)
|
|
||||||
|
|
||||||
// Build the steps
|
|
||||||
steps := []multistep.Step{
|
|
||||||
&ocommon.StepKeyPair{
|
|
||||||
Debug: b.config.PackerDebug,
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
DebugKeyPath: fmt.Sprintf("oci_%s.pem", b.config.PackerBuildName),
|
|
||||||
},
|
|
||||||
&stepCreateInstance{},
|
|
||||||
&stepInstanceInfo{},
|
|
||||||
&stepGetDefaultCredentials{
|
|
||||||
Debug: b.config.PackerDebug,
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
BuildName: b.config.PackerBuildName,
|
|
||||||
},
|
|
||||||
&communicator.StepConnect{
|
|
||||||
Config: &b.config.Comm,
|
|
||||||
Host: communicator.CommHost(b.config.Comm.Host(), "instance_ip"),
|
|
||||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
|
||||||
},
|
|
||||||
&commonsteps.StepProvision{},
|
|
||||||
&commonsteps.StepCleanupTempKeys{
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
},
|
|
||||||
&stepImage{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the steps
|
|
||||||
b.runner = commonsteps.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
|
||||||
b.runner.Run(ctx, state)
|
|
||||||
|
|
||||||
// If there was an error, return that
|
|
||||||
if rawErr, ok := state.GetOk("error"); ok {
|
|
||||||
return nil, rawErr.(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
region, err := b.config.configProvider.Region()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
image, ok := state.GetOk("image")
|
|
||||||
if !ok {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the artifact and return it
|
|
||||||
artifact := &Artifact{
|
|
||||||
Image: image.(core.Image),
|
|
||||||
Region: region,
|
|
||||||
driver: driver,
|
|
||||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel terminates a running build.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &Builder{}
|
|
||||||
if _, ok := raw.(packersdk.Builder); !ok {
|
|
||||||
t.Fatalf("Builder should be a builder")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,415 +0,0 @@
|
|||||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest,FlexShapeConfig
|
|
||||||
|
|
||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/pathing"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
|
||||||
ocicommon "github.com/oracle/oci-go-sdk/v36/common"
|
|
||||||
ociauth "github.com/oracle/oci-go-sdk/v36/common/auth"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CreateVNICDetails struct {
|
|
||||||
// fields that can be specified under "create_vnic_details"
|
|
||||||
AssignPublicIp *bool `mapstructure:"assign_public_ip" required:"false"`
|
|
||||||
DefinedTags map[string]map[string]interface{} `mapstructure:"defined_tags" required:"false"`
|
|
||||||
DisplayName *string `mapstructure:"display_name" required:"false"`
|
|
||||||
FreeformTags map[string]string `mapstructure:"tags" required:"false"`
|
|
||||||
HostnameLabel *string `mapstructure:"hostname_label" required:"false"`
|
|
||||||
NsgIds []string `mapstructure:"nsg_ids" required:"false"`
|
|
||||||
PrivateIp *string `mapstructure:"private_ip" required:"false"`
|
|
||||||
SkipSourceDestCheck *bool `mapstructure:"skip_source_dest_check" required:"false"`
|
|
||||||
SubnetId *string `mapstructure:"subnet_id" required:"false"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListImagesRequest struct {
|
|
||||||
// fields that can be specified under "base_image_filter"
|
|
||||||
CompartmentId *string `mapstructure:"compartment_id"`
|
|
||||||
DisplayName *string `mapstructure:"display_name"`
|
|
||||||
DisplayNameSearch *string `mapstructure:"display_name_search"`
|
|
||||||
OperatingSystem *string `mapstructure:"operating_system"`
|
|
||||||
OperatingSystemVersion *string `mapstructure:"operating_system_version"`
|
|
||||||
Shape *string `mapstructure:"shape"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FlexShapeConfig struct {
|
|
||||||
Ocpus *float32 `mapstructure:"ocpus" required:"false"`
|
|
||||||
MemoryInGBs *float32 `mapstructure:"memory_in_gbs" required:"false"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
|
||||||
|
|
||||||
configProvider ocicommon.ConfigurationProvider
|
|
||||||
|
|
||||||
// Instance Principals (OPTIONAL)
|
|
||||||
// If set to true the following can't have non empty values
|
|
||||||
// - AccessCfgFile
|
|
||||||
// - AccessCfgFileAccount
|
|
||||||
// - UserID
|
|
||||||
// - TenancyID
|
|
||||||
// - Region
|
|
||||||
// - Fingerprint
|
|
||||||
// - KeyFile
|
|
||||||
// - PassPhrase
|
|
||||||
InstancePrincipals bool `mapstructure:"use_instance_principals"`
|
|
||||||
|
|
||||||
AccessCfgFile string `mapstructure:"access_cfg_file"`
|
|
||||||
AccessCfgFileAccount string `mapstructure:"access_cfg_file_account"`
|
|
||||||
|
|
||||||
// Access config overrides
|
|
||||||
UserID string `mapstructure:"user_ocid"`
|
|
||||||
TenancyID string `mapstructure:"tenancy_ocid"`
|
|
||||||
Region string `mapstructure:"region"`
|
|
||||||
Fingerprint string `mapstructure:"fingerprint"`
|
|
||||||
KeyFile string `mapstructure:"key_file"`
|
|
||||||
PassPhrase string `mapstructure:"pass_phrase"`
|
|
||||||
UsePrivateIP bool `mapstructure:"use_private_ip"`
|
|
||||||
|
|
||||||
AvailabilityDomain string `mapstructure:"availability_domain"`
|
|
||||||
CompartmentID string `mapstructure:"compartment_ocid"`
|
|
||||||
|
|
||||||
// Image
|
|
||||||
BaseImageID string `mapstructure:"base_image_ocid"`
|
|
||||||
BaseImageFilter ListImagesRequest `mapstructure:"base_image_filter"`
|
|
||||||
ImageName string `mapstructure:"image_name"`
|
|
||||||
ImageCompartmentID string `mapstructure:"image_compartment_ocid"`
|
|
||||||
LaunchMode string `mapstructure:"image_launch_mode"`
|
|
||||||
|
|
||||||
// Instance
|
|
||||||
InstanceName *string `mapstructure:"instance_name"`
|
|
||||||
InstanceTags map[string]string `mapstructure:"instance_tags"`
|
|
||||||
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
|
|
||||||
Shape string `mapstructure:"shape"`
|
|
||||||
ShapeConfig FlexShapeConfig `mapstructure:"shape_config"`
|
|
||||||
BootVolumeSizeInGBs int64 `mapstructure:"disk_size"`
|
|
||||||
|
|
||||||
// Metadata optionally contains custom metadata key/value pairs provided in the
|
|
||||||
// configuration. While this can be used to set metadata["user_data"] the explicit
|
|
||||||
// "user_data" and "user_data_file" values will have precedence.
|
|
||||||
// An instance's metadata can be obtained from at http://169.254.169.254 on the
|
|
||||||
// launched instance.
|
|
||||||
Metadata map[string]string `mapstructure:"metadata"`
|
|
||||||
|
|
||||||
// UserData and UserDataFile file are both optional and mutually exclusive.
|
|
||||||
UserData string `mapstructure:"user_data"`
|
|
||||||
UserDataFile string `mapstructure:"user_data_file"`
|
|
||||||
|
|
||||||
// Networking
|
|
||||||
SubnetID string `mapstructure:"subnet_ocid"`
|
|
||||||
CreateVnicDetails CreateVNICDetails `mapstructure:"create_vnic_details"`
|
|
||||||
|
|
||||||
// Tagging
|
|
||||||
Tags map[string]string `mapstructure:"tags"`
|
|
||||||
DefinedTags map[string]map[string]interface{} `mapstructure:"defined_tags"`
|
|
||||||
|
|
||||||
ctx interpolate.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) ConfigProvider() ocicommon.ConfigurationProvider {
|
|
||||||
return c.configProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) error {
|
|
||||||
|
|
||||||
// Decode from template
|
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
|
||||||
Interpolate: true,
|
|
||||||
InterpolateContext: &c.ctx,
|
|
||||||
}, raws...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to mapstructure Config: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs *packersdk.MultiError
|
|
||||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, es...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var tenancyOCID string
|
|
||||||
|
|
||||||
if c.InstancePrincipals {
|
|
||||||
// We could go through all keys in one go and report that the below set
|
|
||||||
// of keys cannot coexist with use_instance_principals but decided to
|
|
||||||
// split them and report them seperately so that the user sees the specific
|
|
||||||
// key involved.
|
|
||||||
var message string = " cannot be present when use_instance_principals is set to true."
|
|
||||||
if c.AccessCfgFile != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("access_cfg_file"+message))
|
|
||||||
}
|
|
||||||
if c.AccessCfgFileAccount != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("access_cfg_file_account"+message))
|
|
||||||
}
|
|
||||||
if c.UserID != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("user_ocid"+message))
|
|
||||||
}
|
|
||||||
if c.TenancyID != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("tenancy_ocid"+message))
|
|
||||||
}
|
|
||||||
if c.Region != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("region"+message))
|
|
||||||
}
|
|
||||||
if c.Fingerprint != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("fingerprint"+message))
|
|
||||||
}
|
|
||||||
if c.KeyFile != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("key_file"+message))
|
|
||||||
}
|
|
||||||
if c.PassPhrase != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("pass_phrase"+message))
|
|
||||||
}
|
|
||||||
// This check is used to facilitate testing. During testing a Mock struct
|
|
||||||
// is assigned to c.configProvider otherwise testing fails because Instance
|
|
||||||
// Principals cannot be obtained.
|
|
||||||
if c.configProvider == nil {
|
|
||||||
// Even though the previous configuraion checks might fail we don't want
|
|
||||||
// to skip this step. It seems that the logic behind the checks in this
|
|
||||||
// file is to check everything even getting the configProvider.
|
|
||||||
c.configProvider, err = ociauth.InstancePrincipalConfigurationProvider()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tenancyOCID, err = c.configProvider.TenancyOCID()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Determine where the SDK config is located
|
|
||||||
if c.AccessCfgFile == "" {
|
|
||||||
c.AccessCfgFile, err = getDefaultOCISettingsPath()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Default OCI settings file not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.AccessCfgFileAccount == "" {
|
|
||||||
c.AccessCfgFileAccount = "DEFAULT"
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyContent []byte
|
|
||||||
if c.KeyFile != "" {
|
|
||||||
path, err := pathing.ExpandUser(c.KeyFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read API signing key
|
|
||||||
keyContent, err = ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileProvider, _ := ocicommon.ConfigurationProviderFromFileWithProfile(c.AccessCfgFile, c.AccessCfgFileAccount, c.PassPhrase)
|
|
||||||
if c.Region == "" {
|
|
||||||
var region string
|
|
||||||
if fileProvider != nil {
|
|
||||||
region, _ = fileProvider.Region()
|
|
||||||
}
|
|
||||||
if region == "" {
|
|
||||||
c.Region = "us-phoenix-1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
providers := []ocicommon.ConfigurationProvider{
|
|
||||||
ocicommon.NewRawConfigurationProvider(c.TenancyID, c.UserID, c.Region, c.Fingerprint, string(keyContent), &c.PassPhrase),
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileProvider != nil {
|
|
||||||
providers = append(providers, fileProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load API access configuration from SDK
|
|
||||||
configProvider, err := ocicommon.ComposingConfigurationProvider(providers)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if userOCID, _ := configProvider.UserOCID(); userOCID == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("'user_ocid' must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
tenancyOCID, _ = configProvider.TenancyOCID()
|
|
||||||
if tenancyOCID == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("'tenancy_ocid' must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if fingerprint, _ := configProvider.KeyFingerprint(); fingerprint == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("'fingerprint' must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := configProvider.PrivateRSAKey(); err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("'key_file' must be correctly specified. %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
c.configProvider = configProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.AvailabilityDomain == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("'availability_domain' must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.CompartmentID == "" && tenancyOCID != "" {
|
|
||||||
c.CompartmentID = tenancyOCID
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.ImageCompartmentID == "" {
|
|
||||||
c.ImageCompartmentID = c.CompartmentID
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Shape == "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("'shape' must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasSuffix(c.Shape, "Flex") {
|
|
||||||
if c.ShapeConfig.Ocpus == nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("'Ocpus' must be specified when using flexible shapes"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.ShapeConfig.MemoryInGBs != nil && c.ShapeConfig.Ocpus == nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("'Ocpus' must be specified if memory_in_gbs is specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c.SubnetID == "") && (c.CreateVnicDetails.SubnetId == nil) {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("'subnet_ocid' must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.CreateVnicDetails.SubnetId == nil {
|
|
||||||
c.CreateVnicDetails.SubnetId = &c.SubnetID
|
|
||||||
} else if (*c.CreateVnicDetails.SubnetId != c.SubnetID) && (c.SubnetID != "") {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("'create_vnic_details[subnet]' must match 'subnet_ocid' if both are specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c.BaseImageID == "") && (c.BaseImageFilter == ListImagesRequest{}) {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("'base_image_ocid' or 'base_image_filter' must be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.BaseImageFilter.CompartmentId == nil {
|
|
||||||
c.BaseImageFilter.CompartmentId = &c.CompartmentID
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.BaseImageFilter.Shape == nil {
|
|
||||||
c.BaseImageFilter.Shape = &c.Shape
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate tag lengths. TODO (hlowndes) maximum number of tags allowed.
|
|
||||||
if c.Tags != nil {
|
|
||||||
for k, v := range c.Tags {
|
|
||||||
k = strings.TrimSpace(k)
|
|
||||||
v = strings.TrimSpace(v)
|
|
||||||
if len(k) > 100 {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("Tag key length too long. Maximum 100 but found %d. Key: %s", len(k), k))
|
|
||||||
}
|
|
||||||
if len(k) == 0 {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("Tag key empty in config"))
|
|
||||||
}
|
|
||||||
if len(v) > 100 {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("Tag value length too long. Maximum 100 but found %d. Key: %s", len(v), k))
|
|
||||||
}
|
|
||||||
if len(v) == 0 {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("Tag value empty in config"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.ImageName == "" {
|
|
||||||
name, err := interpolate.Render("packer-{{timestamp}}", nil)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs,
|
|
||||||
fmt.Errorf("unable to parse image name: %s", err))
|
|
||||||
} else {
|
|
||||||
c.ImageName = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optional UserData config
|
|
||||||
if c.UserData != "" && c.UserDataFile != "" {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
|
|
||||||
} else if c.UserDataFile != "" {
|
|
||||||
if _, err := os.Stat(c.UserDataFile); err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// read UserDataFile into string.
|
|
||||||
if c.UserDataFile != "" {
|
|
||||||
fiData, err := ioutil.ReadFile(c.UserDataFile)
|
|
||||||
if err != nil {
|
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("Problem reading user_data_file: %s", err))
|
|
||||||
}
|
|
||||||
c.UserData = string(fiData)
|
|
||||||
}
|
|
||||||
// Test if UserData is encoded already, and if not, encode it
|
|
||||||
if c.UserData != "" {
|
|
||||||
if _, err := base64.StdEncoding.DecodeString(c.UserData); err != nil {
|
|
||||||
log.Printf("[DEBUG] base64 encoding user data...")
|
|
||||||
c.UserData = base64.StdEncoding.EncodeToString([]byte(c.UserData))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set default boot volume size to 50 if not set
|
|
||||||
// Check if size set is allowed by OCI
|
|
||||||
if c.BootVolumeSizeInGBs == 0 {
|
|
||||||
c.BootVolumeSizeInGBs = 50
|
|
||||||
} else if c.BootVolumeSizeInGBs < 50 || c.BootVolumeSizeInGBs > 16384 {
|
|
||||||
errs = packersdk.MultiErrorAppend(
|
|
||||||
errs, errors.New("'disk_size' must be between 50 and 16384 GBs"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDefaultOCISettingsPath uses os/user to compute the default
|
|
||||||
// config file location ($HOME/.oci/config).
|
|
||||||
func getDefaultOCISettingsPath() (string, error) {
|
|
||||||
u, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.HomeDir == "" {
|
|
||||||
return "", fmt.Errorf("Unable to determine the home directory for the current user.")
|
|
||||||
}
|
|
||||||
|
|
||||||
path := filepath.Join(u.HomeDir, ".oci", "config")
|
|
||||||
if _, err := os.Stat(path); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
@@ -1,300 +0,0 @@
|
|||||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatConfig struct {
|
|
||||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
|
||||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
|
||||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
|
||||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
|
||||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
|
||||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
|
||||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
|
||||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
|
||||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
|
||||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
|
||||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
|
||||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
|
||||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
|
||||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
|
||||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
|
||||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
|
||||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
|
||||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
|
||||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
|
||||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
|
||||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
|
||||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
|
||||||
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
|
||||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
|
||||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
|
||||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
|
||||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
|
||||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
|
||||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
|
||||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
|
||||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
|
||||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
|
||||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
|
||||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
|
||||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
|
||||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
|
||||||
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
|
||||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
|
||||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
|
||||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
|
||||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
|
||||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
|
||||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
|
||||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
|
||||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
|
||||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
|
||||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
|
||||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
|
||||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
|
||||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
|
||||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
|
||||||
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
|
||||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
|
||||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
|
||||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
|
||||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
|
||||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
|
||||||
InstancePrincipals *bool `mapstructure:"use_instance_principals" cty:"use_instance_principals" hcl:"use_instance_principals"`
|
|
||||||
AccessCfgFile *string `mapstructure:"access_cfg_file" cty:"access_cfg_file" hcl:"access_cfg_file"`
|
|
||||||
AccessCfgFileAccount *string `mapstructure:"access_cfg_file_account" cty:"access_cfg_file_account" hcl:"access_cfg_file_account"`
|
|
||||||
UserID *string `mapstructure:"user_ocid" cty:"user_ocid" hcl:"user_ocid"`
|
|
||||||
TenancyID *string `mapstructure:"tenancy_ocid" cty:"tenancy_ocid" hcl:"tenancy_ocid"`
|
|
||||||
Region *string `mapstructure:"region" cty:"region" hcl:"region"`
|
|
||||||
Fingerprint *string `mapstructure:"fingerprint" cty:"fingerprint" hcl:"fingerprint"`
|
|
||||||
KeyFile *string `mapstructure:"key_file" cty:"key_file" hcl:"key_file"`
|
|
||||||
PassPhrase *string `mapstructure:"pass_phrase" cty:"pass_phrase" hcl:"pass_phrase"`
|
|
||||||
UsePrivateIP *bool `mapstructure:"use_private_ip" cty:"use_private_ip" hcl:"use_private_ip"`
|
|
||||||
AvailabilityDomain *string `mapstructure:"availability_domain" cty:"availability_domain" hcl:"availability_domain"`
|
|
||||||
CompartmentID *string `mapstructure:"compartment_ocid" cty:"compartment_ocid" hcl:"compartment_ocid"`
|
|
||||||
BaseImageID *string `mapstructure:"base_image_ocid" cty:"base_image_ocid" hcl:"base_image_ocid"`
|
|
||||||
BaseImageFilter *FlatListImagesRequest `mapstructure:"base_image_filter" cty:"base_image_filter" hcl:"base_image_filter"`
|
|
||||||
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
|
||||||
ImageCompartmentID *string `mapstructure:"image_compartment_ocid" cty:"image_compartment_ocid" hcl:"image_compartment_ocid"`
|
|
||||||
LaunchMode *string `mapstructure:"image_launch_mode" cty:"image_launch_mode" hcl:"image_launch_mode"`
|
|
||||||
InstanceName *string `mapstructure:"instance_name" cty:"instance_name" hcl:"instance_name"`
|
|
||||||
InstanceTags map[string]string `mapstructure:"instance_tags" cty:"instance_tags" hcl:"instance_tags"`
|
|
||||||
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags" cty:"instance_defined_tags" hcl:"instance_defined_tags"`
|
|
||||||
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
|
|
||||||
ShapeConfig *FlatFlexShapeConfig `mapstructure:"shape_config" cty:"shape_config" hcl:"shape_config"`
|
|
||||||
BootVolumeSizeInGBs *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
|
|
||||||
Metadata map[string]string `mapstructure:"metadata" cty:"metadata" hcl:"metadata"`
|
|
||||||
UserData *string `mapstructure:"user_data" cty:"user_data" hcl:"user_data"`
|
|
||||||
UserDataFile *string `mapstructure:"user_data_file" cty:"user_data_file" hcl:"user_data_file"`
|
|
||||||
SubnetID *string `mapstructure:"subnet_ocid" cty:"subnet_ocid" hcl:"subnet_ocid"`
|
|
||||||
CreateVnicDetails *FlatCreateVNICDetails `mapstructure:"create_vnic_details" cty:"create_vnic_details" hcl:"create_vnic_details"`
|
|
||||||
Tags map[string]string `mapstructure:"tags" cty:"tags" hcl:"tags"`
|
|
||||||
DefinedTags map[string]map[string]interface{} `mapstructure:"defined_tags" cty:"defined_tags" hcl:"defined_tags"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatConfig.
|
|
||||||
// FlatConfig is an auto-generated flat version of Config.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a Config.
|
|
||||||
// This spec is used by HCL to read the fields of Config.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
|
||||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
|
||||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
|
||||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
|
||||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
|
||||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
|
||||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
|
||||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
|
||||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
|
||||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
|
||||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
|
||||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
|
||||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
|
||||||
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
|
||||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
|
||||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
|
||||||
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
|
||||||
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
|
||||||
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
|
||||||
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
|
||||||
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
|
||||||
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
|
||||||
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
|
||||||
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
|
||||||
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
|
||||||
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
|
||||||
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
|
||||||
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
|
||||||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
|
||||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
|
||||||
"use_instance_principals": &hcldec.AttrSpec{Name: "use_instance_principals", Type: cty.Bool, Required: false},
|
|
||||||
"access_cfg_file": &hcldec.AttrSpec{Name: "access_cfg_file", Type: cty.String, Required: false},
|
|
||||||
"access_cfg_file_account": &hcldec.AttrSpec{Name: "access_cfg_file_account", Type: cty.String, Required: false},
|
|
||||||
"user_ocid": &hcldec.AttrSpec{Name: "user_ocid", Type: cty.String, Required: false},
|
|
||||||
"tenancy_ocid": &hcldec.AttrSpec{Name: "tenancy_ocid", Type: cty.String, Required: false},
|
|
||||||
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
|
|
||||||
"fingerprint": &hcldec.AttrSpec{Name: "fingerprint", Type: cty.String, Required: false},
|
|
||||||
"key_file": &hcldec.AttrSpec{Name: "key_file", Type: cty.String, Required: false},
|
|
||||||
"pass_phrase": &hcldec.AttrSpec{Name: "pass_phrase", Type: cty.String, Required: false},
|
|
||||||
"use_private_ip": &hcldec.AttrSpec{Name: "use_private_ip", Type: cty.Bool, Required: false},
|
|
||||||
"availability_domain": &hcldec.AttrSpec{Name: "availability_domain", Type: cty.String, Required: false},
|
|
||||||
"compartment_ocid": &hcldec.AttrSpec{Name: "compartment_ocid", Type: cty.String, Required: false},
|
|
||||||
"base_image_ocid": &hcldec.AttrSpec{Name: "base_image_ocid", Type: cty.String, Required: false},
|
|
||||||
"base_image_filter": &hcldec.BlockSpec{TypeName: "base_image_filter", Nested: hcldec.ObjectSpec((*FlatListImagesRequest)(nil).HCL2Spec())},
|
|
||||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
|
||||||
"image_compartment_ocid": &hcldec.AttrSpec{Name: "image_compartment_ocid", Type: cty.String, Required: false},
|
|
||||||
"image_launch_mode": &hcldec.AttrSpec{Name: "image_launch_mode", Type: cty.String, Required: false},
|
|
||||||
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
|
|
||||||
"instance_tags": &hcldec.AttrSpec{Name: "instance_tags", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"instance_defined_tags": &hcldec.AttrSpec{Name: "instance_defined_tags", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
|
|
||||||
"shape_config": &hcldec.BlockSpec{TypeName: "shape_config", Nested: hcldec.ObjectSpec((*FlatFlexShapeConfig)(nil).HCL2Spec())},
|
|
||||||
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
|
|
||||||
"metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
|
||||||
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
|
|
||||||
"subnet_ocid": &hcldec.AttrSpec{Name: "subnet_ocid", Type: cty.String, Required: false},
|
|
||||||
"create_vnic_details": &hcldec.BlockSpec{TypeName: "create_vnic_details", Nested: hcldec.ObjectSpec((*FlatCreateVNICDetails)(nil).HCL2Spec())},
|
|
||||||
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"defined_tags": &hcldec.AttrSpec{Name: "defined_tags", Type: cty.Map(cty.String), Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatCreateVNICDetails is an auto-generated flat version of CreateVNICDetails.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatCreateVNICDetails struct {
|
|
||||||
AssignPublicIp *bool `mapstructure:"assign_public_ip" required:"false" cty:"assign_public_ip" hcl:"assign_public_ip"`
|
|
||||||
DefinedTags map[string]map[string]interface{} `mapstructure:"defined_tags" required:"false" cty:"defined_tags" hcl:"defined_tags"`
|
|
||||||
DisplayName *string `mapstructure:"display_name" required:"false" cty:"display_name" hcl:"display_name"`
|
|
||||||
FreeformTags map[string]string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"`
|
|
||||||
HostnameLabel *string `mapstructure:"hostname_label" required:"false" cty:"hostname_label" hcl:"hostname_label"`
|
|
||||||
NsgIds []string `mapstructure:"nsg_ids" required:"false" cty:"nsg_ids" hcl:"nsg_ids"`
|
|
||||||
PrivateIp *string `mapstructure:"private_ip" required:"false" cty:"private_ip" hcl:"private_ip"`
|
|
||||||
SkipSourceDestCheck *bool `mapstructure:"skip_source_dest_check" required:"false" cty:"skip_source_dest_check" hcl:"skip_source_dest_check"`
|
|
||||||
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatCreateVNICDetails.
|
|
||||||
// FlatCreateVNICDetails is an auto-generated flat version of CreateVNICDetails.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*CreateVNICDetails) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatCreateVNICDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a CreateVNICDetails.
|
|
||||||
// This spec is used by HCL to read the fields of CreateVNICDetails.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatCreateVNICDetails.
|
|
||||||
func (*FlatCreateVNICDetails) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"assign_public_ip": &hcldec.AttrSpec{Name: "assign_public_ip", Type: cty.Bool, Required: false},
|
|
||||||
"defined_tags": &hcldec.AttrSpec{Name: "defined_tags", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"display_name": &hcldec.AttrSpec{Name: "display_name", Type: cty.String, Required: false},
|
|
||||||
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"hostname_label": &hcldec.AttrSpec{Name: "hostname_label", Type: cty.String, Required: false},
|
|
||||||
"nsg_ids": &hcldec.AttrSpec{Name: "nsg_ids", Type: cty.List(cty.String), Required: false},
|
|
||||||
"private_ip": &hcldec.AttrSpec{Name: "private_ip", Type: cty.String, Required: false},
|
|
||||||
"skip_source_dest_check": &hcldec.AttrSpec{Name: "skip_source_dest_check", Type: cty.Bool, Required: false},
|
|
||||||
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatFlexShapeConfig is an auto-generated flat version of FlexShapeConfig.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatFlexShapeConfig struct {
|
|
||||||
Ocpus *float32 `mapstructure:"ocpus" required:"false" cty:"ocpus" hcl:"ocpus"`
|
|
||||||
MemoryInGBs *float32 `mapstructure:"memory_in_gbs" required:"false" cty:"memory_in_gbs" hcl:"memory_in_gbs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatFlexShapeConfig.
|
|
||||||
// FlatFlexShapeConfig is an auto-generated flat version of FlexShapeConfig.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*FlexShapeConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatFlexShapeConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a FlexShapeConfig.
|
|
||||||
// This spec is used by HCL to read the fields of FlexShapeConfig.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatFlexShapeConfig.
|
|
||||||
func (*FlatFlexShapeConfig) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"ocpus": &hcldec.AttrSpec{Name: "ocpus", Type: cty.Number, Required: false},
|
|
||||||
"memory_in_gbs": &hcldec.AttrSpec{Name: "memory_in_gbs", Type: cty.Number, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatListImagesRequest is an auto-generated flat version of ListImagesRequest.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatListImagesRequest struct {
|
|
||||||
CompartmentId *string `mapstructure:"compartment_id" cty:"compartment_id" hcl:"compartment_id"`
|
|
||||||
DisplayName *string `mapstructure:"display_name" cty:"display_name" hcl:"display_name"`
|
|
||||||
DisplayNameSearch *string `mapstructure:"display_name_search" cty:"display_name_search" hcl:"display_name_search"`
|
|
||||||
OperatingSystem *string `mapstructure:"operating_system" cty:"operating_system" hcl:"operating_system"`
|
|
||||||
OperatingSystemVersion *string `mapstructure:"operating_system_version" cty:"operating_system_version" hcl:"operating_system_version"`
|
|
||||||
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatListImagesRequest.
|
|
||||||
// FlatListImagesRequest is an auto-generated flat version of ListImagesRequest.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*ListImagesRequest) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatListImagesRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a ListImagesRequest.
|
|
||||||
// This spec is used by HCL to read the fields of ListImagesRequest.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatListImagesRequest.
|
|
||||||
func (*FlatListImagesRequest) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"compartment_id": &hcldec.AttrSpec{Name: "compartment_id", Type: cty.String, Required: false},
|
|
||||||
"display_name": &hcldec.AttrSpec{Name: "display_name", Type: cty.String, Required: false},
|
|
||||||
"display_name_search": &hcldec.AttrSpec{Name: "display_name_search", Type: cty.String, Required: false},
|
|
||||||
"operating_system": &hcldec.AttrSpec{Name: "operating_system", Type: cty.String, Required: false},
|
|
||||||
"operating_system_version": &hcldec.AttrSpec{Name: "operating_system_version", Type: cty.String, Required: false},
|
|
||||||
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,412 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/go-ini/ini"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testConfig(accessConfFile *os.File) map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
|
|
||||||
"availability_domain": "aaaa:PHX-AD-3",
|
|
||||||
"access_cfg_file": accessConfFile.Name(),
|
|
||||||
|
|
||||||
// Image
|
|
||||||
"base_image_ocid": "ocd1...",
|
|
||||||
"image_name": "HelloWorld",
|
|
||||||
|
|
||||||
// Networking
|
|
||||||
"subnet_ocid": "ocd1...",
|
|
||||||
|
|
||||||
// Comm
|
|
||||||
"ssh_username": "opc",
|
|
||||||
"use_private_ip": false,
|
|
||||||
"metadata": map[string]string{
|
|
||||||
"key": "value",
|
|
||||||
},
|
|
||||||
"defined_tags": map[string]map[string]interface{}{
|
|
||||||
"namespace": {"key": "value"},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Instance Details
|
|
||||||
"instance_name": "hello-world",
|
|
||||||
"instance_tags": map[string]string{
|
|
||||||
"key": "value",
|
|
||||||
},
|
|
||||||
"create_vnic_details": map[string]interface{}{
|
|
||||||
"nsg_ids": []string{"ocd1..."},
|
|
||||||
},
|
|
||||||
"shape": "VM.Standard1.1",
|
|
||||||
"disk_size": 60,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfig(t *testing.T) {
|
|
||||||
// Shared set-up and deferred deletion
|
|
||||||
|
|
||||||
cfg, keyFile, err := baseTestConfigWithTmpKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.Remove(keyFile.Name())
|
|
||||||
|
|
||||||
cfgFile, err := writeTestConfig(cfg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.Remove(cfgFile.Name())
|
|
||||||
|
|
||||||
// Temporarily set $HOME to temp directory to bypass default
|
|
||||||
// access config loading.
|
|
||||||
|
|
||||||
tmpHome, err := ioutil.TempDir("", "packer_config_test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error when creating temporary directory: %+v", err)
|
|
||||||
}
|
|
||||||
defer os.Remove(tmpHome)
|
|
||||||
|
|
||||||
home := os.Getenv("HOME")
|
|
||||||
os.Setenv("HOME", tmpHome)
|
|
||||||
defer os.Setenv("HOME", home)
|
|
||||||
|
|
||||||
// Config tests
|
|
||||||
t.Run("BaseConfig", func(t *testing.T) {
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("Unexpected error in configuration %+v", errs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("BaseImageFilterWithoutOCID", func(t *testing.T) {
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
raw["base_image_ocid"] = ""
|
|
||||||
raw["base_image_filter"] = map[string]interface{}{
|
|
||||||
"display_name": "hello_world",
|
|
||||||
}
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("Unexpected error in configuration %+v", errs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("BaseImageFilterDefault", func(t *testing.T) {
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("Unexpected error in configuration %+v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *c.BaseImageFilter.Shape != raw["shape"] {
|
|
||||||
t.Fatalf("Default base_image_filter shape %v does not equal config shape %v",
|
|
||||||
*c.BaseImageFilter.Shape, raw["shape"])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("LaunchMode", func(t *testing.T) {
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
raw["image_launch_mode"] = "NATIVE"
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("Unexpected error in configuration %+v", errs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("NoAccessConfig", func(t *testing.T) {
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
raw["access_cfg_file"] = "/tmp/random/access/config/file/should/not/exist"
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
|
|
||||||
expectedErrors := []string{
|
|
||||||
"'user_ocid'", "'tenancy_ocid'", "'fingerprint'", "'key_file'",
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs == nil {
|
|
||||||
t.Fatalf("Expected errors %q but got none", expectedErrors)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := errs.Error()
|
|
||||||
for _, expected := range expectedErrors {
|
|
||||||
if !strings.Contains(s, expected) {
|
|
||||||
t.Errorf("Expected %q to contain '%s'", s, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("AccessConfigTemplateOnly", func(t *testing.T) {
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
delete(raw, "access_cfg_file")
|
|
||||||
raw["user_ocid"] = "ocid1..."
|
|
||||||
raw["tenancy_ocid"] = "ocid1..."
|
|
||||||
raw["fingerprint"] = "00:00..."
|
|
||||||
raw["key_file"] = keyFile.Name()
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("err: %+v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("TenancyReadFromAccessCfgFile", func(t *testing.T) {
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("Unexpected error in configuration %+v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
tenancy, err := c.configProvider.TenancyOCID()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error getting tenancy ocid: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := "ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
||||||
if tenancy != expected {
|
|
||||||
t.Errorf("Expected tenancy: %s, got %s.", expected, tenancy)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("RegionNotDefaultedToPHXWhenSetInOCISettings", func(t *testing.T) {
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("Unexpected error in configuration %+v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
region, err := c.configProvider.Region()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error getting region: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := "us-ashburn-1"
|
|
||||||
if region != expected {
|
|
||||||
t.Errorf("Expected region: %s, got %s.", expected, region)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// Test the correct errors are produced when required template keys are
|
|
||||||
// omitted.
|
|
||||||
requiredKeys := []string{"availability_domain", "base_image_ocid", "shape", "subnet_ocid"}
|
|
||||||
for _, k := range requiredKeys {
|
|
||||||
t.Run(k+"_required", func(t *testing.T) {
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
delete(raw, k)
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
|
|
||||||
if !strings.Contains(errs.Error(), k) {
|
|
||||||
t.Errorf("Expected '%s' to contain '%s'", errs.Error(), k)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("ImageNameDefaultedIfEmpty", func(t *testing.T) {
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
delete(raw, "image_name")
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("Unexpected error in configuration %+v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(c.ImageName, "packer-") {
|
|
||||||
t.Errorf("got default ImageName %q, want image name 'packer-{{timestamp}}'", c.ImageName)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("user_ocid_overridden", func(t *testing.T) {
|
|
||||||
expected := "override"
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
raw["user_ocid"] = expected
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("Unexpected error in configuration %+v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
user, _ := c.configProvider.UserOCID()
|
|
||||||
if user != expected {
|
|
||||||
t.Errorf("Expected ConfigProvider.UserOCID: %s, got %s", expected, user)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("tenancy_ocid_overidden", func(t *testing.T) {
|
|
||||||
expected := "override"
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
raw["tenancy_ocid"] = expected
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("Unexpected error in configuration %+v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
tenancy, _ := c.configProvider.TenancyOCID()
|
|
||||||
if tenancy != expected {
|
|
||||||
t.Errorf("Expected ConfigProvider.TenancyOCID: %s, got %s", expected, tenancy)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("region_overidden", func(t *testing.T) {
|
|
||||||
expected := "override"
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
raw["region"] = expected
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("Unexpected error in configuration %+v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
region, _ := c.configProvider.Region()
|
|
||||||
if region != expected {
|
|
||||||
t.Errorf("Expected ConfigProvider.Region: %s, got %s", expected, region)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("fingerprint_overidden", func(t *testing.T) {
|
|
||||||
expected := "override"
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
raw["fingerprint"] = expected
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
if errs != nil {
|
|
||||||
t.Fatalf("Unexpected error in configuration: %+v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fingerprint, _ := c.configProvider.KeyFingerprint()
|
|
||||||
if fingerprint != expected {
|
|
||||||
t.Errorf("Expected ConfigProvider.KeyFingerprint: %s, got %s", expected, fingerprint)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Test the correct errors are produced when certain template keys
|
|
||||||
// are present alongside use_instance_principals key.
|
|
||||||
invalidKeys := []string{
|
|
||||||
"access_cfg_file",
|
|
||||||
"access_cfg_file_account",
|
|
||||||
"user_ocid",
|
|
||||||
"tenancy_ocid",
|
|
||||||
"region",
|
|
||||||
"fingerprint",
|
|
||||||
"key_file",
|
|
||||||
"pass_phrase",
|
|
||||||
}
|
|
||||||
for _, k := range invalidKeys {
|
|
||||||
t.Run(k+"_mixed_with_use_instance_principals", func(t *testing.T) {
|
|
||||||
raw := testConfig(cfgFile)
|
|
||||||
raw["use_instance_principals"] = "true"
|
|
||||||
raw[k] = "some_random_value"
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
|
|
||||||
c.configProvider = instancePrincipalConfigurationProviderMock{}
|
|
||||||
|
|
||||||
errs := c.Prepare(raw)
|
|
||||||
|
|
||||||
if !strings.Contains(errs.Error(), k) {
|
|
||||||
t.Errorf("Expected '%s' to contain '%s'", errs.Error(), k)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BaseTestConfig creates the base (DEFAULT) config including a temporary key
|
|
||||||
// file.
|
|
||||||
// NOTE: Caller is responsible for removing temporary key file.
|
|
||||||
func baseTestConfigWithTmpKeyFile() (*ini.File, *os.File, error) {
|
|
||||||
keyFile, err := generateRSAKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
return nil, keyFile, err
|
|
||||||
}
|
|
||||||
// Build ini
|
|
||||||
cfg := ini.Empty()
|
|
||||||
section, _ := cfg.NewSection("DEFAULT")
|
|
||||||
section.NewKey("region", "us-ashburn-1")
|
|
||||||
section.NewKey("tenancy", "ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
|
||||||
section.NewKey("user", "ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
|
||||||
section.NewKey("fingerprint", "70:04:5z:b3:19:ab:90:75:a4:1f:50:d4:c7:c3:33:20")
|
|
||||||
section.NewKey("key_file", keyFile.Name())
|
|
||||||
|
|
||||||
return cfg, keyFile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteTestConfig writes a ini.File to a temporary file for use in unit tests.
|
|
||||||
// NOTE: Caller is responsible for removing temporary file.
|
|
||||||
func writeTestConfig(cfg *ini.File) (*os.File, error) {
|
|
||||||
confFile, err := ioutil.TempFile("", "config_file")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := confFile.Write([]byte("[DEFAULT]\n")); err != nil {
|
|
||||||
os.Remove(confFile.Name())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := cfg.WriteTo(confFile); err != nil {
|
|
||||||
os.Remove(confFile.Name())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return confFile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateRSAKeyFile generates an RSA key file for use in unit tests.
|
|
||||||
// NOTE: The caller is responsible for deleting the temporary file.
|
|
||||||
func generateRSAKeyFile() (*os.File, error) {
|
|
||||||
// Create temporary file for the key
|
|
||||||
f, err := ioutil.TempFile("", "key")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate key
|
|
||||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ASN.1 DER encoded form
|
|
||||||
privDer := x509.MarshalPKCS1PrivateKey(priv)
|
|
||||||
privBlk := pem.Block{
|
|
||||||
Type: "RSA PRIVATE KEY",
|
|
||||||
Headers: nil,
|
|
||||||
Bytes: privDer,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the key out
|
|
||||||
if _, err := f.Write(pem.EncodeToMemory(&privBlk)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/oracle/oci-go-sdk/v36/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Driver interfaces between the builder steps and the OCI SDK.
|
|
||||||
type Driver interface {
|
|
||||||
CreateInstance(ctx context.Context, publicKey string) (string, error)
|
|
||||||
CreateImage(ctx context.Context, id string) (core.Image, error)
|
|
||||||
DeleteImage(ctx context.Context, id string) error
|
|
||||||
GetInstanceIP(ctx context.Context, id string) (string, error)
|
|
||||||
TerminateInstance(ctx context.Context, id string) error
|
|
||||||
WaitForImageCreation(ctx context.Context, id string) error
|
|
||||||
WaitForInstanceState(ctx context.Context, id string, waitStates []string, terminalState string) error
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/oracle/oci-go-sdk/v36/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
// driverMock implements the Driver interface and communicates with Oracle
|
|
||||||
// OCI.
|
|
||||||
type driverMock struct {
|
|
||||||
CreateInstanceID string
|
|
||||||
CreateInstanceErr error
|
|
||||||
|
|
||||||
CreateImageID string
|
|
||||||
CreateImageErr error
|
|
||||||
|
|
||||||
DeleteImageID string
|
|
||||||
DeleteImageErr error
|
|
||||||
|
|
||||||
GetInstanceIPErr error
|
|
||||||
|
|
||||||
TerminateInstanceID string
|
|
||||||
TerminateInstanceErr error
|
|
||||||
|
|
||||||
WaitForImageCreationErr error
|
|
||||||
|
|
||||||
WaitForInstanceStateErr error
|
|
||||||
|
|
||||||
cfg *Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateInstance creates a new compute instance.
|
|
||||||
func (d *driverMock) CreateInstance(ctx context.Context, publicKey string) (string, error) {
|
|
||||||
if d.CreateInstanceErr != nil {
|
|
||||||
return "", d.CreateInstanceErr
|
|
||||||
}
|
|
||||||
|
|
||||||
d.CreateInstanceID = "ocid1..."
|
|
||||||
|
|
||||||
return d.CreateInstanceID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateImage creates a new custom image.
|
|
||||||
func (d *driverMock) CreateImage(ctx context.Context, id string) (core.Image, error) {
|
|
||||||
if d.CreateImageErr != nil {
|
|
||||||
return core.Image{}, d.CreateImageErr
|
|
||||||
}
|
|
||||||
d.CreateImageID = id
|
|
||||||
return core.Image{Id: &id}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteImage mocks deleting a custom image.
|
|
||||||
func (d *driverMock) DeleteImage(ctx context.Context, id string) error {
|
|
||||||
if d.DeleteImageErr != nil {
|
|
||||||
return d.DeleteImageErr
|
|
||||||
}
|
|
||||||
|
|
||||||
d.DeleteImageID = id
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInstanceIP returns the public or private IP corresponding to the given instance id.
|
|
||||||
func (d *driverMock) GetInstanceIP(ctx context.Context, id string) (string, error) {
|
|
||||||
if d.GetInstanceIPErr != nil {
|
|
||||||
return "", d.GetInstanceIPErr
|
|
||||||
}
|
|
||||||
if d.cfg.UsePrivateIP {
|
|
||||||
return "private_ip", nil
|
|
||||||
}
|
|
||||||
return "ip", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TerminateInstance terminates a compute instance.
|
|
||||||
func (d *driverMock) TerminateInstance(ctx context.Context, id string) error {
|
|
||||||
if d.TerminateInstanceErr != nil {
|
|
||||||
return d.TerminateInstanceErr
|
|
||||||
}
|
|
||||||
|
|
||||||
d.TerminateInstanceID = id
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForImageCreation waits for a provisioning custom image to reach the
|
|
||||||
// "AVAILABLE" state.
|
|
||||||
func (d *driverMock) WaitForImageCreation(ctx context.Context, id string) error {
|
|
||||||
return d.WaitForImageCreationErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForInstanceState waits for an instance to reach the a given terminal
|
|
||||||
// state.
|
|
||||||
func (d *driverMock) WaitForInstanceState(ctx context.Context, id string, waitStates []string, terminalState string) error {
|
|
||||||
return d.WaitForInstanceStateErr
|
|
||||||
}
|
|
||||||
@@ -1,339 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/oracle/oci-go-sdk/v36/common"
|
|
||||||
core "github.com/oracle/oci-go-sdk/v36/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
// driverOCI implements the Driver interface and communicates with Oracle
|
|
||||||
// OCI.
|
|
||||||
type driverOCI struct {
|
|
||||||
computeClient core.ComputeClient
|
|
||||||
vcnClient core.VirtualNetworkClient
|
|
||||||
cfg *Config
|
|
||||||
context context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
var retryPolicy = &common.RetryPolicy{
|
|
||||||
MaximumNumberAttempts: 10,
|
|
||||||
ShouldRetryOperation: func(res common.OCIOperationResponse) bool {
|
|
||||||
var e common.ServiceError
|
|
||||||
if errors.As(res.Error, &e) {
|
|
||||||
switch e.GetHTTPStatusCode() {
|
|
||||||
case http.StatusTooManyRequests, http.StatusInternalServerError, http.StatusServiceUnavailable:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
NextDuration: func(res common.OCIOperationResponse) time.Duration {
|
|
||||||
x := uint64(res.AttemptNumber)
|
|
||||||
d := time.Duration(math.Pow(2, float64(atomic.LoadUint64(&x)))) * time.Second
|
|
||||||
j := time.Duration(rand.Float64()*(2000)) * time.Millisecond
|
|
||||||
w := d + j
|
|
||||||
return w
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var requestMetadata = common.RequestMetadata{
|
|
||||||
RetryPolicy: retryPolicy,
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDriverOCI Creates a new driverOCI with a connected compute client and a connected vcn client.
|
|
||||||
func NewDriverOCI(cfg *Config) (Driver, error) {
|
|
||||||
coreClient, err := core.NewComputeClientWithConfigurationProvider(cfg.configProvider)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vcnClient, err := core.NewVirtualNetworkClientWithConfigurationProvider(cfg.configProvider)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &driverOCI{
|
|
||||||
computeClient: coreClient,
|
|
||||||
vcnClient: vcnClient,
|
|
||||||
cfg: cfg,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateInstance creates a new compute instance.
|
|
||||||
func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (string, error) {
|
|
||||||
metadata := map[string]string{
|
|
||||||
"ssh_authorized_keys": publicKey,
|
|
||||||
}
|
|
||||||
if d.cfg.Metadata != nil {
|
|
||||||
for key, value := range d.cfg.Metadata {
|
|
||||||
metadata[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if d.cfg.UserData != "" {
|
|
||||||
metadata["user_data"] = d.cfg.UserData
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create VNIC details for instance
|
|
||||||
CreateVnicDetails := core.CreateVnicDetails{
|
|
||||||
AssignPublicIp: d.cfg.CreateVnicDetails.AssignPublicIp,
|
|
||||||
DisplayName: d.cfg.CreateVnicDetails.DisplayName,
|
|
||||||
HostnameLabel: d.cfg.CreateVnicDetails.HostnameLabel,
|
|
||||||
NsgIds: d.cfg.CreateVnicDetails.NsgIds,
|
|
||||||
PrivateIp: d.cfg.CreateVnicDetails.PrivateIp,
|
|
||||||
SkipSourceDestCheck: d.cfg.CreateVnicDetails.SkipSourceDestCheck,
|
|
||||||
SubnetId: d.cfg.CreateVnicDetails.SubnetId,
|
|
||||||
DefinedTags: d.cfg.CreateVnicDetails.DefinedTags,
|
|
||||||
FreeformTags: d.cfg.CreateVnicDetails.FreeformTags,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine base image ID
|
|
||||||
var imageId *string
|
|
||||||
if d.cfg.BaseImageID != "" {
|
|
||||||
imageId = &d.cfg.BaseImageID
|
|
||||||
} else {
|
|
||||||
// Pull images and determine which image ID to use, if BaseImageId not specified
|
|
||||||
response, err := d.computeClient.ListImages(ctx, core.ListImagesRequest{
|
|
||||||
CompartmentId: d.cfg.BaseImageFilter.CompartmentId,
|
|
||||||
DisplayName: d.cfg.BaseImageFilter.DisplayName,
|
|
||||||
OperatingSystem: d.cfg.BaseImageFilter.OperatingSystem,
|
|
||||||
OperatingSystemVersion: d.cfg.BaseImageFilter.OperatingSystemVersion,
|
|
||||||
Shape: d.cfg.BaseImageFilter.Shape,
|
|
||||||
LifecycleState: "AVAILABLE",
|
|
||||||
SortBy: "TIMECREATED",
|
|
||||||
SortOrder: "DESC",
|
|
||||||
RequestMetadata: requestMetadata,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if len(response.Items) == 0 {
|
|
||||||
return "", errors.New("base_image_filter returned no images")
|
|
||||||
}
|
|
||||||
if d.cfg.BaseImageFilter.DisplayNameSearch != nil {
|
|
||||||
// Return most recent image that matches regex
|
|
||||||
imageNameRegex, err := regexp.Compile(*d.cfg.BaseImageFilter.DisplayNameSearch)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
for _, image := range response.Items {
|
|
||||||
if imageNameRegex.MatchString(*image.DisplayName) {
|
|
||||||
imageId = image.Id
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if imageId == nil {
|
|
||||||
return "", errors.New("No image matched display_name_search criteria")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If no regex provided, simply return most recent image pulled
|
|
||||||
imageId = response.Items[0].Id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Source details which will be used to Launch Instance
|
|
||||||
InstanceSourceDetails := core.InstanceSourceViaImageDetails{
|
|
||||||
ImageId: imageId,
|
|
||||||
BootVolumeSizeInGBs: &d.cfg.BootVolumeSizeInGBs,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build instance details
|
|
||||||
instanceDetails := core.LaunchInstanceDetails{
|
|
||||||
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
|
||||||
CompartmentId: &d.cfg.CompartmentID,
|
|
||||||
CreateVnicDetails: &CreateVnicDetails,
|
|
||||||
DefinedTags: d.cfg.InstanceDefinedTags,
|
|
||||||
DisplayName: d.cfg.InstanceName,
|
|
||||||
FreeformTags: d.cfg.InstanceTags,
|
|
||||||
Shape: &d.cfg.Shape,
|
|
||||||
SourceDetails: InstanceSourceDetails,
|
|
||||||
Metadata: metadata,
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.cfg.ShapeConfig.Ocpus != nil {
|
|
||||||
LaunchInstanceShapeConfigDetails := core.LaunchInstanceShapeConfigDetails{
|
|
||||||
Ocpus: d.cfg.ShapeConfig.Ocpus,
|
|
||||||
MemoryInGBs: d.cfg.ShapeConfig.MemoryInGBs,
|
|
||||||
}
|
|
||||||
instanceDetails.ShapeConfig = &LaunchInstanceShapeConfigDetails
|
|
||||||
}
|
|
||||||
|
|
||||||
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{
|
|
||||||
LaunchInstanceDetails: instanceDetails,
|
|
||||||
RequestMetadata: requestMetadata,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return *instance.Id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateImage creates a new custom image.
|
|
||||||
func (d *driverOCI) CreateImage(ctx context.Context, id string) (core.Image, error) {
|
|
||||||
res, err := d.computeClient.CreateImage(ctx, core.CreateImageRequest{CreateImageDetails: core.CreateImageDetails{
|
|
||||||
CompartmentId: &d.cfg.ImageCompartmentID,
|
|
||||||
InstanceId: &id,
|
|
||||||
DisplayName: &d.cfg.ImageName,
|
|
||||||
FreeformTags: d.cfg.Tags,
|
|
||||||
DefinedTags: d.cfg.DefinedTags,
|
|
||||||
LaunchMode: core.CreateImageDetailsLaunchModeEnum(d.cfg.LaunchMode),
|
|
||||||
},
|
|
||||||
RequestMetadata: requestMetadata,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return core.Image{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.Image, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteImage deletes a custom image.
|
|
||||||
func (d *driverOCI) DeleteImage(ctx context.Context, id string) error {
|
|
||||||
_, err := d.computeClient.DeleteImage(ctx, core.DeleteImageRequest{
|
|
||||||
ImageId: &id,
|
|
||||||
RequestMetadata: requestMetadata,
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInstanceIP returns the public or private IP corresponding to the given instance id.
|
|
||||||
func (d *driverOCI) GetInstanceIP(ctx context.Context, id string) (string, error) {
|
|
||||||
vnics, err := d.computeClient.ListVnicAttachments(ctx, core.ListVnicAttachmentsRequest{
|
|
||||||
InstanceId: &id,
|
|
||||||
CompartmentId: &d.cfg.CompartmentID,
|
|
||||||
RequestMetadata: requestMetadata,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(vnics.Items) == 0 {
|
|
||||||
return "", errors.New("instance has zero VNICs")
|
|
||||||
}
|
|
||||||
|
|
||||||
vnic, err := d.vcnClient.GetVnic(ctx, core.GetVnicRequest{
|
|
||||||
VnicId: vnics.Items[0].VnicId,
|
|
||||||
RequestMetadata: requestMetadata,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Error getting VNIC details: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.cfg.UsePrivateIP {
|
|
||||||
return *vnic.PrivateIp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if vnic.PublicIp == nil {
|
|
||||||
return "", fmt.Errorf("Error getting VNIC Public Ip for: %s", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return *vnic.PublicIp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driverOCI) GetInstanceInitialCredentials(ctx context.Context, id string) (string, string, error) {
|
|
||||||
credentials, err := d.computeClient.GetWindowsInstanceInitialCredentials(ctx, core.GetWindowsInstanceInitialCredentialsRequest{
|
|
||||||
InstanceId: &id,
|
|
||||||
RequestMetadata: requestMetadata,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return *credentials.InstanceCredentials.Username, *credentials.InstanceCredentials.Password, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TerminateInstance terminates a compute instance.
|
|
||||||
func (d *driverOCI) TerminateInstance(ctx context.Context, id string) error {
|
|
||||||
_, err := d.computeClient.TerminateInstance(ctx, core.TerminateInstanceRequest{
|
|
||||||
InstanceId: &id,
|
|
||||||
RequestMetadata: requestMetadata,
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForImageCreation waits for a provisioning custom image to reach the
|
|
||||||
// "AVAILABLE" state.
|
|
||||||
func (d *driverOCI) WaitForImageCreation(ctx context.Context, id string) error {
|
|
||||||
return waitForResourceToReachState(
|
|
||||||
func(string) (string, error) {
|
|
||||||
image, err := d.computeClient.GetImage(ctx, core.GetImageRequest{
|
|
||||||
ImageId: &id,
|
|
||||||
RequestMetadata: requestMetadata,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(image.LifecycleState), nil
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
[]string{"PROVISIONING"},
|
|
||||||
"AVAILABLE",
|
|
||||||
0, //Unlimited Retries
|
|
||||||
5*time.Second, //5 second wait between retries
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForInstanceState waits for an instance to reach the a given terminal
|
|
||||||
// state.
|
|
||||||
func (d *driverOCI) WaitForInstanceState(ctx context.Context, id string, waitStates []string, terminalState string) error {
|
|
||||||
return waitForResourceToReachState(
|
|
||||||
func(string) (string, error) {
|
|
||||||
instance, err := d.computeClient.GetInstance(ctx, core.GetInstanceRequest{
|
|
||||||
InstanceId: &id,
|
|
||||||
RequestMetadata: requestMetadata,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(instance.LifecycleState), nil
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
waitStates,
|
|
||||||
terminalState,
|
|
||||||
0, //Unlimited Retries
|
|
||||||
5*time.Second, //5 second wait between retries
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForResourceToReachState checks the response of a request through a
|
|
||||||
// polled get and waits until the desired state or until the max retried has
|
|
||||||
// been reached.
|
|
||||||
func waitForResourceToReachState(getResourceState func(string) (string, error), id string, waitStates []string, terminalState string, maxRetries int, waitDuration time.Duration) error {
|
|
||||||
for i := 0; maxRetries == 0 || i < maxRetries; i++ {
|
|
||||||
state, err := getResourceState(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if stringSliceContains(waitStates, state) {
|
|
||||||
time.Sleep(waitDuration)
|
|
||||||
continue
|
|
||||||
} else if state == terminalState {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Unexpected resource state %q, expecting a waiting state %s or terminal state %q ", state, waitStates, terminalState)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Maximum number of retries (%d) exceeded; resource did not reach state %q", maxRetries, terminalState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stringSliceContains loops through a slice of strings returning a boolean
|
|
||||||
// based on whether a given value is contained in the slice.
|
|
||||||
func stringSliceContains(slice []string, value string) bool {
|
|
||||||
for _, elem := range slice {
|
|
||||||
if elem == value {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
|
|
||||||
"github.com/oracle/oci-go-sdk/v36/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mock struct to be used during testing to obtain Instance Principals.
|
|
||||||
type instancePrincipalConfigurationProviderMock struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p instancePrincipalConfigurationProviderMock) PrivateRSAKey() (*rsa.PrivateKey, error) {
|
|
||||||
return rsa.GenerateKey(rand.Reader, 1024)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p instancePrincipalConfigurationProviderMock) KeyID() (string, error) {
|
|
||||||
return "some_random_key_id", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p instancePrincipalConfigurationProviderMock) TenancyOCID() (string, error) {
|
|
||||||
return "some_random_tenancy", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p instancePrincipalConfigurationProviderMock) UserOCID() (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p instancePrincipalConfigurationProviderMock) KeyFingerprint() (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p instancePrincipalConfigurationProviderMock) Region() (string, error) {
|
|
||||||
return "some_random_region", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p instancePrincipalConfigurationProviderMock) AuthType() (common.AuthConfig, error) {
|
|
||||||
return common.AuthConfig{
|
|
||||||
AuthType: common.InstancePrincipal,
|
|
||||||
IsFromConfigFile: false,
|
|
||||||
OboToken: nil}, nil
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepCreateInstance struct{}
|
|
||||||
|
|
||||||
func (s *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
var (
|
|
||||||
driver = state.Get("driver").(Driver)
|
|
||||||
ui = state.Get("ui").(packersdk.Ui)
|
|
||||||
config = state.Get("config").(*Config)
|
|
||||||
)
|
|
||||||
|
|
||||||
ui.Say("Creating instance...")
|
|
||||||
|
|
||||||
instanceID, err := driver.CreateInstance(ctx, string(config.Comm.SSHPublicKey))
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Problem creating instance: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Put("instance_id", instanceID)
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Created instance (%s).", instanceID))
|
|
||||||
|
|
||||||
ui.Say("Waiting for instance to enter 'RUNNING' state...")
|
|
||||||
|
|
||||||
if err = driver.WaitForInstanceState(ctx, instanceID, []string{"STARTING", "PROVISIONING"}, "RUNNING"); err != nil {
|
|
||||||
err = fmt.Errorf("Error waiting for instance to start: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Instance 'RUNNING'.")
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepCreateInstance) Cleanup(state multistep.StateBag) {
|
|
||||||
driver := state.Get("driver").(Driver)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
|
||||||
|
|
||||||
idRaw, ok := state.GetOk("instance_id")
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
id := idRaw.(string)
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Terminating instance (%s)...", id))
|
|
||||||
|
|
||||||
if err := driver.TerminateInstance(context.TODO(), id); err != nil {
|
|
||||||
err = fmt.Errorf("Error terminating instance. Please terminate manually: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := driver.WaitForInstanceState(context.TODO(), id, []string{"TERMINATING"}, "TERMINATED")
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error terminating instance. Please terminate manually: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Terminated instance.")
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStepCreateInstance(t *testing.T) {
|
|
||||||
state := testState()
|
|
||||||
state.Put("publicKey", "key")
|
|
||||||
|
|
||||||
step := new(stepCreateInstance)
|
|
||||||
defer step.Cleanup(state)
|
|
||||||
|
|
||||||
driver := state.Get("driver").(*driverMock)
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceIDRaw, ok := state.GetOk("instance_id")
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("should have machine")
|
|
||||||
}
|
|
||||||
|
|
||||||
step.Cleanup(state)
|
|
||||||
|
|
||||||
if driver.TerminateInstanceID != instanceIDRaw.(string) {
|
|
||||||
t.Fatalf(
|
|
||||||
"should've deleted instance (%s != %s)",
|
|
||||||
driver.TerminateInstanceID, instanceIDRaw.(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStepCreateInstance_CreateInstanceErr(t *testing.T) {
|
|
||||||
state := testState()
|
|
||||||
state.Put("publicKey", "key")
|
|
||||||
|
|
||||||
step := new(stepCreateInstance)
|
|
||||||
defer step.Cleanup(state)
|
|
||||||
|
|
||||||
driver := state.Get("driver").(*driverMock)
|
|
||||||
driver.CreateInstanceErr = errors.New("error")
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("error"); !ok {
|
|
||||||
t.Fatalf("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("instance_id"); ok {
|
|
||||||
t.Fatalf("should NOT have instance_id")
|
|
||||||
}
|
|
||||||
|
|
||||||
step.Cleanup(state)
|
|
||||||
|
|
||||||
if driver.TerminateInstanceID != "" {
|
|
||||||
t.Fatalf("Should not have tried to terminate an instance")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStepCreateInstance_WaitForInstanceStateErr(t *testing.T) {
|
|
||||||
state := testState()
|
|
||||||
state.Put("publicKey", "key")
|
|
||||||
|
|
||||||
step := new(stepCreateInstance)
|
|
||||||
defer step.Cleanup(state)
|
|
||||||
|
|
||||||
driver := state.Get("driver").(*driverMock)
|
|
||||||
driver.WaitForInstanceStateErr = errors.New("error")
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("error"); !ok {
|
|
||||||
t.Fatalf("should have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStepCreateInstance_TerminateInstanceErr(t *testing.T) {
|
|
||||||
state := testState()
|
|
||||||
state.Put("publicKey", "key")
|
|
||||||
|
|
||||||
step := new(stepCreateInstance)
|
|
||||||
defer step.Cleanup(state)
|
|
||||||
|
|
||||||
driver := state.Get("driver").(*driverMock)
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok := state.GetOk("instance_id")
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("should have machine")
|
|
||||||
}
|
|
||||||
|
|
||||||
driver.TerminateInstanceErr = errors.New("error")
|
|
||||||
step.Cleanup(state)
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("error"); !ok {
|
|
||||||
t.Fatalf("should have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStepCreateInstanceCleanup_WaitForInstanceStateErr(t *testing.T) {
|
|
||||||
state := testState()
|
|
||||||
state.Put("publicKey", "key")
|
|
||||||
|
|
||||||
step := new(stepCreateInstance)
|
|
||||||
defer step.Cleanup(state)
|
|
||||||
|
|
||||||
driver := state.Get("driver").(*driverMock)
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
driver.WaitForInstanceStateErr = errors.New("error")
|
|
||||||
step.Cleanup(state)
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("error"); !ok {
|
|
||||||
t.Fatalf("should have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepGetDefaultCredentials struct {
|
|
||||||
Debug bool
|
|
||||||
Comm *communicator.Config
|
|
||||||
BuildName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepGetDefaultCredentials) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
var (
|
|
||||||
driver = state.Get("driver").(*driverOCI)
|
|
||||||
ui = state.Get("ui").(packersdk.Ui)
|
|
||||||
id = state.Get("instance_id").(string)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Skip if we're not using winrm
|
|
||||||
if s.Comm.Type != "winrm" {
|
|
||||||
log.Printf("[INFO] Not using winrm communicator, skipping get password...")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we already have a password, skip it
|
|
||||||
if s.Comm.WinRMPassword != "" {
|
|
||||||
ui.Say("Skipping waiting for password since WinRM password set...")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
username, password, err := driver.GetInstanceInitialCredentials(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error getting instance's credentials: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
s.Comm.WinRMPassword = password
|
|
||||||
s.Comm.WinRMUser = username
|
|
||||||
|
|
||||||
if s.Debug {
|
|
||||||
ui.Message(fmt.Sprintf(
|
|
||||||
"[DEBUG] (OCI default credentials): Credentials (since debug is enabled): %s", password))
|
|
||||||
}
|
|
||||||
|
|
||||||
// store so that we can access this later during provisioning
|
|
||||||
state.Put("winrm_password", s.Comm.WinRMPassword)
|
|
||||||
packersdk.LogSecretFilter.Set(s.Comm.WinRMPassword)
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepGetDefaultCredentials) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepImage struct{}
|
|
||||||
|
|
||||||
func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
var (
|
|
||||||
driver = state.Get("driver").(Driver)
|
|
||||||
ui = state.Get("ui").(packersdk.Ui)
|
|
||||||
instanceID = state.Get("instance_id").(string)
|
|
||||||
)
|
|
||||||
|
|
||||||
ui.Say("Creating image from instance...")
|
|
||||||
|
|
||||||
image, err := driver.CreateImage(ctx, instanceID)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error creating image from instance: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
err = driver.WaitForImageCreation(ctx, *image.Id)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error waiting for image creation to finish: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(apryde): This is stale as .LifecycleState has changed to
|
|
||||||
// AVAILABLE at this point. Does it matter?
|
|
||||||
state.Put("image", image)
|
|
||||||
|
|
||||||
ui.Say("Image created.")
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepImage) Cleanup(state multistep.StateBag) {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStepImage(t *testing.T) {
|
|
||||||
state := testState()
|
|
||||||
state.Put("instance_id", "ocid1...")
|
|
||||||
|
|
||||||
step := new(stepImage)
|
|
||||||
defer step.Cleanup(state)
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("image"); !ok {
|
|
||||||
t.Fatalf("should have image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStepImage_CreateImageErr(t *testing.T) {
|
|
||||||
state := testState()
|
|
||||||
state.Put("instance_id", "ocid1...")
|
|
||||||
|
|
||||||
step := new(stepImage)
|
|
||||||
defer step.Cleanup(state)
|
|
||||||
|
|
||||||
driver := state.Get("driver").(*driverMock)
|
|
||||||
driver.CreateImageErr = errors.New("error")
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("error"); !ok {
|
|
||||||
t.Fatalf("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("image"); ok {
|
|
||||||
t.Fatalf("should NOT have image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStepImage_WaitForImageCreationErr(t *testing.T) {
|
|
||||||
state := testState()
|
|
||||||
state.Put("instance_id", "ocid1...")
|
|
||||||
|
|
||||||
step := new(stepImage)
|
|
||||||
defer step.Cleanup(state)
|
|
||||||
|
|
||||||
driver := state.Get("driver").(*driverMock)
|
|
||||||
driver.WaitForImageCreationErr = errors.New("error")
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("error"); !ok {
|
|
||||||
t.Fatalf("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("image"); ok {
|
|
||||||
t.Fatalf("should not have image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stepInstanceInfo struct{}
|
|
||||||
|
|
||||||
func (s *stepInstanceInfo) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
var (
|
|
||||||
driver = state.Get("driver").(Driver)
|
|
||||||
ui = state.Get("ui").(packersdk.Ui)
|
|
||||||
id = state.Get("instance_id").(string)
|
|
||||||
)
|
|
||||||
|
|
||||||
ip, err := driver.GetInstanceIP(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error getting instance's IP: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Put("instance_ip", ip)
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Instance has IP: %s.", ip))
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepInstanceInfo) Cleanup(state multistep.StateBag) {
|
|
||||||
// no cleanup
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInstanceInfo(t *testing.T) {
|
|
||||||
state := testState()
|
|
||||||
state.Put("instance_id", "ocid1...")
|
|
||||||
|
|
||||||
step := new(stepInstanceInfo)
|
|
||||||
defer step.Cleanup(state)
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceIPRaw, ok := state.GetOk("instance_ip")
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("should have instance_ip")
|
|
||||||
}
|
|
||||||
|
|
||||||
if instanceIPRaw.(string) != "ip" {
|
|
||||||
t.Fatalf("should've got ip ('%s' != 'ip')", instanceIPRaw.(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInstanceInfoPrivateIP(t *testing.T) {
|
|
||||||
baseTestConfig := baseTestConfig()
|
|
||||||
baseTestConfig.UsePrivateIP = true
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("config", baseTestConfig)
|
|
||||||
state.Put("driver", &driverMock{cfg: baseTestConfig})
|
|
||||||
state.Put("hook", &packersdk.MockHook{})
|
|
||||||
state.Put("ui", &packersdk.BasicUi{
|
|
||||||
Reader: new(bytes.Buffer),
|
|
||||||
Writer: new(bytes.Buffer),
|
|
||||||
})
|
|
||||||
state.Put("instance_id", "ocid1...")
|
|
||||||
|
|
||||||
step := new(stepInstanceInfo)
|
|
||||||
defer step.Cleanup(state)
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceIPRaw, ok := state.GetOk("instance_ip")
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("should have instance_ip")
|
|
||||||
}
|
|
||||||
|
|
||||||
if instanceIPRaw.(string) != "private_ip" {
|
|
||||||
t.Fatalf("should've got ip ('%s' != 'private_ip')", instanceIPRaw.(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInstanceInfo_GetInstanceIPErr(t *testing.T) {
|
|
||||||
state := testState()
|
|
||||||
state.Put("instance_id", "ocid1...")
|
|
||||||
|
|
||||||
step := new(stepInstanceInfo)
|
|
||||||
defer step.Cleanup(state)
|
|
||||||
|
|
||||||
driver := state.Get("driver").(*driverMock)
|
|
||||||
driver.GetInstanceIPErr = errors.New("error")
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("error"); !ok {
|
|
||||||
t.Fatalf("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk("instance_ip"); ok {
|
|
||||||
t.Fatalf("should NOT have instance_ip")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package oci
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO(apryde): It would be good not to have to write a key file to disk to
|
|
||||||
// load the config.
|
|
||||||
func baseTestConfig() *Config {
|
|
||||||
_, keyFile, err := baseTestConfigWithTmpKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
err = c.Prepare(map[string]interface{}{
|
|
||||||
"availability_domain": "aaaa:US-ASHBURN-AD-1",
|
|
||||||
|
|
||||||
// Image
|
|
||||||
"base_image_ocid": "ocid1.image.oc1.iad.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
||||||
"shape": "VM.Standard1.1",
|
|
||||||
"image_name": "HelloWorld",
|
|
||||||
"region": "us-ashburn-1",
|
|
||||||
|
|
||||||
// Networking
|
|
||||||
"subnet_ocid": "ocid1.subnet.oc1.iad.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
||||||
|
|
||||||
// AccessConfig
|
|
||||||
"user_ocid": "ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
||||||
"tenancy_ocid": "ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
||||||
"fingerprint": "70:04:5z:b3:19:ab:90:75:a4:1f:50:d4:c7:c3:33:20",
|
|
||||||
"key_file": keyFile.Name(),
|
|
||||||
|
|
||||||
// Comm
|
|
||||||
"ssh_username": "opc",
|
|
||||||
"use_private_ip": false,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Once we have a config object they key file isn't re-read so we can
|
|
||||||
// remove it now.
|
|
||||||
os.Remove(keyFile.Name())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
func testState() multistep.StateBag {
|
|
||||||
baseTestConfig := baseTestConfig()
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("config", baseTestConfig)
|
|
||||||
state.Put("driver", &driverMock{cfg: baseTestConfig})
|
|
||||||
state.Put("hook", &packersdk.MockHook{})
|
|
||||||
state.Put("ui", &packersdk.BasicUi{
|
|
||||||
Reader: new(bytes.Buffer),
|
|
||||||
Writer: new(bytes.Buffer),
|
|
||||||
})
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user