Compare commits

...

21 Commits

Author SHA1 Message Date
Wilken Rivera 4a091b7bd3 Fix up nav 2021-04-23 14:36:51 -04:00
Wilken Rivera c56677d399 Initial commit of plugin FAQs 2021-04-23 11:47:36 -04:00
Megan Marsh 8a3912c54b Merge pull request #10938 from AHuusom/master
Added custom nicname and osdiskname
2021-04-22 11:05:19 -07:00
Adrien Delorme 38837848f9 Breakout yandex (#10970) 2021-04-22 17:03:14 +02:00
Sylvia Moss e681669c70 Remove codecov config file (#10969) 2021-04-22 16:58:43 +02:00
Sylvia Moss 58bf783a2f Update plugins badge (#10968) 2021-04-22 16:58:17 +02:00
Sylvia Moss bcb25f1916 Extract Tencent Cloud (#10967)
* extract and vendor tencentcloud plugin

* fix fmt
2021-04-22 15:21:34 +02:00
Adrien Delorme ef612c0eb1 Breakout hcloud (#10966)
* Delete hetzner-cloud.mdx

* delete hcloud builder

* use hcloud plugin

* up mods

* use github.com/hashicorp/packer-plugin-hcloud v0.0.1
2021-04-22 14:52:07 +02:00
Sylvia Moss 972497589e extract and vendor lxc and lxd (#10965) 2021-04-22 14:21:23 +02:00
Adrien Delorme 2cd296874e Triton plugin breakout (#10963) 2021-04-22 14:06:30 +02:00
Megan Marsh f161f2bed2 extract oracle plugin (#10962) 2021-04-22 11:50:00 +02:00
Megan Marsh 6b59525408 remove digitalocean directories, revendor, add to vendored_plugins, regenerate code, and update website paths (#10961) 2021-04-22 11:45:27 +02:00
Megan Marsh d0a15f9a15 Merge pull request #10956 from hashicorp/extract-converge
Extract converge provisioner
2021-04-21 13:40:15 -07:00
Megan Marsh af37f53439 Extract vagrant (#10960)
* remove vagrant, rework website

* regenerate command/plugin, and go mod tidy
2021-04-21 16:31:28 -04:00
Wilken Rivera d0588580fa Empty commit to trigger Vercel 2021-04-21 16:14:50 -04:00
Wilken Rivera bb511e9592 Extract converge provisioner 2021-04-21 14:19:34 -04:00
Megan Marsh b3ba270f2d fix typo and regenerate 2021-04-21 11:19:16 -07:00
Anders Huusom ed4ca8e6dc formatted code 2021-04-20 19:53:49 +02:00
Anders Huusom de86b45848 removed obsolete line 2021-04-20 15:54:29 +02:00
Anders Huusom 770124207c added TempOSDiskName definition 2021-04-20 15:51:16 +02:00
Anders Huusom dbe6010510 Added custom nicname and osdiskname 2021-04-20 14:16:27 +02:00
313 changed files with 327 additions and 29575 deletions
-18
View File
@@ -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"
+18 -1
View File
@@ -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
+4
View File
@@ -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},
-56
View File
@@ -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
}
-81
View File
@@ -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")
}
}
-135
View File
@@ -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
}
-65
View File
@@ -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": ""
}]
}
`
-416
View 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")
}
}
-218
View File
@@ -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
}
-179
View File
@@ -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
}
-107
View File
@@ -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))
}
}
-71
View File
@@ -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
}
-66
View File
@@ -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
}
-90
View File
@@ -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
}
-130
View File
@@ -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
}
-15
View File
@@ -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
}
-13
View File
@@ -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)
}
-209
View File
@@ -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
}
}
-51
View File
@@ -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
}
-57
View File
@@ -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")
}
}
-91
View File
@@ -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
}
-35
View File
@@ -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": ""
}]
}
`
-152
View 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
}
-196
View File
@@ -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
}
-234
View File
@@ -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
}
-54
View File
@@ -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
}
-127
View File
@@ -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))
}
}
-49
View File
@@ -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
}
-13
View File
@@ -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)
}
-38
View File
@@ -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)
}
-91
View File
@@ -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
}
-56
View File
@@ -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")
}
}
-41
View File
@@ -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
}
-181
View File
@@ -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
}
-15
View File
@@ -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")
}
}
-114
View File
@@ -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
}
-69
View File
@@ -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
}
-87
View File
@@ -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) {}
-78
View File
@@ -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))
}
}
-51
View File
@@ -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)
}
}
}
-47
View File
@@ -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) {}
-105
View File
@@ -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
}
-13
View File
@@ -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)
}
-38
View File
@@ -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
}
-70
View File
@@ -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
}
-77
View File
@@ -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")
}
}
-43
View File
@@ -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
}
-142
View File
@@ -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
}
-21
View File
@@ -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
-92
View File
@@ -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
}
-61
View File
@@ -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
}
-68
View File
@@ -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))
}
}
-44
View File
@@ -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) {}
-60
View File
@@ -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) {}
-13
View File
@@ -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)
}
-49
View File
@@ -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
}
-206
View File
@@ -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.
-184
View File
@@ -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
}
-155
View File
@@ -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
}
-82
View File
@@ -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)
}
}
}
}
-14
View File
@@ -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...)
}
-140
View File
@@ -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
}
-31
View File
@@ -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)
}
}
-75
View File
@@ -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.")
}
-103
View File
@@ -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
}
-161
View File
@@ -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()))
}
}
}
-72
View File
@@ -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)
}
-116
View File
@@ -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
}
-375
View File
@@ -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.
-57
View File
@@ -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)
}
-41
View File
@@ -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")
}
}
-110
View File
@@ -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.
-15
View File
@@ -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")
}
}
-415
View File
@@ -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
}
-300
View File
@@ -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
}
-412
View File
@@ -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
}
-18
View File
@@ -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
}
-96
View File
@@ -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
}
-339
View File
@@ -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
}
-49
View File
@@ -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
}
-71
View File
@@ -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")
}
}
-37
View File
@@ -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")
}
}
-64
View File
@@ -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