Compare commits
171 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2ba700ac0 | |||
| 1621ab59d8 | |||
| 799c548c40 | |||
| 6d4fae0f2d | |||
| 2f26e6d823 | |||
| 584fea678b | |||
| b8e6e2c081 | |||
| cc7dbf6092 | |||
| 4f5e878a17 | |||
| b24911661f | |||
| dd068acfd1 | |||
| ef1b401a22 | |||
| cee3a14392 | |||
| ecc8c6a44a | |||
| 476eefa26b | |||
| 456214eb51 | |||
| 608307cd1e | |||
| d5d1a8708e | |||
| 6967e02103 | |||
| a8ee23a9f9 | |||
| f696e8286e | |||
| 09c6d2880f | |||
| 66b81d9bee | |||
| a17b1a5a89 | |||
| b0cc71d35e | |||
| 0c34d6ca12 | |||
| 7ba2ffe171 | |||
| 5d2f2b20e7 | |||
| f79b9dc735 | |||
| 27128dc62f | |||
| 343ac2d48d | |||
| 06efa61c13 | |||
| da15b85451 | |||
| 2cdc052d7c | |||
| 31df482b61 | |||
| 00cc425b84 | |||
| 5522980e4d | |||
| 84d5de369f | |||
| f1b57b1e66 | |||
| a81811663a | |||
| c16354d594 | |||
| e98b2d39db | |||
| 8121d6d8dc | |||
| 15f97421e2 | |||
| c7ba5c9a14 | |||
| 22622b234b | |||
| 22a49a2f2e | |||
| 33019a0e01 | |||
| 472b008a94 | |||
| 4810a3d86f | |||
| 2578bbbb50 | |||
| 09f57537bf | |||
| 56807553fc | |||
| c5dffc1bff | |||
| 3abae29752 | |||
| ad129e7df9 | |||
| 75cfc97da4 | |||
| 01ed398756 | |||
| d378a4477a | |||
| 8154e5772f | |||
| 709a173060 | |||
| dcaf56596d | |||
| b05fd9303e | |||
| b460d687ea | |||
| 774a168957 | |||
| 7ed2c2920f | |||
| 3e35630d8d | |||
| 153d4111fa | |||
| 62a401ef28 | |||
| d6aa172edc | |||
| d7ba10c23e | |||
| f076c97e5c | |||
| 8e632866b1 | |||
| b54b778572 | |||
| ea3b804af1 | |||
| 14e253f316 | |||
| 7f0a043a63 | |||
| d4d78feed2 | |||
| a478bf6f37 | |||
| 809f38be3a | |||
| 61c6085651 | |||
| d05eb3401b | |||
| 7ee9a4b638 | |||
| 10e356abe6 | |||
| ef9713b348 | |||
| b34e61c961 | |||
| deb08409f1 | |||
| aeb63f9bef | |||
| 721cbac645 | |||
| 4cb218749b | |||
| fcaf766569 | |||
| 9f97173371 | |||
| 2222d112bd | |||
| c892f0582b | |||
| 157b5cdc28 | |||
| 37b4533cba | |||
| b105e5e416 | |||
| 8f9001b531 | |||
| 10b648b282 | |||
| ebb1cb406c | |||
| fcf16315a3 | |||
| bb22cfcf34 | |||
| 1c4b8c3d31 | |||
| b1c74f9df0 | |||
| c0be097afc | |||
| 2228b18fcb | |||
| d18b7839b9 | |||
| f13099edfb | |||
| 8c07e21be8 | |||
| 18c4f271ac | |||
| 984f21d409 | |||
| 465ce3596f | |||
| 69fd5a1527 | |||
| e08e7734f5 | |||
| 49e4d83cd7 | |||
| aa3ec3fd0f | |||
| c0c0f003f9 | |||
| d813c3647d | |||
| 79e25bf4ac | |||
| 2a879aff14 | |||
| 72166febee | |||
| 180dc4a54e | |||
| f3e65b5eb1 | |||
| d7507ae67c | |||
| c8874c9382 | |||
| 08d485b1f9 | |||
| 49bc7665c1 | |||
| aa9c162c60 | |||
| e758891878 | |||
| be56632f7e | |||
| 9b121e85f9 | |||
| 3f6230470b | |||
| 2faacfd5d7 | |||
| abdb4bcfbf | |||
| 645e5afa36 | |||
| b844b7f1c7 | |||
| 007f6cce4c | |||
| eed047b83b | |||
| ddc09ded89 | |||
| e0badb3fb7 | |||
| 8885a5ef31 | |||
| f0dc26613a | |||
| 8166dad533 | |||
| aba6e104ae | |||
| 8274d4848c | |||
| 071854ea4a | |||
| a13dfe1f42 | |||
| 60bd61dc18 | |||
| c3a4b60ea5 | |||
| 7a45e4c8b0 | |||
| c58d6f9b33 | |||
| 6f0bb33c9a | |||
| e5c14044fb | |||
| addc3dd6e7 | |||
| d9a7626249 | |||
| 7745369dce | |||
| 86966ecfee | |||
| 6f6a656486 | |||
| 232d5a3ce6 | |||
| 43e9d43ebd | |||
| 7d8f28e1a3 | |||
| 8b2cdc5821 | |||
| 8649496c6c | |||
| daefa4d086 | |||
| 4431bb87b2 | |||
| 823b957a1f | |||
| 22481d1ac2 | |||
| 1fbe715c13 | |||
| 1bfb2d9170 | |||
| 1055007cba | |||
| 74f868da37 |
@@ -7,11 +7,11 @@ version: 2.1
|
||||
executors:
|
||||
golang:
|
||||
docker:
|
||||
- image: circleci/golang:1.13
|
||||
- image: circleci/golang:1.15
|
||||
resource_class: medium+
|
||||
darwin:
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
xcode: "12.0.0"
|
||||
|
||||
commands:
|
||||
install-go-run-tests-unix:
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
steps:
|
||||
- install-go-run-tests-unix:
|
||||
GOOS: darwin
|
||||
GOVERSION: "1.13"
|
||||
GOVERSION: "1.15"
|
||||
- codecov/upload:
|
||||
file: coverage.txt
|
||||
test-windows:
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
shell: bash.exe
|
||||
steps:
|
||||
- install-go-run-tests-windows:
|
||||
GOVERSION: "1.13"
|
||||
GOVERSION: "1.15"
|
||||
- codecov/upload:
|
||||
file: coverage.txt
|
||||
check-lint:
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*.mdx text eol=lf
|
||||
*.ps1 text eol=lf
|
||||
*.hcl text eol=lf
|
||||
*.txt text eol=lf
|
||||
go.mod text eol=lf
|
||||
go.sum text eol=lf
|
||||
common/test-fixtures/root/* eol=lf
|
||||
|
||||
@@ -1,3 +1,39 @@
|
||||
## 1.6.5 (Upcoming)
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
* builder/azure-chroot: Fix typo in option `exlude_from_latest` to
|
||||
`exclude_from_latest`. Old name will still be respected. [GH-10034]
|
||||
* builder/openstack: Fix source image validation regression when using filters.
|
||||
[GH-10065]
|
||||
* core/hcl2: Packer HCL's "Coalesce" function now behaves same way as
|
||||
Terraform's. [GH-10016]
|
||||
* core/HCL: Hide sensitive variables from output. [GH-10031]
|
||||
* core: Fix artifact handling so that input artifacts are properly preserved in
|
||||
postprocessors that don't modify artifacts. [GH-9996]
|
||||
* core: Fix pathing in cd_files to copy proper directory tree when user
|
||||
provided absolute paths. [GH-10022]
|
||||
* provisioner/ansible: Ansible galaxy no longer forces use of collections in v1
|
||||
files. [GH-10010]
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
* builder/amazon: Add `io2` as a supported volume type. [GH-10102]
|
||||
* builder/amazon: Add support for source instance tenancy [GH-10085]
|
||||
* builder/google: Add service account impersonation. [GH-9968] [GH-10054]
|
||||
* builder/googlecompute: Add `skip_create_image` option. [GH-10115]
|
||||
* builder/oracle-oci: New option to specify image compartment separate from
|
||||
build compartment. [GH-10040]
|
||||
* builder/oracle-oci: New option to specify boot volume size. [GH-10017]
|
||||
* builder/outscale: Migrate to new Outscale SDK. [GH-10056]
|
||||
* builder/scaleway: Allow the user to use an image label (eg ubuntu_focal)
|
||||
instead of a hardcoded UUID on the Scaleway builder. [GH-10061]
|
||||
* hcl: Add build.name variable so users can access build name in addition to
|
||||
source name. [GH-10114]
|
||||
* hcl: Add consul_key function to HCL templates. [GH-10119]
|
||||
* hcl: Add packer.version variable to hcl configs so users can access the
|
||||
Packer release version. [GH-10117]
|
||||
|
||||
## 1.6.4 (September 30, 2020)
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// The "AlicloudDiskDevice" object us used for the `ECSSystemDiskMapping` and
|
||||
// `ECSImagesDiskMappings` options, and contains the following fields:
|
||||
type AlicloudDiskDevice struct {
|
||||
// The value of disk name is blank by default. [2,
|
||||
// 128] English or Chinese characters, must begin with an
|
||||
@@ -19,8 +21,7 @@ type AlicloudDiskDevice struct {
|
||||
// ., _ and -. The disk name will appear on the console. It cannot
|
||||
// begin with `http://` or `https://`.
|
||||
DiskName string `mapstructure:"disk_name" required:"false"`
|
||||
// Category of the system disk. Optional values
|
||||
// are:
|
||||
// Category of the system disk. Optional values are:
|
||||
// - cloud - general cloud disk
|
||||
// - cloud_efficiency - efficiency cloud disk
|
||||
// - cloud_ssd - cloud SSD
|
||||
@@ -32,6 +33,8 @@ type AlicloudDiskDevice struct {
|
||||
// Snapshots are used to create the data
|
||||
// disk After this parameter is specified, Size is ignored. The actual
|
||||
// size of the created disk is the size of the specified snapshot.
|
||||
// This field is only used in the ECSImagesDiskMappings option, not
|
||||
// the ECSSystemDiskMapping option.
|
||||
SnapshotId string `mapstructure:"disk_snapshot_id" required:"false"`
|
||||
// The value of disk description is blank by
|
||||
// default. [2, 256] characters. The disk description will appear on the
|
||||
@@ -44,94 +47,51 @@ type AlicloudDiskDevice struct {
|
||||
// such as /dev/xvdb It is null unless the Status is In_use.
|
||||
Device string `mapstructure:"disk_device" required:"false"`
|
||||
// Whether or not to encrypt the data disk.
|
||||
// If this option is set to true, the data disk will be encryped and corresponding snapshot in the target image will also be encrypted. By
|
||||
// If this option is set to true, the data disk will be encryped and
|
||||
// corresponding snapshot in the target image will also be encrypted. By
|
||||
// default, if this is an extra data disk, Packer will not encrypt the
|
||||
// data disk. Otherwise, Packer will keep the encryption setting to what
|
||||
// it was in the source image. Please refer to Introduction of ECS disk encryption
|
||||
// for more details.
|
||||
// it was in the source image. Please refer to Introduction of ECS disk
|
||||
// encryption for more details.
|
||||
Encrypted config.Trilean `mapstructure:"disk_encrypted" required:"false"`
|
||||
}
|
||||
|
||||
// The "AlicloudDiskDevices" object is used to define disk mappings for your
|
||||
// instance.
|
||||
type AlicloudDiskDevices struct {
|
||||
// Image disk mapping for system
|
||||
// disk.
|
||||
// - `disk_category` (string) - Category of the system disk. Optional values
|
||||
// are:
|
||||
// - `cloud` - general cloud disk
|
||||
// - `cloud_efficiency` - efficiency cloud disk
|
||||
// - `cloud_ssd` - cloud SSD
|
||||
//
|
||||
// For phased-out instance types and non-I/O optimized instances, the
|
||||
// default value is cloud. Otherwise, the default value is
|
||||
// cloud\_efficiency.
|
||||
//
|
||||
// - `disk_description` (string) - The value of disk description is blank by
|
||||
// default. \[2, 256\] characters. The disk description will appear on the
|
||||
// console. It cannot begin with `http://` or `https://`.
|
||||
//
|
||||
// - `disk_name` (string) - The value of disk name is blank by default. \[2,
|
||||
// 128\] English or Chinese characters, must begin with an
|
||||
// uppercase/lowercase letter or Chinese character. Can contain numbers,
|
||||
// `.`, `_` and `-`. The disk name will appear on the console. It cannot
|
||||
// begin with `http://` or `https://`.
|
||||
//
|
||||
// - `disk_size` (number) - Size of the system disk, measured in GiB. Value
|
||||
// range: \[20, 500\]. The specified value must be equal to or greater
|
||||
// than max{20, ImageSize}. Default value: max{40, ImageSize}.
|
||||
// Image disk mapping for the system disk.
|
||||
// See the [disk device configuration](#disk-devices-configuration) section
|
||||
// for more information on options.
|
||||
// Usage example:
|
||||
//
|
||||
// ```json
|
||||
// "builders": [{
|
||||
// "type":"alicloud-ecs",
|
||||
// "system_disk_mapping": {
|
||||
// "disk_size": 50,
|
||||
// "disk_name": "mydisk"
|
||||
// },
|
||||
// ...
|
||||
// }
|
||||
// ```
|
||||
ECSSystemDiskMapping AlicloudDiskDevice `mapstructure:"system_disk_mapping" required:"false"`
|
||||
// Add one or more data
|
||||
// disks to the image.
|
||||
//
|
||||
// - `disk_category` (string) - Category of the data disk. Optional values
|
||||
// are:
|
||||
// - `cloud` - general cloud disk
|
||||
// - `cloud_efficiency` - efficiency cloud disk
|
||||
// - `cloud_ssd` - cloud SSD
|
||||
//
|
||||
// Default value: cloud.
|
||||
//
|
||||
// - `disk_delete_with_instance` (boolean) - Whether or not the disk is
|
||||
// released along with the instance:
|
||||
// - True indicates that when the instance is released, this disk will
|
||||
// be released with it
|
||||
// - False indicates that when the instance is released, this disk will
|
||||
// be retained.
|
||||
// - `disk_description` (string) - The value of disk description is blank by
|
||||
// default. \[2, 256\] characters. The disk description will appear on the
|
||||
// console. It cannot begin with `http://` or `https://`.
|
||||
//
|
||||
// - `disk_device` (string) - Device information of the related instance:
|
||||
// such as `/dev/xvdb` It is null unless the Status is In\_use.
|
||||
//
|
||||
// - `disk_name` (string) - The value of disk name is blank by default. \[2,
|
||||
// 128\] English or Chinese characters, must begin with an
|
||||
// uppercase/lowercase letter or Chinese character. Can contain numbers,
|
||||
// `.`, `_` and `-`. The disk name will appear on the console. It cannot
|
||||
// begin with `http://` or `https://`.
|
||||
//
|
||||
// - `disk_size` (number) - Size of the data disk, in GB, values range:
|
||||
// - `cloud` - 5 \~ 2000
|
||||
// - `cloud_efficiency` - 20 \~ 2048
|
||||
// - `cloud_ssd` - 20 \~ 2048
|
||||
//
|
||||
// The value should be equal to or greater than the size of the specific
|
||||
// SnapshotId.
|
||||
//
|
||||
// - `disk_snapshot_id` (string) - Snapshots are used to create the data
|
||||
// disk After this parameter is specified, Size is ignored. The actual
|
||||
// size of the created disk is the size of the specified snapshot.
|
||||
//
|
||||
// Snapshots from on or before July 15, 2013 cannot be used to create a
|
||||
// disk.
|
||||
//
|
||||
// - `disk_encrypted` (boolean) - Whether or not to encrypt the data disk.
|
||||
// If this option is set to true, the data disk will be encryped and corresponding snapshot in the target image will also be encrypted. By
|
||||
// default, if this is an extra data disk, Packer will not encrypt the
|
||||
// data disk. Otherwise, Packer will keep the encryption setting to what
|
||||
// it was in the source image. Please refer to Introduction of [ECS disk encryption](https://www.alibabacloud.com/help/doc-detail/59643.htm)
|
||||
// for more details.
|
||||
// Add one or more data disks to the image.
|
||||
// See the [disk device configuration](#disk-devices-configuration) section
|
||||
// for more information on options.
|
||||
// Usage example:
|
||||
//
|
||||
// ```json
|
||||
// "builders": [{
|
||||
// "type":"alicloud-ecs",
|
||||
// "image_disk_mappings": [
|
||||
// {
|
||||
// "disk_snapshot_id": "someid",
|
||||
// "disk_device": "dev/xvdb"
|
||||
// }
|
||||
// ],
|
||||
// ...
|
||||
// }
|
||||
// ```
|
||||
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings" required:"false"`
|
||||
}
|
||||
|
||||
|
||||
@@ -134,8 +134,8 @@ func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapp
|
||||
ebsBlockDevice.VolumeSize = aws.Int64(blockDevice.VolumeSize)
|
||||
}
|
||||
|
||||
// IOPS is only valid for io1 type
|
||||
if blockDevice.VolumeType == "io1" {
|
||||
// IOPS is only valid for io1 and io2 types
|
||||
if blockDevice.VolumeType == "io1" || blockDevice.VolumeType == "io2" {
|
||||
ebsBlockDevice.Iops = aws.Int64(blockDevice.IOPS)
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,25 @@ func TestBlockDevice(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: &BlockDevice{
|
||||
DeviceName: "/dev/sdb",
|
||||
VolumeType: "io2",
|
||||
VolumeSize: 8,
|
||||
DeleteOnTermination: true,
|
||||
IOPS: 1000,
|
||||
},
|
||||
|
||||
Result: &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String("/dev/sdb"),
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
VolumeType: aws.String("io2"),
|
||||
VolumeSize: aws.Int64(8),
|
||||
DeleteOnTermination: aws.Bool(true),
|
||||
Iops: aws.Int64(1000),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: &BlockDevice{
|
||||
DeviceName: "/dev/sdb",
|
||||
|
||||
@@ -376,6 +376,12 @@ type RunConfig struct {
|
||||
// subnet-12345def, where Packer will launch the EC2 instance. This field is
|
||||
// required if you are using an non-default VPC.
|
||||
SubnetId string `mapstructure:"subnet_id" required:"false"`
|
||||
// [Tenancy](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/dedicated-instance.html) used
|
||||
// when Packer launches the EC2 instance, allowing it to be launched on dedicated hardware.
|
||||
//
|
||||
// The default is "default", meaning shared tenancy. Allowed values are "default",
|
||||
// "dedicated" and "host".
|
||||
Tenancy string `mapstructure:"tenancy" required:"false"`
|
||||
// The name of the temporary key pair to
|
||||
// generate. By default, Packer generates a name that looks like
|
||||
// `packer_<UUID>`, where <UUID> is a 36 character unique identifier.
|
||||
@@ -631,6 +637,13 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
}
|
||||
}
|
||||
|
||||
if c.Tenancy != "" &&
|
||||
c.Tenancy != "default" &&
|
||||
c.Tenancy != "dedicated" &&
|
||||
c.Tenancy != "host" {
|
||||
errs = append(errs, fmt.Errorf("Error: Unknown tenancy type %s", c.Tenancy))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
|
||||
@@ -232,3 +232,23 @@ func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
|
||||
t.Fatal("keypair name does not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_TenancyBad(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.Tenancy = "not_real"
|
||||
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatal("Should error if tenancy is set to an invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_TenancyGood(t *testing.T) {
|
||||
validTenancy := []string{"", "default", "dedicated", "host"}
|
||||
for _, vt := range validTenancy {
|
||||
c := testConfig()
|
||||
c.Tenancy = vt
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("Should not error if tenancy is set to %s", vt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ type StepRunSourceInstance struct {
|
||||
IsRestricted bool
|
||||
SourceAMI string
|
||||
Tags map[string]string
|
||||
Tenancy string
|
||||
UserData string
|
||||
UserDataFile string
|
||||
VolumeTags map[string]string
|
||||
@@ -195,6 +196,10 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
runOpts.InstanceInitiatedShutdownBehavior = &s.InstanceInitiatedShutdownBehavior
|
||||
}
|
||||
|
||||
if s.Tenancy != "" {
|
||||
runOpts.Placement.Tenancy = aws.String(s.Tenancy)
|
||||
}
|
||||
|
||||
var runResp *ec2.Reservation
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
|
||||
@@ -206,6 +206,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
Tags: b.config.RunTags,
|
||||
Tenancy: b.config.Tenancy,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
VolumeTags: b.config.VolumeRunTags,
|
||||
|
||||
@@ -81,6 +81,7 @@ type FlatConfig struct {
|
||||
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
|
||||
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
|
||||
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
|
||||
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
|
||||
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
|
||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
||||
@@ -226,6 +227,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
|
||||
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
|
||||
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
|
||||
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
|
||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||
|
||||
@@ -228,6 +228,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
Tags: b.config.RunTags,
|
||||
Tenancy: b.config.Tenancy,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
VolumeTags: b.config.VolumeRunTags,
|
||||
|
||||
@@ -103,6 +103,7 @@ type FlatConfig struct {
|
||||
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
|
||||
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
|
||||
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
|
||||
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
|
||||
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
|
||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
||||
@@ -249,6 +250,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
|
||||
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
|
||||
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
|
||||
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
|
||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||
|
||||
@@ -209,6 +209,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
Tags: b.config.RunTags,
|
||||
Tenancy: b.config.Tenancy,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
VolumeTags: b.config.VolumeRunTags,
|
||||
|
||||
@@ -105,6 +105,7 @@ type FlatConfig struct {
|
||||
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
|
||||
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
|
||||
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
|
||||
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
|
||||
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
|
||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
||||
@@ -229,6 +230,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
|
||||
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
|
||||
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
|
||||
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
|
||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||
|
||||
@@ -286,6 +286,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
Tags: b.config.RunTags,
|
||||
Tenancy: b.config.Tenancy,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ type FlatConfig struct {
|
||||
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
|
||||
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
|
||||
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
|
||||
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
|
||||
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
|
||||
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
|
||||
@@ -232,6 +233,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
|
||||
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
|
||||
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
|
||||
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
|
||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
ERROR: -> DeploymentFailed : At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.
|
||||
ERROR: -> BadRequest
|
||||
ERROR: -> InvalidRequestFormat : Cannot parse the request.
|
||||
ERROR: -> InvalidJson : Error converting value "playground" to type 'Microsoft.WindowsAzure.Networking.Nrp.Frontend.Contract.Csm.Public.IpAllocationMethod'. Path 'properties.publicIPAllocationMethod', line 1, position 130.
|
||||
ERROR: -> DeploymentFailed : At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.
|
||||
ERROR: -> BadRequest
|
||||
ERROR: -> InvalidRequestFormat : Cannot parse the request.
|
||||
ERROR: -> InvalidJson : Error converting value "playground" to type 'Microsoft.WindowsAzure.Networking.Nrp.Frontend.Contract.Csm.Public.IpAllocationMethod'. Path 'properties.publicIPAllocationMethod', line 1, position 130.
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
ERROR: -> ResourceNotFound : The Resource 'Microsoft.Compute/images/PackerUbuntuImage' under resource group 'packer-test00' was not found.
|
||||
ERROR: -> ResourceNotFound : The Resource 'Microsoft.Compute/images/PackerUbuntuImage' under resource group 'packer-test00' was not found.
|
||||
|
||||
@@ -6,7 +6,6 @@ package arm
|
||||
// * ARM_CLIENT_ID
|
||||
// * ARM_CLIENT_SECRET
|
||||
// * ARM_SUBSCRIPTION_ID
|
||||
// * ARM_OBJECT_ID
|
||||
// * ARM_STORAGE_ACCOUNT
|
||||
//
|
||||
// The subscription in question should have a resource group
|
||||
@@ -151,7 +150,7 @@ const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
|
||||
|
||||
"build_resource_group_name" : "packer-acceptance-test",
|
||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||
"managed_image_name": "testBuilderAccManagedDiskWindows-{{timestamp}}",
|
||||
"managed_image_name": "testBuilderAccManagedDiskWindowsBuildResourceGroup-{{timestamp}}",
|
||||
|
||||
"os_type": "Windows",
|
||||
"image_publisher": "MicrosoftWindowsServer",
|
||||
@@ -262,7 +261,6 @@ const testBuilderAccBlobWindows = `
|
||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}",
|
||||
"object_id": "{{env ` + "`ARM_OBJECT_ID`" + `}}",
|
||||
"storage_account": "{{env ` + "`ARM_STORAGE_ACCOUNT`" + `}}"
|
||||
},
|
||||
"builders": [{
|
||||
@@ -271,7 +269,6 @@ const testBuilderAccBlobWindows = `
|
||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||
"object_id": "{{user ` + "`object_id`" + `}}",
|
||||
|
||||
"storage_account": "{{user ` + "`storage_account`" + `}}",
|
||||
"resource_group_name": "packer-acceptance-test",
|
||||
|
||||
@@ -16,8 +16,9 @@ type SharedImageGalleryDestination struct {
|
||||
ImageName string `mapstructure:"image_name" required:"true"`
|
||||
ImageVersion string `mapstructure:"image_version" required:"true"`
|
||||
|
||||
TargetRegions []TargetRegion `mapstructure:"target_regions"`
|
||||
ExcludeFromLatest bool `mapstructure:"exlude_from_latest"`
|
||||
TargetRegions []TargetRegion `mapstructure:"target_regions"`
|
||||
ExcludeFromLatest bool `mapstructure:"exclude_from_latest"`
|
||||
ExcludeFromLatestTypo bool `mapstructure:"exlude_from_latest" undocumented:"true"`
|
||||
}
|
||||
|
||||
// TargetRegion describes a region where the shared image should be replicated
|
||||
@@ -61,5 +62,10 @@ func (sigd *SharedImageGalleryDestination) Validate(prefix string) (errs []error
|
||||
warns = append(warns,
|
||||
fmt.Sprintf("%s.target_regions is empty; image will only be available in the region of the gallery", prefix))
|
||||
}
|
||||
if sigd.ExcludeFromLatestTypo == true && sigd.ExcludeFromLatest == false {
|
||||
warns = append(warns,
|
||||
fmt.Sprintf("%s.exlude_from_latest is being deprecated, please use exclude_from_latest", prefix))
|
||||
sigd.ExcludeFromLatest = sigd.ExcludeFromLatestTypo
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -9,12 +9,13 @@ import (
|
||||
// FlatSharedImageGalleryDestination is an auto-generated flat version of SharedImageGalleryDestination.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatSharedImageGalleryDestination struct {
|
||||
ResourceGroup *string `mapstructure:"resource_group" required:"true" cty:"resource_group" hcl:"resource_group"`
|
||||
GalleryName *string `mapstructure:"gallery_name" required:"true" cty:"gallery_name" hcl:"gallery_name"`
|
||||
ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
|
||||
ImageVersion *string `mapstructure:"image_version" required:"true" cty:"image_version" hcl:"image_version"`
|
||||
TargetRegions []FlatTargetRegion `mapstructure:"target_regions" cty:"target_regions" hcl:"target_regions"`
|
||||
ExcludeFromLatest *bool `mapstructure:"exlude_from_latest" cty:"exlude_from_latest" hcl:"exlude_from_latest"`
|
||||
ResourceGroup *string `mapstructure:"resource_group" required:"true" cty:"resource_group" hcl:"resource_group"`
|
||||
GalleryName *string `mapstructure:"gallery_name" required:"true" cty:"gallery_name" hcl:"gallery_name"`
|
||||
ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
|
||||
ImageVersion *string `mapstructure:"image_version" required:"true" cty:"image_version" hcl:"image_version"`
|
||||
TargetRegions []FlatTargetRegion `mapstructure:"target_regions" cty:"target_regions" hcl:"target_regions"`
|
||||
ExcludeFromLatest *bool `mapstructure:"exclude_from_latest" cty:"exclude_from_latest" hcl:"exclude_from_latest"`
|
||||
ExcludeFromLatestTypo *bool `mapstructure:"exlude_from_latest" undocumented:"true" cty:"exlude_from_latest" hcl:"exlude_from_latest"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatSharedImageGalleryDestination.
|
||||
@@ -29,12 +30,13 @@ func (*SharedImageGalleryDestination) FlatMapstructure() interface{ HCL2Spec() m
|
||||
// The decoded values from this spec will then be applied to a FlatSharedImageGalleryDestination.
|
||||
func (*FlatSharedImageGalleryDestination) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false},
|
||||
"gallery_name": &hcldec.AttrSpec{Name: "gallery_name", Type: cty.String, Required: false},
|
||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
|
||||
"target_regions": &hcldec.BlockListSpec{TypeName: "target_regions", Nested: hcldec.ObjectSpec((*FlatTargetRegion)(nil).HCL2Spec())},
|
||||
"exlude_from_latest": &hcldec.AttrSpec{Name: "exlude_from_latest", Type: cty.Bool, Required: false},
|
||||
"resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false},
|
||||
"gallery_name": &hcldec.AttrSpec{Name: "gallery_name", Type: cty.String, Required: false},
|
||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
|
||||
"target_regions": &hcldec.BlockListSpec{TypeName: "target_regions", Nested: hcldec.ObjectSpec((*FlatTargetRegion)(nil).HCL2Spec())},
|
||||
"exclude_from_latest": &hcldec.AttrSpec{Name: "exclude_from_latest", Type: cty.Bool, Required: false},
|
||||
"exlude_from_latest": &hcldec.AttrSpec{Name: "exlude_from_latest", Type: cty.Bool, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -21,12 +21,13 @@ func TestSharedImageGalleryDestination_ResourceID(t *testing.T) {
|
||||
|
||||
func TestSharedImageGalleryDestination_Validate(t *testing.T) {
|
||||
type fields struct {
|
||||
ResourceGroup string
|
||||
GalleryName string
|
||||
ImageName string
|
||||
ImageVersion string
|
||||
TargetRegions []TargetRegion
|
||||
ExcludeFromLatest bool
|
||||
ResourceGroup string
|
||||
GalleryName string
|
||||
ImageName string
|
||||
ImageVersion string
|
||||
TargetRegions []TargetRegion
|
||||
ExcludeFromLatest bool
|
||||
ExcludeFromLatestTypo bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -66,6 +67,29 @@ func TestSharedImageGalleryDestination_Validate(t *testing.T) {
|
||||
},
|
||||
wantWarns: []string{"sigdest.target_regions is empty; image will only be available in the region of the gallery"},
|
||||
},
|
||||
{
|
||||
name: "warn if using exlude_from_latest",
|
||||
fields: fields{
|
||||
ResourceGroup: "ResourceGroup",
|
||||
GalleryName: "GalleryName",
|
||||
ImageName: "ImageName",
|
||||
ImageVersion: "0.1.2",
|
||||
TargetRegions: []TargetRegion{
|
||||
TargetRegion{
|
||||
Name: "region1",
|
||||
ReplicaCount: 5,
|
||||
StorageAccountType: "Standard_ZRS",
|
||||
},
|
||||
TargetRegion{
|
||||
Name: "region2",
|
||||
ReplicaCount: 3,
|
||||
StorageAccountType: "Standard_LRS",
|
||||
},
|
||||
},
|
||||
ExcludeFromLatestTypo: true,
|
||||
},
|
||||
wantWarns: []string{"sigdest.exlude_from_latest is being deprecated, please use exclude_from_latest"},
|
||||
},
|
||||
{
|
||||
name: "version format",
|
||||
wantErrs: []string{
|
||||
@@ -105,12 +129,13 @@ func TestSharedImageGalleryDestination_Validate(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sigd := &SharedImageGalleryDestination{
|
||||
ResourceGroup: tt.fields.ResourceGroup,
|
||||
GalleryName: tt.fields.GalleryName,
|
||||
ImageName: tt.fields.ImageName,
|
||||
ImageVersion: tt.fields.ImageVersion,
|
||||
TargetRegions: tt.fields.TargetRegions,
|
||||
ExcludeFromLatest: tt.fields.ExcludeFromLatest,
|
||||
ResourceGroup: tt.fields.ResourceGroup,
|
||||
GalleryName: tt.fields.GalleryName,
|
||||
ImageName: tt.fields.ImageName,
|
||||
ImageVersion: tt.fields.ImageVersion,
|
||||
TargetRegions: tt.fields.TargetRegions,
|
||||
ExcludeFromLatest: tt.fields.ExcludeFromLatest,
|
||||
ExcludeFromLatestTypo: tt.fields.ExcludeFromLatestTypo,
|
||||
}
|
||||
gotErrs, gotWarns := sigd.Validate("sigdest")
|
||||
|
||||
|
||||
@@ -30,19 +30,22 @@ func (a *ImportArtifact) Id() string {
|
||||
}
|
||||
|
||||
func (a *ImportArtifact) String() string {
|
||||
tags := a.StateData["docker_tags"]
|
||||
if tags == nil {
|
||||
return fmt.Sprintf("Imported Docker image: %s", a.Id())
|
||||
}
|
||||
cast := tags.([]interface{})
|
||||
names := []string{}
|
||||
for _, name := range cast {
|
||||
if n, ok := name.(string); ok {
|
||||
names = append(names, n)
|
||||
var tags []string
|
||||
switch t := a.StateData["docker_tags"].(type) {
|
||||
case []string:
|
||||
tags = t
|
||||
case []interface{}:
|
||||
for _, name := range t {
|
||||
if n, ok := name.(string); ok {
|
||||
tags = append(tags, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("Imported Docker image: %s with tags %s",
|
||||
a.Id(), strings.Join(names, " "))
|
||||
if len(tags) > 0 {
|
||||
return fmt.Sprintf("Imported Docker image: %s with tags %s",
|
||||
a.Id(), strings.Join(tags, " "))
|
||||
}
|
||||
return fmt.Sprintf("Imported Docker image: %s", a.Id())
|
||||
}
|
||||
|
||||
func (a *ImportArtifact) State(name string) interface{} {
|
||||
|
||||
@@ -9,26 +9,39 @@ import (
|
||||
"golang.org/x/oauth2/jwt"
|
||||
)
|
||||
|
||||
func ProcessAccountFile(text string) (*jwt.Config, error) {
|
||||
// Assume text is a JSON string
|
||||
conf, err := google.JWTConfigFromJSON([]byte(text), DriverScopes...)
|
||||
if err != nil {
|
||||
// If text was not JSON, assume it is a file path instead
|
||||
if _, err := os.Stat(text); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf(
|
||||
"account_file path does not exist: %s",
|
||||
text)
|
||||
}
|
||||
data, err := ioutil.ReadFile(text)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Error reading account_file from path '%s': %s",
|
||||
text, err)
|
||||
}
|
||||
conf, err = google.JWTConfigFromJSON(data, DriverScopes...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing account_file: %s", err)
|
||||
}
|
||||
}
|
||||
return conf, nil
|
||||
type ServiceAccount struct {
|
||||
jsonKey []byte
|
||||
jwt *jwt.Config
|
||||
}
|
||||
|
||||
// ProcessAccountFile will return a ServiceAccount for the JSON account file stored in text.
|
||||
// Otherwise it will return an error if text does not look or reference a valid account file.
|
||||
func ProcessAccountFile(text string) (*ServiceAccount, error) {
|
||||
// Assume text is a JSON string
|
||||
if conf, err := google.JWTConfigFromJSON([]byte(text), DriverScopes...); err == nil {
|
||||
return &ServiceAccount{
|
||||
jsonKey: []byte(text),
|
||||
jwt: conf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// If text was not JSON, assume it is a file path instead
|
||||
if _, err := os.Stat(text); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("account_file path does not exist: %s", text)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(text)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading account_file from path '%s': %s", text, err)
|
||||
}
|
||||
|
||||
conf, err := google.JWTConfigFromJSON(data, DriverScopes...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing account_file: %s", err)
|
||||
}
|
||||
|
||||
return &ServiceAccount{
|
||||
jsonKey: data,
|
||||
jwt: conf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -36,8 +36,15 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
// Run executes a googlecompute Packer build and returns a packer.Artifact
|
||||
// representing a GCE machine image.
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
driver, err := NewDriverGCE(
|
||||
ui, b.config.ProjectId, b.config.account, b.config.VaultGCPOauthEngine)
|
||||
cfg := GCEDriverConfig{
|
||||
Ui: ui,
|
||||
ProjectId: b.config.ProjectId,
|
||||
Account: b.config.account,
|
||||
ImpersonateServiceAccountName: b.config.ImpersonateServiceAccount,
|
||||
VaultOauthEngineName: b.config.VaultGCPOauthEngine,
|
||||
}
|
||||
|
||||
driver, err := NewDriverGCE(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -70,10 +77,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
Debug: b.config.PackerDebug,
|
||||
},
|
||||
&StepStartTunnel{
|
||||
IAPConf: &b.config.IAPConfig,
|
||||
CommConf: &b.config.Comm,
|
||||
AccountFile: b.config.AccountFile,
|
||||
ProjectId: b.config.ProjectId,
|
||||
IAPConf: &b.config.IAPConfig,
|
||||
CommConf: &b.config.Comm,
|
||||
AccountFile: b.config.AccountFile,
|
||||
ImpersonateAccount: b.config.ImpersonateServiceAccount,
|
||||
ProjectId: b.config.ProjectId,
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Comm,
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
@@ -35,6 +34,8 @@ type Config struct {
|
||||
// run Packer on a GCE instance with a service account. Instructions for
|
||||
// creating the file or using service accounts are above.
|
||||
AccountFile string `mapstructure:"account_file" required:"false"`
|
||||
// This allows service account impersonation as per the [docs](https://cloud.google.com/iam/docs/impersonating-service-accounts).
|
||||
ImpersonateServiceAccount string `mapstructure:"impersonate_service_account" required:"false"`
|
||||
// The project ID that will be used to launch instances and store images.
|
||||
ProjectId string `mapstructure:"project_id" required:"true"`
|
||||
// Full or partial URL of the guest accelerator type. GPU accelerators can
|
||||
@@ -75,6 +76,8 @@ type Config struct {
|
||||
EnableIntegrityMonitoring bool `mapstructure:"enable_integrity_monitoring" required:"false"`
|
||||
// Whether to use an IAP proxy.
|
||||
IAPConfig `mapstructure:",squash"`
|
||||
// Skip creating the image. Useful for setting to `true` during a build test stage. Defaults to `false`.
|
||||
SkipCreateImage bool `mapstructure:"skip_create_image" required:"false"`
|
||||
// The unique name of the resulting image. Defaults to
|
||||
// `packer-{{timestamp}}`.
|
||||
ImageName string `mapstructure:"image_name" required:"false"`
|
||||
@@ -266,7 +269,7 @@ type Config struct {
|
||||
UseOSLogin bool `mapstructure:"use_os_login" required:"false"`
|
||||
// Can be set instead of account_file. If set, this builder will use
|
||||
// HashiCorp Vault to generate an Oauth token for authenticating against
|
||||
// Google's cloud. The value should be the path of the token generator
|
||||
// Google Cloud. The value should be the path of the token generator
|
||||
// within vault.
|
||||
// For information on how to configure your Vault + GCP engine to produce
|
||||
// Oauth tokens, see https://www.vaultproject.io/docs/auth/gcp
|
||||
@@ -280,7 +283,7 @@ type Config struct {
|
||||
// Example: "us-central1-a"
|
||||
Zone string `mapstructure:"zone" required:"true"`
|
||||
|
||||
account *jwt.Config
|
||||
account *ServiceAccount
|
||||
imageAlreadyExists bool
|
||||
ctx interpolate.Context
|
||||
}
|
||||
@@ -481,9 +484,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
|
||||
// Authenticating via an account file
|
||||
if c.AccountFile != "" {
|
||||
if c.VaultGCPOauthEngine != "" {
|
||||
if c.VaultGCPOauthEngine != "" && c.ImpersonateServiceAccount != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("You cannot "+
|
||||
"specify both account_file and vault_gcp_oauth_engine."))
|
||||
"specify impersonate_service_account, account_file and vault_gcp_oauth_engine at the same time"))
|
||||
}
|
||||
cfg, err := ProcessAccountFile(c.AccountFile)
|
||||
if err != nil {
|
||||
|
||||
@@ -64,6 +64,7 @@ type FlatConfig struct {
|
||||
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"`
|
||||
AccountFile *string `mapstructure:"account_file" required:"false" cty:"account_file" hcl:"account_file"`
|
||||
ImpersonateServiceAccount *string `mapstructure:"impersonate_service_account" required:"false" cty:"impersonate_service_account" hcl:"impersonate_service_account"`
|
||||
ProjectId *string `mapstructure:"project_id" required:"true" cty:"project_id" hcl:"project_id"`
|
||||
AcceleratorType *string `mapstructure:"accelerator_type" required:"false" cty:"accelerator_type" hcl:"accelerator_type"`
|
||||
AcceleratorCount *int64 `mapstructure:"accelerator_count" required:"false" cty:"accelerator_count" hcl:"accelerator_count"`
|
||||
@@ -80,6 +81,7 @@ type FlatConfig struct {
|
||||
IAPHashBang *string `mapstructure:"iap_hashbang" required:"false" cty:"iap_hashbang" hcl:"iap_hashbang"`
|
||||
IAPExt *string `mapstructure:"iap_ext" required:"false" cty:"iap_ext" hcl:"iap_ext"`
|
||||
IAPTunnelLaunchWait *int `mapstructure:"iap_tunnel_launch_wait" required:"false" cty:"iap_tunnel_launch_wait" hcl:"iap_tunnel_launch_wait"`
|
||||
SkipCreateImage *bool `mapstructure:"skip_create_image" required:"false" cty:"skip_create_image" hcl:"skip_create_image"`
|
||||
ImageName *string `mapstructure:"image_name" required:"false" cty:"image_name" hcl:"image_name"`
|
||||
ImageDescription *string `mapstructure:"image_description" required:"false" cty:"image_description" hcl:"image_description"`
|
||||
ImageEncryptionKey *FlatCustomerEncryptionKey `mapstructure:"image_encryption_key" required:"false" cty:"image_encryption_key" hcl:"image_encryption_key"`
|
||||
@@ -182,6 +184,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"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},
|
||||
"account_file": &hcldec.AttrSpec{Name: "account_file", Type: cty.String, Required: false},
|
||||
"impersonate_service_account": &hcldec.AttrSpec{Name: "impersonate_service_account", Type: cty.String, Required: false},
|
||||
"project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false},
|
||||
"accelerator_type": &hcldec.AttrSpec{Name: "accelerator_type", Type: cty.String, Required: false},
|
||||
"accelerator_count": &hcldec.AttrSpec{Name: "accelerator_count", Type: cty.Number, Required: false},
|
||||
@@ -198,6 +201,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"iap_hashbang": &hcldec.AttrSpec{Name: "iap_hashbang", Type: cty.String, Required: false},
|
||||
"iap_ext": &hcldec.AttrSpec{Name: "iap_ext", Type: cty.String, Required: false},
|
||||
"iap_tunnel_launch_wait": &hcldec.AttrSpec{Name: "iap_tunnel_launch_wait", Type: cty.Number, Required: false},
|
||||
"skip_create_image": &hcldec.AttrSpec{Name: "skip_create_image", Type: cty.Bool, Required: false},
|
||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
|
||||
"image_encryption_key": &hcldec.BlockSpec{TypeName: "image_encryption_key", Nested: hcldec.ObjectSpec((*FlatCustomerEncryptionKey)(nil).HCL2Spec())},
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -25,7 +24,6 @@ import (
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
)
|
||||
|
||||
// driverGCE is a Driver implementation that actually talks to GCE.
|
||||
@@ -37,6 +35,14 @@ type driverGCE struct {
|
||||
ui packer.Ui
|
||||
}
|
||||
|
||||
type GCEDriverConfig struct {
|
||||
Ui packer.Ui
|
||||
ProjectId string
|
||||
Account *ServiceAccount
|
||||
ImpersonateServiceAccountName string
|
||||
VaultOauthEngineName string
|
||||
}
|
||||
|
||||
var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
|
||||
|
||||
// Define a TokenSource that gets tokens from Vault
|
||||
@@ -72,31 +78,34 @@ func (ots OauthTokenSource) Token() (*oauth2.Token, error) {
|
||||
|
||||
}
|
||||
|
||||
func NewClientGCE(conf *jwt.Config, vaultOauth string) (*http.Client, error) {
|
||||
func NewClientOptionGoogle(account *ServiceAccount, vaultOauth string, impersonatesa string) (option.ClientOption, error) {
|
||||
var err error
|
||||
|
||||
var client *http.Client
|
||||
var opts option.ClientOption
|
||||
|
||||
if vaultOauth != "" {
|
||||
// Auth with Vault Oauth
|
||||
log.Printf("Using Vault to generate Oauth token.")
|
||||
ts := OauthTokenSource{vaultOauth}
|
||||
return oauth2.NewClient(context.TODO(), ts), nil
|
||||
opts = option.WithTokenSource(ts)
|
||||
|
||||
} else if conf != nil && len(conf.PrivateKey) > 0 {
|
||||
} else if impersonatesa != "" {
|
||||
opts = option.ImpersonateCredentials(impersonatesa)
|
||||
} else if account != nil && account.jwt != nil && len(account.jwt.PrivateKey) > 0 {
|
||||
// Auth with AccountFile if provided
|
||||
log.Printf("[INFO] Requesting Google token via account_file...")
|
||||
log.Printf("[INFO] -- Email: %s", conf.Email)
|
||||
log.Printf("[INFO] -- Email: %s", account.jwt.Email)
|
||||
log.Printf("[INFO] -- Scopes: %s", DriverScopes)
|
||||
log.Printf("[INFO] -- Private Key Length: %d", len(conf.PrivateKey))
|
||||
log.Printf("[INFO] -- Private Key Length: %d", len(account.jwt.PrivateKey))
|
||||
|
||||
// Initiate an http.Client. The following GET request will be
|
||||
// authorized and authenticated on the behalf of
|
||||
// your service account.
|
||||
client = conf.Client(context.TODO())
|
||||
opts = option.WithCredentialsJSON(account.jsonKey)
|
||||
} else {
|
||||
log.Printf("[INFO] Requesting Google token via GCE API Default Client Token Source...")
|
||||
client, err = google.DefaultClient(context.TODO(), DriverScopes...)
|
||||
ts, err := google.DefaultTokenSource(context.TODO(), "https://www.googleapis.com/auth/cloud-platform")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts = option.WithTokenSource(ts)
|
||||
// The DefaultClient uses the DefaultTokenSource of the google lib.
|
||||
// The DefaultTokenSource uses the "Application Default Credentials"
|
||||
// It looks for credentials in the following places, preferring the first location found:
|
||||
@@ -115,22 +124,23 @@ func NewClientGCE(conf *jwt.Config, vaultOauth string) (*http.Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config, vaultOauth string) (Driver, error) {
|
||||
client, err := NewClientGCE(conf, vaultOauth)
|
||||
func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
|
||||
opts, err := NewClientOptionGoogle(config.Account, config.VaultOauthEngineName, config.ImpersonateServiceAccountName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Instantiating GCE client...")
|
||||
service, err := compute.NewService(context.TODO(), option.WithHTTPClient(client))
|
||||
service, err := compute.NewService(context.TODO(), opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
osLoginService, err := oslogin.NewService(context.TODO(), option.WithHTTPClient(client))
|
||||
log.Printf("[INFO] Instantiating OS Login client...")
|
||||
osLoginService, err := oslogin.NewService(context.TODO(), opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -139,10 +149,10 @@ func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config, vaultOauth string) (
|
||||
service.UserAgent = useragent.String()
|
||||
|
||||
return &driverGCE{
|
||||
projectId: p,
|
||||
projectId: config.ProjectId,
|
||||
service: service,
|
||||
osLoginService: osLoginService,
|
||||
ui: ui,
|
||||
ui: config.Ui,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@ func (s *StepCreateImage) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if config.SkipCreateImage {
|
||||
ui.Say("Skipping image creation...")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if config.PackerForce && config.imageAlreadyExists {
|
||||
ui.Say("Deleting previous image...")
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ func (s *StepCreateWindowsPassword) Run(ctx context.Context, state multistep.Sta
|
||||
|
||||
email := ""
|
||||
if c.account != nil {
|
||||
email = c.account.Email
|
||||
email = c.account.jwt.Email
|
||||
}
|
||||
|
||||
data := WindowsPasswordConfig{
|
||||
|
||||
@@ -47,7 +47,7 @@ func (s *StepImportOSLoginSSHKey) Run(ctx context.Context, state multistep.State
|
||||
state.Put("ssh_key_public_sha256", hex.EncodeToString(sha256sum[:]))
|
||||
|
||||
if config.account != nil {
|
||||
s.accountEmail = config.account.Email
|
||||
s.accountEmail = config.account.jwt.Email
|
||||
}
|
||||
|
||||
if s.accountEmail == "" {
|
||||
|
||||
@@ -131,10 +131,11 @@ func (e RetryableTunnelError) Error() string {
|
||||
}
|
||||
|
||||
type StepStartTunnel struct {
|
||||
IAPConf *IAPConfig
|
||||
CommConf *communicator.Config
|
||||
AccountFile string
|
||||
ProjectId string
|
||||
IAPConf *IAPConfig
|
||||
CommConf *communicator.Config
|
||||
AccountFile string
|
||||
ImpersonateAccount string
|
||||
ProjectId string
|
||||
|
||||
tunnelDriver TunnelDriver
|
||||
}
|
||||
@@ -276,6 +277,10 @@ func (s *StepStartTunnel) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
"--zone", c.Zone, "--project", s.ProjectId,
|
||||
}
|
||||
|
||||
if s.ImpersonateAccount != "" {
|
||||
args = append(args, fmt.Sprintf("--impersonate-service-account='%s'", s.ImpersonateAccount))
|
||||
}
|
||||
|
||||
// This is the port the IAP tunnel listens on, on localhost.
|
||||
// TODO make setting LocalHostPort optional
|
||||
err = ApplyIAPTunnel(s.CommConf, s.IAPConf.IAPLocalhostPort)
|
||||
|
||||
@@ -252,14 +252,21 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
}
|
||||
}
|
||||
|
||||
hasOnlySourceImage := len(c.SourceImage) > 0 && len(c.SourceImageName) == 0 && len(c.ExternalSourceImageURL) == 0
|
||||
hasOnlySourceImageName := len(c.SourceImageName) > 0 && len(c.SourceImage) == 0 && len(c.ExternalSourceImageURL) == 0
|
||||
hasOnlyExternalSourceImageURL := len(c.ExternalSourceImageURL) > 0 && len(c.SourceImage) == 0 && len(c.SourceImageName) == 0
|
||||
|
||||
if c.SourceImage == "" && c.SourceImageName == "" && c.ExternalSourceImageURL == "" && c.SourceImageFilters.Filters.Empty() {
|
||||
errs = append(errs, errors.New("Either a source_image, a source_image_name, an external_source_image_url or source_image_filter must be specified"))
|
||||
} else if !(hasOnlySourceImage || hasOnlySourceImageName || hasOnlyExternalSourceImageURL) {
|
||||
errs = append(errs, errors.New("Only a source_image, a source_image_name or an external_source_image_url can be specified, not multiple."))
|
||||
} else {
|
||||
// Make sure we've only set one image source option
|
||||
thereCanBeOnlyOne := []bool{len(c.SourceImageName) > 0, len(c.SourceImage) > 0, len(c.ExternalSourceImageURL) > 0, !c.SourceImageFilters.Filters.Empty()}
|
||||
numSet := 0
|
||||
for _, val := range thereCanBeOnlyOne {
|
||||
if val {
|
||||
numSet += 1
|
||||
}
|
||||
}
|
||||
|
||||
if numSet > 1 {
|
||||
errs = append(errs, errors.New("Only one of the options source_image, source_image_name, external_source_image_url, or source_image_filter can be specified, not multiple."))
|
||||
}
|
||||
}
|
||||
|
||||
// if external_source_image_format is not set use qcow2 as default
|
||||
|
||||
@@ -141,6 +141,7 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c = testRunConfig()
|
||||
// test setting both ExternalSourceImageURL and SourceImageName causes an error
|
||||
c.SourceImage = ""
|
||||
c.SourceImageName = "abcd"
|
||||
@@ -149,6 +150,7 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c = testRunConfig()
|
||||
// test neither setting SourceImage, SourceImageName or ExternalSourceImageURL causes an error
|
||||
c.SourceImage = ""
|
||||
c.SourceImageName = ""
|
||||
@@ -157,6 +159,7 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c = testRunConfig()
|
||||
// test setting only ExternalSourceImageURL passes
|
||||
c.SourceImage = ""
|
||||
c.SourceImageName = ""
|
||||
@@ -174,6 +177,27 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
|
||||
if matches, _ := regexp.MatchString(p, c.SourceImageName); !matches {
|
||||
t.Fatalf("invalid format for SourceImageName: %s", c.SourceImageName)
|
||||
}
|
||||
|
||||
c = testRunConfig()
|
||||
// test setting a filter passes
|
||||
c.SourceImage = ""
|
||||
c.SourceImageName = ""
|
||||
c.ExternalSourceImageURL = ""
|
||||
c.SourceImageFilters = ImageFilter{
|
||||
Filters: ImageFilterOptions{
|
||||
Name: "Ubuntu 16.04",
|
||||
Visibility: "public",
|
||||
Owner: "1234567890",
|
||||
Tags: []string{"prod", "ready"},
|
||||
Properties: map[string]string{"os_distro": "ubuntu", "os_version": "16.04"},
|
||||
},
|
||||
MostRecent: true,
|
||||
}
|
||||
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("Should not error if everything but filter is empty: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This test case confirms that only allowed fields will be set to values
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails
|
||||
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest
|
||||
|
||||
package oci
|
||||
|
||||
@@ -35,6 +35,16 @@ type CreateVNICDetails struct {
|
||||
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 Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
@@ -69,14 +79,17 @@ type Config struct {
|
||||
CompartmentID string `mapstructure:"compartment_ocid"`
|
||||
|
||||
// Image
|
||||
BaseImageID string `mapstructure:"base_image_ocid"`
|
||||
ImageName string `mapstructure:"image_name"`
|
||||
BaseImageID string `mapstructure:"base_image_ocid"`
|
||||
BaseImageFilter ListImagesRequest `mapstructure:"base_image_filter"`
|
||||
ImageName string `mapstructure:"image_name"`
|
||||
ImageCompartmentID string `mapstructure:"image_compartment_ocid"`
|
||||
|
||||
// Instance
|
||||
InstanceName string `mapstructure:"instance_name"`
|
||||
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"`
|
||||
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
|
||||
@@ -253,6 +266,10 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||
c.CompartmentID = tenancyOCID
|
||||
}
|
||||
|
||||
if c.ImageCompartmentID == "" {
|
||||
c.ImageCompartmentID = c.CompartmentID
|
||||
}
|
||||
|
||||
if c.Shape == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'shape' must be specified"))
|
||||
@@ -263,9 +280,17 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||
errs, errors.New("'subnet_ocid' must be specified"))
|
||||
}
|
||||
|
||||
if c.BaseImageID == "" {
|
||||
if (c.BaseImageID == "") && (c.BaseImageFilter == ListImagesRequest{}) {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'base_image_ocid' must be specified"))
|
||||
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.
|
||||
@@ -326,6 +351,15 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = packer.MultiErrorAppend(
|
||||
errs, errors.New("'disk_size' must be between 50 and 16384 GBs"))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest"; DO NOT EDIT.
|
||||
package oci
|
||||
|
||||
import (
|
||||
@@ -76,11 +76,14 @@ type FlatConfig struct {
|
||||
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"`
|
||||
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"`
|
||||
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"`
|
||||
@@ -169,11 +172,14 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
"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},
|
||||
"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},
|
||||
"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},
|
||||
@@ -223,3 +229,36 @@ func (*FlatCreateVNICDetails) HCL2Spec() map[string]hcldec.Spec {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -44,7 +44,8 @@ func testConfig(accessConfFile *os.File) map[string]interface{} {
|
||||
"create_vnic_details": map[string]interface{}{
|
||||
"nsg_ids": []string{"ocd1..."},
|
||||
},
|
||||
"shape": "VM.Standard1.1",
|
||||
"shape": "VM.Standard1.1",
|
||||
"disk_size": 60,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +88,36 @@ func TestConfig(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
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("NoAccessConfig", func(t *testing.T) {
|
||||
raw := testConfig(cfgFile)
|
||||
delete(raw, "access_cfg_file")
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
core "github.com/oracle/oci-go-sdk/core"
|
||||
@@ -51,23 +52,7 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
||||
metadata["user_data"] = d.cfg.UserData
|
||||
}
|
||||
|
||||
instanceDetails := core.LaunchInstanceDetails{
|
||||
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
||||
CompartmentId: &d.cfg.CompartmentID,
|
||||
DefinedTags: d.cfg.InstanceDefinedTags,
|
||||
FreeformTags: d.cfg.InstanceTags,
|
||||
ImageId: &d.cfg.BaseImageID,
|
||||
Shape: &d.cfg.Shape,
|
||||
SubnetId: &d.cfg.SubnetID,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
// When empty, the default display name is used.
|
||||
if d.cfg.InstanceName != "" {
|
||||
instanceDetails.DisplayName = &d.cfg.InstanceName
|
||||
}
|
||||
|
||||
// Pass VNIC details, if specified, to the instance
|
||||
// Create VNIC details for instance
|
||||
CreateVnicDetails := core.CreateVnicDetails{
|
||||
AssignPublicIp: d.cfg.CreateVnicDetails.AssignPublicIp,
|
||||
DisplayName: d.cfg.CreateVnicDetails.DisplayName,
|
||||
@@ -80,7 +65,68 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
||||
FreeformTags: d.cfg.CreateVnicDetails.FreeformTags,
|
||||
}
|
||||
|
||||
instanceDetails.CreateVnicDetails = &CreateVnicDetails
|
||||
// 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",
|
||||
})
|
||||
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,
|
||||
SubnetId: &d.cfg.SubnetID,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: instanceDetails})
|
||||
|
||||
@@ -94,7 +140,7 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
||||
// 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.CompartmentID,
|
||||
CompartmentId: &d.cfg.ImageCompartmentID,
|
||||
InstanceId: &id,
|
||||
DisplayName: &d.cfg.ImageName,
|
||||
FreeformTags: d.cfg.Tags,
|
||||
|
||||
+11
-22
@@ -9,9 +9,7 @@ package bsu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
osccommon "github.com/hashicorp/packer/builder/osc/common"
|
||||
@@ -21,7 +19,6 @@ import (
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
)
|
||||
|
||||
// The unique ID for this builder
|
||||
@@ -86,24 +83,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
clientConfig, err := b.config.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
oapiconn := oapi.NewClient(clientConfig, skipClient)
|
||||
oscConn := b.config.NewOSCClient()
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("oapi", oapiconn)
|
||||
state.Put("clientConfig", clientConfig)
|
||||
state.Put("osc", oscConn)
|
||||
state.Put("accessConfig", &b.config.AccessConfig)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
@@ -129,7 +115,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
&osccommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.RunConfig.Comm,
|
||||
DebugKeyPath: fmt.Sprintf("oapi_%s", b.config.PackerBuildName),
|
||||
DebugKeyPath: fmt.Sprintf("osc_%s", b.config.PackerBuildName),
|
||||
},
|
||||
&osccommon.StepPublicIp{
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
@@ -161,6 +147,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
VolumeTags: b.config.VolumeRunTags,
|
||||
RawRegion: b.config.RawRegion,
|
||||
},
|
||||
&osccommon.StepGetPassword{
|
||||
Debug: b.config.PackerDebug,
|
||||
@@ -170,8 +157,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: osccommon.SSHHost(
|
||||
oapiconn,
|
||||
Host: osccommon.OscSSHHost(
|
||||
oscConn.VmApi,
|
||||
b.config.SSHInterface),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
@@ -190,10 +177,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
OMIName: b.config.OMIName,
|
||||
Regions: b.config.OMIRegions,
|
||||
},
|
||||
&stepCreateOMI{},
|
||||
&stepCreateOMI{
|
||||
RawRegion: b.config.RawRegion,
|
||||
},
|
||||
&osccommon.StepUpdateOMIAttributes{
|
||||
AccountIds: b.config.OMIAccountIDs,
|
||||
SnapshotAccountIds: b.config.SnapshotAccountIDs,
|
||||
RawRegion: b.config.RawRegion,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&osccommon.StepCreateTags{
|
||||
@@ -217,7 +207,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
artifact := &osccommon.Artifact{
|
||||
Omis: omis.(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Config: clientConfig,
|
||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,9 @@
|
||||
package bsu
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/builder/osc/common"
|
||||
builderT "github.com/hashicorp/packer/helper/builder/testing"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
@@ -23,31 +19,17 @@ func TestBuilderAcc_basic(t *testing.T) {
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
}
|
||||
|
||||
func testOAPIConn() (*oapi.Client, error) {
|
||||
access := &common.AccessConfig{RawRegion: "us-east-1"}
|
||||
clientConfig, err := access.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
return oapi.NewClient(clientConfig, skipClient), nil
|
||||
}
|
||||
|
||||
const testBuilderAccBasic = `
|
||||
{
|
||||
"builders": [{
|
||||
"type": "test",
|
||||
"region": "eu-west-2",
|
||||
"vm_type": "t2.micro",
|
||||
"source_omi": "ami-65efcc11",
|
||||
"source_omi": "ami-abe953fa",
|
||||
"ssh_username": "outscale",
|
||||
"omi_name": "packer-test {{timestamp}}"
|
||||
"omi_name": "packer-test",
|
||||
"associate_public_ip_address": true,
|
||||
"force_deregister": true
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
||||
@@ -5,62 +5,68 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
osccommon "github.com/hashicorp/packer/builder/osc/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
type stepCreateOMI struct {
|
||||
image *oapi.Image
|
||||
image *osc.Image
|
||||
RawRegion string
|
||||
}
|
||||
|
||||
func (s *stepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
vm := state.Get("vm").(oapi.Vm)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
vm := state.Get("vm").(osc.Vm)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Create the image
|
||||
omiName := config.OMIName
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating OMI %s from vm %s", omiName, vm.VmId))
|
||||
createOpts := oapi.CreateImageRequest{
|
||||
createOpts := osc.CreateImageRequest{
|
||||
VmId: vm.VmId,
|
||||
ImageName: omiName,
|
||||
BlockDeviceMappings: config.BlockDevices.BuildOMIDevices(),
|
||||
BlockDeviceMappings: config.BlockDevices.BuildOscOMIDevices(),
|
||||
}
|
||||
|
||||
resp, err := oapiconn.POST_CreateImage(createOpts)
|
||||
if err != nil || resp.OK == nil {
|
||||
resp, _, err := oscconn.ImageApi.CreateImage(context.Background(), &osc.CreateImageOpts{
|
||||
CreateImageRequest: optional.NewInterface(createOpts),
|
||||
})
|
||||
if err != nil || resp.Image.ImageId == "" {
|
||||
err := fmt.Errorf("Error creating OMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
image := resp.OK.Image
|
||||
image := resp.Image
|
||||
|
||||
// Set the OMI ID in the state
|
||||
ui.Message(fmt.Sprintf("OMI: %s", image.ImageId))
|
||||
omis := make(map[string]string)
|
||||
omis[oapiconn.GetConfig().Region] = image.ImageId
|
||||
omis[s.RawRegion] = image.ImageId
|
||||
state.Put("omis", omis)
|
||||
|
||||
// Wait for the image to become ready
|
||||
ui.Say("Waiting for OMI to become ready...")
|
||||
if err := osccommon.WaitUntilImageAvailable(oapiconn, image.ImageId); err != nil {
|
||||
if err := osccommon.WaitUntilOscImageAvailable(oscconn, image.ImageId); err != nil {
|
||||
log.Printf("Error waiting for OMI: %s", err)
|
||||
imagesResp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
|
||||
Filters: oapi.FiltersImage{
|
||||
ImageIds: []string{image.ImageId},
|
||||
},
|
||||
imagesResp, _, err := oscconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
|
||||
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
|
||||
Filters: osc.FiltersImage{
|
||||
ImageIds: []string{image.ImageId},
|
||||
},
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Unable to determine reason waiting for OMI failed: %s", err)
|
||||
err = fmt.Errorf("Unknown error waiting for OMI.")
|
||||
err = fmt.Errorf("Unknown error waiting for OMI")
|
||||
} else {
|
||||
stateReason := imagesResp.OK.Images[0].StateComment
|
||||
stateReason := imagesResp.Images[0].StateComment
|
||||
err = fmt.Errorf("Error waiting for OMI. Reason: %s", stateReason)
|
||||
}
|
||||
|
||||
@@ -69,10 +75,12 @@ func (s *stepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
imagesResp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
|
||||
Filters: oapi.FiltersImage{
|
||||
ImageIds: []string{image.ImageId},
|
||||
},
|
||||
imagesResp, _, err := oscconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
|
||||
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
|
||||
Filters: osc.FiltersImage{
|
||||
ImageIds: []string{image.ImageId},
|
||||
},
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error searching for OMI: %s", err)
|
||||
@@ -80,12 +88,12 @@ func (s *stepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.image = &imagesResp.OK.Images[0]
|
||||
s.image = &imagesResp.Images[0]
|
||||
|
||||
snapshots := make(map[string][]string)
|
||||
for _, blockDeviceMapping := range imagesResp.OK.Images[0].BlockDeviceMappings {
|
||||
for _, blockDeviceMapping := range imagesResp.Images[0].BlockDeviceMappings {
|
||||
if blockDeviceMapping.Bsu.SnapshotId != "" {
|
||||
snapshots[oapiconn.GetConfig().Region] = append(snapshots[oapiconn.GetConfig().Region], blockDeviceMapping.Bsu.SnapshotId)
|
||||
snapshots[s.RawRegion] = append(snapshots[s.RawRegion], blockDeviceMapping.Bsu.SnapshotId)
|
||||
}
|
||||
}
|
||||
state.Put("snapshots", snapshots)
|
||||
@@ -104,12 +112,14 @@ func (s *stepCreateOMI) Cleanup(state multistep.StateBag) {
|
||||
return
|
||||
}
|
||||
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deregistering the OMI because cancellation or error...")
|
||||
DeleteOpts := oapi.DeleteImageRequest{ImageId: s.image.ImageId}
|
||||
if _, err := oapiconn.POST_DeleteImage(DeleteOpts); err != nil {
|
||||
DeleteOpts := osc.DeleteImageRequest{ImageId: s.image.ImageId}
|
||||
if _, _, err := oscconn.ImageApi.DeleteImage(context.Background(), &osc.DeleteImageOpts{
|
||||
DeleteImageRequest: optional.NewInterface(DeleteOpts),
|
||||
}); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error Deleting OMI, may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,10 +6,8 @@ package bsusurrogate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
osccommon "github.com/hashicorp/packer/builder/osc/common"
|
||||
@@ -19,7 +17,6 @@ import (
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
)
|
||||
|
||||
const BuilderId = "oapi.outscale.bsusurrogate"
|
||||
@@ -104,31 +101,20 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
clientConfig, err := b.config.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
oapiconn := oapi.NewClient(clientConfig, skipClient)
|
||||
oscConn := b.config.NewOSCClient()
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("oapi", oapiconn)
|
||||
state.Put("clientConfig", clientConfig)
|
||||
state.Put("osc", oscConn)
|
||||
state.Put("accessConfig", &b.config.AccessConfig)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
//VMStep
|
||||
|
||||
omiDevices := b.config.BuildOMIDevices()
|
||||
launchDevices := b.config.BuildLaunchDevices()
|
||||
omiDevices := b.config.BuildOscOMIDevices()
|
||||
launchOSCDevices := b.config.BuildOSCLaunchDevices()
|
||||
|
||||
steps := []multistep.Step{
|
||||
&osccommon.StepPreValidate{
|
||||
@@ -193,8 +179,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: osccommon.SSHHost(
|
||||
oapiconn,
|
||||
Host: osccommon.OscSSHHost(
|
||||
oscConn.VmApi,
|
||||
b.config.SSHInterface),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
@@ -207,7 +193,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
DisableStopVm: b.config.DisableStopVm,
|
||||
},
|
||||
&StepSnapshotVolumes{
|
||||
LaunchDevices: launchDevices,
|
||||
LaunchDevices: launchOSCDevices,
|
||||
},
|
||||
&osccommon.StepDeregisterOMI{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
@@ -219,7 +205,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
&StepRegisterOMI{
|
||||
RootDevice: b.config.RootDevice,
|
||||
OMIDevices: omiDevices,
|
||||
LaunchDevices: launchDevices,
|
||||
LaunchDevices: launchOSCDevices,
|
||||
RawRegion: b.config.RawRegion,
|
||||
},
|
||||
&osccommon.StepUpdateOMIAttributes{
|
||||
AccountIds: b.config.OMIAccountIDs,
|
||||
@@ -247,7 +234,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
artifact := &osccommon.Artifact{
|
||||
Omis: omis.(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Config: clientConfig,
|
||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ const testBuilderAccBasic = `
|
||||
"type": "test",
|
||||
"region": "eu-west-2",
|
||||
"vm_type": "t2.micro",
|
||||
"source_omi": "ami-65efcc11",
|
||||
"source_omi": "ami-abe953fa",
|
||||
"ssh_username": "outscale",
|
||||
"omi_name": "packer-test {{timestamp}}",
|
||||
"omi_virtualization_type": "hvm",
|
||||
|
||||
@@ -4,23 +4,25 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
osccommon "github.com/hashicorp/packer/builder/osc/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// StepRegisterOMI creates the OMI.
|
||||
type StepRegisterOMI struct {
|
||||
RootDevice RootBlockDevice
|
||||
OMIDevices []oapi.BlockDeviceMappingImage
|
||||
LaunchDevices []oapi.BlockDeviceMappingVmCreation
|
||||
image *oapi.Image
|
||||
OMIDevices []osc.BlockDeviceMappingImage
|
||||
LaunchDevices []osc.BlockDeviceMappingVmCreation
|
||||
image *osc.Image
|
||||
RawRegion string
|
||||
}
|
||||
|
||||
func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
snapshotIds := state.Get("snapshot_ids").(map[string]string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
@@ -28,14 +30,16 @@ func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
|
||||
blockDevices := s.combineDevices(snapshotIds)
|
||||
|
||||
registerOpts := oapi.CreateImageRequest{
|
||||
registerOpts := osc.CreateImageRequest{
|
||||
ImageName: config.OMIName,
|
||||
Architecture: "x86_64",
|
||||
RootDeviceName: s.RootDevice.DeviceName,
|
||||
BlockDeviceMappings: blockDevices,
|
||||
}
|
||||
|
||||
registerResp, err := oapiconn.POST_CreateImage(registerOpts)
|
||||
registerResp, _, err := oscconn.ImageApi.CreateImage(context.Background(), &osc.CreateImageOpts{
|
||||
CreateImageRequest: optional.NewInterface(registerOpts),
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error registering OMI: %s", err))
|
||||
ui.Error(state.Get("error").(error).Error())
|
||||
@@ -43,24 +47,26 @@ func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
}
|
||||
|
||||
// Set the OMI ID in the state
|
||||
ui.Say(fmt.Sprintf("OMI: %s", registerResp.OK.Image.ImageId))
|
||||
ui.Say(fmt.Sprintf("OMI: %s", registerResp.Image.ImageId))
|
||||
omis := make(map[string]string)
|
||||
omis[oapiconn.GetConfig().Region] = registerResp.OK.Image.ImageId
|
||||
omis[s.RawRegion] = registerResp.Image.ImageId
|
||||
state.Put("omis", omis)
|
||||
|
||||
// Wait for the image to become ready
|
||||
ui.Say("Waiting for OMI to become ready...")
|
||||
if err := osccommon.WaitUntilImageAvailable(oapiconn, registerResp.OK.Image.ImageId); err != nil {
|
||||
if err := osccommon.WaitUntilOscImageAvailable(oscconn, registerResp.Image.ImageId); err != nil {
|
||||
err := fmt.Errorf("Error waiting for OMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
imagesResp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
|
||||
Filters: oapi.FiltersImage{
|
||||
ImageIds: []string{registerResp.OK.Image.ImageId},
|
||||
},
|
||||
imagesResp, _, err := oscconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
|
||||
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
|
||||
Filters: osc.FiltersImage{
|
||||
ImageIds: []string{registerResp.Image.ImageId},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -69,12 +75,12 @@ func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.image = &imagesResp.OK.Images[0]
|
||||
s.image = &imagesResp.Images[0]
|
||||
|
||||
snapshots := make(map[string][]string)
|
||||
for _, blockDeviceMapping := range imagesResp.OK.Images[0].BlockDeviceMappings {
|
||||
for _, blockDeviceMapping := range imagesResp.Images[0].BlockDeviceMappings {
|
||||
if blockDeviceMapping.Bsu.SnapshotId != "" {
|
||||
snapshots[oapiconn.GetConfig().Region] = append(snapshots[oapiconn.GetConfig().Region], blockDeviceMapping.Bsu.SnapshotId)
|
||||
snapshots[s.RawRegion] = append(snapshots[s.RawRegion], blockDeviceMapping.Bsu.SnapshotId)
|
||||
}
|
||||
}
|
||||
state.Put("snapshots", snapshots)
|
||||
@@ -93,19 +99,23 @@ func (s *StepRegisterOMI) Cleanup(state multistep.StateBag) {
|
||||
return
|
||||
}
|
||||
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deregistering the OMI because cancellation or error...")
|
||||
deregisterOpts := oapi.DeleteImageRequest{ImageId: s.image.ImageId}
|
||||
if _, err := oapiconn.POST_DeleteImage(deregisterOpts); err != nil {
|
||||
deregisterOpts := osc.DeleteImageRequest{ImageId: s.image.ImageId}
|
||||
_, _, err := oscconn.ImageApi.DeleteImage(context.Background(), &osc.DeleteImageOpts{
|
||||
DeleteImageRequest: optional.NewInterface(deregisterOpts),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deregistering OMI, may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepRegisterOMI) combineDevices(snapshotIds map[string]string) []oapi.BlockDeviceMappingImage {
|
||||
devices := map[string]oapi.BlockDeviceMappingImage{}
|
||||
func (s *StepRegisterOMI) combineDevices(snapshotIds map[string]string) []osc.BlockDeviceMappingImage {
|
||||
devices := map[string]osc.BlockDeviceMappingImage{}
|
||||
|
||||
for _, device := range s.OMIDevices {
|
||||
devices[device.DeviceName] = device
|
||||
@@ -125,18 +135,18 @@ func (s *StepRegisterOMI) combineDevices(snapshotIds map[string]string) []oapi.B
|
||||
devices[device.DeviceName] = copyToDeviceMappingImage(device)
|
||||
}
|
||||
|
||||
blockDevices := []oapi.BlockDeviceMappingImage{}
|
||||
blockDevices := []osc.BlockDeviceMappingImage{}
|
||||
for _, device := range devices {
|
||||
blockDevices = append(blockDevices, device)
|
||||
}
|
||||
return blockDevices
|
||||
}
|
||||
|
||||
func copyToDeviceMappingImage(device oapi.BlockDeviceMappingVmCreation) oapi.BlockDeviceMappingImage {
|
||||
deviceImage := oapi.BlockDeviceMappingImage{
|
||||
func copyToDeviceMappingImage(device osc.BlockDeviceMappingVmCreation) osc.BlockDeviceMappingImage {
|
||||
deviceImage := osc.BlockDeviceMappingImage{
|
||||
DeviceName: device.DeviceName,
|
||||
VirtualDeviceName: device.VirtualDeviceName,
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
DeleteOnVmDeletion: device.Bsu.DeleteOnVmDeletion,
|
||||
Iops: device.Bsu.Iops,
|
||||
SnapshotId: device.Bsu.SnapshotId,
|
||||
|
||||
@@ -6,11 +6,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
osccommon "github.com/hashicorp/packer/builder/osc/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// StepSnapshotVolumes creates snapshots of the created volumes.
|
||||
@@ -18,14 +19,14 @@ import (
|
||||
// Produces:
|
||||
// snapshot_ids map[string]string - IDs of the created snapshots
|
||||
type StepSnapshotVolumes struct {
|
||||
LaunchDevices []oapi.BlockDeviceMappingVmCreation
|
||||
LaunchDevices []osc.BlockDeviceMappingVmCreation
|
||||
snapshotIds map[string]string
|
||||
}
|
||||
|
||||
func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName string, state multistep.StateBag) error {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vm := state.Get("vm").(oapi.Vm)
|
||||
vm := state.Get("vm").(osc.Vm)
|
||||
|
||||
var volumeId string
|
||||
for _, volume := range vm.BlockDeviceMappings {
|
||||
@@ -40,19 +41,21 @@ func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName str
|
||||
ui.Say(fmt.Sprintf("Creating snapshot of EBS Volume %s...", volumeId))
|
||||
description := fmt.Sprintf("Packer: %s", time.Now().String())
|
||||
|
||||
createSnapResp, err := oapiconn.POST_CreateSnapshot(oapi.CreateSnapshotRequest{
|
||||
VolumeId: volumeId,
|
||||
Description: description,
|
||||
createSnapResp, _, err := oscconn.SnapshotApi.CreateSnapshot(context.Background(), &osc.CreateSnapshotOpts{
|
||||
CreateSnapshotRequest: optional.NewInterface(osc.CreateSnapshotRequest{
|
||||
VolumeId: volumeId,
|
||||
Description: description,
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the snapshot ID so we can delete it later
|
||||
s.snapshotIds[deviceName] = createSnapResp.OK.Snapshot.SnapshotId
|
||||
s.snapshotIds[deviceName] = createSnapResp.Snapshot.SnapshotId
|
||||
|
||||
// Wait for snapshot to be created
|
||||
err = osccommon.WaitUntilSnapshotCompleted(oapiconn, createSnapResp.OK.Snapshot.SnapshotId)
|
||||
err = osccommon.WaitUntilOscSnapshotCompleted(oscconn, createSnapResp.Snapshot.SnapshotId)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -65,7 +68,7 @@ func (s *StepSnapshotVolumes) Run(ctx context.Context, state multistep.StateBag)
|
||||
var errs *multierror.Error
|
||||
for _, device := range s.LaunchDevices {
|
||||
wg.Add(1)
|
||||
go func(device oapi.BlockDeviceMappingVmCreation) {
|
||||
go func(device osc.BlockDeviceMappingVmCreation) {
|
||||
defer wg.Done()
|
||||
if err := s.snapshotVolume(ctx, device.DeviceName, state); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
@@ -94,11 +97,13 @@ func (s *StepSnapshotVolumes) Cleanup(state multistep.StateBag) {
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
if cancelled || halted {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
ui.Say("Removing snapshots since we cancelled or halted...")
|
||||
for _, snapshotId := range s.snapshotIds {
|
||||
_, err := oapiconn.POST_DeleteSnapshot(oapi.DeleteSnapshotRequest{SnapshotId: snapshotId})
|
||||
for _, snapshotID := range s.snapshotIds {
|
||||
_, _, err := oscconn.SnapshotApi.DeleteSnapshot(context.Background(), &osc.DeleteSnapshotOpts{
|
||||
DeleteSnapshotRequest: optional.NewInterface(osc.DeleteSnapshotRequest{SnapshotId: snapshotID}),
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error: %s", err))
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package bsuvolume
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// map of region to list of volume IDs
|
||||
@@ -22,7 +24,7 @@ type Artifact struct {
|
||||
BuilderIdValue string
|
||||
|
||||
// Client connection for performing API stuff.
|
||||
Conn *oapi.Client
|
||||
Conn *osc.APIClient
|
||||
|
||||
// StateData should store data such as GeneratedData
|
||||
// to be shared with post-processors
|
||||
@@ -70,10 +72,12 @@ func (a *Artifact) Destroy() error {
|
||||
for _, volumeID := range volumeIDs {
|
||||
log.Printf("Deregistering Volume ID (%s) from region (%s)", volumeID, region)
|
||||
|
||||
input := oapi.DeleteVolumeRequest{
|
||||
input := osc.DeleteVolumeRequest{
|
||||
VolumeId: volumeID,
|
||||
}
|
||||
if _, err := a.Conn.POST_DeleteVolume(input); err != nil {
|
||||
if _, _, err := a.Conn.VolumeApi.DeleteVolume(context.Background(), &osc.DeleteVolumeOpts{
|
||||
DeleteVolumeRequest: optional.NewInterface(optional.NewInterface(input)),
|
||||
}); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,8 @@ package bsuvolume
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
osccommon "github.com/hashicorp/packer/builder/osc/common"
|
||||
@@ -19,7 +17,6 @@ import (
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
)
|
||||
|
||||
const BuilderId = "oapi.outscale.bsuvolume"
|
||||
@@ -89,22 +86,22 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
clientConfig, err := b.config.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// clientConfig, err := b.config.Config()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
// skipClient := &http.Client{
|
||||
// Transport: &http.Transport{
|
||||
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
// },
|
||||
// }
|
||||
|
||||
oapiconn := oapi.NewClient(clientConfig, skipClient)
|
||||
oscConn := b.config.NewOSCClient()
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("oapi", oapiconn)
|
||||
state.Put("osc", oscConn)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
@@ -117,7 +114,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
Debug: b.config.PackerDebug,
|
||||
BsuOptimized: b.config.BsuOptimized,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
ExpectedRootDevice: "ebs",
|
||||
ExpectedRootDevice: "bsu",
|
||||
IamVmProfile: b.config.IamVmProfile,
|
||||
VmInitiatedShutdownBehavior: b.config.VmInitiatedShutdownBehavior,
|
||||
VmType: b.config.VmType,
|
||||
@@ -170,8 +167,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: osccommon.SSHHost(
|
||||
oapiconn,
|
||||
Host: osccommon.OscSSHHost(
|
||||
oscConn.VmApi,
|
||||
b.config.SSHInterface),
|
||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
@@ -198,7 +195,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
artifact := &Artifact{
|
||||
Volumes: state.Get("bsuvolumes").(BsuVolumes),
|
||||
BuilderIdValue: BuilderId,
|
||||
Conn: oapiconn,
|
||||
Conn: oscConn,
|
||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Created Volumes: %s", artifact))
|
||||
|
||||
@@ -2,13 +2,9 @@
|
||||
package bsuvolume
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/builder/osc/common"
|
||||
builderT "github.com/hashicorp/packer/helper/builder/testing"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
@@ -23,22 +19,6 @@ func TestBuilderAcc_basic(t *testing.T) {
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
}
|
||||
|
||||
func testOAPIConn() (*oapi.Client, error) {
|
||||
access := &common.AccessConfig{RawRegion: "us-east-1"}
|
||||
clientConfig, err := access.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
return oapi.NewClient(clientConfig, skipClient), nil
|
||||
}
|
||||
|
||||
const testBuilderAccBasic = `
|
||||
{
|
||||
"builders": [
|
||||
@@ -46,7 +26,7 @@ const testBuilderAccBasic = `
|
||||
"type": "test",
|
||||
"region": "eu-west-2",
|
||||
"vm_type": "t2.micro",
|
||||
"source_omi": "ami-65efcc11",
|
||||
"source_omi": "ami-abe953fa",
|
||||
"ssh_username": "outscale",
|
||||
"bsu_volumes": [
|
||||
{
|
||||
|
||||
@@ -4,28 +4,30 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
type stepTagBSUVolumes struct {
|
||||
VolumeMapping []BlockDevice
|
||||
RawRegion string
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (s *stepTagBSUVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
vm := state.Get("vm").(oapi.Vm)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
vm := state.Get("vm").(osc.Vm)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
volumes := make(BsuVolumes)
|
||||
for _, instanceBlockDevices := range vm.BlockDeviceMappings {
|
||||
for _, configVolumeMapping := range s.VolumeMapping {
|
||||
if configVolumeMapping.DeviceName == instanceBlockDevices.DeviceName {
|
||||
volumes[oapiconn.GetConfig().Region] = append(
|
||||
volumes[oapiconn.GetConfig().Region],
|
||||
volumes[s.RawRegion] = append(
|
||||
volumes[s.RawRegion],
|
||||
instanceBlockDevices.Bsu.VolumeId)
|
||||
}
|
||||
}
|
||||
@@ -35,14 +37,14 @@ func (s *stepTagBSUVolumes) Run(_ context.Context, state multistep.StateBag) mul
|
||||
if len(s.VolumeMapping) > 0 {
|
||||
ui.Say("Tagging BSU volumes...")
|
||||
|
||||
toTag := map[string][]oapi.ResourceTag{}
|
||||
toTag := map[string][]osc.ResourceTag{}
|
||||
for _, mapping := range s.VolumeMapping {
|
||||
if len(mapping.Tags) == 0 {
|
||||
ui.Say(fmt.Sprintf("No tags specified for volume on %s...", mapping.DeviceName))
|
||||
continue
|
||||
}
|
||||
|
||||
tags, err := mapping.Tags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
|
||||
tags, err := mapping.Tags.OSCTags(s.Ctx, s.RawRegion, state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging device %s with %s", mapping.DeviceName, err)
|
||||
state.Put("error", err)
|
||||
@@ -59,9 +61,11 @@ func (s *stepTagBSUVolumes) Run(_ context.Context, state multistep.StateBag) mul
|
||||
}
|
||||
|
||||
for volumeId, tags := range toTag {
|
||||
_, err := oapiconn.POST_CreateTags(oapi.CreateTagsRequest{
|
||||
ResourceIds: []string{volumeId},
|
||||
Tags: tags,
|
||||
_, _, err := oscconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
|
||||
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
|
||||
ResourceIds: []string{volumeId},
|
||||
Tags: tags,
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging BSU Volume %s on %s: %s", volumeId, vm.VmId, err)
|
||||
|
||||
@@ -8,9 +8,7 @@ package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
@@ -20,7 +18,6 @@ import (
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
)
|
||||
|
||||
// The unique ID for this builder
|
||||
@@ -193,19 +190,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
return nil, errors.New("The outscale-chroot builder only works on Linux environments.")
|
||||
}
|
||||
|
||||
clientConfig, err := b.config.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
oapiconn := oapi.NewClient(clientConfig, skipClient)
|
||||
|
||||
oscConn := b.config.NewOSCClient()
|
||||
wrappedCommand := func(command string) (string, error) {
|
||||
ctx := b.config.ctx
|
||||
ctx.Data = &wrappedCommandTemplate{Command: command}
|
||||
@@ -215,8 +200,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("oapi", oapiconn)
|
||||
state.Put("clientConfig", clientConfig)
|
||||
state.Put("osc", oscConn)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
|
||||
@@ -266,7 +250,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
&StepCopyFiles{},
|
||||
&StepChrootProvision{},
|
||||
&StepEarlyCleanup{},
|
||||
&StepSnapshot{},
|
||||
&StepSnapshot{
|
||||
RawRegion: b.config.RawRegion,
|
||||
},
|
||||
&osccommon.StepDeregisterOMI{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
ForceDeregister: b.config.OMIForceDeregister,
|
||||
@@ -276,6 +262,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
},
|
||||
&StepCreateOMI{
|
||||
RootVolumeSize: b.config.RootVolumeSize,
|
||||
RawRegion: b.config.RawRegion,
|
||||
},
|
||||
&osccommon.StepUpdateOMIAttributes{
|
||||
AccountIds: b.config.OMIAccountIDs,
|
||||
@@ -307,7 +294,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
artifact := &osccommon.Artifact{
|
||||
Omis: state.Get("omis").(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Config: clientConfig,
|
||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||
}
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@ import (
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// StepCheckRootDevice makes sure the root device on the OMI is BSU-backed.
|
||||
type StepCheckRootDevice struct{}
|
||||
|
||||
func (s *StepCheckRootDevice) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
image := state.Get("source_image").(oapi.Image)
|
||||
image := state.Get("source_image").(osc.Image)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Checking the root device on source OMI...")
|
||||
|
||||
@@ -4,42 +4,44 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
osccommon "github.com/hashicorp/packer/builder/osc/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// StepCreateOMI creates the OMI.
|
||||
type StepCreateOMI struct {
|
||||
RootVolumeSize int64
|
||||
RawRegion string
|
||||
}
|
||||
|
||||
func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
osconn := state.Get("osc").(*osc.APIClient)
|
||||
snapshotId := state.Get("snapshot_id").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Creating the OMI...")
|
||||
|
||||
var (
|
||||
registerOpts oapi.CreateImageRequest
|
||||
mappings []oapi.BlockDeviceMappingImage
|
||||
image oapi.Image
|
||||
registerOpts osc.CreateImageRequest
|
||||
mappings []osc.BlockDeviceMappingImage
|
||||
image osc.Image
|
||||
rootDeviceName string
|
||||
)
|
||||
|
||||
if config.FromScratch {
|
||||
mappings = config.OMIBlockDevices.BuildOMIDevices()
|
||||
mappings = config.OMIBlockDevices.BuildOscOMIDevices()
|
||||
rootDeviceName = config.RootDeviceName
|
||||
} else {
|
||||
image = state.Get("source_image").(oapi.Image)
|
||||
image = state.Get("source_image").(osc.Image)
|
||||
mappings = image.BlockDeviceMappings
|
||||
rootDeviceName = image.RootDeviceName
|
||||
}
|
||||
|
||||
newMappings := make([]oapi.BlockDeviceMappingImage, len(mappings))
|
||||
newMappings := make([]osc.BlockDeviceMappingImage, len(mappings))
|
||||
for i, device := range mappings {
|
||||
newDevice := device
|
||||
|
||||
@@ -48,14 +50,14 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
|
||||
newDevice.Bsu.VolumeSize = gibSize
|
||||
|
||||
if newDevice.DeviceName == rootDeviceName {
|
||||
if newDevice.Bsu != (oapi.BsuToCreate{}) {
|
||||
if newDevice.Bsu != (osc.BsuToCreate{}) {
|
||||
newDevice.Bsu.SnapshotId = snapshotId
|
||||
} else {
|
||||
newDevice.Bsu = oapi.BsuToCreate{SnapshotId: snapshotId}
|
||||
newDevice.Bsu = osc.BsuToCreate{SnapshotId: snapshotId}
|
||||
}
|
||||
|
||||
if config.FromScratch || s.RootVolumeSize > newDevice.Bsu.VolumeSize {
|
||||
newDevice.Bsu.VolumeSize = s.RootVolumeSize
|
||||
if config.FromScratch || int32(s.RootVolumeSize) > newDevice.Bsu.VolumeSize {
|
||||
newDevice.Bsu.VolumeSize = int32(s.RootVolumeSize)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +65,7 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
|
||||
}
|
||||
|
||||
if config.FromScratch {
|
||||
registerOpts = oapi.CreateImageRequest{
|
||||
registerOpts = osc.CreateImageRequest{
|
||||
ImageName: config.OMIName,
|
||||
Architecture: "x86_64",
|
||||
RootDeviceName: rootDeviceName,
|
||||
@@ -73,23 +75,25 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
|
||||
registerOpts = buildRegisterOpts(config, image, newMappings)
|
||||
}
|
||||
|
||||
registerResp, err := oapiconn.POST_CreateImage(registerOpts)
|
||||
registerResp, _, err := osconn.ImageApi.CreateImage(context.Background(), &osc.CreateImageOpts{
|
||||
CreateImageRequest: optional.NewInterface(registerOpts),
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error registering OMI: %s", err))
|
||||
ui.Error(state.Get("error").(error).Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
imageID := registerResp.OK.Image.ImageId
|
||||
imageID := registerResp.Image.ImageId
|
||||
|
||||
// Set the OMI ID in the state
|
||||
ui.Say(fmt.Sprintf("OMI: %s", imageID))
|
||||
omis := make(map[string]string)
|
||||
omis[oapiconn.GetConfig().Region] = imageID
|
||||
omis[s.RawRegion] = imageID
|
||||
state.Put("omis", omis)
|
||||
|
||||
ui.Say("Waiting for OMI to become ready...")
|
||||
if err := osccommon.WaitUntilImageAvailable(oapiconn, imageID); err != nil {
|
||||
if err := osccommon.WaitUntilOscImageAvailable(osconn, imageID); err != nil {
|
||||
err := fmt.Errorf("Error waiting for OMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
@@ -101,8 +105,8 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
|
||||
|
||||
func (s *StepCreateOMI) Cleanup(state multistep.StateBag) {}
|
||||
|
||||
func buildRegisterOpts(config *Config, image oapi.Image, mappings []oapi.BlockDeviceMappingImage) oapi.CreateImageRequest {
|
||||
registerOpts := oapi.CreateImageRequest{
|
||||
func buildRegisterOpts(config *Config, image osc.Image, mappings []osc.BlockDeviceMappingImage) osc.CreateImageRequest {
|
||||
registerOpts := osc.CreateImageRequest{
|
||||
ImageName: config.OMIName,
|
||||
Architecture: image.Architecture,
|
||||
RootDeviceName: image.RootDeviceName,
|
||||
|
||||
@@ -6,11 +6,12 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
osccommon "github.com/hashicorp/packer/builder/osc/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// StepCreateVolume creates a new volume from the snapshot of the root
|
||||
@@ -23,18 +24,19 @@ type StepCreateVolume struct {
|
||||
RootVolumeSize int64
|
||||
RootVolumeType string
|
||||
RootVolumeTags osccommon.TagMap
|
||||
RawRegion string
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
vm := state.Get("vm").(oapi.Vm)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
vm := state.Get("vm").(osc.Vm)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
var err error
|
||||
|
||||
volTags, err := s.RootVolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
|
||||
volTags, err := s.RootVolumeTags.OSCTags(s.Ctx, s.RawRegion, state)
|
||||
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
@@ -42,7 +44,7 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var createVolume *oapi.CreateVolumeRequest
|
||||
var createVolume *osc.CreateVolumeRequest
|
||||
if config.FromScratch {
|
||||
rootVolumeType := osccommon.VolumeTypeGp2
|
||||
if s.RootVolumeType == "io1" {
|
||||
@@ -53,17 +55,17 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
} else if s.RootVolumeType != "" {
|
||||
rootVolumeType = s.RootVolumeType
|
||||
}
|
||||
createVolume = &oapi.CreateVolumeRequest{
|
||||
createVolume = &osc.CreateVolumeRequest{
|
||||
SubregionName: vm.Placement.SubregionName,
|
||||
Size: s.RootVolumeSize,
|
||||
Size: int32(s.RootVolumeSize),
|
||||
VolumeType: rootVolumeType,
|
||||
}
|
||||
|
||||
} else {
|
||||
// Determine the root device snapshot
|
||||
image := state.Get("source_image").(oapi.Image)
|
||||
image := state.Get("source_image").(osc.Image)
|
||||
log.Printf("Searching for root device of the image (%s)", image.RootDeviceName)
|
||||
var rootDevice *oapi.BlockDeviceMappingImage
|
||||
var rootDevice *osc.BlockDeviceMappingImage
|
||||
for _, device := range image.BlockDeviceMappings {
|
||||
if device.DeviceName == image.RootDeviceName {
|
||||
rootDevice = &device
|
||||
@@ -82,7 +84,9 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
|
||||
log.Printf("Create args: %+v", createVolume)
|
||||
|
||||
createVolumeResp, err := oapiconn.POST_CreateVolume(*createVolume)
|
||||
createVolumeResp, _, err := oscconn.VolumeApi.CreateVolume(context.Background(), &osc.CreateVolumeOpts{
|
||||
CreateVolumeRequest: optional.NewInterface(*createVolume),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating root volume: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -91,12 +95,12 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
}
|
||||
|
||||
// Set the volume ID so we remember to delete it later
|
||||
s.volumeId = createVolumeResp.OK.Volume.VolumeId
|
||||
s.volumeId = createVolumeResp.Volume.VolumeId
|
||||
log.Printf("Volume ID: %s", s.volumeId)
|
||||
|
||||
//Create tags for volume
|
||||
if len(volTags) > 0 {
|
||||
if err := osccommon.CreateTags(oapiconn, s.volumeId, ui, volTags); err != nil {
|
||||
if err := osccommon.CreateOSCTags(oscconn, s.volumeId, ui, volTags); err != nil {
|
||||
err := fmt.Errorf("Error creating tags for volume: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
@@ -105,7 +109,7 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
}
|
||||
|
||||
// Wait for the volume to become ready
|
||||
err = osccommon.WaitUntilVolumeAvailable(oapiconn, s.volumeId)
|
||||
err = osccommon.WaitUntilOscVolumeAvailable(oscconn, s.volumeId)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for volume: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -122,32 +126,34 @@ func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
|
||||
return
|
||||
}
|
||||
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deleting the created BSU volume...")
|
||||
_, err := oapiconn.POST_DeleteVolume(oapi.DeleteVolumeRequest{VolumeId: s.volumeId})
|
||||
_, _, err := oscconn.VolumeApi.DeleteVolume(context.Background(), &osc.DeleteVolumeOpts{
|
||||
DeleteVolumeRequest: optional.NewInterface(osc.DeleteVolumeRequest{VolumeId: s.volumeId}),
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting BSU volume: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepCreateVolume) buildCreateVolumeInput(suregionName string, rootDevice *oapi.BlockDeviceMappingImage) (*oapi.CreateVolumeRequest, error) {
|
||||
func (s *StepCreateVolume) buildCreateVolumeInput(suregionName string, rootDevice *osc.BlockDeviceMappingImage) (*osc.CreateVolumeRequest, error) {
|
||||
if rootDevice == nil {
|
||||
return nil, fmt.Errorf("Couldn't find root device!")
|
||||
return nil, fmt.Errorf("Couldn't find root device")
|
||||
}
|
||||
|
||||
//FIX: Temporary fix
|
||||
gibSize := rootDevice.Bsu.VolumeSize / (1024 * 1024 * 1024)
|
||||
createVolumeInput := &oapi.CreateVolumeRequest{
|
||||
createVolumeInput := &osc.CreateVolumeRequest{
|
||||
SubregionName: suregionName,
|
||||
Size: gibSize,
|
||||
SnapshotId: rootDevice.Bsu.SnapshotId,
|
||||
VolumeType: rootDevice.Bsu.VolumeType,
|
||||
Iops: rootDevice.Bsu.Iops,
|
||||
}
|
||||
if s.RootVolumeSize > rootDevice.Bsu.VolumeSize {
|
||||
createVolumeInput.Size = s.RootVolumeSize
|
||||
if int32(s.RootVolumeSize) > rootDevice.Bsu.VolumeSize {
|
||||
createVolumeInput.Size = int32(s.RootVolumeSize)
|
||||
}
|
||||
|
||||
if s.RootVolumeType == "" || s.RootVolumeType == rootDevice.Bsu.VolumeType {
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
osccommon "github.com/hashicorp/packer/builder/osc/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// StepLinkVolume attaches the previously created volume to an
|
||||
@@ -22,9 +23,9 @@ type StepLinkVolume struct {
|
||||
}
|
||||
|
||||
func (s *StepLinkVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
device := state.Get("device").(string)
|
||||
vm := state.Get("vm").(oapi.Vm)
|
||||
vm := state.Get("vm").(osc.Vm)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
volumeId := state.Get("volume_id").(string)
|
||||
|
||||
@@ -33,10 +34,12 @@ func (s *StepLinkVolume) Run(ctx context.Context, state multistep.StateBag) mult
|
||||
linkVolume := device
|
||||
|
||||
ui.Say(fmt.Sprintf("Attaching the root volume to %s", linkVolume))
|
||||
_, err := oapiconn.POST_LinkVolume(oapi.LinkVolumeRequest{
|
||||
VmId: vm.VmId,
|
||||
VolumeId: volumeId,
|
||||
DeviceName: linkVolume,
|
||||
_, _, err := oscconn.VolumeApi.LinkVolume(context.Background(), &osc.LinkVolumeOpts{
|
||||
LinkVolumeRequest: optional.NewInterface(osc.LinkVolumeRequest{
|
||||
VmId: vm.VmId,
|
||||
VolumeId: volumeId,
|
||||
DeviceName: linkVolume,
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -51,7 +54,7 @@ func (s *StepLinkVolume) Run(ctx context.Context, state multistep.StateBag) mult
|
||||
s.volumeId = volumeId
|
||||
|
||||
// Wait for the volume to become attached
|
||||
err = osccommon.WaitUntilVolumeIsLinked(oapiconn, s.volumeId)
|
||||
err = osccommon.WaitUntilOscVolumeIsLinked(oscconn, s.volumeId)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for volume: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -75,11 +78,14 @@ func (s *StepLinkVolume) CleanupFunc(state multistep.StateBag) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Detaching BSU volume...")
|
||||
_, err := oapiconn.POST_UnlinkVolume(oapi.UnlinkVolumeRequest{VolumeId: s.volumeId})
|
||||
_, _, err := oscconn.VolumeApi.UnlinkVolume(context.Background(), &osc.UnlinkVolumeOpts{
|
||||
UnlinkVolumeRequest: optional.NewInterface(osc.UnlinkVolumeRequest{VolumeId: s.volumeId}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error detaching BSU volume: %s", err)
|
||||
}
|
||||
@@ -87,7 +93,7 @@ func (s *StepLinkVolume) CleanupFunc(state multistep.StateBag) error {
|
||||
s.attached = false
|
||||
|
||||
// Wait for the volume to detach
|
||||
err = osccommon.WaitUntilVolumeIsUnlinked(oapiconn, s.volumeId)
|
||||
err = osccommon.WaitUntilOscVolumeIsUnlinked(oscconn, s.volumeId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error waiting for volume: %s", err)
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
osccommon "github.com/hashicorp/packer/builder/osc/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// StepSnapshot creates a snapshot of the created volume.
|
||||
@@ -17,19 +18,22 @@ import (
|
||||
// snapshot_id string - ID of the created snapshot
|
||||
type StepSnapshot struct {
|
||||
snapshotId string
|
||||
RawRegion string
|
||||
}
|
||||
|
||||
func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
volumeId := state.Get("volume_id").(string)
|
||||
|
||||
ui.Say("Creating snapshot...")
|
||||
description := fmt.Sprintf("Packer: %s", time.Now().String())
|
||||
|
||||
createSnapResp, err := oapiconn.POST_CreateSnapshot(oapi.CreateSnapshotRequest{
|
||||
VolumeId: volumeId,
|
||||
Description: description,
|
||||
createSnapResp, _, err := oscconn.SnapshotApi.CreateSnapshot(context.Background(), &osc.CreateSnapshotOpts{
|
||||
CreateSnapshotRequest: optional.NewInterface(osc.CreateSnapshotRequest{
|
||||
VolumeId: volumeId,
|
||||
Description: description,
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
||||
@@ -39,11 +43,11 @@ func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
|
||||
}
|
||||
|
||||
// Set the snapshot ID so we can delete it later
|
||||
s.snapshotId = createSnapResp.OK.Snapshot.SnapshotId
|
||||
s.snapshotId = createSnapResp.Snapshot.SnapshotId
|
||||
ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId))
|
||||
|
||||
// Wait for the snapshot to be ready
|
||||
err = osccommon.WaitUntilSnapshotDone(oapiconn, s.snapshotId)
|
||||
err = osccommon.WaitUntilOscSnapshotDone(oscconn, s.snapshotId)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for snapshot: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -54,7 +58,7 @@ func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
|
||||
state.Put("snapshot_id", s.snapshotId)
|
||||
|
||||
snapshots := map[string][]string{
|
||||
oapiconn.GetConfig().Region: {s.snapshotId},
|
||||
s.RawRegion: {s.snapshotId},
|
||||
}
|
||||
state.Put("snapshots", snapshots)
|
||||
|
||||
@@ -70,10 +74,12 @@ func (s *StepSnapshot) Cleanup(state multistep.StateBag) {
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
if cancelled || halted {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
ui.Say("Removing snapshot since we cancelled or halted...")
|
||||
_, err := oapiconn.POST_DeleteSnapshot(oapi.DeleteSnapshotRequest{SnapshotId: s.snapshotId})
|
||||
_, _, err := oscconn.SnapshotApi.DeleteSnapshot(context.Background(), &osc.DeleteSnapshotOpts{
|
||||
DeleteSnapshotRequest: optional.NewInterface(osc.DeleteSnapshotRequest{SnapshotId: s.snapshotId}),
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error: %s", err))
|
||||
}
|
||||
|
||||
@@ -5,16 +5,17 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// StepVmInfo verifies that this builder is running on an Outscale vm.
|
||||
type StepVmInfo struct{}
|
||||
|
||||
func (s *StepVmInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
//session := state.Get("clientConfig").(*session.Session)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
@@ -36,9 +37,11 @@ func (s *StepVmInfo) Run(_ context.Context, state multistep.StateBag) multistep.
|
||||
log.Printf("[Debug] VmID got: %s", string(vmID))
|
||||
|
||||
// Query the entire vm metadata
|
||||
resp, err := oapiconn.POST_ReadVms(oapi.ReadVmsRequest{Filters: oapi.FiltersVm{
|
||||
VmIds: []string{string(vmID)},
|
||||
}})
|
||||
resp, _, err := oscconn.VmApi.ReadVms(context.Background(), &osc.ReadVmsOpts{
|
||||
ReadVmsRequest: optional.NewInterface(osc.ReadVmsRequest{Filters: osc.FiltersVm{
|
||||
VmIds: []string{string(vmID)},
|
||||
}}),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error getting vm data: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -46,10 +49,10 @@ func (s *StepVmInfo) Run(_ context.Context, state multistep.StateBag) multistep.
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
vmsResp := resp.OK
|
||||
vmsResp := resp
|
||||
|
||||
if len(vmsResp.Vms) == 0 {
|
||||
err := fmt.Errorf("Error getting vm data: no vm found.")
|
||||
err := fmt.Errorf("Error getting vm data: no vm found")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// AccessConfig is for common configuration related to Outscale API access
|
||||
@@ -23,20 +23,10 @@ type AccessConfig struct {
|
||||
SkipValidation bool `mapstructure:"skip_region_validation"`
|
||||
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
|
||||
Token string `mapstructure:"token"`
|
||||
clientConfig *oapi.Config
|
||||
|
||||
getOAPIConnection func() oapi.OAPIClient
|
||||
}
|
||||
|
||||
// Config returns a valid oapi.Config object for access to Outscale services, or
|
||||
// an error if the authentication and region couldn't be resolved
|
||||
func (c *AccessConfig) Config() (*oapi.Config, error) {
|
||||
if c.clientConfig != nil {
|
||||
return c.clientConfig, nil
|
||||
}
|
||||
|
||||
//Check env variables if access configuration is not set.
|
||||
|
||||
// NewOSCClient retrieves the Outscale OSC-SDK client
|
||||
func (c *AccessConfig) NewOSCClient() *osc.APIClient {
|
||||
if c.AccessKey == "" {
|
||||
c.AccessKey = os.Getenv("OUTSCALE_ACCESSKEYID")
|
||||
}
|
||||
@@ -57,36 +47,32 @@ func (c *AccessConfig) Config() (*oapi.Config, error) {
|
||||
c.CustomEndpointOAPI = "outscale.com/oapi/latest"
|
||||
}
|
||||
|
||||
config := &oapi.Config{
|
||||
AccessKey: c.AccessKey,
|
||||
SecretKey: c.SecretKey,
|
||||
Region: c.RawRegion,
|
||||
URL: c.CustomEndpointOAPI,
|
||||
Service: "api",
|
||||
}
|
||||
|
||||
return config, nil
|
||||
|
||||
return c.NewOSCClientByRegion(c.RawRegion)
|
||||
}
|
||||
|
||||
func (c *AccessConfig) NewOAPIConnection() (oapi.OAPIClient, error) {
|
||||
if c.getOAPIConnection != nil {
|
||||
return c.getOAPIConnection(), nil
|
||||
}
|
||||
oapicfg, err := c.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// GetRegion retrieves the Outscale OSC-SDK Region set
|
||||
func (c *AccessConfig) GetRegion() string {
|
||||
return c.RawRegion
|
||||
}
|
||||
|
||||
// NewOSCClientByRegion returns the connection depdending of the region given
|
||||
func (c *AccessConfig) NewOSCClientByRegion(region string) *osc.APIClient {
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.InsecureSkipTLSVerify},
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
}
|
||||
|
||||
oapiClient := oapi.NewClient(oapicfg, skipClient)
|
||||
skipClient.Transport = NewTransport(c.AccessKey, c.SecretKey, c.RawRegion, skipClient.Transport)
|
||||
|
||||
return oapiClient, nil
|
||||
return osc.NewAPIClient(&osc.Configuration{
|
||||
BasePath: fmt.Sprintf("https://api.%s.%s", region, c.CustomEndpointOAPI),
|
||||
DefaultHeader: make(map[string]string),
|
||||
UserAgent: "packer-osc",
|
||||
HTTPClient: skipClient,
|
||||
Debug: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
|
||||
@@ -2,50 +2,29 @@ package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
)
|
||||
|
||||
type mockOAPIClient struct {
|
||||
oapi.OAPIClient
|
||||
}
|
||||
|
||||
func testAccessConfig() *AccessConfig {
|
||||
return &AccessConfig{
|
||||
getOAPIConnection: func() oapi.OAPIClient {
|
||||
return &mockOAPIClient{}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockOAPIClient) POST_ReadRegions(oapi.ReadRegionsRequest) (*oapi.POST_ReadRegionsResponses, error) {
|
||||
return &oapi.POST_ReadRegionsResponses{
|
||||
OK: &oapi.ReadRegionsResponse{
|
||||
Regions: []oapi.Region{
|
||||
{RegionEndpoint: "us-west1", RegionName: "us-west1"},
|
||||
{RegionEndpoint: "us-east-1", RegionName: "us-east-1"},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
return &AccessConfig{}
|
||||
}
|
||||
|
||||
func TestAccessConfigPrepare_Region(t *testing.T) {
|
||||
c := testAccessConfig()
|
||||
|
||||
c.RawRegion = "us-east-12"
|
||||
err := c.ValidateRegion(c.RawRegion)
|
||||
err := c.ValidateOSCRegion(c.RawRegion)
|
||||
if err == nil {
|
||||
t.Fatalf("should have region validation err: %s", c.RawRegion)
|
||||
}
|
||||
|
||||
c.RawRegion = "us-east-1"
|
||||
err = c.ValidateRegion(c.RawRegion)
|
||||
if err != nil {
|
||||
t.Fatalf("shouldn't have region validation err: %s", c.RawRegion)
|
||||
err = c.ValidateOSCRegion(c.RawRegion)
|
||||
if err == nil {
|
||||
t.Fatalf("should have region validation err: %s", c.RawRegion)
|
||||
}
|
||||
|
||||
c.RawRegion = "custom"
|
||||
err = c.ValidateRegion(c.RawRegion)
|
||||
err = c.ValidateOSCRegion(c.RawRegion)
|
||||
if err == nil {
|
||||
t.Fatalf("should have region validation err: %s", c.RawRegion)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// Artifact is an artifact implementation that contains built OMIs.
|
||||
@@ -20,9 +20,6 @@ type Artifact struct {
|
||||
// BuilderId is the unique ID for the builder that created this OMI
|
||||
BuilderIdValue string
|
||||
|
||||
// OAPI connection for performing API stuff.
|
||||
Config *oapi.Config
|
||||
|
||||
// StateData should store data such as GeneratedData
|
||||
// to be shared with post-processors
|
||||
StateData map[string]interface{}
|
||||
@@ -74,47 +71,36 @@ func (a *Artifact) State(name string) interface{} {
|
||||
func (a *Artifact) Destroy() error {
|
||||
errors := make([]error, 0)
|
||||
|
||||
config := a.State("accessConfig").(*AccessConfig)
|
||||
|
||||
for region, imageId := range a.Omis {
|
||||
log.Printf("Deregistering image ID (%s) from region (%s)", imageId, region)
|
||||
|
||||
newConfig := &oapi.Config{
|
||||
UserAgent: a.Config.UserAgent,
|
||||
AccessKey: a.Config.AccessKey,
|
||||
SecretKey: a.Config.SecretKey,
|
||||
Service: a.Config.Service,
|
||||
Region: region, //New region
|
||||
URL: a.Config.URL,
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] New Client config %+v", newConfig)
|
||||
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
regionConn := oapi.NewClient(newConfig, skipClient)
|
||||
regionConn := config.NewOSCClientByRegion(region)
|
||||
|
||||
// Get image metadata
|
||||
imageResp, err := regionConn.POST_ReadImages(oapi.ReadImagesRequest{
|
||||
Filters: oapi.FiltersImage{
|
||||
ImageIds: []string{imageId},
|
||||
},
|
||||
imageResp, _, err := regionConn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
|
||||
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
|
||||
Filters: osc.FiltersImage{
|
||||
ImageIds: []string{imageId},
|
||||
},
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
if len(imageResp.OK.Images) == 0 {
|
||||
if len(imageResp.Images) == 0 {
|
||||
err := fmt.Errorf("Error retrieving details for OMI (%s), no images found", imageId)
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
// Deregister ami
|
||||
input := oapi.DeleteImageRequest{
|
||||
input := osc.DeleteImageRequest{
|
||||
ImageId: imageId,
|
||||
}
|
||||
if _, err := regionConn.POST_DeleteImage(input); err != nil {
|
||||
if _, _, err := regionConn.ImageApi.DeleteImage(context.Background(), &osc.DeleteImageOpts{
|
||||
DeleteImageRequest: optional.NewInterface(input),
|
||||
}); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@ import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// BlockDevice
|
||||
@@ -35,11 +34,11 @@ type LaunchBlockDevices struct {
|
||||
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"`
|
||||
}
|
||||
|
||||
func buildBlockDevicesImage(b []BlockDevice) []oapi.BlockDeviceMappingImage {
|
||||
var blockDevices []oapi.BlockDeviceMappingImage
|
||||
func buildOscBlockDevicesImage(b []BlockDevice) []osc.BlockDeviceMappingImage {
|
||||
var blockDevices []osc.BlockDeviceMappingImage
|
||||
|
||||
for _, blockDevice := range b {
|
||||
mapping := oapi.BlockDeviceMappingImage{
|
||||
mapping := osc.BlockDeviceMappingImage{
|
||||
DeviceName: blockDevice.DeviceName,
|
||||
}
|
||||
|
||||
@@ -48,8 +47,8 @@ func buildBlockDevicesImage(b []BlockDevice) []oapi.BlockDeviceMappingImage {
|
||||
mapping.VirtualDeviceName = blockDevice.VirtualName
|
||||
}
|
||||
} else {
|
||||
bsu := oapi.BsuToCreate{
|
||||
DeleteOnVmDeletion: aws.Bool(blockDevice.DeleteOnVmDeletion),
|
||||
bsu := osc.BsuToCreate{
|
||||
DeleteOnVmDeletion: blockDevice.DeleteOnVmDeletion,
|
||||
}
|
||||
|
||||
if blockDevice.VolumeType != "" {
|
||||
@@ -57,12 +56,12 @@ func buildBlockDevicesImage(b []BlockDevice) []oapi.BlockDeviceMappingImage {
|
||||
}
|
||||
|
||||
if blockDevice.VolumeSize > 0 {
|
||||
bsu.VolumeSize = blockDevice.VolumeSize
|
||||
bsu.VolumeSize = int32(blockDevice.VolumeSize)
|
||||
}
|
||||
|
||||
// IOPS is only valid for io1 type
|
||||
if blockDevice.VolumeType == "io1" {
|
||||
bsu.Iops = blockDevice.IOPS
|
||||
bsu.Iops = int32(blockDevice.IOPS)
|
||||
}
|
||||
|
||||
if blockDevice.SnapshotId != "" {
|
||||
@@ -77,13 +76,13 @@ func buildBlockDevicesImage(b []BlockDevice) []oapi.BlockDeviceMappingImage {
|
||||
return blockDevices
|
||||
}
|
||||
|
||||
func buildBlockDevicesVmCreation(b []BlockDevice) []oapi.BlockDeviceMappingVmCreation {
|
||||
func buildOscBlockDevicesVmCreation(b []BlockDevice) []osc.BlockDeviceMappingVmCreation {
|
||||
log.Printf("[DEBUG] Launch Block Device %#v", b)
|
||||
|
||||
var blockDevices []oapi.BlockDeviceMappingVmCreation
|
||||
var blockDevices []osc.BlockDeviceMappingVmCreation
|
||||
|
||||
for _, blockDevice := range b {
|
||||
mapping := oapi.BlockDeviceMappingVmCreation{
|
||||
mapping := osc.BlockDeviceMappingVmCreation{
|
||||
DeviceName: blockDevice.DeviceName,
|
||||
}
|
||||
|
||||
@@ -94,8 +93,8 @@ func buildBlockDevicesVmCreation(b []BlockDevice) []oapi.BlockDeviceMappingVmCre
|
||||
mapping.VirtualDeviceName = blockDevice.VirtualName
|
||||
}
|
||||
} else {
|
||||
bsu := oapi.BsuToCreate{
|
||||
DeleteOnVmDeletion: aws.Bool(blockDevice.DeleteOnVmDeletion),
|
||||
bsu := osc.BsuToCreate{
|
||||
DeleteOnVmDeletion: blockDevice.DeleteOnVmDeletion,
|
||||
}
|
||||
|
||||
if blockDevice.VolumeType != "" {
|
||||
@@ -103,12 +102,12 @@ func buildBlockDevicesVmCreation(b []BlockDevice) []oapi.BlockDeviceMappingVmCre
|
||||
}
|
||||
|
||||
if blockDevice.VolumeSize > 0 {
|
||||
bsu.VolumeSize = blockDevice.VolumeSize
|
||||
bsu.VolumeSize = int32(blockDevice.VolumeSize)
|
||||
}
|
||||
|
||||
// IOPS is only valid for io1 type
|
||||
if blockDevice.VolumeType == "io1" {
|
||||
bsu.Iops = blockDevice.IOPS
|
||||
bsu.Iops = int32(blockDevice.IOPS)
|
||||
}
|
||||
|
||||
if blockDevice.SnapshotId != "" {
|
||||
@@ -145,10 +144,10 @@ func (b *BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
|
||||
return errs
|
||||
}
|
||||
|
||||
func (b *OMIBlockDevices) BuildOMIDevices() []oapi.BlockDeviceMappingImage {
|
||||
return buildBlockDevicesImage(b.OMIMappings)
|
||||
func (b *OMIBlockDevices) BuildOscOMIDevices() []osc.BlockDeviceMappingImage {
|
||||
return buildOscBlockDevicesImage(b.OMIMappings)
|
||||
}
|
||||
|
||||
func (b *LaunchBlockDevices) BuildLaunchDevices() []oapi.BlockDeviceMappingVmCreation {
|
||||
return buildBlockDevicesVmCreation(b.LaunchMappings)
|
||||
func (b *LaunchBlockDevices) BuildOSCLaunchDevices() []osc.BlockDeviceMappingVmCreation {
|
||||
return buildOscBlockDevicesVmCreation(b.LaunchMappings)
|
||||
}
|
||||
|
||||
@@ -4,19 +4,13 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
func TestBlockDevice_LaunchDevices(t *testing.T) {
|
||||
tr := new(bool)
|
||||
f := new(bool)
|
||||
|
||||
*tr = true
|
||||
*f = false
|
||||
|
||||
cases := []struct {
|
||||
Config *BlockDevice
|
||||
Result oapi.BlockDeviceMappingVmCreation
|
||||
Result osc.BlockDeviceMappingVmCreation
|
||||
}{
|
||||
{
|
||||
Config: &BlockDevice{
|
||||
@@ -27,13 +21,13 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingVmCreation{
|
||||
Result: osc.BlockDeviceMappingVmCreation{
|
||||
DeviceName: "/dev/sdb",
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
SnapshotId: "snap-1234",
|
||||
VolumeType: "standard",
|
||||
VolumeSize: 8,
|
||||
DeleteOnVmDeletion: tr,
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -43,11 +37,11 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
|
||||
VolumeSize: 8,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingVmCreation{
|
||||
Result: osc.BlockDeviceMappingVmCreation{
|
||||
DeviceName: "/dev/sdb",
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
VolumeSize: 8,
|
||||
DeleteOnVmDeletion: f,
|
||||
DeleteOnVmDeletion: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -60,12 +54,12 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
|
||||
IOPS: 1000,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingVmCreation{
|
||||
Result: osc.BlockDeviceMappingVmCreation{
|
||||
DeviceName: "/dev/sdb",
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
VolumeType: "io1",
|
||||
VolumeSize: 8,
|
||||
DeleteOnVmDeletion: tr,
|
||||
DeleteOnVmDeletion: true,
|
||||
Iops: 1000,
|
||||
},
|
||||
},
|
||||
@@ -78,12 +72,12 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingVmCreation{
|
||||
Result: osc.BlockDeviceMappingVmCreation{
|
||||
DeviceName: "/dev/sdb",
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
VolumeType: "gp2",
|
||||
VolumeSize: 8,
|
||||
DeleteOnVmDeletion: tr,
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -95,12 +89,12 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingVmCreation{
|
||||
Result: osc.BlockDeviceMappingVmCreation{
|
||||
DeviceName: "/dev/sdb",
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
VolumeType: "gp2",
|
||||
VolumeSize: 8,
|
||||
DeleteOnVmDeletion: tr,
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -111,11 +105,11 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingVmCreation{
|
||||
Result: osc.BlockDeviceMappingVmCreation{
|
||||
DeviceName: "/dev/sdb",
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
VolumeType: "standard",
|
||||
DeleteOnVmDeletion: tr,
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -125,7 +119,7 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
|
||||
VirtualName: "ephemeral0",
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingVmCreation{
|
||||
Result: osc.BlockDeviceMappingVmCreation{
|
||||
DeviceName: "/dev/sdb",
|
||||
VirtualDeviceName: "ephemeral0",
|
||||
},
|
||||
@@ -136,7 +130,7 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
|
||||
NoDevice: true,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingVmCreation{
|
||||
Result: osc.BlockDeviceMappingVmCreation{
|
||||
DeviceName: "/dev/sdb",
|
||||
NoDevice: "",
|
||||
},
|
||||
@@ -149,9 +143,9 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
|
||||
LaunchMappings: []BlockDevice{*tc.Config},
|
||||
}
|
||||
|
||||
expected := []oapi.BlockDeviceMappingVmCreation{tc.Result}
|
||||
expected := []osc.BlockDeviceMappingVmCreation{tc.Result}
|
||||
|
||||
launchResults := launchBlockDevices.BuildLaunchDevices()
|
||||
launchResults := launchBlockDevices.BuildOSCLaunchDevices()
|
||||
if !reflect.DeepEqual(expected, launchResults) {
|
||||
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
|
||||
expected, launchResults)
|
||||
@@ -160,15 +154,9 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBlockDevice_OMI(t *testing.T) {
|
||||
tr := new(bool)
|
||||
f := new(bool)
|
||||
|
||||
*tr = true
|
||||
*f = false
|
||||
|
||||
cases := []struct {
|
||||
Config *BlockDevice
|
||||
Result oapi.BlockDeviceMappingImage
|
||||
Result osc.BlockDeviceMappingImage
|
||||
}{
|
||||
{
|
||||
Config: &BlockDevice{
|
||||
@@ -179,27 +167,28 @@ func TestBlockDevice_OMI(t *testing.T) {
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingImage{
|
||||
Result: osc.BlockDeviceMappingImage{
|
||||
DeviceName: "/dev/sdb",
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
SnapshotId: "snap-1234",
|
||||
VolumeType: "standard",
|
||||
VolumeSize: 8,
|
||||
DeleteOnVmDeletion: tr,
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: &BlockDevice{
|
||||
DeviceName: "/dev/sdb",
|
||||
VolumeSize: 8,
|
||||
DeviceName: "/dev/sdb",
|
||||
VolumeSize: 8,
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingImage{
|
||||
Result: osc.BlockDeviceMappingImage{
|
||||
DeviceName: "/dev/sdb",
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
VolumeSize: 8,
|
||||
DeleteOnVmDeletion: f,
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -212,12 +201,12 @@ func TestBlockDevice_OMI(t *testing.T) {
|
||||
IOPS: 1000,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingImage{
|
||||
Result: osc.BlockDeviceMappingImage{
|
||||
DeviceName: "/dev/sdb",
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
VolumeType: "io1",
|
||||
VolumeSize: 8,
|
||||
DeleteOnVmDeletion: tr,
|
||||
DeleteOnVmDeletion: true,
|
||||
Iops: 1000,
|
||||
},
|
||||
},
|
||||
@@ -230,12 +219,12 @@ func TestBlockDevice_OMI(t *testing.T) {
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingImage{
|
||||
Result: osc.BlockDeviceMappingImage{
|
||||
DeviceName: "/dev/sdb",
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
VolumeType: "gp2",
|
||||
VolumeSize: 8,
|
||||
DeleteOnVmDeletion: tr,
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -247,12 +236,12 @@ func TestBlockDevice_OMI(t *testing.T) {
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingImage{
|
||||
Result: osc.BlockDeviceMappingImage{
|
||||
DeviceName: "/dev/sdb",
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
VolumeType: "gp2",
|
||||
VolumeSize: 8,
|
||||
DeleteOnVmDeletion: tr,
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -263,11 +252,11 @@ func TestBlockDevice_OMI(t *testing.T) {
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingImage{
|
||||
Result: osc.BlockDeviceMappingImage{
|
||||
DeviceName: "/dev/sdb",
|
||||
Bsu: oapi.BsuToCreate{
|
||||
Bsu: osc.BsuToCreate{
|
||||
VolumeType: "standard",
|
||||
DeleteOnVmDeletion: tr,
|
||||
DeleteOnVmDeletion: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -277,24 +266,24 @@ func TestBlockDevice_OMI(t *testing.T) {
|
||||
VirtualName: "ephemeral0",
|
||||
},
|
||||
|
||||
Result: oapi.BlockDeviceMappingImage{
|
||||
Result: osc.BlockDeviceMappingImage{
|
||||
DeviceName: "/dev/sdb",
|
||||
VirtualDeviceName: "ephemeral0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
for i, tc := range cases {
|
||||
omiBlockDevices := OMIBlockDevices{
|
||||
OMIMappings: []BlockDevice{*tc.Config},
|
||||
}
|
||||
|
||||
expected := []oapi.BlockDeviceMappingImage{tc.Result}
|
||||
expected := []osc.BlockDeviceMappingImage{tc.Result}
|
||||
|
||||
omiResults := omiBlockDevices.BuildOMIDevices()
|
||||
omiResults := omiBlockDevices.BuildOscOMIDevices()
|
||||
if !reflect.DeepEqual(expected, omiResults) {
|
||||
t.Fatalf("Bad block device, \nexpected: %+#v\n\ngot: %+#v",
|
||||
expected, omiResults)
|
||||
t.Fatalf("%d - Bad block device, \nexpected: %+#v\n\ngot: %+#v",
|
||||
i, expected, omiResults)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
func buildNetFilters(input map[string]string) oapi.FiltersNet {
|
||||
var filters oapi.FiltersNet
|
||||
func buildOscNetFilters(input map[string]string) osc.FiltersNet {
|
||||
var filters osc.FiltersNet
|
||||
for k, v := range input {
|
||||
filterValue := []string{v}
|
||||
switch name := k; name {
|
||||
@@ -33,14 +33,14 @@ func buildNetFilters(input map[string]string) oapi.FiltersNet {
|
||||
return filters
|
||||
}
|
||||
|
||||
func buildSubnetFilters(input map[string]string) oapi.FiltersSubnet {
|
||||
var filters oapi.FiltersSubnet
|
||||
func buildOscSubnetFilters(input map[string]string) osc.FiltersSubnet {
|
||||
var filters osc.FiltersSubnet
|
||||
for k, v := range input {
|
||||
filterValue := []string{v}
|
||||
switch name := k; name {
|
||||
case "available-ips-counts":
|
||||
if ipCount, err := strconv.Atoi(v); err == nil {
|
||||
filters.AvailableIpsCounts = []int64{int64(ipCount)}
|
||||
filters.AvailableIpsCounts = []int32{int32(ipCount)}
|
||||
}
|
||||
case "ip-ranges":
|
||||
filters.IpRanges = filterValue
|
||||
@@ -59,8 +59,8 @@ func buildSubnetFilters(input map[string]string) oapi.FiltersSubnet {
|
||||
return filters
|
||||
}
|
||||
|
||||
func buildOMIFilters(input map[string]string) oapi.FiltersImage {
|
||||
var filters oapi.FiltersImage
|
||||
func buildOSCOMIFilters(input map[string]string) osc.FiltersImage {
|
||||
var filters osc.FiltersImage
|
||||
for k, v := range input {
|
||||
filterValue := []string{v}
|
||||
|
||||
@@ -75,14 +75,14 @@ func buildOMIFilters(input map[string]string) oapi.FiltersImage {
|
||||
filters.ImageIds = filterValue
|
||||
case "image-name":
|
||||
filters.ImageNames = filterValue
|
||||
case "image-type":
|
||||
filters.ImageTypes = filterValue
|
||||
// case "image-type":
|
||||
// filters.ImageTypes = filterValue
|
||||
case "virtualization-type":
|
||||
filters.VirtualizationTypes = filterValue
|
||||
case "root-device-type":
|
||||
filters.RootDeviceTypes = filterValue
|
||||
case "block-device-mapping-volume-type":
|
||||
filters.BlockDeviceMappingVolumeType = filterValue
|
||||
// case "block-device-mapping-volume-type":
|
||||
// filters.BlockDeviceMappingVolumeType = filterValue
|
||||
//Some params are missing.
|
||||
default:
|
||||
log.Printf("[WARN] Unknown Filter Name: %s.", name)
|
||||
@@ -90,68 +90,3 @@ func buildOMIFilters(input map[string]string) oapi.FiltersImage {
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
func buildSecurityGroupFilters(input map[string]string) oapi.FiltersSecurityGroup {
|
||||
var filters oapi.FiltersSecurityGroup
|
||||
for k, v := range input {
|
||||
filterValue := []string{v}
|
||||
|
||||
switch name := k; name {
|
||||
case "account-ids":
|
||||
filters.AccountIds = filterValue
|
||||
case "descriptions":
|
||||
filters.Descriptions = filterValue
|
||||
case "inbound-rule-account-ids":
|
||||
filters.InboundRuleAccountIds = filterValue
|
||||
case "inbound-rule-from-port-ranges":
|
||||
if val, err := strconv.Atoi(v); err == nil {
|
||||
filters.InboundRuleFromPortRanges = []int64{int64(val)}
|
||||
}
|
||||
case "inbound-rule-ip-ranges":
|
||||
filters.InboundRuleIpRanges = filterValue
|
||||
case "inbound-rule-protocols":
|
||||
filters.InboundRuleProtocols = filterValue
|
||||
case "inbound-rule-security-group-ids":
|
||||
filters.InboundRuleSecurityGroupIds = filterValue
|
||||
case "inbound-rule-security-group-names":
|
||||
filters.InboundRuleSecurityGroupNames = filterValue
|
||||
case "inbound-rule-to-port-ranges":
|
||||
if val, err := strconv.Atoi(v); err == nil {
|
||||
filters.InboundRuleToPortRanges = []int64{int64(val)}
|
||||
}
|
||||
case "net-ids":
|
||||
filters.NetIds = filterValue
|
||||
|
||||
case "outbound-rule-account-ids":
|
||||
filters.OutboundRuleAccountIds = filterValue
|
||||
case "outbound-rule-from-port-ranges":
|
||||
if val, err := strconv.Atoi(v); err == nil {
|
||||
filters.OutboundRuleFromPortRanges = []int64{int64(val)}
|
||||
}
|
||||
case "outbound-rule-ip-ranges":
|
||||
filters.OutboundRuleIpRanges = filterValue
|
||||
case "outbound-rule-protocols":
|
||||
filters.OutboundRuleProtocols = filterValue
|
||||
case "outbound-rule-security-group-ids":
|
||||
filters.OutboundRuleSecurityGroupIds = filterValue
|
||||
case "outbound-rule-security-group-names":
|
||||
filters.OutboundRuleSecurityGroupNames = filterValue
|
||||
case "outbound-rule-to-port-ranges":
|
||||
if val, err := strconv.Atoi(v); err == nil {
|
||||
filters.OutboundRuleToPortRanges = []int64{int64(val)}
|
||||
}
|
||||
case "security-group-ids":
|
||||
filters.SecurityGroupIds = filterValue
|
||||
case "security-group-names":
|
||||
filters.SecurityGroupNames = filterValue
|
||||
case "tags-keys":
|
||||
filters.TagKeys = filterValue
|
||||
case "tags-values":
|
||||
filters.TagValues = filterValue
|
||||
//Some params are missing.
|
||||
default:
|
||||
log.Printf("[Debug] Unknown Filter Name: %s.", name)
|
||||
}
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package common
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
type BuildInfoTemplate struct {
|
||||
@@ -20,7 +20,7 @@ func extractBuildInfo(region string, state multistep.StateBag) *BuildInfoTemplat
|
||||
}
|
||||
}
|
||||
|
||||
sourceOMI := rawSourceOMI.(oapi.Image)
|
||||
sourceOMI := rawSourceOMI.(osc.Image)
|
||||
sourceOMITags := make(map[string]string, len(sourceOMI.Tags))
|
||||
for _, tag := range sourceOMI.Tags {
|
||||
sourceOMITags[tag.Key] = tag.Value
|
||||
|
||||
@@ -5,14 +5,14 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
func testImage() oapi.Image {
|
||||
return oapi.Image{
|
||||
func testImage() osc.Image {
|
||||
return osc.Image{
|
||||
ImageId: "ami-abcd1234",
|
||||
ImageName: "ami_test_name",
|
||||
Tags: []oapi.ResourceTag{
|
||||
Tags: []osc.ResourceTag{
|
||||
{
|
||||
Key: "key-1",
|
||||
Value: "value-1",
|
||||
|
||||
@@ -36,17 +36,13 @@ func TestOMIConfigPrepare_regions(t *testing.T) {
|
||||
c.OMIRegions = nil
|
||||
|
||||
var errs []error
|
||||
var err error
|
||||
accessConf := testAccessConfig()
|
||||
mockConn := &mockOAPIClient{}
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatalf("shouldn't have err: %#v", errs)
|
||||
}
|
||||
|
||||
c.OMIRegions, err = listOAPIRegions(mockConn)
|
||||
if err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err.Error())
|
||||
}
|
||||
c.OMIRegions = []string{"us-east-1", "us-west-1"}
|
||||
|
||||
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
|
||||
t.Fatalf("shouldn't have err: %#v", errs)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/antihax/optional"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
func listOAPIRegions(oapiconn oapi.OAPIClient) ([]string, error) {
|
||||
func listOSCRegions(oscconn *osc.RegionApiService) ([]string, error) {
|
||||
var regions []string
|
||||
resp, err := oapiconn.POST_ReadRegions(oapi.ReadRegionsRequest{})
|
||||
if resp.OK == nil || err != nil {
|
||||
resp, _, err := oscconn.ReadRegions(context.Background(), &osc.ReadRegionsOpts{
|
||||
ReadRegionsRequest: optional.NewInterface(osc.ReadRegionsRequest{}),
|
||||
})
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
resultRegions := resp.OK
|
||||
resultRegions := resp
|
||||
|
||||
for _, region := range resultRegions.Regions {
|
||||
regions = append(regions, region.RegionName)
|
||||
@@ -24,13 +28,10 @@ func listOAPIRegions(oapiconn oapi.OAPIClient) ([]string, error) {
|
||||
|
||||
// ValidateRegion returns true if the supplied region is a valid Outscale
|
||||
// region and false if it's not.
|
||||
func (c *AccessConfig) ValidateRegion(regions ...string) error {
|
||||
oapiconn, err := c.NewOAPIConnection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (c *AccessConfig) ValidateOSCRegion(regions ...string) error {
|
||||
oscconn := c.NewOSCClient()
|
||||
|
||||
validRegions, err := listOAPIRegions(oapiconn)
|
||||
validRegions, err := listOSCRegions(oscconn.RegionApi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+86
-11
@@ -1,16 +1,20 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
type oapiDescriber interface {
|
||||
POST_ReadVms(oapi.ReadVmsRequest) (*oapi.POST_ReadVmsResponses, error)
|
||||
type oscDescriber interface {
|
||||
ReadVms(ctx context.Context, localVarOptionals *osc.ReadVmsOpts) (osc.ReadVmsResponse, *http.Response, error)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -20,13 +24,13 @@ var (
|
||||
|
||||
// SSHHost returns a function that can be given to the SSH communicator
|
||||
// for determining the SSH address based on the vm DNS name.
|
||||
func SSHHost(e oapiDescriber, sshInterface string) func(multistep.StateBag) (string, error) {
|
||||
func SSHHost(e oscDescriber, sshInterface string) func(multistep.StateBag) (string, error) {
|
||||
return func(state multistep.StateBag) (string, error) {
|
||||
const tries = 2
|
||||
// <= with current structure to check result of describing `tries` times
|
||||
for j := 0; j <= tries; j++ {
|
||||
var host string
|
||||
i := state.Get("vm").(oapi.Vm)
|
||||
i := state.Get("vm").(osc.Vm)
|
||||
|
||||
if sshInterface != "" {
|
||||
switch sshInterface {
|
||||
@@ -63,20 +67,91 @@ func SSHHost(e oapiDescriber, sshInterface string) func(multistep.StateBag) (str
|
||||
return host, nil
|
||||
}
|
||||
|
||||
r, err := e.POST_ReadVms(oapi.ReadVmsRequest{
|
||||
Filters: oapi.FiltersVm{
|
||||
VmIds: []string{i.VmId},
|
||||
},
|
||||
r, _, err := e.ReadVms(context.Background(), &osc.ReadVmsOpts{
|
||||
ReadVmsRequest: optional.NewInterface(osc.ReadVmsRequest{
|
||||
Filters: osc.FiltersVm{
|
||||
VmIds: []string{i.VmId},
|
||||
},
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(r.OK.Vms) == 0 {
|
||||
if len(r.Vms) == 0 {
|
||||
return "", fmt.Errorf("vm not found: %s", i.VmId)
|
||||
}
|
||||
|
||||
state.Put("vm", r.OK.Vms[0])
|
||||
state.Put("vm", r.Vms[0])
|
||||
time.Sleep(sshHostSleepDuration)
|
||||
}
|
||||
|
||||
return "", errors.New("couldn't determine address for vm")
|
||||
}
|
||||
}
|
||||
|
||||
// SSHHost returns a function that can be given to the SSH communicator
|
||||
// for determining the SSH address based on the vm DNS name.
|
||||
func OscSSHHost(e oscDescriber, sshInterface string) func(multistep.StateBag) (string, error) {
|
||||
return func(state multistep.StateBag) (string, error) {
|
||||
const tries = 2
|
||||
// <= with current structure to check result of describing `tries` times
|
||||
for j := 0; j <= tries; j++ {
|
||||
var host string
|
||||
i := state.Get("vm").(osc.Vm)
|
||||
|
||||
if sshInterface != "" {
|
||||
switch sshInterface {
|
||||
case "public_ip":
|
||||
if i.PublicIp != "" {
|
||||
host = i.PublicIp
|
||||
}
|
||||
case "public_dns":
|
||||
if i.PublicDnsName != "" {
|
||||
host = i.PublicDnsName
|
||||
}
|
||||
case "private_ip":
|
||||
if i.PrivateIp != "" {
|
||||
host = i.PrivateIp
|
||||
}
|
||||
case "private_dns":
|
||||
if i.PrivateDnsName != "" {
|
||||
host = i.PrivateDnsName
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown interface type: %s", sshInterface))
|
||||
}
|
||||
} else if i.NetId != "" {
|
||||
if i.PublicIp != "" {
|
||||
host = i.PublicIp
|
||||
} else if i.PrivateIp != "" {
|
||||
host = i.PrivateIp
|
||||
}
|
||||
} else if i.PublicDnsName != "" {
|
||||
host = i.PublicDnsName
|
||||
}
|
||||
|
||||
if host != "" {
|
||||
return host, nil
|
||||
}
|
||||
|
||||
r, _, err := e.ReadVms(context.Background(), &osc.ReadVmsOpts{
|
||||
ReadVmsRequest: optional.NewInterface(osc.ReadVmsRequest{
|
||||
Filters: osc.FiltersVm{
|
||||
VmIds: []string{i.VmId},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(r.Vms) == 0 {
|
||||
return "", fmt.Errorf("vm not found: %s", i.VmId)
|
||||
}
|
||||
|
||||
state.Put("vm", r.Vms[0])
|
||||
time.Sleep(sshHostSleepDuration)
|
||||
}
|
||||
|
||||
|
||||
+96
-152
@@ -1,78 +1,73 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
type stateRefreshFunc func() (string, error)
|
||||
|
||||
func waitForSecurityGroup(conn *oapi.Client, securityGroupID string) error {
|
||||
func waitUntilForOscVmRunning(conn *osc.APIClient, vmID string) error {
|
||||
errCh := make(chan error, 1)
|
||||
go waitForState(errCh, "exists", securityGroupWaitFunc(conn, securityGroupID))
|
||||
go waitForState(errCh, "running", waitUntilOscVmStateFunc(conn, vmID))
|
||||
err := <-errCh
|
||||
return err
|
||||
}
|
||||
|
||||
func waitUntilForVmRunning(conn *oapi.Client, vmID string) error {
|
||||
func waitUntilOscVmDeleted(conn *osc.APIClient, vmID string) error {
|
||||
errCh := make(chan error, 1)
|
||||
go waitForState(errCh, "running", waitUntilVmStateFunc(conn, vmID))
|
||||
err := <-errCh
|
||||
return err
|
||||
}
|
||||
|
||||
func waitUntilVmDeleted(conn *oapi.Client, vmID string) error {
|
||||
errCh := make(chan error, 1)
|
||||
go waitForState(errCh, "terminated", waitUntilVmStateFunc(conn, vmID))
|
||||
go waitForState(errCh, "terminated", waitUntilOscVmStateFunc(conn, vmID))
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
func waitUntilVmStopped(conn *oapi.Client, vmID string) error {
|
||||
func waitUntilOscVmStopped(conn *osc.APIClient, vmID string) error {
|
||||
errCh := make(chan error, 1)
|
||||
go waitForState(errCh, "stopped", waitUntilVmStateFunc(conn, vmID))
|
||||
go waitForState(errCh, "stopped", waitUntilOscVmStateFunc(conn, vmID))
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
func WaitUntilSnapshotCompleted(conn *oapi.Client, id string) error {
|
||||
func WaitUntilOscSnapshotCompleted(conn *osc.APIClient, id string) error {
|
||||
errCh := make(chan error, 1)
|
||||
go waitForState(errCh, "completed", waitUntilSnapshotStateFunc(conn, id))
|
||||
go waitForState(errCh, "completed", waitUntilOscSnapshotStateFunc(conn, id))
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
func WaitUntilImageAvailable(conn *oapi.Client, imageID string) error {
|
||||
func WaitUntilOscImageAvailable(conn *osc.APIClient, imageID string) error {
|
||||
errCh := make(chan error, 1)
|
||||
go waitForState(errCh, "available", waitUntilImageStateFunc(conn, imageID))
|
||||
go waitForState(errCh, "available", waitUntilOscImageStateFunc(conn, imageID))
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
func WaitUntilVolumeAvailable(conn *oapi.Client, volumeID string) error {
|
||||
func WaitUntilOscVolumeAvailable(conn *osc.APIClient, volumeID string) error {
|
||||
errCh := make(chan error, 1)
|
||||
go waitForState(errCh, "available", volumeWaitFunc(conn, volumeID))
|
||||
go waitForState(errCh, "available", volumeOscWaitFunc(conn, volumeID))
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
func WaitUntilVolumeIsLinked(conn *oapi.Client, volumeID string) error {
|
||||
func WaitUntilOscVolumeIsLinked(conn *osc.APIClient, volumeID string) error {
|
||||
errCh := make(chan error, 1)
|
||||
go waitForState(errCh, "attached", waitUntilVolumeLinkedStateFunc(conn, volumeID))
|
||||
go waitForState(errCh, "attached", waitUntilOscVolumeLinkedStateFunc(conn, volumeID))
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
func WaitUntilVolumeIsUnlinked(conn *oapi.Client, volumeID string) error {
|
||||
func WaitUntilOscVolumeIsUnlinked(conn *osc.APIClient, volumeID string) error {
|
||||
errCh := make(chan error, 1)
|
||||
go waitForState(errCh, "dettached", waitUntilVolumeUnLinkedStateFunc(conn, volumeID))
|
||||
go waitForState(errCh, "dettached", waitUntilOscVolumeUnLinkedStateFunc(conn, volumeID))
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
func WaitUntilSnapshotDone(conn *oapi.Client, snapshotID string) error {
|
||||
func WaitUntilOscSnapshotDone(conn *osc.APIClient, snapshotID string) error {
|
||||
errCh := make(chan error, 1)
|
||||
go waitForState(errCh, "completed", waitUntilSnapshotDoneStateFunc(conn, snapshotID))
|
||||
go waitForState(errCh, "completed", waitUntilOscSnapshotDoneStateFunc(conn, snapshotID))
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error {
|
||||
func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) {
|
||||
err := common.Retry(2, 2, 0, func(_ uint) (bool, error) {
|
||||
state, err := refresh()
|
||||
if err != nil {
|
||||
@@ -83,91 +78,83 @@ func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) e
|
||||
return false, nil
|
||||
})
|
||||
errCh <- err
|
||||
return err
|
||||
}
|
||||
|
||||
func waitUntilVmStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
|
||||
func waitUntilOscVmStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
|
||||
return func() (string, error) {
|
||||
log.Printf("[Debug] Retrieving state for VM with id %s", id)
|
||||
resp, err := conn.POST_ReadVms(oapi.ReadVmsRequest{
|
||||
Filters: oapi.FiltersVm{
|
||||
VmIds: []string{id},
|
||||
},
|
||||
resp, _, err := conn.VmApi.ReadVms(context.Background(), &osc.ReadVmsOpts{
|
||||
ReadVmsRequest: optional.NewInterface(osc.ReadVmsRequest{
|
||||
Filters: osc.FiltersVm{
|
||||
VmIds: []string{id},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
log.Printf("[Debug] Read Response %+v", resp.OK)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.OK == nil {
|
||||
return "", fmt.Errorf("Vm with ID %s not Found", id)
|
||||
}
|
||||
//TODO: check if needed
|
||||
// if resp == nil {
|
||||
// return "", fmt.Errorf("Vm with ID %s not Found", id)
|
||||
// }
|
||||
|
||||
if len(resp.OK.Vms) == 0 {
|
||||
if len(resp.Vms) == 0 {
|
||||
return "pending", nil
|
||||
}
|
||||
|
||||
return resp.OK.Vms[0].State, nil
|
||||
return resp.Vms[0].State, nil
|
||||
}
|
||||
}
|
||||
|
||||
func waitUntilVolumeLinkedStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
|
||||
func waitUntilOscVolumeLinkedStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
|
||||
return func() (string, error) {
|
||||
log.Printf("[Debug] Check if volume with id %s exists", id)
|
||||
resp, err := conn.POST_ReadVolumes(oapi.ReadVolumesRequest{
|
||||
Filters: oapi.FiltersVolume{
|
||||
VolumeIds: []string{id},
|
||||
},
|
||||
resp, _, err := conn.VolumeApi.ReadVolumes(context.Background(), &osc.ReadVolumesOpts{
|
||||
ReadVolumesRequest: optional.NewInterface(osc.ReadVolumesRequest{
|
||||
Filters: osc.FiltersVolume{
|
||||
VolumeIds: []string{id},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.OK == nil {
|
||||
return "", fmt.Errorf("Vm with ID %s. Not Found", id)
|
||||
}
|
||||
|
||||
log.Printf("[Debug] Read Response %+v", resp.OK)
|
||||
|
||||
if len(resp.OK.Volumes) == 0 {
|
||||
if len(resp.Volumes) == 0 {
|
||||
return "pending", nil
|
||||
}
|
||||
|
||||
if len(resp.OK.Volumes[0].LinkedVolumes) == 0 {
|
||||
if len(resp.Volumes[0].LinkedVolumes) == 0 {
|
||||
return "pending", nil
|
||||
}
|
||||
|
||||
return resp.OK.Volumes[0].LinkedVolumes[0].State, nil
|
||||
return resp.Volumes[0].LinkedVolumes[0].State, nil
|
||||
}
|
||||
}
|
||||
|
||||
func waitUntilVolumeUnLinkedStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
|
||||
func waitUntilOscVolumeUnLinkedStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
|
||||
return func() (string, error) {
|
||||
log.Printf("[Debug] Check if volume with id %s exists", id)
|
||||
resp, err := conn.POST_ReadVolumes(oapi.ReadVolumesRequest{
|
||||
Filters: oapi.FiltersVolume{
|
||||
VolumeIds: []string{id},
|
||||
},
|
||||
resp, _, err := conn.VolumeApi.ReadVolumes(context.Background(), &osc.ReadVolumesOpts{
|
||||
ReadVolumesRequest: optional.NewInterface(osc.ReadVolumesRequest{
|
||||
Filters: osc.FiltersVolume{
|
||||
VolumeIds: []string{id},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.OK == nil {
|
||||
return "", fmt.Errorf("Vm with ID %s. Not Found", id)
|
||||
}
|
||||
|
||||
log.Printf("[Debug] Read Response %+v", resp.OK)
|
||||
|
||||
if len(resp.OK.Volumes) == 0 {
|
||||
if len(resp.Volumes) == 0 {
|
||||
return "pending", nil
|
||||
}
|
||||
|
||||
if len(resp.OK.Volumes[0].LinkedVolumes) == 0 {
|
||||
if len(resp.Volumes[0].LinkedVolumes) == 0 {
|
||||
return "dettached", nil
|
||||
}
|
||||
|
||||
@@ -175,149 +162,106 @@ func waitUntilVolumeUnLinkedStateFunc(conn *oapi.Client, id string) stateRefresh
|
||||
}
|
||||
}
|
||||
|
||||
func waitUntilSnapshotStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
|
||||
func waitUntilOscSnapshotStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
|
||||
return func() (string, error) {
|
||||
log.Printf("[Debug] Check if Snapshot with id %s exists", id)
|
||||
resp, err := conn.POST_ReadSnapshots(oapi.ReadSnapshotsRequest{
|
||||
Filters: oapi.FiltersSnapshot{
|
||||
SnapshotIds: []string{id},
|
||||
},
|
||||
resp, _, err := conn.SnapshotApi.ReadSnapshots(context.Background(), &osc.ReadSnapshotsOpts{
|
||||
ReadSnapshotsRequest: optional.NewInterface(osc.ReadSnapshotsRequest{
|
||||
Filters: osc.FiltersSnapshot{
|
||||
SnapshotIds: []string{id},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
log.Printf("[Debug] Read Response %+v", resp.OK)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.OK == nil {
|
||||
return "", fmt.Errorf("Vm with ID %s. Not Found", id)
|
||||
}
|
||||
|
||||
if len(resp.OK.Snapshots) == 0 {
|
||||
if len(resp.Snapshots) == 0 {
|
||||
return "pending", nil
|
||||
}
|
||||
|
||||
return resp.OK.Snapshots[0].State, nil
|
||||
return resp.Snapshots[0].State, nil
|
||||
}
|
||||
}
|
||||
|
||||
func waitUntilImageStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
|
||||
func waitUntilOscImageStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
|
||||
return func() (string, error) {
|
||||
log.Printf("[Debug] Check if Image with id %s exists", id)
|
||||
resp, err := conn.POST_ReadImages(oapi.ReadImagesRequest{
|
||||
Filters: oapi.FiltersImage{
|
||||
ImageIds: []string{id},
|
||||
},
|
||||
resp, _, err := conn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
|
||||
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
|
||||
Filters: osc.FiltersImage{
|
||||
ImageIds: []string{id},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
log.Printf("[Debug] Read Response %+v", resp.OK)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.OK == nil {
|
||||
return "", fmt.Errorf("Vm with ID %s. Not Found", id)
|
||||
}
|
||||
|
||||
if len(resp.OK.Images) == 0 {
|
||||
if len(resp.Images) == 0 {
|
||||
return "pending", nil
|
||||
}
|
||||
|
||||
if resp.OK.Images[0].State == "failed" {
|
||||
return resp.OK.Images[0].State, fmt.Errorf("Image (%s) creation is failed", id)
|
||||
if resp.Images[0].State == "failed" {
|
||||
return resp.Images[0].State, fmt.Errorf("Image (%s) creation is failed", id)
|
||||
}
|
||||
|
||||
return resp.OK.Images[0].State, nil
|
||||
return resp.Images[0].State, nil
|
||||
}
|
||||
}
|
||||
|
||||
func securityGroupWaitFunc(conn *oapi.Client, id string) stateRefreshFunc {
|
||||
return func() (string, error) {
|
||||
log.Printf("[Debug] Check if SG with id %s exists", id)
|
||||
resp, err := conn.POST_ReadSecurityGroups(oapi.ReadSecurityGroupsRequest{
|
||||
Filters: oapi.FiltersSecurityGroup{
|
||||
SecurityGroupIds: []string{id},
|
||||
},
|
||||
})
|
||||
|
||||
log.Printf("[Debug] Read Response %+v", resp.OK)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.OK == nil {
|
||||
return "", fmt.Errorf("Security Group with ID %s. Not Found", id)
|
||||
}
|
||||
|
||||
if len(resp.OK.SecurityGroups) == 0 {
|
||||
return "waiting", nil
|
||||
}
|
||||
|
||||
return "exists", nil
|
||||
}
|
||||
}
|
||||
|
||||
func waitUntilSnapshotDoneStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
|
||||
func waitUntilOscSnapshotDoneStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
|
||||
return func() (string, error) {
|
||||
log.Printf("[Debug] Check if Snapshot with id %s exists", id)
|
||||
resp, err := conn.POST_ReadSnapshots(oapi.ReadSnapshotsRequest{
|
||||
Filters: oapi.FiltersSnapshot{
|
||||
SnapshotIds: []string{id},
|
||||
},
|
||||
resp, _, err := conn.SnapshotApi.ReadSnapshots(context.Background(), &osc.ReadSnapshotsOpts{
|
||||
ReadSnapshotsRequest: optional.NewInterface(osc.ReadSnapshotsRequest{
|
||||
Filters: osc.FiltersSnapshot{
|
||||
SnapshotIds: []string{id},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
log.Printf("[Debug] Read Response %+v", resp.OK)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.OK == nil {
|
||||
if len(resp.Snapshots) == 0 {
|
||||
return "", fmt.Errorf("Snapshot with ID %s. Not Found", id)
|
||||
}
|
||||
|
||||
if len(resp.OK.Snapshots) == 0 {
|
||||
return "", fmt.Errorf("Snapshot with ID %s. Not Found", id)
|
||||
if resp.Snapshots[0].State == "error" {
|
||||
return resp.Snapshots[0].State, fmt.Errorf("Snapshot (%s) creation is failed", id)
|
||||
}
|
||||
|
||||
if resp.OK.Snapshots[0].State == "error" {
|
||||
return resp.OK.Snapshots[0].State, fmt.Errorf("Snapshot (%s) creation is failed", id)
|
||||
}
|
||||
|
||||
return resp.OK.Snapshots[0].State, nil
|
||||
return resp.Snapshots[0].State, nil
|
||||
}
|
||||
}
|
||||
|
||||
func volumeWaitFunc(conn *oapi.Client, id string) stateRefreshFunc {
|
||||
func volumeOscWaitFunc(conn *osc.APIClient, id string) stateRefreshFunc {
|
||||
return func() (string, error) {
|
||||
log.Printf("[Debug] Check if SvolumeG with id %s exists", id)
|
||||
resp, err := conn.POST_ReadVolumes(oapi.ReadVolumesRequest{
|
||||
Filters: oapi.FiltersVolume{
|
||||
VolumeIds: []string{id},
|
||||
},
|
||||
resp, _, err := conn.VolumeApi.ReadVolumes(context.Background(), &osc.ReadVolumesOpts{
|
||||
ReadVolumesRequest: optional.NewInterface(osc.ReadVolumesRequest{
|
||||
Filters: osc.FiltersVolume{
|
||||
VolumeIds: []string{id},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
log.Printf("[Debug] Read Response %+v", resp.OK)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.OK == nil {
|
||||
return "", fmt.Errorf("Volume with ID %s. Not Found", id)
|
||||
}
|
||||
|
||||
if len(resp.OK.Volumes) == 0 {
|
||||
if len(resp.Volumes) == 0 {
|
||||
return "waiting", nil
|
||||
}
|
||||
|
||||
if resp.OK.Volumes[0].State == "error" {
|
||||
return resp.OK.Volumes[0].State, fmt.Errorf("Volume (%s) creation is failed", id)
|
||||
if resp.Volumes[0].State == "error" {
|
||||
return resp.Volumes[0].State, fmt.Errorf("Volume (%s) creation is failed", id)
|
||||
}
|
||||
|
||||
return resp.OK.Volumes[0].State, nil
|
||||
return resp.Volumes[0].State, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,29 +5,32 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// stepCleanupVolumes cleans up any orphaned volumes that were not designated to
|
||||
// StepCleanupVolumes cleans up any orphaned volumes that were not designated to
|
||||
// remain after termination of the vm. These volumes are typically ones
|
||||
// that are marked as "delete on terminate:false" in the source_ami of a build.
|
||||
type StepCleanupVolumes struct {
|
||||
BlockDevices BlockDevices
|
||||
}
|
||||
|
||||
//Run ...
|
||||
func (s *StepCleanupVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
// stepCleanupVolumes is for Cleanup only
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Cleanup ...
|
||||
func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
vmRaw := state.Get("vm")
|
||||
var vm oapi.Vm
|
||||
var vm osc.Vm
|
||||
if vmRaw != nil {
|
||||
vm = vmRaw.(oapi.Vm)
|
||||
vm = vmRaw.(osc.Vm)
|
||||
}
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
if vm.VmId == "" {
|
||||
@@ -42,7 +45,7 @@ func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
var vl []string
|
||||
volList := make(map[string]string)
|
||||
for _, bdm := range vm.BlockDeviceMappings {
|
||||
if !reflect.DeepEqual(bdm.Bsu, oapi.BsuCreated{}) {
|
||||
if !reflect.DeepEqual(bdm.Bsu, osc.BsuCreated{}) {
|
||||
vl = append(vl, bdm.Bsu.VolumeId)
|
||||
volList[bdm.Bsu.VolumeId] = bdm.DeviceName
|
||||
}
|
||||
@@ -50,10 +53,12 @@ func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
|
||||
// Using the volume list from the cached Vm, check with Outscale for up to
|
||||
// date information on them
|
||||
resp, err := oapiconn.POST_ReadVolumes(oapi.ReadVolumesRequest{
|
||||
Filters: oapi.FiltersVolume{
|
||||
VolumeIds: vl,
|
||||
},
|
||||
resp, _, err := oscconn.VolumeApi.ReadVolumes(context.Background(), &osc.ReadVolumesOpts{
|
||||
ReadVolumesRequest: optional.NewInterface(osc.ReadVolumesRequest{
|
||||
Filters: osc.FiltersVolume{
|
||||
VolumeIds: vl,
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -63,13 +68,13 @@ func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
|
||||
// If any of the returned volumes are in a "deleting" stage or otherwise not
|
||||
// available, remove them from the list of volumes
|
||||
for _, v := range resp.OK.Volumes {
|
||||
for _, v := range resp.Volumes {
|
||||
if v.State != "" && v.State != "available" {
|
||||
delete(volList, v.VolumeId)
|
||||
}
|
||||
}
|
||||
|
||||
if len(resp.OK.Volumes) == 0 {
|
||||
if len(resp.Volumes) == 0 {
|
||||
ui.Say("No volumes to clean up, skipping")
|
||||
return
|
||||
}
|
||||
@@ -87,10 +92,11 @@ func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
|
||||
// Destroy remaining volumes
|
||||
for k := range volList {
|
||||
ui.Say(fmt.Sprintf("Destroying volume (%s)...", k))
|
||||
_, err := oapiconn.POST_DeleteVolume(oapi.DeleteVolumeRequest{VolumeId: k})
|
||||
_, _, err := oscconn.VolumeApi.DeleteVolume(context.Background(), &osc.DeleteVolumeOpts{
|
||||
DeleteVolumeRequest: optional.NewInterface(osc.DeleteVolumeRequest{VolumeId: k}),
|
||||
})
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("Error deleting volume: %s", err))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,15 @@ package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
retry "github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
type StepCreateTags struct {
|
||||
@@ -21,8 +20,7 @@ type StepCreateTags struct {
|
||||
}
|
||||
|
||||
func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
config := state.Get("clientConfig").(*oapi.Config)
|
||||
config := state.Get("accessConfig").(*AccessConfig)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
omis := state.Get("omis").(map[string]string)
|
||||
|
||||
@@ -34,28 +32,16 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||
for region, ami := range omis {
|
||||
ui.Say(fmt.Sprintf("Adding tags to OMI (%s)...", ami))
|
||||
|
||||
newConfig := &oapi.Config{
|
||||
UserAgent: config.UserAgent,
|
||||
SecretKey: config.SecretKey,
|
||||
Service: config.Service,
|
||||
Region: region, //New region
|
||||
URL: config.URL,
|
||||
}
|
||||
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
regionConn := oapi.NewClient(newConfig, skipClient)
|
||||
regionconn := config.NewOSCClientByRegion(region)
|
||||
|
||||
// Retrieve image list for given OMI
|
||||
resourceIds := []string{ami}
|
||||
imageResp, err := regionConn.POST_ReadImages(oapi.ReadImagesRequest{
|
||||
Filters: oapi.FiltersImage{
|
||||
ImageIds: resourceIds,
|
||||
},
|
||||
imageResp, _, err := regionconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
|
||||
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
|
||||
Filters: osc.FiltersImage{
|
||||
ImageIds: resourceIds,
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -65,14 +51,14 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(imageResp.OK.Images) == 0 {
|
||||
if len(imageResp.Images) == 0 {
|
||||
err := fmt.Errorf("Error retrieving details for OMI (%s), no images found", ami)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
image := imageResp.OK.Images[0]
|
||||
image := imageResp.Images[0]
|
||||
snapshotIds := []string{}
|
||||
|
||||
// Add only those with a Snapshot ID, i.e. not Ephemeral
|
||||
@@ -86,7 +72,7 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||
|
||||
// Convert tags to oapi.Tag format
|
||||
ui.Say("Creating OMI tags")
|
||||
amiTags, err := s.Tags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
|
||||
amiTags, err := s.Tags.OSCTags(s.Ctx, config.RawRegion, state)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
@@ -95,7 +81,7 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||
amiTags.Report(ui)
|
||||
|
||||
ui.Say("Creating snapshot tags")
|
||||
snapshotTags, err := s.SnapshotTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
|
||||
snapshotTags, err := s.SnapshotTags.OSCTags(s.Ctx, config.RawRegion, state)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
@@ -106,9 +92,11 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
|
||||
// Tag images and snapshots
|
||||
_, err := regionConn.POST_CreateTags(oapi.CreateTagsRequest{
|
||||
ResourceIds: resourceIds,
|
||||
Tags: amiTags,
|
||||
_, _, err := regionconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
|
||||
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
|
||||
ResourceIds: resourceIds,
|
||||
Tags: amiTags,
|
||||
}),
|
||||
})
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
if awsErr.Code() == "InvalidOMIID.NotFound" ||
|
||||
@@ -119,9 +107,11 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||
|
||||
// Override tags on snapshots
|
||||
if len(snapshotTags) > 0 {
|
||||
_, err = regionConn.POST_CreateTags(oapi.CreateTagsRequest{
|
||||
ResourceIds: snapshotIds,
|
||||
Tags: snapshotTags,
|
||||
_, _, err = regionconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
|
||||
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
|
||||
ResourceIds: snapshotIds,
|
||||
Tags: snapshotTags,
|
||||
}),
|
||||
})
|
||||
}
|
||||
if err == nil {
|
||||
|
||||
@@ -2,13 +2,14 @@ package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"log"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
)
|
||||
|
||||
type StepDeregisterOMI struct {
|
||||
@@ -26,76 +27,72 @@ func (s *StepDeregisterOMI) Run(_ context.Context, state multistep.StateBag) mul
|
||||
}
|
||||
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
// Add the session region to list of regions will will deregister OMIs in
|
||||
regions := append(s.Regions, oapiconn.GetConfig().Region)
|
||||
|
||||
for _, region := range regions {
|
||||
s.Regions = append(s.Regions, s.AccessConfig.GetRegion())
|
||||
|
||||
log.Printf("LOG_ s.Regions: %#+v\n", s.Regions)
|
||||
|
||||
for _, region := range s.Regions {
|
||||
// get new connection for each region in which we need to deregister vms
|
||||
config, err := s.AccessConfig.Config()
|
||||
if err != nil {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
conn := s.AccessConfig.NewOSCClientByRegion(region)
|
||||
|
||||
newConfig := &oapi.Config{
|
||||
UserAgent: config.UserAgent,
|
||||
SecretKey: config.SecretKey,
|
||||
Service: config.Service,
|
||||
Region: region, //New region
|
||||
URL: config.URL,
|
||||
}
|
||||
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
regionconn := oapi.NewClient(newConfig, skipClient)
|
||||
|
||||
resp, err := regionconn.POST_ReadImages(oapi.ReadImagesRequest{
|
||||
Filters: oapi.FiltersImage{
|
||||
ImageNames: []string{s.OMIName},
|
||||
AccountAliases: []string{"self"},
|
||||
},
|
||||
resp, _, err := conn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
|
||||
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
|
||||
Filters: osc.FiltersImage{
|
||||
ImageNames: []string{s.OMIName},
|
||||
//AccountAliases: []string{"self"},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error describing OMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("LOG_ resp.Images: %#+v\n", resp.Images)
|
||||
|
||||
// Deregister image(s) by name
|
||||
for _, i := range resp.OK.Images {
|
||||
for i := range resp.Images {
|
||||
//We are supposing that DeleteImage does the same action as DeregisterImage
|
||||
_, err := regionconn.POST_DeleteImage(oapi.DeleteImageRequest{
|
||||
ImageId: i.ImageId,
|
||||
_, _, err := conn.ImageApi.DeleteImage(context.Background(), &osc.DeleteImageOpts{
|
||||
DeleteImageRequest: optional.NewInterface(osc.DeleteImageRequest{
|
||||
ImageId: resp.Images[i].ImageId,
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error deregistering existing OMI: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Deregistered OMI %s, id: %s", s.OMIName, i.ImageId))
|
||||
|
||||
ui.Say(fmt.Sprintf("Deregistered OMI %s, id: %s", s.OMIName, resp.Images[i].ImageId))
|
||||
|
||||
// Delete snapshot(s) by image
|
||||
if s.ForceDeleteSnapshot {
|
||||
for _, b := range i.BlockDeviceMappings {
|
||||
for _, b := range resp.Images[i].BlockDeviceMappings {
|
||||
if b.Bsu.SnapshotId != "" {
|
||||
_, err := regionconn.POST_DeleteSnapshot(oapi.DeleteSnapshotRequest{
|
||||
SnapshotId: b.Bsu.SnapshotId,
|
||||
|
||||
_, _, err := conn.SnapshotApi.DeleteSnapshot(context.Background(), &osc.DeleteSnapshotOpts{
|
||||
DeleteSnapshotRequest: optional.NewInterface(osc.DeleteSnapshotRequest{
|
||||
SnapshotId: b.Bsu.SnapshotId,
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error deleting existing snapshot: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Deleted snapshot: %s", b.Bsu.SnapshotId))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,11 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// StepGetPassword reads the password from a Windows server and sets it
|
||||
@@ -102,8 +103,8 @@ WaitLoop:
|
||||
func (s *StepGetPassword) Cleanup(multistep.StateBag) {}
|
||||
|
||||
func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
vm := state.Get("vm").(oapi.Vm)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
vm := state.Get("vm").(osc.Vm)
|
||||
privateKey := s.Comm.SSHPrivateKey
|
||||
|
||||
for {
|
||||
@@ -114,17 +115,19 @@ func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-cha
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
|
||||
resp, err := oapiconn.POST_ReadAdminPassword(oapi.ReadAdminPasswordRequest{
|
||||
VmId: vm.VmId,
|
||||
resp, _, err := oscconn.VmApi.ReadAdminPassword(context.Background(), &osc.ReadAdminPasswordOpts{
|
||||
ReadAdminPasswordRequest: optional.NewInterface(osc.ReadAdminPasswordRequest{
|
||||
VmId: vm.VmId,
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error retrieving auto-generated vm password: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.OK.AdminPassword != "" {
|
||||
if resp.AdminPassword != "" {
|
||||
decryptedPassword, err := decryptPasswordDataWithPrivateKey(
|
||||
resp.OK.AdminPassword, []byte(privateKey))
|
||||
resp.AdminPassword, privateKey)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error decrypting auto-generated vm password: %s", err)
|
||||
return "", err
|
||||
|
||||
@@ -6,10 +6,11 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
type StepKeyPair struct {
|
||||
@@ -52,11 +53,16 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
conn := state.Get("osc").(*osc.APIClient)
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
|
||||
keyResp, err := oapiconn.POST_CreateKeypair(oapi.CreateKeypairRequest{
|
||||
KeypairName: s.Comm.SSHTemporaryKeyPairName})
|
||||
|
||||
resp, _, err := conn.KeypairApi.CreateKeypair(context.Background(), &osc.CreateKeypairOpts{
|
||||
CreateKeypairRequest: optional.NewInterface(osc.CreateKeypairRequest{
|
||||
KeypairName: s.Comm.SSHTemporaryKeyPairName,
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
|
||||
return multistep.ActionHalt
|
||||
@@ -66,7 +72,7 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
|
||||
|
||||
// Set some data for use in future steps
|
||||
s.Comm.SSHKeyPairName = s.Comm.SSHTemporaryKeyPairName
|
||||
s.Comm.SSHPrivateKey = []byte(keyResp.OK.Keypair.PrivateKey)
|
||||
s.Comm.SSHPrivateKey = []byte(resp.Keypair.PrivateKey)
|
||||
|
||||
// If we're in debug mode, output the private key to the working
|
||||
// directory.
|
||||
@@ -80,7 +86,7 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
|
||||
defer f.Close()
|
||||
|
||||
// Write the key out
|
||||
if _, err := f.Write([]byte(keyResp.OK.Keypair.PrivateKey)); err != nil {
|
||||
if _, err := f.Write([]byte(resp.Keypair.PrivateKey)); err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
@@ -102,12 +108,19 @@ func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
|
||||
return
|
||||
}
|
||||
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
var (
|
||||
conn = state.Get("osc").(*osc.APIClient)
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
)
|
||||
|
||||
// Remove the keypair
|
||||
ui.Say("Deleting temporary keypair...")
|
||||
_, err := oapiconn.POST_DeleteKeypair(oapi.DeleteKeypairRequest{KeypairName: s.Comm.SSHTemporaryKeyPairName})
|
||||
_, _, err := conn.KeypairApi.DeleteKeypair(context.Background(), &osc.DeleteKeypairOpts{
|
||||
DeleteKeypairRequest: optional.NewInterface(osc.DeleteKeypairRequest{
|
||||
KeypairName: s.Comm.SSHTemporaryKeyPairName,
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error cleaning up keypair. Please delete the key manually: %s", s.Comm.SSHTemporaryKeyPairName))
|
||||
|
||||
@@ -7,9 +7,10 @@ import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// StepNetworkInfo queries OUTSCALE for information about
|
||||
@@ -29,34 +30,37 @@ type StepNetworkInfo struct {
|
||||
SecurityGroupFilter SecurityGroupFilterOptions
|
||||
}
|
||||
|
||||
type subnetsSort []oapi.Subnet
|
||||
type subnetsOscSort []osc.Subnet
|
||||
|
||||
func (a subnetsSort) Len() int { return len(a) }
|
||||
func (a subnetsSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a subnetsSort) Less(i, j int) bool {
|
||||
func (a subnetsOscSort) Len() int { return len(a) }
|
||||
func (a subnetsOscSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a subnetsOscSort) Less(i, j int) bool {
|
||||
return a[i].AvailableIpsCount < a[j].AvailableIpsCount
|
||||
}
|
||||
|
||||
// Returns the most recent OMI out of a slice of images.
|
||||
func mostFreeSubnet(subnets []oapi.Subnet) oapi.Subnet {
|
||||
func mostFreeOscSubnet(subnets []osc.Subnet) osc.Subnet {
|
||||
sortedSubnets := subnets
|
||||
sort.Sort(subnetsSort(sortedSubnets))
|
||||
sort.Sort(subnetsOscSort(sortedSubnets))
|
||||
return sortedSubnets[len(sortedSubnets)-1]
|
||||
}
|
||||
|
||||
//Run ...
|
||||
func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// NET
|
||||
if s.NetId == "" && !s.NetFilter.Empty() {
|
||||
params := oapi.ReadNetsRequest{}
|
||||
params.Filters = buildNetFilters(s.NetFilter.Filters)
|
||||
params := osc.ReadNetsRequest{}
|
||||
params.Filters = buildOscNetFilters(s.NetFilter.Filters)
|
||||
s.NetFilter.Filters["state"] = "available"
|
||||
|
||||
log.Printf("Using NET Filters %v", params)
|
||||
|
||||
vpcResp, err := oapiconn.POST_ReadNets(params)
|
||||
vpcResp, _, err := oscconn.NetApi.ReadNets(context.Background(), &osc.ReadNetsOpts{
|
||||
ReadNetsRequest: optional.NewInterface(params),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying NETs: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -64,20 +68,20 @@ func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multi
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(vpcResp.OK.Nets) != 1 {
|
||||
err := fmt.Errorf("Exactly one NET should match the filter, but %d NET's was found matching filters: %v", len(vpcResp.OK.Nets), params)
|
||||
if len(vpcResp.Nets) != 1 {
|
||||
err := fmt.Errorf("Exactly one NET should match the filter, but %d NET's was found matching filters: %v", len(vpcResp.Nets), params)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.NetId = vpcResp.OK.Nets[0].NetId
|
||||
s.NetId = vpcResp.Nets[0].NetId
|
||||
ui.Message(fmt.Sprintf("Found NET ID: %s", s.NetId))
|
||||
}
|
||||
|
||||
// Subnet
|
||||
if s.SubnetId == "" && !s.SubnetFilter.Empty() {
|
||||
params := oapi.ReadSubnetsRequest{}
|
||||
params := osc.ReadSubnetsRequest{}
|
||||
s.SubnetFilter.Filters["state"] = "available"
|
||||
|
||||
if s.NetId != "" {
|
||||
@@ -86,39 +90,41 @@ func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multi
|
||||
if s.SubregionName != "" {
|
||||
s.SubnetFilter.Filters["availability-zone"] = s.SubregionName
|
||||
}
|
||||
params.Filters = buildSubnetFilters(s.SubnetFilter.Filters)
|
||||
params.Filters = buildOscSubnetFilters(s.SubnetFilter.Filters)
|
||||
log.Printf("Using Subnet Filters %v", params)
|
||||
|
||||
subnetsResp, err := oapiconn.POST_ReadSubnets(params)
|
||||
subnetsResp, _, err := oscconn.SubnetApi.ReadSubnets(context.Background(), &osc.ReadSubnetsOpts{
|
||||
ReadSubnetsRequest: optional.NewInterface(params),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying Subnets: %s", err)
|
||||
err := fmt.Errorf("error querying Subnets: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(subnetsResp.OK.Subnets) == 0 {
|
||||
if len(subnetsResp.Subnets) == 0 {
|
||||
err := fmt.Errorf("No Subnets was found matching filters: %v", params)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(subnetsResp.OK.Subnets) > 1 && !s.SubnetFilter.Random && !s.SubnetFilter.MostFree {
|
||||
err := fmt.Errorf("Your filter matched %d Subnets. Please try a more specific search, or set random or most_free to true.", len(subnetsResp.OK.Subnets))
|
||||
if len(subnetsResp.Subnets) > 1 && !s.SubnetFilter.Random && !s.SubnetFilter.MostFree {
|
||||
err := fmt.Errorf("your filter matched %d Subnets. Please try a more specific search, or set random or most_free to true", len(subnetsResp.Subnets))
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var subnet oapi.Subnet
|
||||
var subnet osc.Subnet
|
||||
switch {
|
||||
case s.SubnetFilter.MostFree:
|
||||
subnet = mostFreeSubnet(subnetsResp.OK.Subnets)
|
||||
subnet = mostFreeOscSubnet(subnetsResp.Subnets)
|
||||
case s.SubnetFilter.Random:
|
||||
subnet = subnetsResp.OK.Subnets[rand.Intn(len(subnetsResp.OK.Subnets))]
|
||||
subnet = subnetsResp.Subnets[rand.Intn(len(subnetsResp.Subnets))]
|
||||
default:
|
||||
subnet = subnetsResp.OK.Subnets[0]
|
||||
subnet = subnetsResp.Subnets[0]
|
||||
}
|
||||
s.SubnetId = subnet.SubnetId
|
||||
ui.Message(fmt.Sprintf("Found Subnet ID: %s", s.SubnetId))
|
||||
@@ -127,24 +133,25 @@ func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multi
|
||||
// Try to find Subregion and NET Id from Subnet if they are not yet found/given
|
||||
if s.SubnetId != "" && (s.SubregionName == "" || s.NetId == "") {
|
||||
log.Printf("[INFO] Finding Subregion and NetId for the given subnet '%s'", s.SubnetId)
|
||||
resp, err := oapiconn.POST_ReadSubnets(
|
||||
oapi.ReadSubnetsRequest{
|
||||
Filters: oapi.FiltersSubnet{
|
||||
resp, _, err := oscconn.SubnetApi.ReadSubnets(context.Background(), &osc.ReadSubnetsOpts{
|
||||
ReadSubnetsRequest: optional.NewInterface(osc.ReadSubnetsRequest{
|
||||
Filters: osc.FiltersSubnet{
|
||||
SubnetIds: []string{s.SubnetId},
|
||||
},
|
||||
})
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Describing the subnet: %s returned error: %s.", s.SubnetId, err)
|
||||
err := fmt.Errorf("describing the subnet: %s returned error: %s", s.SubnetId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if s.SubregionName == "" {
|
||||
s.SubregionName = resp.OK.Subnets[0].SubregionName
|
||||
s.SubregionName = resp.Subnets[0].SubregionName
|
||||
log.Printf("[INFO] SubregionName found: '%s'", s.SubregionName)
|
||||
}
|
||||
if s.NetId == "" {
|
||||
s.NetId = resp.OK.Subnets[0].NetId
|
||||
s.NetId = resp.Subnets[0].NetId
|
||||
log.Printf("[INFO] NetId found: '%s'", s.NetId)
|
||||
}
|
||||
}
|
||||
@@ -155,4 +162,5 @@ func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multi
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
//Cleanup ...
|
||||
func (s *StepNetworkInfo) Cleanup(multistep.StateBag) {}
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// StepPreValidate provides an opportunity to pre-validate any configuration for
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
type StepPreValidate struct {
|
||||
DestOmiName string
|
||||
ForceDeregister bool
|
||||
API string
|
||||
}
|
||||
|
||||
func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
@@ -24,11 +26,19 @@ func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multi
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
var (
|
||||
conn = state.Get("osc").(*osc.APIClient)
|
||||
images []interface{}
|
||||
)
|
||||
|
||||
ui.Say(fmt.Sprintf("Prevalidating OMI Name: %s", s.DestOmiName))
|
||||
resp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
|
||||
Filters: oapi.FiltersImage{ImageNames: []string{s.DestOmiName}},
|
||||
|
||||
resp, _, err := conn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
|
||||
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
|
||||
Filters: osc.FiltersImage{
|
||||
ImageNames: []string{s.DestOmiName},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -38,18 +48,14 @@ func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multi
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
//FIXME: Remove when the oAPI filters works
|
||||
images := make([]oapi.Image, 0)
|
||||
|
||||
for _, omi := range resp.OK.Images {
|
||||
for _, omi := range resp.Images {
|
||||
if omi.ImageName == s.DestOmiName {
|
||||
images = append(images, omi)
|
||||
}
|
||||
}
|
||||
|
||||
//if len(resp.OK.Images) > 0 {
|
||||
if len(images) > 0 {
|
||||
err := fmt.Errorf("Error: name conflicts with an existing OMI: %s", resp.OK.Images[0].ImageId)
|
||||
err := fmt.Errorf("Error: name conflicts with an existing OMI: %s", s.DestOmiName)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
type StepPublicIp struct {
|
||||
@@ -20,20 +21,24 @@ type StepPublicIp struct {
|
||||
}
|
||||
|
||||
func (s *StepPublicIp) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
netId := state.Get("net_id").(string)
|
||||
subnetId := state.Get("subnet_id").(string)
|
||||
var (
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
conn = state.Get("osc").(*osc.APIClient)
|
||||
)
|
||||
|
||||
if !s.AssociatePublicIpAddress {
|
||||
|
||||
if netId == "" || subnetId == "" || !s.AssociatePublicIpAddress {
|
||||
// In this case, we are in the public Cloud, so we'll
|
||||
// not explicitely allocate a public IP.
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating temporary PublicIp for instance in subnet %s (net %s)", subnetId, netId))
|
||||
ui.Say("Creating temporary PublicIp for instance ")
|
||||
|
||||
resp, _, err := conn.PublicIpApi.CreatePublicIp(context.Background(), &osc.CreatePublicIpOpts{
|
||||
CreatePublicIpRequest: optional.NewInterface(osc.CreatePublicIpRequest{}),
|
||||
})
|
||||
|
||||
publicIpResp, err := oapiconn.POST_CreatePublicIp(oapi.CreatePublicIpRequest{})
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error creating temporary PublicIp: %s", err))
|
||||
return multistep.ActionHalt
|
||||
@@ -43,8 +48,8 @@ func (s *StepPublicIp) Run(_ context.Context, state multistep.StateBag) multiste
|
||||
s.doCleanup = true
|
||||
|
||||
// Set some data for use in future steps
|
||||
s.publicIpId = publicIpResp.OK.PublicIp.PublicIpId
|
||||
state.Put("publicip_id", publicIpResp.OK.PublicIp.PublicIpId)
|
||||
s.publicIpId = resp.PublicIp.PublicIpId
|
||||
state.Put("publicip_id", resp.PublicIp.PublicIpId)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
@@ -54,15 +59,20 @@ func (s *StepPublicIp) Cleanup(state multistep.StateBag) {
|
||||
return
|
||||
}
|
||||
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
var (
|
||||
conn = state.Get("osc").(*osc.APIClient)
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
)
|
||||
|
||||
// Remove the Public IP
|
||||
ui.Say("Deleting temporary PublicIp...")
|
||||
_, err := oapiconn.POST_DeletePublicIp(oapi.DeletePublicIpRequest{PublicIpId: s.publicIpId})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error cleaning up PublicIp. Please delete the PublicIp manually: %s", s.publicIpId))
|
||||
}
|
||||
_, _, err := conn.PublicIpApi.DeletePublicIp(context.Background(), &osc.DeletePublicIpOpts{
|
||||
DeletePublicIpRequest: optional.NewInterface(osc.DeletePublicIpRequest{
|
||||
PublicIpId: s.publicIpId,
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error cleaning up PublicIp. Please delete the PublicIp manually: %s", s.publicIpId))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
"log"
|
||||
"reflect"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
|
||||
retry "github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
@@ -39,13 +40,13 @@ type StepRunSourceVm struct {
|
||||
UserData string
|
||||
UserDataFile string
|
||||
VolumeTags TagMap
|
||||
RawRegion string
|
||||
|
||||
vmId string
|
||||
}
|
||||
|
||||
func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
securityGroupIds := state.Get("securityGroupIds").([]string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
@@ -67,7 +68,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
}
|
||||
|
||||
ui.Say("Launching a source OUTSCALE vm...")
|
||||
image, ok := state.Get("source_image").(oapi.Image)
|
||||
image, ok := state.Get("source_image").(osc.Image)
|
||||
if !ok {
|
||||
state.Put("error", fmt.Errorf("source_image type assertion failed"))
|
||||
return multistep.ActionHalt
|
||||
@@ -89,7 +90,9 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
s.Tags["Name"] = "Packer Builder"
|
||||
}
|
||||
|
||||
oapiTags, err := s.Tags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
|
||||
rawRegion := s.RawRegion
|
||||
|
||||
oscTags, err := s.Tags.OSCTags(s.Ctx, rawRegion, state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging source vm: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -97,7 +100,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
volTags, err := s.VolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
|
||||
volTags, err := s.VolumeTags.OSCTags(s.Ctx, rawRegion, state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging volumes: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -106,73 +109,42 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
}
|
||||
|
||||
subregion := state.Get("subregion_name").(string)
|
||||
runOpts := oapi.CreateVmsRequest{
|
||||
runOpts := osc.CreateVmsRequest{
|
||||
ImageId: s.SourceOMI,
|
||||
VmType: s.VmType,
|
||||
UserData: userData,
|
||||
MaxVmsCount: 1,
|
||||
MinVmsCount: 1,
|
||||
Placement: oapi.Placement{SubregionName: subregion},
|
||||
Placement: osc.Placement{SubregionName: subregion},
|
||||
BsuOptimized: s.BsuOptimized,
|
||||
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
|
||||
//IamVmProfile: oapi.IamVmProfileSpecification{Name: &s.IamVmProfile},
|
||||
BlockDeviceMappings: s.BlockDevices.BuildOSCLaunchDevices(),
|
||||
}
|
||||
|
||||
// if s.EnableT2Unlimited {
|
||||
// creditOption := "unlimited"
|
||||
// runOpts.CreditSpecification = &oapi.CreditSpecificationRequest{CpuCredits: &creditOption}
|
||||
// }
|
||||
|
||||
// Collect tags for tagging on resource creation
|
||||
// var tagSpecs []oapi.ResourceTag
|
||||
|
||||
// if len(oapiTags) > 0 {
|
||||
// runTags := &oapi.ResourceTag{
|
||||
// ResourceType: aws.String("vm"),
|
||||
// Tags: oapiTags,
|
||||
// }
|
||||
|
||||
// tagSpecs = append(tagSpecs, runTags)
|
||||
// }
|
||||
|
||||
// if len(volTags) > 0 {
|
||||
// runVolTags := &oapi.TagSpecification{
|
||||
// ResourceType: aws.String("volume"),
|
||||
// Tags: volTags,
|
||||
// }
|
||||
|
||||
// tagSpecs = append(tagSpecs, runVolTags)
|
||||
// }
|
||||
|
||||
// // If our region supports it, set tag specifications
|
||||
// if len(tagSpecs) > 0 && !s.IsRestricted {
|
||||
// runOpts.SetTagSpecifications(tagSpecs)
|
||||
// oapiTags.Report(ui)
|
||||
// volTags.Report(ui)
|
||||
// }
|
||||
|
||||
if s.Comm.SSHKeyPairName != "" {
|
||||
runOpts.KeypairName = s.Comm.SSHKeyPairName
|
||||
}
|
||||
|
||||
subnetId := state.Get("subnet_id").(string)
|
||||
subnetID := state.Get("subnet_id").(string)
|
||||
|
||||
runOpts.SubnetId = subnetId
|
||||
runOpts.SubnetId = subnetID
|
||||
runOpts.SecurityGroupIds = securityGroupIds
|
||||
|
||||
if s.ExpectedRootDevice == "bsu" {
|
||||
runOpts.VmInitiatedShutdownBehavior = s.VmInitiatedShutdownBehavior
|
||||
}
|
||||
|
||||
runResp, err := oapiconn.POST_CreateVms(runOpts)
|
||||
runResp, _, err := oscconn.VmApi.CreateVms(context.Background(), &osc.CreateVmsOpts{
|
||||
CreateVmsRequest: optional.NewInterface(runOpts),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error launching source vm: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
vmId = runResp.OK.Vms[0].VmId
|
||||
volumeId := runResp.OK.Vms[0].BlockDeviceMappings[0].Bsu.VolumeId
|
||||
vmId = runResp.Vms[0].VmId
|
||||
volumeId := runResp.Vms[0].BlockDeviceMappings[0].Bsu.VolumeId
|
||||
|
||||
// Set the vm ID so that the cleanup works properly
|
||||
s.vmId = vmId
|
||||
@@ -180,12 +152,12 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
ui.Message(fmt.Sprintf("Vm ID: %s", vmId))
|
||||
ui.Say(fmt.Sprintf("Waiting for vm (%v) to become ready...", vmId))
|
||||
|
||||
request := oapi.ReadVmsRequest{
|
||||
Filters: oapi.FiltersVm{
|
||||
request := osc.ReadVmsRequest{
|
||||
Filters: osc.FiltersVm{
|
||||
VmIds: []string{vmId},
|
||||
},
|
||||
}
|
||||
if err := waitUntilForVmRunning(oapiconn, vmId); err != nil {
|
||||
if err := waitUntilForOscVmRunning(oscconn, vmId); err != nil {
|
||||
err := fmt.Errorf("Error waiting for vm (%s) to become ready: %s", vmId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
@@ -193,8 +165,8 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
}
|
||||
|
||||
//Set Vm tags and vollume tags
|
||||
if len(oapiTags) > 0 {
|
||||
if err := CreateTags(oapiconn, s.vmId, ui, oapiTags); err != nil {
|
||||
if len(oscTags) > 0 {
|
||||
if err := CreateOSCTags(oscconn, s.vmId, ui, oscTags); err != nil {
|
||||
err := fmt.Errorf("Error creating tags for vm (%s): %s", s.vmId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
@@ -203,7 +175,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
}
|
||||
|
||||
if len(volTags) > 0 {
|
||||
if err := CreateTags(oapiconn, volumeId, ui, volTags); err != nil {
|
||||
if err := CreateOSCTags(oscconn, volumeId, ui, volTags); err != nil {
|
||||
err := fmt.Errorf("Error creating tags for volume (%s): %s", volumeId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
@@ -213,7 +185,9 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
|
||||
if publicip_id, ok := state.Get("publicip_id").(string); ok {
|
||||
ui.Say(fmt.Sprintf("Linking temporary PublicIp %s to instance %s", publicip_id, vmId))
|
||||
_, err := oapiconn.POST_LinkPublicIp(oapi.LinkPublicIpRequest{PublicIpId: publicip_id, VmId: vmId})
|
||||
_, _, err := oscconn.PublicIpApi.LinkPublicIp(context.Background(), &osc.LinkPublicIpOpts{
|
||||
LinkPublicIpRequest: optional.NewInterface(osc.LinkPublicIpRequest{PublicIpId: publicip_id, VmId: vmId}),
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error linking PublicIp to VM: %s", err))
|
||||
ui.Error(err.Error())
|
||||
@@ -221,9 +195,11 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := oapiconn.POST_ReadVms(request)
|
||||
resp, _, err := oscconn.VmApi.ReadVms(context.Background(), &osc.ReadVmsOpts{
|
||||
ReadVmsRequest: optional.NewInterface(request),
|
||||
})
|
||||
|
||||
r := resp.OK
|
||||
r := resp
|
||||
|
||||
if err != nil || len(r.Vms) == 0 {
|
||||
err := fmt.Errorf("Error finding source vm.")
|
||||
@@ -256,12 +232,14 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
// do that now.
|
||||
|
||||
if s.IsRestricted {
|
||||
oapiTags.Report(ui)
|
||||
oscTags.Report(ui)
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
|
||||
_, err := oapiconn.POST_CreateTags(oapi.CreateTagsRequest{
|
||||
Tags: oapiTags,
|
||||
ResourceIds: []string{vmId},
|
||||
_, _, err := oscconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
|
||||
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
|
||||
Tags: oscTags,
|
||||
ResourceIds: []string{vmId},
|
||||
}),
|
||||
})
|
||||
if err == nil {
|
||||
return true, nil
|
||||
@@ -286,7 +264,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
|
||||
volumeIds := make([]string, 0)
|
||||
for _, v := range vm.BlockDeviceMappings {
|
||||
if bsu := v.Bsu; !reflect.DeepEqual(bsu, oapi.BsuCreated{}) {
|
||||
if bsu := v.Bsu; !reflect.DeepEqual(bsu, osc.BsuCreated{}) {
|
||||
volumeIds = append(volumeIds, bsu.VolumeId)
|
||||
}
|
||||
}
|
||||
@@ -294,7 +272,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
if len(volumeIds) > 0 && s.VolumeTags.IsSet() {
|
||||
ui.Say("Adding tags to source BSU Volumes")
|
||||
|
||||
volumeTags, err := s.VolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
|
||||
volumeTags, err := s.VolumeTags.OSCTags(s.Ctx, rawRegion, state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error tagging source BSU Volumes on %s: %s", vm.VmId, err)
|
||||
state.Put("error", err)
|
||||
@@ -303,9 +281,11 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
}
|
||||
volumeTags.Report(ui)
|
||||
|
||||
_, err = oapiconn.POST_CreateTags(oapi.CreateTagsRequest{
|
||||
ResourceIds: volumeIds,
|
||||
Tags: volumeTags,
|
||||
_, _, err = oscconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
|
||||
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
|
||||
ResourceIds: volumeIds,
|
||||
Tags: volumeTags,
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -321,19 +301,20 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
}
|
||||
|
||||
func (s *StepRunSourceVm) Cleanup(state multistep.StateBag) {
|
||||
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Terminate the source vm if it exists
|
||||
if s.vmId != "" {
|
||||
ui.Say("Terminating the source OUTSCALE vm...")
|
||||
if _, err := oapiconn.POST_DeleteVms(oapi.DeleteVmsRequest{VmIds: []string{s.vmId}}); err != nil {
|
||||
if _, _, err := oscconn.VmApi.DeleteVms(context.Background(), &osc.DeleteVmsOpts{
|
||||
DeleteVmsRequest: optional.NewInterface(osc.DeleteVmsRequest{VmIds: []string{s.vmId}}),
|
||||
}); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error terminating vm, may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := waitUntilVmDeleted(oapiconn, s.vmId); err != nil {
|
||||
if err := waitUntilOscVmDeleted(oscconn, s.vmId); err != nil {
|
||||
ui.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/common/uuid"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
type StepSecurityGroup struct {
|
||||
@@ -24,50 +24,56 @@ type StepSecurityGroup struct {
|
||||
}
|
||||
|
||||
func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
netId := state.Get("net_id").(string)
|
||||
var (
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
conn = state.Get("osc").(*osc.APIClient)
|
||||
netID = state.Get("net_id").(string)
|
||||
)
|
||||
|
||||
if len(s.SecurityGroupIds) > 0 {
|
||||
resp, err := oapiconn.POST_ReadSecurityGroups(
|
||||
oapi.ReadSecurityGroupsRequest{
|
||||
Filters: oapi.FiltersSecurityGroup{
|
||||
resp, _, err := conn.SecurityGroupApi.ReadSecurityGroups(context.Background(), &osc.ReadSecurityGroupsOpts{
|
||||
ReadSecurityGroupsRequest: optional.NewInterface(osc.ReadSecurityGroupsRequest{
|
||||
Filters: osc.FiltersSecurityGroup{
|
||||
SecurityGroupIds: s.SecurityGroupIds,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil || resp.OK == nil || len(resp.OK.SecurityGroups) <= 0 {
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil || len(resp.SecurityGroups) == 0 {
|
||||
err := fmt.Errorf("Couldn't find specified security group: %s", err)
|
||||
log.Printf("[DEBUG] %s", err.Error())
|
||||
state.Put("error", err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Using specified security groups: %v", s.SecurityGroupIds)
|
||||
state.Put("securityGroupIds", s.SecurityGroupIds)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if !s.SecurityGroupFilter.Empty() {
|
||||
filterReq := buildSecurityGroupFilters(s.SecurityGroupFilter.Filters)
|
||||
|
||||
params := oapi.ReadSecurityGroupsRequest{}
|
||||
if netId != "" {
|
||||
s.SecurityGroupFilter.Filters["net-id"] = netId
|
||||
}
|
||||
params.Filters = buildSecurityGroupFilters(s.SecurityGroupFilter.Filters)
|
||||
log.Printf("Using SecurityGroup Filters %v", filterReq)
|
||||
|
||||
log.Printf("Using SecurityGroup Filters %v", params)
|
||||
resp, _, err := conn.SecurityGroupApi.ReadSecurityGroups(context.Background(), &osc.ReadSecurityGroupsOpts{
|
||||
ReadSecurityGroupsRequest: optional.NewInterface(osc.ReadSecurityGroupsRequest{
|
||||
Filters: filterReq,
|
||||
}),
|
||||
})
|
||||
|
||||
sgResp, err := oapiconn.POST_ReadSecurityGroups(params)
|
||||
if err != nil || sgResp.OK == nil {
|
||||
if err != nil || len(resp.SecurityGroups) == 0 {
|
||||
err := fmt.Errorf("Couldn't find security groups for filter: %s", err)
|
||||
log.Printf("[DEBUG] %s", err.Error())
|
||||
state.Put("error", err)
|
||||
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
securityGroupIds := []string{}
|
||||
for _, sg := range sgResp.OK.SecurityGroups {
|
||||
for _, sg := range resp.SecurityGroups {
|
||||
securityGroupIds = append(securityGroupIds, sg.SecurityGroupId)
|
||||
}
|
||||
|
||||
@@ -77,24 +83,20 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
port := s.CommConfig.Port()
|
||||
if port == 0 {
|
||||
if s.CommConfig.Type != "none" {
|
||||
panic("port must be set to a non-zero value.")
|
||||
}
|
||||
}
|
||||
|
||||
// Create the group
|
||||
groupName := fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
/* Create the group */
|
||||
groupName := fmt.Sprintf("packer_osc_%s", uuid.TimeOrderedUUID())
|
||||
ui.Say(fmt.Sprintf("Creating temporary security group for this instance: %s", groupName))
|
||||
group := oapi.CreateSecurityGroupRequest{
|
||||
|
||||
createSGReq := osc.CreateSecurityGroupRequest{
|
||||
SecurityGroupName: groupName,
|
||||
NetId: netID,
|
||||
Description: "Temporary group for Packer",
|
||||
}
|
||||
|
||||
group.NetId = netId
|
||||
resp, _, err := conn.SecurityGroupApi.CreateSecurityGroup(context.Background(), &osc.CreateSecurityGroupOpts{
|
||||
CreateSecurityGroupRequest: optional.NewInterface(createSGReq),
|
||||
})
|
||||
|
||||
groupResp, err := oapiconn.POST_CreateSecurityGroup(group)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
state.Put("error", err)
|
||||
@@ -102,38 +104,36 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
|
||||
}
|
||||
|
||||
// Set the group ID so we can delete it later
|
||||
s.createdGroupId = groupResp.OK.SecurityGroup.SecurityGroupId
|
||||
s.createdGroupId = resp.SecurityGroup.SecurityGroupId
|
||||
|
||||
// Wait for the security group become available for authorizing
|
||||
log.Printf("[DEBUG] Waiting for temporary security group: %s", s.createdGroupId)
|
||||
err = waitForSecurityGroup(oapiconn, s.createdGroupId)
|
||||
if err == nil {
|
||||
log.Printf("[DEBUG] Found security group %s", s.createdGroupId)
|
||||
} else {
|
||||
err := fmt.Errorf("Timed out waiting for security group %s: %s", s.createdGroupId, err)
|
||||
log.Printf("[DEBUG] %s", err.Error())
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
port := s.CommConfig.Port()
|
||||
if port == 0 {
|
||||
if s.CommConfig.Type != "none" {
|
||||
state.Put("error", "port must be set to a non-zero value.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
// Authorize the SSH access for the security group
|
||||
groupRules := oapi.CreateSecurityGroupRuleRequest{
|
||||
SecurityGroupId: groupResp.OK.SecurityGroup.SecurityGroupId,
|
||||
createSGRReq := osc.CreateSecurityGroupRuleRequest{
|
||||
SecurityGroupId: resp.SecurityGroup.SecurityGroupId,
|
||||
Flow: "Inbound",
|
||||
Rules: []oapi.SecurityGroupRule{
|
||||
Rules: []osc.SecurityGroupRule{
|
||||
{
|
||||
FromPortRange: int64(port),
|
||||
ToPortRange: int64(port),
|
||||
FromPortRange: int32(port),
|
||||
ToPortRange: int32(port),
|
||||
IpRanges: []string{s.TemporarySGSourceCidr},
|
||||
IpProtocol: "tcp",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf(
|
||||
"Authorizing access to port %d from %s in the temporary security group...",
|
||||
port, s.TemporarySGSourceCidr))
|
||||
_, err = oapiconn.POST_CreateSecurityGroupRule(groupRules)
|
||||
ui.Say(fmt.Sprintf("Authorizing access to port %d from %s in the temporary security group...", port, s.TemporarySGSourceCidr))
|
||||
|
||||
_, _, err = conn.SecurityGroupRuleApi.CreateSecurityGroupRule(context.Background(), &osc.CreateSecurityGroupRuleOpts{
|
||||
CreateSecurityGroupRuleRequest: optional.NewInterface(createSGRReq),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error authorizing temporary security group: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -152,24 +152,48 @@ func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) {
|
||||
return
|
||||
}
|
||||
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
var (
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
conn = state.Get("osc").(*osc.APIClient)
|
||||
)
|
||||
|
||||
ui.Say("Deleting temporary security group...")
|
||||
|
||||
var err error
|
||||
for i := 0; i < 5; i++ {
|
||||
_, err = oapiconn.POST_DeleteSecurityGroup(oapi.DeleteSecurityGroupRequest{SecurityGroupId: s.createdGroupId})
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Error deleting security group: %s", err)
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
_, _, err := conn.SecurityGroupApi.DeleteSecurityGroup(context.Background(), &osc.DeleteSecurityGroupOpts{
|
||||
DeleteSecurityGroupRequest: optional.NewInterface(osc.DeleteSecurityGroupRequest{
|
||||
SecurityGroupId: s.createdGroupId,
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error cleaning up security group. Please delete the group manually: %s", s.createdGroupId))
|
||||
}
|
||||
}
|
||||
|
||||
func buildSecurityGroupFilters(input map[string]string) osc.FiltersSecurityGroup {
|
||||
var filters osc.FiltersSecurityGroup
|
||||
|
||||
for k, v := range input {
|
||||
filterValue := []string{v}
|
||||
|
||||
switch name := k; name {
|
||||
case "account_ids":
|
||||
filters.AccountIds = filterValue
|
||||
case "security_group_ids":
|
||||
filters.SecurityGroupIds = filterValue
|
||||
case "security_group_names":
|
||||
filters.SecurityGroupNames = filterValue
|
||||
case "tag_keys":
|
||||
filters.TagKeys = filterValue
|
||||
case "tag_values":
|
||||
filters.TagValues = filterValue
|
||||
case "tags":
|
||||
filters.Tags = filterValue
|
||||
default:
|
||||
log.Printf("[Debug] Unknown Filter Name: %s.", name)
|
||||
}
|
||||
}
|
||||
|
||||
return filters
|
||||
}
|
||||
|
||||
@@ -7,45 +7,46 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
// StepSourceOMIInfo extracts critical information from the source OMI
|
||||
// that is used throughout the OMI creation process.
|
||||
//
|
||||
// Produces:
|
||||
// source_image *oapi.Image - the source OMI info
|
||||
// source_image *osc.Image - the source OMI info
|
||||
type StepSourceOMIInfo struct {
|
||||
SourceOmi string
|
||||
OMIVirtType string
|
||||
OmiFilters OmiFilterOptions
|
||||
}
|
||||
|
||||
type imageSort []oapi.Image
|
||||
type imageOscSort []osc.Image
|
||||
|
||||
func (a imageSort) Len() int { return len(a) }
|
||||
func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a imageSort) Less(i, j int) bool {
|
||||
func (a imageOscSort) Len() int { return len(a) }
|
||||
func (a imageOscSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a imageOscSort) Less(i, j int) bool {
|
||||
itime, _ := time.Parse(time.RFC3339, a[i].CreationDate)
|
||||
jtime, _ := time.Parse(time.RFC3339, a[j].CreationDate)
|
||||
return itime.Unix() < jtime.Unix()
|
||||
}
|
||||
|
||||
// Returns the most recent OMI out of a slice of images.
|
||||
func mostRecentOmi(images []oapi.Image) oapi.Image {
|
||||
func mostRecentOscOmi(images []osc.Image) osc.Image {
|
||||
sortedImages := images
|
||||
sort.Sort(imageSort(sortedImages))
|
||||
sort.Sort(imageOscSort(sortedImages))
|
||||
return sortedImages[len(sortedImages)-1]
|
||||
}
|
||||
|
||||
func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
params := oapi.ReadImagesRequest{
|
||||
Filters: oapi.FiltersImage{},
|
||||
params := osc.ReadImagesRequest{
|
||||
Filters: osc.FiltersImage{},
|
||||
}
|
||||
|
||||
if s.SourceOmi != "" {
|
||||
@@ -54,7 +55,7 @@ func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) mul
|
||||
|
||||
// We have filters to apply
|
||||
if len(s.OmiFilters.Filters) > 0 {
|
||||
params.Filters = buildOMIFilters(s.OmiFilters.Filters)
|
||||
params.Filters = buildOSCOMIFilters(s.OmiFilters.Filters)
|
||||
}
|
||||
//TODO:Check if AccountIds correspond to Owners.
|
||||
if len(s.OmiFilters.Owners) > 0 {
|
||||
@@ -62,7 +63,9 @@ func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) mul
|
||||
}
|
||||
|
||||
log.Printf("Using OMI Filters %#v", params)
|
||||
imageResp, err := oapiconn.POST_ReadImages(params)
|
||||
imageResp, _, err := oscconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
|
||||
ReadImagesRequest: optional.NewInterface(params),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying OMI: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -70,25 +73,25 @@ func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) mul
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(imageResp.OK.Images) == 0 {
|
||||
if len(imageResp.Images) == 0 {
|
||||
err := fmt.Errorf("No OMI was found matching filters: %#v", params)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(imageResp.OK.Images) > 1 && !s.OmiFilters.MostRecent {
|
||||
err := fmt.Errorf("Your query returned more than one result. Please try a more specific search, or set most_recent to true.")
|
||||
if len(imageResp.Images) > 1 && !s.OmiFilters.MostRecent {
|
||||
err := fmt.Errorf("your query returned more than one result. Please try a more specific search, or set most_recent to true")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var image oapi.Image
|
||||
var image osc.Image
|
||||
if s.OmiFilters.MostRecent {
|
||||
image = mostRecentOmi(imageResp.OK.Images)
|
||||
image = mostRecentOscOmi(imageResp.Images)
|
||||
} else {
|
||||
image = imageResp.OK.Images[0]
|
||||
image = imageResp.Images[0]
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Found Image ID: %s", image.ImageId))
|
||||
|
||||
@@ -4,11 +4,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
type StepStopBSUBackedVm struct {
|
||||
@@ -17,8 +18,8 @@ type StepStopBSUBackedVm struct {
|
||||
}
|
||||
|
||||
func (s *StepStopBSUBackedVm) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
vm := state.Get("vm").(oapi.Vm)
|
||||
oscconn := state.Get("osc").(*osc.APIClient)
|
||||
vm := state.Get("vm").(osc.Vm)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Skip when it is a spot vm
|
||||
@@ -43,8 +44,10 @@ func (s *StepStopBSUBackedVm) Run(ctx context.Context, state multistep.StateBag)
|
||||
err := common.Retry(10, 60, 6, func(i uint) (bool, error) {
|
||||
ui.Message(fmt.Sprintf("Stopping vm, attempt %d", i+1))
|
||||
|
||||
_, err = oapiconn.POST_StopVms(oapi.StopVmsRequest{
|
||||
VmIds: []string{vm.VmId},
|
||||
_, _, err = oscconn.VmApi.StopVms(context.Background(), &osc.StopVmsOpts{
|
||||
StopVmsRequest: optional.NewInterface(osc.StopVmsRequest{
|
||||
VmIds: []string{vm.VmId},
|
||||
}),
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
@@ -78,7 +81,7 @@ func (s *StepStopBSUBackedVm) Run(ctx context.Context, state multistep.StateBag)
|
||||
|
||||
// Wait for the vm to actually stop
|
||||
ui.Say("Waiting for the vm to stop...")
|
||||
err = waitUntilVmStopped(oapiconn, vm.VmId)
|
||||
err = waitUntilOscVmStopped(oscconn, vm.VmId)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for vm to stop: %s", err)
|
||||
|
||||
@@ -2,25 +2,24 @@ package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
type StepUpdateOMIAttributes struct {
|
||||
AccountIds []string
|
||||
SnapshotAccountIds []string
|
||||
RawRegion string
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (s *StepUpdateOMIAttributes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
oapiconn := state.Get("oapi").(*oapi.Client)
|
||||
config := state.Get("clientConfig").(*oapi.Config)
|
||||
config := state.Get("accessConfig").(*AccessConfig)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
omis := state.Get("omis").(map[string]string)
|
||||
snapshots := state.Get("snapshots").(map[string][]string)
|
||||
@@ -34,20 +33,20 @@ func (s *StepUpdateOMIAttributes) Run(_ context.Context, state multistep.StateBa
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
s.Ctx.Data = extractBuildInfo(oapiconn.GetConfig().Region, state)
|
||||
s.Ctx.Data = extractBuildInfo(s.RawRegion, state)
|
||||
|
||||
updateSnapshoptRequest := oapi.UpdateSnapshotRequest{
|
||||
PermissionsToCreateVolume: oapi.PermissionsOnResourceCreation{
|
||||
Additions: oapi.PermissionsOnResource{
|
||||
updateSnapshoptRequest := osc.UpdateSnapshotRequest{
|
||||
PermissionsToCreateVolume: osc.PermissionsOnResourceCreation{
|
||||
Additions: osc.PermissionsOnResource{
|
||||
AccountIds: s.AccountIds,
|
||||
GlobalPermission: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
updateImageRequest := oapi.UpdateImageRequest{
|
||||
PermissionsToLaunch: oapi.PermissionsOnResourceCreation{
|
||||
Additions: oapi.PermissionsOnResource{
|
||||
updateImageRequest := osc.UpdateImageRequest{
|
||||
PermissionsToLaunch: osc.PermissionsOnResourceCreation{
|
||||
Additions: osc.PermissionsOnResource{
|
||||
AccountIds: s.AccountIds,
|
||||
GlobalPermission: false,
|
||||
},
|
||||
@@ -57,26 +56,31 @@ func (s *StepUpdateOMIAttributes) Run(_ context.Context, state multistep.StateBa
|
||||
// Updating image attributes
|
||||
for region, omi := range omis {
|
||||
ui.Say(fmt.Sprintf("Updating attributes on OMI (%s)...", omi))
|
||||
newConfig := &oapi.Config{
|
||||
UserAgent: config.UserAgent,
|
||||
AccessKey: config.AccessKey,
|
||||
SecretKey: config.SecretKey,
|
||||
Service: config.Service,
|
||||
Region: region, //New region
|
||||
URL: config.URL,
|
||||
}
|
||||
regionconn := config.NewOSCClientByRegion(region)
|
||||
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
// newConfig := &osc.Configuration{
|
||||
// UserAgent: config.UserAgent,
|
||||
// AccessKey: config.AccessKey,
|
||||
// SecretKey: config.SecretKey,
|
||||
// Service: config.Service,
|
||||
// Region: region, //New region
|
||||
// URL: config.URL,
|
||||
// }
|
||||
|
||||
regionconn := oapi.NewClient(newConfig, skipClient)
|
||||
// skipClient := &http.Client{
|
||||
// Transport: &http.Transport{
|
||||
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
// },
|
||||
// }
|
||||
|
||||
//regionconn := oapi.NewClient(newConfig, skipClient)
|
||||
|
||||
ui.Message(fmt.Sprintf("Updating: %s", omi))
|
||||
updateImageRequest.ImageId = omi
|
||||
_, err := regionconn.POST_UpdateImage(updateImageRequest)
|
||||
_, _, err := regionconn.ImageApi.UpdateImage(context.Background(), &osc.UpdateImageOpts{
|
||||
UpdateImageRequest: optional.NewInterface(updateImageRequest),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error updating OMI: %s", err)
|
||||
state.Put("error", err)
|
||||
@@ -89,26 +93,13 @@ func (s *StepUpdateOMIAttributes) Run(_ context.Context, state multistep.StateBa
|
||||
for region, region_snapshots := range snapshots {
|
||||
for _, snapshot := range region_snapshots {
|
||||
ui.Say(fmt.Sprintf("Updating attributes on snapshot (%s)...", snapshot))
|
||||
newConfig := &oapi.Config{
|
||||
UserAgent: config.UserAgent,
|
||||
AccessKey: config.AccessKey,
|
||||
SecretKey: config.SecretKey,
|
||||
Service: config.Service,
|
||||
Region: region, //New region
|
||||
URL: config.URL,
|
||||
}
|
||||
|
||||
skipClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
regionconn := oapi.NewClient(newConfig, skipClient)
|
||||
regionconn := config.NewOSCClientByRegion(region)
|
||||
|
||||
ui.Message(fmt.Sprintf("Updating: %s", snapshot))
|
||||
updateSnapshoptRequest.SnapshotId = snapshot
|
||||
_, err := regionconn.POST_UpdateSnapshot(updateSnapshoptRequest)
|
||||
_, _, err := regionconn.SnapshotApi.UpdateSnapshot(context.Background(), &osc.UpdateSnapshotOpts{
|
||||
UpdateSnapshotRequest: optional.NewInterface(updateSnapshoptRequest),
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error updating snapshot: %s", err)
|
||||
state.Put("error", err)
|
||||
|
||||
+15
-11
@@ -1,18 +1,20 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/antihax/optional"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/outscale/osc-go/oapi"
|
||||
"github.com/outscale/osc-sdk-go/osc"
|
||||
)
|
||||
|
||||
type TagMap map[string]string
|
||||
type OAPITags []oapi.ResourceTag
|
||||
type OSCTags []osc.ResourceTag
|
||||
|
||||
func (t OAPITags) Report(ui packer.Ui) {
|
||||
func (t OSCTags) Report(ui packer.Ui) {
|
||||
for _, tag := range t {
|
||||
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"",
|
||||
tag.Key, tag.Value))
|
||||
@@ -23,8 +25,8 @@ func (t TagMap) IsSet() bool {
|
||||
return len(t) > 0
|
||||
}
|
||||
|
||||
func (t TagMap) OAPITags(ctx interpolate.Context, region string, state multistep.StateBag) (OAPITags, error) {
|
||||
var oapiTags []oapi.ResourceTag
|
||||
func (t TagMap) OSCTags(ctx interpolate.Context, region string, state multistep.StateBag) (OSCTags, error) {
|
||||
var oscTags []osc.ResourceTag
|
||||
ctx.Data = extractBuildInfo(region, state)
|
||||
|
||||
for key, value := range t {
|
||||
@@ -36,20 +38,22 @@ func (t TagMap) OAPITags(ctx interpolate.Context, region string, state multistep
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
|
||||
}
|
||||
oapiTags = append(oapiTags, oapi.ResourceTag{
|
||||
oscTags = append(oscTags, osc.ResourceTag{
|
||||
Key: interpolatedKey,
|
||||
Value: interpolatedValue,
|
||||
})
|
||||
}
|
||||
return oapiTags, nil
|
||||
return oscTags, nil
|
||||
}
|
||||
|
||||
func CreateTags(conn *oapi.Client, resourceID string, ui packer.Ui, tags OAPITags) error {
|
||||
func CreateOSCTags(conn *osc.APIClient, resourceID string, ui packer.Ui, tags OSCTags) error {
|
||||
tags.Report(ui)
|
||||
|
||||
_, err := conn.POST_CreateTags(oapi.CreateTagsRequest{
|
||||
ResourceIds: []string{resourceID},
|
||||
Tags: tags,
|
||||
_, _, err := conn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
|
||||
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
|
||||
ResourceIds: []string{resourceID},
|
||||
Tags: tags,
|
||||
}),
|
||||
})
|
||||
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
|
||||
)
|
||||
|
||||
// Transport is used to sing the user for each request
|
||||
type Transport struct {
|
||||
transport http.RoundTripper
|
||||
signer *v4.Signer
|
||||
region string
|
||||
}
|
||||
|
||||
func (t *Transport) sign(req *http.Request, body []byte) error {
|
||||
reader := strings.NewReader(string(body))
|
||||
timestamp := time.Now()
|
||||
_, err := t.signer.Sign(req, reader, "osc", t.region, timestamp)
|
||||
return err
|
||||
}
|
||||
|
||||
// RoundTrip is implemented according with the interface RoundTrip to sing for each request
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
//Get the body
|
||||
getBody := req.GetBody
|
||||
copyBody, err := getBody()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(copyBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := t.sign(req, body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := t.transport.RoundTrip(req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// NewTransport returns the transport signing with the given credentials
|
||||
func NewTransport(accessKey, accessSecret, region string, t http.RoundTripper) *Transport {
|
||||
s := &v4.Signer{
|
||||
Credentials: credentials.NewStaticCredentials(accessKey,
|
||||
accessSecret, ""),
|
||||
}
|
||||
return &Transport{t, s, region}
|
||||
}
|
||||
@@ -192,6 +192,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
|
||||
artifact.state["generated_data"] = state.Get("generated_data")
|
||||
artifact.state["diskName"] = b.config.VMName
|
||||
|
||||
// placed in state in step_create_disk.go
|
||||
diskpaths, ok := state.Get("qemu_disk_paths").([]string)
|
||||
if ok {
|
||||
artifact.state["diskPaths"] = diskpaths
|
||||
|
||||
+16
-8
@@ -208,19 +208,27 @@ type Config struct {
|
||||
// the builder. By default this is output-BUILDNAME where "BUILDNAME" is the
|
||||
// name of the build.
|
||||
OutputDir string `mapstructure:"output_directory" required:"false"`
|
||||
// Allows complete control over the qemu command line (though not, at this
|
||||
// time, qemu-img). Each array of strings makes up a command line switch
|
||||
// Allows complete control over the qemu command line (though not qemu-img).
|
||||
// Each array of strings makes up a command line switch
|
||||
// that overrides matching default switch/value pairs. Any value specified
|
||||
// as an empty string is ignored. All values after the switch are
|
||||
// concatenated with no separator.
|
||||
//
|
||||
// ~> **Warning:** The qemu command line allows extreme flexibility, so
|
||||
// beware of conflicting arguments causing failures of your run. For
|
||||
// instance, using --no-acpi could break the ability to send power signal
|
||||
// type commands (e.g., shutdown -P now) to the virtual machine, thus
|
||||
// preventing proper shutdown. To see the defaults, look in the packer.log
|
||||
// file and search for the qemu-system-x86 command. The arguments are all
|
||||
// printed for review.
|
||||
// beware of conflicting arguments causing failures of your run.
|
||||
// For instance adding a "--drive" or "--device" override will mean that
|
||||
// none of the default configuration Packer sets will be used. To see the
|
||||
// defaults that Packer sets, look in your packer.log
|
||||
// file (set PACKER_LOG=1 to get verbose logging) and search for the
|
||||
// qemu-system-x86 command. The arguments are all printed for review, and
|
||||
// you can use those arguments along with the template engines allowed
|
||||
// by qemu-args to set up a working configuration that includes both the
|
||||
// Packer defaults and your extra arguments.
|
||||
//
|
||||
// Another pitfall could be setting arguments like --no-acpi, which could
|
||||
// break the ability to send power signal type commands
|
||||
// (e.g., shutdown -P now) to the virtual machine, thus preventing proper
|
||||
// shutdown.
|
||||
//
|
||||
// The following shows a sample usage:
|
||||
//
|
||||
|
||||
+214
-173
@@ -16,41 +16,45 @@ import (
|
||||
// stepRun runs the virtual machine
|
||||
type stepRun struct {
|
||||
DiskImage bool
|
||||
}
|
||||
|
||||
type qemuArgsTemplateData struct {
|
||||
HTTPIP string
|
||||
HTTPPort int
|
||||
HTTPDir string
|
||||
OutputDir string
|
||||
Name string
|
||||
SSHHostPort int
|
||||
atLeastVersion2 bool
|
||||
ui packer.Ui
|
||||
}
|
||||
|
||||
func (s *stepRun) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
s.ui = state.Get("ui").(packer.Ui)
|
||||
|
||||
// Run command is different depending whether we're booting from an
|
||||
// installation CD or a pre-baked image
|
||||
bootDrive := "once=d"
|
||||
message := "Starting VM, booting from CD-ROM"
|
||||
if s.DiskImage {
|
||||
bootDrive = "c"
|
||||
message = "Starting VM, booting disk image"
|
||||
// Figure out version of qemu; store on step for later use
|
||||
rawVersion, err := driver.Version()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error determining qemu version: %s", err)
|
||||
s.ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ui.Say(message)
|
||||
qemuVersion, err := version.NewVersion(rawVersion)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error parsing qemu version: %s", err)
|
||||
s.ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
v2 := version.Must(version.NewVersion("2.0"))
|
||||
|
||||
command, err := getCommandArgs(bootDrive, state)
|
||||
s.atLeastVersion2 = qemuVersion.GreaterThanOrEqual(v2)
|
||||
|
||||
// Generate the qemu command
|
||||
command, err := s.getCommandArgs(config, state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error processing QemuArgs: %s", err)
|
||||
ui.Error(err.Error())
|
||||
s.ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// run the qemu command
|
||||
if err := driver.Qemu(command...); err != nil {
|
||||
err := fmt.Errorf("Error launching VM: %s", err)
|
||||
ui.Error(err.Error())
|
||||
s.ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
@@ -66,140 +70,187 @@ func (s *stepRun) Cleanup(state multistep.StateBag) {
|
||||
}
|
||||
}
|
||||
|
||||
func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error) {
|
||||
config := state.Get("config").(*Config)
|
||||
isoPath := state.Get("iso_path").(string)
|
||||
vncIP := config.VNCBindAddress
|
||||
vncPort := state.Get("vnc_port").(int)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
driver := state.Get("driver").(Driver)
|
||||
vmName := config.VMName
|
||||
imgPath := filepath.Join(config.OutputDir, vmName)
|
||||
func (s *stepRun) getDefaultArgs(config *Config, state multistep.StateBag) map[string]interface{} {
|
||||
|
||||
defaultArgs := make(map[string]interface{})
|
||||
var deviceArgs []string
|
||||
var driveArgs []string
|
||||
var commHostPort int
|
||||
|
||||
// Configure "boot" arguement
|
||||
// Run command is different depending whether we're booting from an
|
||||
// installation CD or a pre-baked image
|
||||
bootDrive := "once=d"
|
||||
message := "Starting VM, booting from CD-ROM"
|
||||
if s.DiskImage {
|
||||
bootDrive = "c"
|
||||
message = "Starting VM, booting disk image"
|
||||
}
|
||||
s.ui.Say(message)
|
||||
defaultArgs["-boot"] = bootDrive
|
||||
|
||||
// configure "-qmp" arguments
|
||||
if config.QMPEnable {
|
||||
defaultArgs["-qmp"] = fmt.Sprintf("unix:%s,server,nowait", config.QMPSocketPath)
|
||||
}
|
||||
|
||||
// configure "-name" arguments
|
||||
defaultArgs["-name"] = config.VMName
|
||||
|
||||
// Configure "-machine" arguments
|
||||
if config.Accelerator == "none" {
|
||||
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
|
||||
s.ui.Message("WARNING: The VM will be started with no hardware acceleration.\n" +
|
||||
"The installation may take considerably longer to finish.\n")
|
||||
} else {
|
||||
defaultArgs["-machine"] = fmt.Sprintf("type=%s,accel=%s",
|
||||
config.MachineType, config.Accelerator)
|
||||
}
|
||||
|
||||
// Configure "-netdev" arguments
|
||||
defaultArgs["-netdev"] = fmt.Sprintf("bridge,id=user.0,br=%s", config.NetBridge)
|
||||
if config.NetBridge == "" {
|
||||
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0")
|
||||
if config.CommConfig.Comm.Type != "none" {
|
||||
commHostPort := state.Get("commHostPort").(int)
|
||||
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:%d", commHostPort, config.CommConfig.Comm.Port())
|
||||
}
|
||||
}
|
||||
|
||||
// Configure "-vnc" arguments
|
||||
// vncPort is always set in stepConfigureVNC, so we don't need to
|
||||
// defensively assert
|
||||
vncPort := state.Get("vnc_port").(int)
|
||||
vncIP := config.VNCBindAddress
|
||||
|
||||
vncPort = vncPort - config.VNCPortMin
|
||||
vnc := fmt.Sprintf("%s:%d", vncIP, vncPort)
|
||||
if config.VNCUsePassword {
|
||||
vnc = fmt.Sprintf("%s:%d,password", vncIP, vncPort)
|
||||
}
|
||||
defaultArgs["-vnc"] = vnc
|
||||
|
||||
if config.QMPEnable {
|
||||
defaultArgs["-qmp"] = fmt.Sprintf("unix:%s,server,nowait", config.QMPSocketPath)
|
||||
// Track the connection for the user
|
||||
vncPass, _ := state.Get("vnc_password").(string)
|
||||
|
||||
message = getVncConnectionMessage(config.Headless, vnc, vncPass)
|
||||
if message != "" {
|
||||
s.ui.Message(message)
|
||||
}
|
||||
|
||||
defaultArgs["-name"] = vmName
|
||||
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
|
||||
// Configure "-m" memory argument
|
||||
defaultArgs["-m"] = fmt.Sprintf("%dM", config.MemorySize)
|
||||
|
||||
if config.NetBridge == "" {
|
||||
if config.CommConfig.Comm.Type != "none" {
|
||||
commHostPort = state.Get("commHostPort").(int)
|
||||
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:%d", commHostPort, config.CommConfig.Comm.Port())
|
||||
} else {
|
||||
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0")
|
||||
}
|
||||
// Configure "-smp" processor hardware arguments
|
||||
if config.CpuCount > 1 {
|
||||
defaultArgs["-smp"] = fmt.Sprintf("cpus=%d,sockets=%d", config.CpuCount, config.CpuCount)
|
||||
}
|
||||
|
||||
// Configure "-fda" floppy disk attachment
|
||||
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
|
||||
defaultArgs["-fda"] = floppyPathRaw.(string)
|
||||
} else {
|
||||
defaultArgs["-netdev"] = fmt.Sprintf("bridge,id=user.0,br=%s", config.NetBridge)
|
||||
log.Println("Qemu Builder has no floppy files, not attaching a floppy.")
|
||||
}
|
||||
|
||||
rawVersion, err := driver.Version()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qemuVersion, err := version.NewVersion(rawVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v2 := version.Must(version.NewVersion("2.0"))
|
||||
|
||||
if qemuVersion.GreaterThanOrEqual(v2) {
|
||||
if config.DiskInterface == "virtio-scsi" {
|
||||
if config.DiskImage {
|
||||
deviceArgs = append(deviceArgs, "virtio-scsi-pci,id=scsi0", "scsi-hd,bus=scsi0.0,drive=drive0")
|
||||
driveArgumentString := fmt.Sprintf("if=none,file=%s,id=drive0,cache=%s,discard=%s,format=%s", imgPath, config.DiskCache, config.DiskDiscard, config.Format)
|
||||
if config.DetectZeroes != "off" {
|
||||
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
|
||||
}
|
||||
driveArgs = append(driveArgs, driveArgumentString)
|
||||
} else {
|
||||
deviceArgs = append(deviceArgs, "virtio-scsi-pci,id=scsi0")
|
||||
diskFullPaths := state.Get("qemu_disk_paths").([]string)
|
||||
for i, diskFullPath := range diskFullPaths {
|
||||
deviceArgs = append(deviceArgs, fmt.Sprintf("scsi-hd,bus=scsi0.0,drive=drive%d", i))
|
||||
driveArgumentString := fmt.Sprintf("if=none,file=%s,id=drive%d,cache=%s,discard=%s,format=%s", diskFullPath, i, config.DiskCache, config.DiskDiscard, config.Format)
|
||||
if config.DetectZeroes != "off" {
|
||||
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
|
||||
}
|
||||
driveArgs = append(driveArgs, driveArgumentString)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if config.DiskImage {
|
||||
driveArgumentString := fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.DiskDiscard, config.Format)
|
||||
if config.DetectZeroes != "off" {
|
||||
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
|
||||
}
|
||||
driveArgs = append(driveArgs, driveArgumentString)
|
||||
} else {
|
||||
diskFullPaths := state.Get("qemu_disk_paths").([]string)
|
||||
for _, diskFullPath := range diskFullPaths {
|
||||
driveArgumentString := fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s,format=%s", diskFullPath, config.DiskInterface, config.DiskCache, config.DiskDiscard, config.Format)
|
||||
if config.DetectZeroes != "off" {
|
||||
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
|
||||
}
|
||||
driveArgs = append(driveArgs, driveArgumentString)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.Format))
|
||||
}
|
||||
deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
|
||||
|
||||
if config.Headless == true {
|
||||
vncPortRaw, vncPortOk := state.GetOk("vnc_port")
|
||||
vncPass := state.Get("vnc_password")
|
||||
|
||||
if vncPortOk && vncPass != nil && len(vncPass.(string)) > 0 {
|
||||
vncPort := vncPortRaw.(int)
|
||||
|
||||
ui.Message(fmt.Sprintf(
|
||||
"The VM will be run headless, without a GUI. If you want to\n"+
|
||||
"view the screen of the VM, connect via VNC to vnc://%s:%d\n"+
|
||||
"with the password: %s", vncIP, vncPort, vncPass))
|
||||
} else if vncPortOk {
|
||||
vncPort := vncPortRaw.(int)
|
||||
|
||||
ui.Message(fmt.Sprintf(
|
||||
"The VM will be run headless, without a GUI. If you want to\n"+
|
||||
"view the screen of the VM, connect via VNC without a password to\n"+
|
||||
"vnc://%s:%d", vncIP, vncPort))
|
||||
} else {
|
||||
ui.Message("The VM will be run headless, without a GUI, as configured.\n" +
|
||||
"If the run isn't succeeding as you expect, please enable the GUI\n" +
|
||||
"to inspect the progress of the build.")
|
||||
}
|
||||
} else {
|
||||
if qemuVersion.GreaterThanOrEqual(v2) {
|
||||
if len(config.Display) > 0 {
|
||||
if config.Display != "none" {
|
||||
defaultArgs["-display"] = config.Display
|
||||
}
|
||||
// Configure GUI display
|
||||
if !config.Headless {
|
||||
if s.atLeastVersion2 {
|
||||
// FIXME: "none" is a valid display option in qemu but we have
|
||||
// departed from the qemu usage here to instaed mean "let qemu
|
||||
// set a reasonable default". We need to deprecate this behavior
|
||||
// and let users just set "UseDefaultDisplay" if they want to let
|
||||
// qemu do its thing.
|
||||
if len(config.Display) > 0 && config.Display != "none" {
|
||||
defaultArgs["-display"] = config.Display
|
||||
} else if !config.UseDefaultDisplay {
|
||||
defaultArgs["-display"] = "gtk"
|
||||
}
|
||||
} else {
|
||||
ui.Message("WARNING: The version of qemu on your host doesn't support display mode.\n" +
|
||||
s.ui.Message("WARNING: The version of qemu on your host doesn't support display mode.\n" +
|
||||
"The display parameter will be ignored.")
|
||||
}
|
||||
}
|
||||
|
||||
deviceArgs, driveArgs := s.getDeviceAndDriveArgs(config, state)
|
||||
defaultArgs["-device"] = deviceArgs
|
||||
defaultArgs["-drive"] = driveArgs
|
||||
|
||||
return defaultArgs
|
||||
}
|
||||
|
||||
func getVncConnectionMessage(headless bool, vnc string, vncPass string) string {
|
||||
// Configure GUI display
|
||||
if headless {
|
||||
if vnc == "" {
|
||||
return "The VM will be run headless, without a GUI, as configured.\n" +
|
||||
"If the run isn't succeeding as you expect, please enable the GUI\n" +
|
||||
"to inspect the progress of the build."
|
||||
}
|
||||
|
||||
if vncPass != "" {
|
||||
return fmt.Sprintf(
|
||||
"The VM will be run headless, without a GUI. If you want to\n"+
|
||||
"view the screen of the VM, connect via VNC to vnc://%s\n"+
|
||||
"with the password: %s", vnc, vncPass)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"The VM will be run headless, without a GUI. If you want to\n"+
|
||||
"view the screen of the VM, connect via VNC without a password to\n"+
|
||||
"vnc://%s", vnc)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *stepRun) getDeviceAndDriveArgs(config *Config, state multistep.StateBag) ([]string, []string) {
|
||||
var deviceArgs []string
|
||||
var driveArgs []string
|
||||
|
||||
vmName := config.VMName
|
||||
imgPath := filepath.Join(config.OutputDir, vmName)
|
||||
|
||||
// Configure virtual hard drives
|
||||
if s.atLeastVersion2 {
|
||||
// We have different things to attach based on whether we are booting
|
||||
// from an iso or a boot image.
|
||||
drivesToAttach := []string{}
|
||||
if config.DiskImage {
|
||||
drivesToAttach = append(drivesToAttach, imgPath)
|
||||
}
|
||||
|
||||
diskFullPaths := state.Get("qemu_disk_paths").([]string)
|
||||
drivesToAttach = append(drivesToAttach, diskFullPaths...)
|
||||
|
||||
for i, drivePath := range drivesToAttach {
|
||||
driveArgumentString := fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s,format=%s", drivePath, config.DiskInterface, config.DiskCache, config.DiskDiscard, config.Format)
|
||||
if config.DiskInterface == "virtio-scsi" {
|
||||
// TODO: Megan: Remove this conditional. This, and the code
|
||||
// under the TODO below, reproduce the old behavior. While it
|
||||
// may be broken, the goal of this commit is to refactor in a way
|
||||
// that creates a result that is testably the same as the old
|
||||
// code. A pr will follow fixing this broken behavior.
|
||||
if i == 0 {
|
||||
deviceArgs = append(deviceArgs, fmt.Sprintf("virtio-scsi-pci,id=scsi%d", i))
|
||||
}
|
||||
// TODO: Megan: When you remove above conditional,
|
||||
// set deviceArgs = append(deviceArgs, fmt.Sprintf("scsi-hd,bus=scsi%d.0,drive=drive%d", i, i))
|
||||
deviceArgs = append(deviceArgs, fmt.Sprintf("scsi-hd,bus=scsi0.0,drive=drive%d", i))
|
||||
driveArgumentString = fmt.Sprintf("if=none,file=%s,id=drive%d,cache=%s,discard=%s,format=%s", drivePath, i, config.DiskCache, config.DiskDiscard, config.Format)
|
||||
}
|
||||
if config.DetectZeroes != "off" {
|
||||
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
|
||||
}
|
||||
driveArgs = append(driveArgs, driveArgumentString)
|
||||
}
|
||||
} else {
|
||||
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.Format))
|
||||
}
|
||||
|
||||
deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
|
||||
|
||||
// Configure virtual CDs
|
||||
cdPaths := []string{}
|
||||
// Add the installation CD to the run command
|
||||
if !config.DiskImage {
|
||||
isoPath := state.Get("iso_path").(string)
|
||||
cdPaths = append(cdPaths, isoPath)
|
||||
}
|
||||
// Add our custom CD created from cd_files, if it exists
|
||||
@@ -220,64 +271,48 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
||||
}
|
||||
}
|
||||
|
||||
defaultArgs["-device"] = deviceArgs
|
||||
defaultArgs["-drive"] = driveArgs
|
||||
return deviceArgs, driveArgs
|
||||
}
|
||||
|
||||
defaultArgs["-boot"] = bootDrive
|
||||
defaultArgs["-m"] = fmt.Sprintf("%dM", config.MemorySize)
|
||||
if config.CpuCount > 1 {
|
||||
defaultArgs["-smp"] = fmt.Sprintf("cpus=%d,sockets=%d", config.CpuCount, config.CpuCount)
|
||||
}
|
||||
defaultArgs["-vnc"] = vnc
|
||||
|
||||
// Append the accelerator to the machine type if it is specified
|
||||
if config.Accelerator != "none" {
|
||||
defaultArgs["-machine"] = fmt.Sprintf("%s,accel=%s", defaultArgs["-machine"], config.Accelerator)
|
||||
} else {
|
||||
ui.Message("WARNING: The VM will be started with no hardware acceleration.\n" +
|
||||
"The installation may take considerably longer to finish.\n")
|
||||
}
|
||||
|
||||
// Determine if we have a floppy disk to attach
|
||||
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
|
||||
defaultArgs["-fda"] = floppyPathRaw.(string)
|
||||
} else {
|
||||
log.Println("Qemu Builder has no floppy files, not attaching a floppy.")
|
||||
}
|
||||
func (s *stepRun) applyUserOverrides(defaultArgs map[string]interface{}, config *Config, state multistep.StateBag) ([]string, error) {
|
||||
// Done setting up defaults; time to process user args and defaults together
|
||||
// and generate output args
|
||||
|
||||
inArgs := make(map[string][]string)
|
||||
if len(config.QemuArgs) > 0 {
|
||||
ui.Say("Overriding default Qemu arguments with QemuArgs...")
|
||||
s.ui.Say("Overriding default Qemu arguments with qemuargs template option...")
|
||||
|
||||
commHostPort := state.Get("commHostPort").(int)
|
||||
httpIp := state.Get("http_ip").(string)
|
||||
httpPort := state.Get("http_port").(int)
|
||||
ictx := config.ctx
|
||||
if config.CommConfig.Comm.Type != "none" {
|
||||
ictx.Data = qemuArgsTemplateData{
|
||||
httpIp,
|
||||
httpPort,
|
||||
config.HTTPDir,
|
||||
config.OutputDir,
|
||||
config.VMName,
|
||||
commHostPort,
|
||||
}
|
||||
} else {
|
||||
ictx.Data = qemuArgsTemplateData{
|
||||
HTTPIP: httpIp,
|
||||
HTTPPort: httpPort,
|
||||
HTTPDir: config.HTTPDir,
|
||||
OutputDir: config.OutputDir,
|
||||
Name: config.VMName,
|
||||
}
|
||||
|
||||
type qemuArgsTemplateData struct {
|
||||
HTTPIP string
|
||||
HTTPPort int
|
||||
HTTPDir string
|
||||
OutputDir string
|
||||
Name string
|
||||
SSHHostPort int
|
||||
}
|
||||
|
||||
ictx := config.ctx
|
||||
ictx.Data = qemuArgsTemplateData{
|
||||
HTTPIP: httpIp,
|
||||
HTTPPort: httpPort,
|
||||
HTTPDir: config.HTTPDir,
|
||||
OutputDir: config.OutputDir,
|
||||
Name: config.VMName,
|
||||
SSHHostPort: commHostPort,
|
||||
}
|
||||
|
||||
// Interpolate each string in qemuargs
|
||||
newQemuArgs, err := processArgs(config.QemuArgs, &ictx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// because qemu supports multiple appearances of the same
|
||||
// switch, just different values, each key in the args hash
|
||||
// will have an array of string values
|
||||
// Qemu supports multiple appearances of the same switch. This means
|
||||
// each key in the args hash will have an array of string values
|
||||
for _, qemuArgs := range newQemuArgs {
|
||||
key := qemuArgs[0]
|
||||
val := strings.Join(qemuArgs[1:], "")
|
||||
@@ -326,6 +361,12 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
||||
return outArgs, nil
|
||||
}
|
||||
|
||||
func (s *stepRun) getCommandArgs(config *Config, state multistep.StateBag) ([]string, error) {
|
||||
defaultArgs := s.getDefaultArgs(config, state)
|
||||
|
||||
return s.applyUserOverrides(defaultArgs, config, state)
|
||||
}
|
||||
|
||||
func processArgs(args [][]string, ctx *interpolate.Context) ([][]string, error) {
|
||||
var err error
|
||||
|
||||
|
||||
+632
-73
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -11,8 +13,6 @@ import (
|
||||
|
||||
func runTestState(t *testing.T, config *Config) multistep.StateBag {
|
||||
state := new(multistep.BasicStateBag)
|
||||
|
||||
state.Put("ui", packer.TestUi(t))
|
||||
state.Put("config", config)
|
||||
|
||||
d := new(DriverMock)
|
||||
@@ -31,86 +31,396 @@ func runTestState(t *testing.T, config *Config) multistep.StateBag {
|
||||
return state
|
||||
}
|
||||
|
||||
func Test_getCommandArgs(t *testing.T) {
|
||||
state := runTestState(t, &Config{})
|
||||
|
||||
args, err := getCommandArgs("", state)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have an error getting args. Error: %s", err)
|
||||
func Test_UserOverrides(t *testing.T) {
|
||||
type testCase struct {
|
||||
Config *Config
|
||||
Expected []string
|
||||
Reason string
|
||||
}
|
||||
testcases := []testCase{
|
||||
{
|
||||
&Config{
|
||||
HTTPConfig: common.HTTPConfig{
|
||||
HTTPDir: "http/directory",
|
||||
},
|
||||
OutputDir: "output/directory",
|
||||
VMName: "myvm",
|
||||
QemuArgs: [][]string{
|
||||
{"-randomflag1", "{{.HTTPIP}}-{{.HTTPPort}}-{{.HTTPDir}}"},
|
||||
{"-randomflag2", "{{.OutputDir}}-{{.Name}}"},
|
||||
},
|
||||
},
|
||||
[]string{
|
||||
"-display", "gtk",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
"-randomflag1", "127.0.0.1-1234-http/directory",
|
||||
"-randomflag2", "output/directory-myvm",
|
||||
"-device", ",netdev=user.0",
|
||||
},
|
||||
"Test that interpolation overrides work.",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
VMName: "myvm",
|
||||
QemuArgs: [][]string{{"-display", "partydisplay"}},
|
||||
},
|
||||
[]string{
|
||||
"-display", "partydisplay",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
"-device", ",netdev=user.0",
|
||||
},
|
||||
"User input overrides default, rest is populated as normal",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
VMName: "myvm",
|
||||
NetDevice: "mynetdevice",
|
||||
QemuArgs: [][]string{{"-device", "somerandomdevice"}},
|
||||
},
|
||||
[]string{
|
||||
"-display", "gtk",
|
||||
"-device", "somerandomdevice",
|
||||
"-device", "mynetdevice,netdev=user.0",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
},
|
||||
"Net device gets added",
|
||||
},
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"-display", "gtk",
|
||||
"-m", "0M",
|
||||
"-boot", "",
|
||||
"-fda", "fake_floppy_path",
|
||||
"-name", "",
|
||||
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
|
||||
"-vnc", ":5905",
|
||||
"-machine", "type=,accel=",
|
||||
"-device", ",netdev=user.0",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
for _, tc := range testcases {
|
||||
state := runTestState(t, tc.Config)
|
||||
|
||||
step := &stepRun{
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
}
|
||||
args, err := step.getCommandArgs(tc.Config, state)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have an error getting args. Error: %s", err)
|
||||
}
|
||||
|
||||
expected := append([]string{
|
||||
"-m", "0M",
|
||||
"-boot", "once=d",
|
||||
"-fda", "fake_floppy_path",
|
||||
"-name", "myvm",
|
||||
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
|
||||
"-vnc", ":5905",
|
||||
"-machine", "type=,accel="},
|
||||
tc.Expected...)
|
||||
|
||||
assert.ElementsMatch(t, args, expected,
|
||||
fmt.Sprintf("%s, \nRecieved: %#v", tc.Reason, args))
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, args, expected, "unexpected generated args")
|
||||
}
|
||||
|
||||
func Test_CDFilesPath(t *testing.T) {
|
||||
// cd_path is set and DiskImage is false
|
||||
state := runTestState(t, &Config{})
|
||||
state.Put("cd_path", "fake_cd_path.iso")
|
||||
|
||||
args, err := getCommandArgs("", state)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have an error getting args. Error: %s", err)
|
||||
func Test_DriveAndDeviceArgs(t *testing.T) {
|
||||
type testCase struct {
|
||||
Config *Config
|
||||
ExtraState map[string]interface{}
|
||||
Step *stepRun
|
||||
Expected []string
|
||||
Reason string
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"-display", "gtk",
|
||||
"-m", "0M",
|
||||
"-boot", "",
|
||||
"-fda", "fake_floppy_path",
|
||||
"-name", "",
|
||||
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
|
||||
"-vnc", ":5905",
|
||||
"-machine", "type=,accel=",
|
||||
"-device", ",netdev=user.0",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
|
||||
testcases := []testCase{
|
||||
{
|
||||
&Config{},
|
||||
map[string]interface{}{},
|
||||
&stepRun{
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{
|
||||
"-display", "gtk",
|
||||
"-boot", "once=d",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
},
|
||||
"Boot value should default to once=d when diskImage isn't set",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
DiskImage: true,
|
||||
DiskInterface: "virtio-scsi",
|
||||
|
||||
OutputDir: "path_to_output",
|
||||
DiskCache: "writeback",
|
||||
Format: "qcow2",
|
||||
DetectZeroes: "off",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"cd_path": "fake_cd_path.iso",
|
||||
},
|
||||
&stepRun{
|
||||
DiskImage: true,
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{
|
||||
"-display", "gtk",
|
||||
"-boot", "c",
|
||||
"-device", "virtio-scsi-pci,id=scsi0",
|
||||
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
|
||||
"-drive", "if=none,file=path_to_output,id=drive0,cache=writeback,discard=,format=qcow2",
|
||||
"-drive", "file=fake_cd_path.iso,index=0,media=cdrom",
|
||||
},
|
||||
"virtio-scsi interface, DiskImage true, extra cdrom, detectZeroes off",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
DiskImage: true,
|
||||
DiskInterface: "virtio-scsi",
|
||||
|
||||
OutputDir: "path_to_output",
|
||||
DiskCache: "writeback",
|
||||
Format: "qcow2",
|
||||
DetectZeroes: "on",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"cd_path": "fake_cd_path.iso",
|
||||
},
|
||||
&stepRun{
|
||||
DiskImage: true,
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{
|
||||
"-display", "gtk",
|
||||
"-boot", "c",
|
||||
"-device", "virtio-scsi-pci,id=scsi0",
|
||||
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
|
||||
"-drive", "if=none,file=path_to_output,id=drive0,cache=writeback,discard=,format=qcow2,detect-zeroes=on",
|
||||
"-drive", "file=fake_cd_path.iso,index=0,media=cdrom",
|
||||
},
|
||||
"virtio-scsi interface, DiskImage true, extra cdrom, detectZeroes on",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
DiskInterface: "virtio-scsi",
|
||||
|
||||
OutputDir: "path_to_output",
|
||||
DiskCache: "writeback",
|
||||
Format: "qcow2",
|
||||
DetectZeroes: "off",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"cd_path": "fake_cd_path.iso",
|
||||
// when disk image is false, we will always have at least one
|
||||
// disk path: the one we create to be the main disk.
|
||||
"qemu_disk_paths": []string{"qemupath1", "qemupath2"},
|
||||
},
|
||||
&stepRun{
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{
|
||||
"-display", "gtk",
|
||||
"-boot", "once=d",
|
||||
"-device", "virtio-scsi-pci,id=scsi0",
|
||||
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
|
||||
"-device", "scsi-hd,bus=scsi0.0,drive=drive1",
|
||||
"-drive", "if=none,file=qemupath1,id=drive0,cache=writeback,discard=,format=qcow2",
|
||||
"-drive", "if=none,file=qemupath2,id=drive1,cache=writeback,discard=,format=qcow2",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
|
||||
},
|
||||
"virtio-scsi interface, bootable iso, cdrom",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
DiskInterface: "virtio-scsi",
|
||||
|
||||
OutputDir: "path_to_output",
|
||||
DiskCache: "writeback",
|
||||
Format: "qcow2",
|
||||
DetectZeroes: "on",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"cd_path": "fake_cd_path.iso",
|
||||
// when disk image is false, we will always have at least one
|
||||
// disk path: the one we create to be the main disk.
|
||||
"qemu_disk_paths": []string{"qemupath1", "qemupath2"},
|
||||
},
|
||||
&stepRun{
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{
|
||||
"-display", "gtk",
|
||||
"-boot", "once=d",
|
||||
"-device", "virtio-scsi-pci,id=scsi0",
|
||||
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
|
||||
"-device", "scsi-hd,bus=scsi0.0,drive=drive1",
|
||||
"-drive", "if=none,file=qemupath1,id=drive0,cache=writeback,discard=,format=qcow2,detect-zeroes=on",
|
||||
"-drive", "if=none,file=qemupath2,id=drive1,cache=writeback,discard=,format=qcow2,detect-zeroes=on",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
|
||||
},
|
||||
"virtio-scsi interface, DiskImage false, extra cdrom, detect zeroes on",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
DiskInterface: "virtio-scsi",
|
||||
|
||||
OutputDir: "path_to_output",
|
||||
DiskCache: "writeback",
|
||||
Format: "qcow2",
|
||||
},
|
||||
map[string]interface{}{
|
||||
// when disk image is false, we will always have at least one
|
||||
// disk path: the one we create to be the main disk.
|
||||
"qemu_disk_paths": []string{"output/dir/path/mydisk.qcow2"},
|
||||
},
|
||||
&stepRun{
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{
|
||||
"-display", "gtk",
|
||||
"-boot", "once=d",
|
||||
"-device", "virtio-scsi-pci,id=scsi0",
|
||||
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
|
||||
"-drive", "if=none,file=output/dir/path/mydisk.qcow2,id=drive0,cache=writeback,discard=,format=qcow2,detect-zeroes=",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
},
|
||||
"virtio-scsi interface, DiskImage false, no extra disks or cds",
|
||||
},
|
||||
{
|
||||
&Config{},
|
||||
map[string]interface{}{
|
||||
"cd_path": "fake_cd_path.iso",
|
||||
"qemu_disk_paths": []string{"output/dir/path/mydisk.qcow2"},
|
||||
},
|
||||
&stepRun{
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{
|
||||
"-display", "gtk",
|
||||
"-boot", "once=d",
|
||||
"-drive", "file=output/dir/path/mydisk.qcow2,if=,cache=,discard=,format=,detect-zeroes=",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
|
||||
},
|
||||
"cd_path is set and DiskImage is false",
|
||||
},
|
||||
{
|
||||
&Config{},
|
||||
map[string]interface{}{
|
||||
// when disk image is false, we will always have at least one
|
||||
// disk path: the one we create to be the main disk.
|
||||
"qemu_disk_paths": []string{"output/dir/path/mydisk.qcow2"},
|
||||
},
|
||||
&stepRun{
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{
|
||||
"-display", "gtk",
|
||||
"-boot", "once=d",
|
||||
"-drive", "file=output/dir/path/mydisk.qcow2,if=,cache=,discard=,format=,detect-zeroes=",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
},
|
||||
"empty config",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
OutputDir: "path_to_output",
|
||||
DiskInterface: "virtio",
|
||||
DiskCache: "writeback",
|
||||
Format: "qcow2",
|
||||
},
|
||||
map[string]interface{}{
|
||||
// when disk image is false, we will always have at least one
|
||||
// disk path: the one we create to be the main disk.
|
||||
"qemu_disk_paths": []string{"output/dir/path/mydisk.qcow2"},
|
||||
},
|
||||
&stepRun{
|
||||
atLeastVersion2: false,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{
|
||||
"-boot", "once=d",
|
||||
"-drive", "file=path_to_output,if=virtio,cache=writeback,format=qcow2",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
},
|
||||
"version less than 2",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
OutputDir: "path_to_output",
|
||||
DiskInterface: "virtio",
|
||||
DiskCache: "writeback",
|
||||
Format: "qcow2",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"cd_path": "fake_cd_path.iso",
|
||||
"qemu_disk_paths": []string{"qemupath1", "qemupath2"},
|
||||
},
|
||||
&stepRun{
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{
|
||||
"-display", "gtk",
|
||||
"-boot", "once=d",
|
||||
"-drive", "file=qemupath1,if=virtio,cache=writeback,discard=,format=qcow2,detect-zeroes=",
|
||||
"-drive", "file=qemupath2,if=virtio,cache=writeback,discard=,format=qcow2,detect-zeroes=",
|
||||
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
},
|
||||
"virtio interface with extra disks",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
DiskImage: true,
|
||||
OutputDir: "path_to_output",
|
||||
DiskInterface: "virtio",
|
||||
DiskCache: "writeback",
|
||||
Format: "qcow2",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"cd_path": "fake_cd_path.iso",
|
||||
},
|
||||
&stepRun{
|
||||
DiskImage: true,
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{
|
||||
"-display", "gtk",
|
||||
"-boot", "c",
|
||||
"-drive", "file=path_to_output,if=virtio,cache=writeback,discard=,format=qcow2,detect-zeroes=",
|
||||
"-drive", "file=fake_cd_path.iso,index=0,media=cdrom",
|
||||
},
|
||||
"virtio interface with disk image",
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
state := runTestState(t, &Config{})
|
||||
for k, v := range tc.ExtraState {
|
||||
state.Put(k, v)
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, args, expected, fmt.Sprintf("unexpected generated args: %#v", args))
|
||||
args, err := tc.Step.getCommandArgs(tc.Config, state)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have an error getting args. Error: %s", err)
|
||||
}
|
||||
|
||||
// cd_path is set and DiskImage is true
|
||||
config := &Config{
|
||||
DiskImage: true,
|
||||
DiskInterface: "virtio-scsi",
|
||||
expected := append([]string{
|
||||
"-m", "0M",
|
||||
"-fda", "fake_floppy_path",
|
||||
"-name", "",
|
||||
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
|
||||
"-vnc", ":5905",
|
||||
"-machine", "type=,accel=",
|
||||
"-device", ",netdev=user.0"},
|
||||
tc.Expected...)
|
||||
|
||||
assert.ElementsMatch(t, args, expected,
|
||||
fmt.Sprintf("%s, \nRecieved: %#v", tc.Reason, args))
|
||||
}
|
||||
state = runTestState(t, config)
|
||||
state.Put("cd_path", "fake_cd_path.iso")
|
||||
|
||||
args, err = getCommandArgs("c", state)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have an error getting args. Error: %s", err)
|
||||
}
|
||||
|
||||
expected = []string{
|
||||
"-display", "gtk",
|
||||
"-m", "0M",
|
||||
"-boot", "c",
|
||||
"-fda", "fake_floppy_path",
|
||||
"-name", "",
|
||||
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
|
||||
"-vnc", ":5905",
|
||||
"-machine", "type=,accel=",
|
||||
"-device", ",netdev=user.0",
|
||||
"-device", "virtio-scsi-pci,id=scsi0",
|
||||
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
|
||||
"-drive", "if=none,file=,id=drive0,cache=,discard=,format=,detect-zeroes=",
|
||||
"-drive", "file=fake_cd_path.iso,index=0,media=cdrom",
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, args, expected, fmt.Sprintf("unexpected generated args: %#v", args))
|
||||
}
|
||||
|
||||
func Test_OptionalConfigOptionsGetSet(t *testing.T) {
|
||||
@@ -124,8 +434,11 @@ func Test_OptionalConfigOptionsGetSet(t *testing.T) {
|
||||
}
|
||||
|
||||
state := runTestState(t, c)
|
||||
|
||||
args, err := getCommandArgs("once=d", state)
|
||||
step := &stepRun{
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
}
|
||||
args, err := step.getCommandArgs(c, state)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have an error getting args. Error: %s", err)
|
||||
}
|
||||
@@ -144,5 +457,251 @@ func Test_OptionalConfigOptionsGetSet(t *testing.T) {
|
||||
"-qmp", "unix:qmp_path,server,nowait",
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, args, expected, "password flag should be set, and d drive should be set.")
|
||||
assert.ElementsMatch(t, args, expected, "password flag should be set, and d drive should be set: %s", args)
|
||||
}
|
||||
|
||||
// Tests for presence of Packer-generated arguments. Doesn't test that
|
||||
// arguments which shouldn't be there are absent.
|
||||
func Test_Defaults(t *testing.T) {
|
||||
type testCase struct {
|
||||
Config *Config
|
||||
ExtraState map[string]interface{}
|
||||
Step *stepRun
|
||||
Expected []string
|
||||
Reason string
|
||||
}
|
||||
|
||||
testcases := []testCase{
|
||||
{
|
||||
&Config{},
|
||||
map[string]interface{}{},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-boot", "once=d"},
|
||||
"Boot value should default to once=d",
|
||||
},
|
||||
{
|
||||
&Config{},
|
||||
map[string]interface{}{},
|
||||
&stepRun{
|
||||
DiskImage: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{"-boot", "c"},
|
||||
"Boot value should be set to c when DiskImage is set on step",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
QMPEnable: true,
|
||||
QMPSocketPath: "/path/to/socket",
|
||||
},
|
||||
map[string]interface{}{},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-qmp", "unix:/path/to/socket,server,nowait"},
|
||||
"Args should contain -qmp when qmp_enable is set",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
QMPEnable: true,
|
||||
},
|
||||
map[string]interface{}{},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-qmp", "unix:,server,nowait"},
|
||||
"Args contain -qmp even when socket path isn't set, if qmp enabled",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
VMName: "partyname",
|
||||
},
|
||||
map[string]interface{}{},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-name", "partyname"},
|
||||
"Name is set from config",
|
||||
},
|
||||
{
|
||||
&Config{},
|
||||
map[string]interface{}{},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-name", ""},
|
||||
"Name is set from config, even when name is blank (which won't " +
|
||||
"happen for real thanks to defaulting in build prepare)",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
Accelerator: "none",
|
||||
MachineType: "fancymachine",
|
||||
},
|
||||
map[string]interface{}{},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-machine", "type=fancymachine"},
|
||||
"Don't add accelerator tag when no accelerator is set.",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
Accelerator: "kvm",
|
||||
MachineType: "fancymachine",
|
||||
},
|
||||
map[string]interface{}{},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-machine", "type=fancymachine,accel=kvm"},
|
||||
"Add accelerator tag when accelerator is set.",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
NetBridge: "fakebridge",
|
||||
},
|
||||
map[string]interface{}{},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-netdev", "bridge,id=user.0,br=fakebridge"},
|
||||
"Add netbridge tag when netbridge is set.",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
CommConfig: CommConfig{
|
||||
Comm: communicator.Config{
|
||||
Type: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-netdev", "user,id=user.0"},
|
||||
"No host forwarding when no net bridge and no communicator",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
CommConfig: CommConfig{
|
||||
Comm: communicator.Config{
|
||||
Type: "ssh",
|
||||
SSH: communicator.SSH{
|
||||
SSHPort: 4567,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"commHostPort": 1111,
|
||||
},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-netdev", "user,id=user.0,hostfwd=tcp::1111-:4567"},
|
||||
"Host forwarding when a communicator is configured",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
VNCBindAddress: "1.1.1.1",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"vnc_port": 5959,
|
||||
},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-vnc", "1.1.1.1:5959"},
|
||||
"no VNC password should be set",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
VNCBindAddress: "1.1.1.1",
|
||||
VNCUsePassword: true,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"vnc_port": 5959,
|
||||
},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-vnc", "1.1.1.1:5959,password"},
|
||||
"VNC password should be set",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
MemorySize: 2345,
|
||||
},
|
||||
map[string]interface{}{},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-m", "2345M"},
|
||||
"Memory is set, with unit M",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
CpuCount: 2,
|
||||
},
|
||||
map[string]interface{}{},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-smp", "cpus=2,sockets=2"},
|
||||
"both cpus and sockets are set to config's CpuCount",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
CpuCount: 2,
|
||||
},
|
||||
map[string]interface{}{},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-smp", "cpus=2,sockets=2"},
|
||||
"both cpus and sockets are set to config's CpuCount",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
CpuCount: 2,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"floppy_path": "/path/to/floppy",
|
||||
},
|
||||
&stepRun{ui: packer.TestUi(t)},
|
||||
[]string{"-fda", "/path/to/floppy"},
|
||||
"floppy path should be set under fda flag, when it exists",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
Headless: false,
|
||||
Display: "fakedisplay",
|
||||
UseDefaultDisplay: false,
|
||||
},
|
||||
map[string]interface{}{},
|
||||
&stepRun{
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{"-display", "fakedisplay"},
|
||||
"Display option should value config display",
|
||||
},
|
||||
{
|
||||
&Config{
|
||||
Headless: false,
|
||||
},
|
||||
map[string]interface{}{},
|
||||
&stepRun{
|
||||
atLeastVersion2: true,
|
||||
ui: packer.TestUi(t),
|
||||
},
|
||||
[]string{"-display", "gtk"},
|
||||
"Display option should default to gtk",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
state := runTestState(t, &Config{})
|
||||
for k, v := range tc.ExtraState {
|
||||
state.Put(k, v)
|
||||
}
|
||||
|
||||
args, err := tc.Step.getCommandArgs(tc.Config, state)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have an error getting args. Error: %s", err)
|
||||
}
|
||||
if !matchArgument(args, tc.Expected) {
|
||||
t.Fatalf("Couldn't find %#v in result. Got: %#v, Reason: %s",
|
||||
tc.Expected, args, tc.Reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This test makes sure that arguments don't end up in the final boot command
|
||||
// if they aren't configured in the config.
|
||||
// func TestDefaultsAbsentValues(t *testing.T) {}
|
||||
func matchArgument(actual []string, expected []string) bool {
|
||||
key := expected[0]
|
||||
for i, k := range actual {
|
||||
if key == k {
|
||||
if expected[1] == actual[i+1] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -44,12 +44,11 @@ type Config struct {
|
||||
// that will be used to launch a new server and provision it. See
|
||||
// the images list
|
||||
// get the complete list of the accepted image UUID.
|
||||
// The marketplace image label (eg `ubuntu_focal`) also works.
|
||||
Image string `mapstructure:"image" required:"true"`
|
||||
// The name of the server commercial type:
|
||||
// ARM64-128GB, ARM64-16GB, ARM64-2GB, ARM64-32GB, ARM64-4GB,
|
||||
// ARM64-64GB, ARM64-8GB, C1, C2L, C2M, C2S, START1-L,
|
||||
// START1-M, START1-S, START1-XS, X64-120GB, X64-15GB, X64-30GB,
|
||||
// X64-60GB
|
||||
// C1, C2L, C2M, C2S, DEV1-S, DEV1-M, DEV1-L, DEV1-XL,
|
||||
// GP1-XS, GP1-S, GP1-M, GP1-L, GP1-XL, RENDER-S
|
||||
CommercialType string `mapstructure:"commercial_type" required:"true"`
|
||||
// The name of the resulting snapshot that will
|
||||
// appear in your account. Default packer-TIMESTAMP
|
||||
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
|
||||
"github.com/scaleway/scaleway-sdk-go/api/marketplace/v1"
|
||||
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||
)
|
||||
|
||||
@@ -22,8 +24,27 @@ func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating image: %v", c.ImageName))
|
||||
|
||||
imageID := c.Image
|
||||
|
||||
// if not a UUID, we check the Marketplace API
|
||||
_, err := uuid.ParseUUID(c.Image)
|
||||
if err != nil {
|
||||
apiMarketplace := marketplace.NewAPI(state.Get("client").(*scw.Client))
|
||||
imageID, err = apiMarketplace.GetLocalImageIDByLabel(&marketplace.GetLocalImageIDByLabelRequest{
|
||||
ImageLabel: c.Image,
|
||||
Zone: scw.Zone(c.Zone),
|
||||
CommercialType: c.CommercialType,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error getting initial image info from marketplace: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
imageResp, err := instanceAPI.GetImage(&instance.GetImageRequest{
|
||||
ImageID: c.Image,
|
||||
ImageID: imageID,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error getting initial image info: %s", err)
|
||||
|
||||
@@ -14,15 +14,18 @@ type LocationConfig struct {
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
// VM folder to create the VM in.
|
||||
Folder string `mapstructure:"folder"`
|
||||
// ESXi cluster where target VM is created. See
|
||||
// [Working with Clusters](#working-with-clusters).
|
||||
// ESXi cluster where target VM is created. See the
|
||||
// [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
|
||||
// section above for more details.
|
||||
Cluster string `mapstructure:"cluster"`
|
||||
// ESXi host where target VM is created. A full path must be specified if
|
||||
// the host is in a folder. For example `folder/host`. See the
|
||||
// `Specifying Clusters and Hosts` section above for more details.
|
||||
// [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
|
||||
// section above for more details.
|
||||
Host string `mapstructure:"host"`
|
||||
// VMWare resource pool. If not set, it will look for the root resource pool of the `host` or `cluster`.
|
||||
// If a root resource is not found, it will then look for a default resource pool.
|
||||
// VMWare resource pool. If not set, it will look for the root resource
|
||||
// pool of the `host` or `cluster`. If a root resource is not found, it
|
||||
// will then look for a default resource pool.
|
||||
ResourcePool string `mapstructure:"resource_pool"`
|
||||
// VMWare datastore. Required if `host` is a cluster, or if `host` has
|
||||
// multiple datastores.
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/packer/helper/communicator/sshkey"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
Type string
|
||||
Bits int
|
||||
Filename string
|
||||
}
|
||||
|
||||
func (o *options) AddFlagSets(fs *flag.FlagSet) {
|
||||
fs.StringVar(&o.Type, "type", "rsa", `dsa | ecdsa | ed25519 | rsa
|
||||
Specifies the type of key to create. The possible values are 'dsa', 'ecdsa',
|
||||
'ed25519', or 'rsa'.
|
||||
`)
|
||||
fs.IntVar(&o.Bits, "bits", 0, `Specifies the number of bits in the key to create. By default maximum
|
||||
number will be picked. For RSA keys, the minimum size is 1024 bits and the
|
||||
default is 3072 bits. Generally, 3072 bits is considered sufficient. DSA
|
||||
keys must be exactly 1024 bits as specified by FIPS 186-2. For ECDSA keys,
|
||||
the bits flag determines the key length by selecting from one of three
|
||||
elliptic curve sizes: 256, 384 or 521 bits. Attempting to use bit lengths
|
||||
other than these three values for ECDSA keys will fail. Ed25519 keys have a
|
||||
fixed length and the bits flag will be ignored.
|
||||
`)
|
||||
|
||||
defaultPath := ""
|
||||
user, err := user.Current()
|
||||
if err == nil {
|
||||
defaultPath = filepath.Join(user.HomeDir, ".ssh", "tests")
|
||||
}
|
||||
|
||||
fs.StringVar(&o.Filename, "filename", defaultPath, `Specifies the filename of the key file.
|
||||
`)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("ssh-keygen: ")
|
||||
fs := flag.NewFlagSet("ssh-keygen", flag.ContinueOnError)
|
||||
cla := options{}
|
||||
cla.AddFlagSets(fs)
|
||||
if err := fs.Parse(os.Args[1:]); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
algo, err := sshkey.AlgorithmString(cla.Type)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Printf("Generating public/private %s key pair.", algo)
|
||||
|
||||
keypair, err := sshkey.GeneratePair(algo, nil, cla.Bits)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if isDir(cla.Filename) {
|
||||
cla.Filename = filepath.Join(cla.Filename, "id_"+algo.String())
|
||||
}
|
||||
if fileExists(cla.Filename) {
|
||||
log.Fatalf("%s already exists.", cla.Filename)
|
||||
}
|
||||
log.Printf("Saving private key to %s", cla.Filename)
|
||||
if err := ioutil.WriteFile(cla.Filename, keypair.Private, 0600); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
publicFilename := cla.Filename + ".pub"
|
||||
log.Printf("Saving public key to %s", publicFilename)
|
||||
if err := ioutil.WriteFile(publicFilename, keypair.Public, 0644); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func isDir(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return info.IsDir()
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
return err == nil
|
||||
}
|
||||
+2
-2
@@ -382,7 +382,7 @@ Options:
|
||||
|
||||
-color=false Disable color output. (Default: color)
|
||||
-debug Debug mode enabled for builds.
|
||||
-except=foo,bar,baz Run all builds and post-procesors other than these.
|
||||
-except=foo,bar,baz Run all builds and post-processors other than these.
|
||||
-only=foo,bar,baz Build only the specified builds.
|
||||
-force Force a build to continue if artifacts exist, deletes existing artifacts.
|
||||
-machine-readable Produce machine-readable output.
|
||||
@@ -390,7 +390,7 @@ Options:
|
||||
-parallel-builds=1 Number of builds to run in parallel. 1 disables parallelization. 0 means no limit (Default: 0)
|
||||
-timestamp-ui Enable prefixing of each ui output with an RFC3339 timestamp.
|
||||
-var 'key=value' Variable for templates, can be used multiple times.
|
||||
-var-file=path JSON file containing user variables.
|
||||
-var-file=path JSON or HCL2 file containing user variables.
|
||||
`
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
|
||||
+12
-2
@@ -138,7 +138,7 @@ func TestBuild(t *testing.T) {
|
||||
},
|
||||
|
||||
{
|
||||
name: "build name: HCL",
|
||||
name: "source name: HCL",
|
||||
args: []string{
|
||||
"-parallel-builds=1", // to ensure order is kept
|
||||
testFixture("build-name-and-type"),
|
||||
@@ -297,7 +297,6 @@ func TestBuild(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "hcl - recipes - only recipes",
|
||||
args: []string{
|
||||
@@ -314,6 +313,17 @@ func TestBuild(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hcl - build.name accessible",
|
||||
args: []string{
|
||||
filepath.Join(testFixture("build-name-and-type"), "buildname.pkr.hcl"),
|
||||
},
|
||||
fileCheck: fileCheck{
|
||||
expected: []string{
|
||||
"pineapple.pizza.txt",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tc {
|
||||
|
||||
@@ -2,6 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -27,6 +28,15 @@ func outputCommand(t *testing.T, m Meta) (string, string) {
|
||||
return out.String(), err.String()
|
||||
}
|
||||
|
||||
func testFixtureContent(n ...string) string {
|
||||
path := filepath.Join(append([]string{fixturesDir}, n...)...)
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func testFixture(n ...string) string {
|
||||
paths := []string{fixturesDir}
|
||||
paths = append(paths, n...)
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ Usage: packer console [options] [TEMPLATE]
|
||||
|
||||
Options:
|
||||
-var 'key=value' Variable for templates, can be used multiple times.
|
||||
-var-file=path JSON file containing user variables. [ Note that even in HCL mode this expects file to contain JSON, a fix is comming soon ]
|
||||
-var-file=path JSON or HCL2 file containing user variables. [ Note that even in HCL mode this expects file to contain JSON, a fix is comming soon ]
|
||||
`
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
|
||||
@@ -55,7 +55,7 @@ func (c *HCL2UpgradeCommand) ParseArgs(args []string) (*HCL2UpgradeArgs, int) {
|
||||
}
|
||||
|
||||
const (
|
||||
hcl2UpgradeFileHeader = `# This file was autogenerate by the BETA 'packer hcl2_upgrade' command. We
|
||||
hcl2UpgradeFileHeader = `# This file was autogenerated by the BETA 'packer hcl2_upgrade' command. We
|
||||
# recommend double checking that everything is correct before going forward. We
|
||||
# also recommend treating this file as disposable. The HCL2 blocks in this
|
||||
# file can be moved to other files. For example, the variable blocks could be
|
||||
@@ -64,20 +64,20 @@ const (
|
||||
# once they also need to be in the same folder. 'packer inspect folder/'
|
||||
# will describe to you what is in that folder.
|
||||
|
||||
# All generated input variables will be of string type as this how Packer JSON
|
||||
# views them; you can later on change their type. Read the variables type
|
||||
# All generated input variables will be of 'string' type as this is how Packer JSON
|
||||
# views them; you can change their type later on. Read the variables type
|
||||
# constraints documentation
|
||||
# https://www.packer.io/docs/from-1.5/variables#type-constraints for more info.
|
||||
`
|
||||
|
||||
sourcesHeader = `
|
||||
# source blocks are generated from your builders; a source can be referenced in
|
||||
# build blocks. A build block runs provisioner and post-processors onto a
|
||||
# build blocks. A build block runs provisioner and post-processors on a
|
||||
# source. Read the documentation for source blocks here:
|
||||
# https://www.packer.io/docs/from-1.5/blocks/source`
|
||||
|
||||
buildHeader = `
|
||||
# a build block invokes sources and runs provisionning steps on them. The
|
||||
# a build block invokes sources and runs provisioning steps on them. The
|
||||
# documentation for build blocks can be found here:
|
||||
# https://www.packer.io/docs/from-1.5/blocks/build
|
||||
build {
|
||||
@@ -413,14 +413,14 @@ func (*HCL2UpgradeCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: packer hcl2_upgrade -output-file=JSON_TEMPLATE.pkr.hcl JSON_TEMPLATE...
|
||||
|
||||
Will transform your JSON template to a HCL2 configuration.
|
||||
Will transform your JSON template into an HCL2 configuration.
|
||||
`
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (*HCL2UpgradeCommand) Synopsis() string {
|
||||
return "transform a JSON template into a HCL2 configuration"
|
||||
return "transform a JSON template into an HCL2 configuration"
|
||||
}
|
||||
|
||||
func (*HCL2UpgradeCommand) AutocompleteArgs() complete.Predictor {
|
||||
|
||||
@@ -153,6 +153,13 @@ Note: If your build names contain user variables or template
|
||||
functions such as 'timestamp', these are processed at build time,
|
||||
and therefore only show in their raw form here.
|
||||
`},
|
||||
{
|
||||
[]string{
|
||||
"inspect", filepath.Join(testFixture("hcl-inspect-with-sensitive-vars")),
|
||||
},
|
||||
nil,
|
||||
testFixtureContent("hcl-inspect-with-sensitive-vars", "expected-output.txt"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tc {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
source "null" "pizza" {
|
||||
communicator = "none"
|
||||
}
|
||||
|
||||
build {
|
||||
name = "pineapple"
|
||||
sources = [
|
||||
"sources.null.pizza",
|
||||
]
|
||||
|
||||
provisioner "shell-local" {
|
||||
inline = [
|
||||
"echo '' > ${build.name}.${source.name}.txt"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
Packer Inspect: HCL2 mode
|
||||
|
||||
> input-variables:
|
||||
|
||||
var.not_sensitive: "I am soooo not sensitive"
|
||||
var.not_sensitive_unknown: "<unknown>"
|
||||
var.sensitive: "<sensitive>"
|
||||
var.sensitive_array: "[\n \"<sensitive>\",\n \"<sensitive>\",\n]"
|
||||
var.sensitive_tags: "{\n \"first_key\" = \"<sensitive>\"\n \"second_key\" = \"<sensitive>\"\n}"
|
||||
var.sensitive_unknown: "<unknown>"
|
||||
|
||||
> local-variables:
|
||||
|
||||
|
||||
> builds:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user