Compare commits

..

1 Commits

Author SHA1 Message Date
Wilken Rivera a2b7a516a8 builder/amazon: Update logic for session credentials
When failing back to Session credentials the build should not error if
creds were returned successfully.

* Switching to %v verb of %w in cases where error is nil.

Closes #1000
2020-09-28 12:21:08 -04:00
1265 changed files with 91159 additions and 83896 deletions
+4 -4
View File
@@ -7,11 +7,11 @@ version: 2.1
executors:
golang:
docker:
- image: circleci/golang:1.15
- image: circleci/golang:1.13
resource_class: medium+
darwin:
macos:
xcode: "12.0.0"
xcode: "9.0"
commands:
install-go-run-tests-unix:
@@ -67,7 +67,7 @@ jobs:
steps:
- install-go-run-tests-unix:
GOOS: darwin
GOVERSION: "1.15"
GOVERSION: "1.13"
- codecov/upload:
file: coverage.txt
test-windows:
@@ -76,7 +76,7 @@ jobs:
shell: bash.exe
steps:
- install-go-run-tests-windows:
GOVERSION: "1.15"
GOVERSION: "1.13"
- codecov/upload:
file: coverage.txt
check-lint:
-1
View File
@@ -6,7 +6,6 @@
*.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
+2 -46
View File
@@ -1,48 +1,4 @@
## 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:
* builder/amazon: Fix authentication issue when using instance profiles or
assumed roles for loading session-derived credentials. [GH-10007]
* builder/azure: Fix crash when using `azure_tag` or `azure_tags` configuration
options. [GH-10014]
* builder/qemu: Ensure `qemu_img_args` are honored during the disk convert
step. [GH-10001]
## 1.6.4 (Upcoming)
## 1.6.3 (September 25, 2020)
@@ -65,7 +21,7 @@
* builder/proxmox: Add `io_thread` option for supporting io threads when using
a `virtio-scsi-single` controller with a `scsi` or `virtio` disk type.
[GH-9969]
* builder/proxmox: Add ability to specify interfaces for http_directory and VM.
* builder/proxmox: Add ability to specify interfaces for http_directroy and VM.
[GH-9874]
* builder/proxmox: Allow the mounting of multiple ISOs via the `cd_drive`
option. [GH-9653]
+81 -41
View File
@@ -12,8 +12,6 @@ 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
@@ -21,7 +19,8 @@ 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
@@ -33,8 +32,6 @@ 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
@@ -47,51 +44,94 @@ 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 the system disk.
// See the [disk device configuration](#disk-devices-configuration) section
// for more information on options.
// Usage example:
// 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}.
//
// ```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.
// See the [disk device configuration](#disk-devices-configuration) section
// for more information on options.
// Usage example:
// 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.
//
// ```json
// "builders": [{
// "type":"alicloud-ecs",
// "image_disk_mappings": [
// {
// "disk_snapshot_id": "someid",
// "disk_device": "dev/xvdb"
// }
// ],
// ...
// }
// ```
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings" required:"false"`
}
@@ -52,7 +52,6 @@ type FlatConfig struct {
RawRegion *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"`
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
@@ -131,7 +130,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
+168 -31
View File
@@ -9,16 +9,19 @@ import (
"log"
"net/http"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
awsCredentials "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
awsbase "github.com/hashicorp/aws-sdk-go-base"
"github.com/aws/aws-sdk-go/service/sts"
cleanhttp "github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/packer/template/interpolate"
vaultapi "github.com/hashicorp/vault/api"
homedir "github.com/mitchellh/go-homedir"
)
// AssumeRoleConfig lets users set configuration options for assuming a special
@@ -145,8 +148,6 @@ type AccessConfig struct {
// validation of the ami_regions configuration option. Default false.
SkipValidation bool `mapstructure:"skip_region_validation" required:"false"`
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
// Set to true if you want to skip validating AWS credentials before runtime.
SkipCredsValidation bool `mapstructure:"skip_credential_validation"`
// The access token to use. This is different from the
// access key and secret key. If you're not sure what this is, then you
// probably don't need it. This will also be read from the AWS_SESSION_TOKEN
@@ -308,35 +309,171 @@ func (c *AccessConfig) IsChinaCloud() bool {
// endpoints. GetCredentials also validates the credentials and the ability to
// assume a role or will return an error if unsuccessful.
func (c *AccessConfig) GetCredentials(config *aws.Config) (*awsCredentials.Credentials, error) {
// Reload values into the config used by the Packer-Terraform shared SDK
awsbaseConfig := &awsbase.Config{
AccessKey: c.AccessKey,
AssumeRoleARN: c.AssumeRole.AssumeRoleARN,
AssumeRoleDurationSeconds: c.AssumeRole.AssumeRoleDurationSeconds,
AssumeRoleExternalID: c.AssumeRole.AssumeRoleExternalID,
AssumeRolePolicy: c.AssumeRole.AssumeRolePolicy,
AssumeRolePolicyARNs: c.AssumeRole.AssumeRolePolicyARNs,
AssumeRoleSessionName: c.AssumeRole.AssumeRoleSessionName,
AssumeRoleTags: c.AssumeRole.AssumeRoleTags,
AssumeRoleTransitiveTagKeys: c.AssumeRole.AssumeRoleTransitiveTagKeys,
CredsFilename: c.CredsFilename,
DebugLogging: false,
// TODO: implement for Packer
// IamEndpoint: c.Endpoints["iam"],
Insecure: c.InsecureSkipTLSVerify,
MaxRetries: c.MaxRetries,
Profile: c.ProfileName,
Region: c.RawRegion,
SecretKey: c.SecretKey,
SkipCredsValidation: c.SkipCredsValidation,
SkipMetadataApiCheck: c.SkipMetadataApiCheck,
// TODO: implement for Packer
// SkipRequestingAccountId: c.SkipRequestingAccountId,
// StsEndpoint: c.Endpoints["sts"],
Token: c.Token,
sharedCredentialsFilename, err := homedir.Expand(c.CredsFilename)
if err != nil {
return nil, fmt.Errorf("error expanding shared credentials filename: %v", err)
}
return awsbase.GetCredentials(awsbaseConfig)
// Create a credentials chain that tries to load credentials from various
// common sources: config vars, then local profiles.
// Rather than using the default credentials chain, build a chain provider,
// lazy-evaluated by aws-sdk
providers := []awsCredentials.Provider{
// Tries to set new credentials object using the given
// access_key/secret_key/token. If they are not set, this will fail
// over to the other credentials providers
&awsCredentials.StaticProvider{Value: awsCredentials.Value{
AccessKeyID: c.AccessKey,
SecretAccessKey: c.SecretKey,
SessionToken: c.Token,
}},
// Tries to load credentials from environment.
&awsCredentials.EnvProvider{},
// Tries to load credentials from local file.
// If sharedCredentialsFilename is empty, the AWS sdk will use the
// environment var AWS_SHARED_CREDENTIALS_FILE to determine the file
// location, and if that's not set, AWS will use the default locations
// of:
// - Linux/Unix: $HOME/.aws/credentials
// - Windows: %USERPROFILE%\.aws\credentials
&awsCredentials.SharedCredentialsProvider{
Filename: sharedCredentialsFilename,
Profile: c.ProfileName,
},
}
// Validate the credentials before returning them
creds := awsCredentials.NewChainCredentials(providers)
cp, err := creds.Get()
if IsAWSErr(err, "NoCredentialProviders", "") {
creds, err = c.GetCredentialsFromSession()
if err != nil {
return nil, err
}
}
if err != nil {
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %v", err)
}
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
// In the "normal" flow (i.e. not assuming a role), we return here.
if c.AssumeRole.AssumeRoleARN == "" {
return creds, nil
}
// create a config for the assume role session based off the config we
// created for our main sessions
assumeRoleAWSConfig := config.Copy()
assumeRoleAWSConfig.CredentialsChainVerboseErrors = aws.Bool(true)
assumeRoleSession, err := session.NewSession(assumeRoleAWSConfig)
if err != nil {
return nil, fmt.Errorf("error creating assume role session: %v", err)
}
stsclient := sts.New(assumeRoleSession)
assumeRoleProvider := &stscreds.AssumeRoleProvider{
Client: stsclient,
RoleARN: c.AssumeRole.AssumeRoleARN,
}
if c.AssumeRole.AssumeRoleDurationSeconds > 0 {
assumeRoleProvider.Duration = time.Duration(c.AssumeRole.AssumeRoleDurationSeconds) * time.Second
}
if c.AssumeRole.AssumeRoleExternalID != "" {
assumeRoleProvider.ExternalID = aws.String(c.AssumeRole.AssumeRoleExternalID)
}
if c.AssumeRole.AssumeRolePolicy != "" {
assumeRoleProvider.Policy = aws.String(c.AssumeRole.AssumeRolePolicy)
}
if len(c.AssumeRole.AssumeRolePolicyARNs) > 0 {
var policyDescriptorTypes []*sts.PolicyDescriptorType
for _, policyARN := range c.AssumeRole.AssumeRolePolicyARNs {
policyDescriptorType := &sts.PolicyDescriptorType{
Arn: aws.String(policyARN),
}
policyDescriptorTypes = append(policyDescriptorTypes, policyDescriptorType)
}
assumeRoleProvider.PolicyArns = policyDescriptorTypes
}
if c.AssumeRole.AssumeRoleSessionName != "" {
assumeRoleProvider.RoleSessionName = c.AssumeRole.AssumeRoleSessionName
}
if len(c.AssumeRole.AssumeRoleTags) > 0 {
var tags []*sts.Tag
for k, v := range c.AssumeRole.AssumeRoleTags {
tag := &sts.Tag{
Key: aws.String(k),
Value: aws.String(v),
}
tags = append(tags, tag)
}
assumeRoleProvider.Tags = tags
}
if len(c.AssumeRole.AssumeRoleTransitiveTagKeys) > 0 {
assumeRoleProvider.TransitiveTagKeys = aws.StringSlice(c.AssumeRole.AssumeRoleTransitiveTagKeys)
}
providers = []awsCredentials.Provider{assumeRoleProvider}
assumeRoleCreds := awsCredentials.NewChainCredentials(providers)
_, err = assumeRoleCreds.Get()
if err != nil {
return nil, fmt.Errorf("Unable to assume role: %w", err)
}
return assumeRoleCreds, nil
}
// GetCredentialsFromSession returns credentials derived from a session. A
// session uses the AWS SDK Go chain of providers so may use a provider (e.g.,
// ProcessProvider) that is not part of the Packer provider chain.
func (c *AccessConfig) GetCredentialsFromSession() (*awsCredentials.Credentials, error) {
log.Printf("[INFO] Attempting to use session-derived credentials")
// Avoid setting HTTPClient here as it will prevent the ec2metadata
// client from automatically lowering the timeout to 1 second.
options := &session.Options{
Config: aws.Config{
MaxRetries: aws.Int(0),
Region: aws.String(c.RawRegion),
},
Profile: c.ProfileName,
SharedConfigState: session.SharedConfigEnable,
}
sess, err := session.NewSessionWithOptions(*options)
if err != nil {
if IsAWSErr(err, "NoCredentialProviders", "") {
return nil, c.NewNoValidCredentialSourcesError(err)
}
return nil, fmt.Errorf("Error creating AWS session: %v", err)
}
creds := sess.Config.Credentials
cp, err := sess.Config.Credentials.Get()
if err != nil {
return nil, c.NewNoValidCredentialSourcesError(err)
}
log.Printf("[INFO] Successfully derived credentials from session")
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
return creds, nil
}
func (c *AccessConfig) GetCredsFromVault() error {
@@ -412,7 +549,7 @@ func (c *AccessConfig) NewNoValidCredentialSourcesError(err error) error {
return fmt.Errorf("No valid credential sources found for AWS Builder. "+
"Please see https://www.packer.io/docs/builders/amazon#authentication "+
"for more information on providing credentials for the AWS Builder. "+
"Error: %w", err)
"Error: %v", err)
}
func (c *AccessConfig) NewEC2Connection() (ec2iface.EC2API, error) {
+2 -2
View File
@@ -134,8 +134,8 @@ func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapp
ebsBlockDevice.VolumeSize = aws.Int64(blockDevice.VolumeSize)
}
// IOPS is only valid for io1 and io2 types
if blockDevice.VolumeType == "io1" || blockDevice.VolumeType == "io2" {
// IOPS is only valid for io1 type
if blockDevice.VolumeType == "io1" {
ebsBlockDevice.Iops = aws.Int64(blockDevice.IOPS)
}
@@ -66,25 +66,6 @@ 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",
-13
View File
@@ -376,12 +376,6 @@ 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 &lt;UUID&gt; is a 36 character unique identifier.
@@ -637,13 +631,6 @@ 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
}
-20
View File
@@ -232,23 +232,3 @@ 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,7 +33,6 @@ type StepRunSourceInstance struct {
IsRestricted bool
SourceAMI string
Tags map[string]string
Tenancy string
UserData string
UserDataFile string
VolumeTags map[string]string
@@ -196,10 +195,6 @@ 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,
-1
View File
@@ -206,7 +206,6 @@ 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,
-4
View File
@@ -31,7 +31,6 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
@@ -81,7 +80,6 @@ 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"`
@@ -177,7 +175,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
@@ -227,7 +224,6 @@ 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},
-1
View File
@@ -228,7 +228,6 @@ 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,
@@ -74,7 +74,6 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
@@ -103,7 +102,6 @@ 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"`
@@ -221,7 +219,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
@@ -250,7 +247,6 @@ 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},
-1
View File
@@ -209,7 +209,6 @@ 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,
@@ -76,7 +76,6 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
@@ -105,7 +104,6 @@ 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"`
@@ -201,7 +199,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
@@ -230,7 +227,6 @@ 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},
-1
View File
@@ -286,7 +286,6 @@ 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,
}
@@ -31,7 +31,6 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"`
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
@@ -81,7 +80,6 @@ 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"`
@@ -183,7 +181,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
@@ -233,7 +230,6 @@ 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},
@@ -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 @@
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.
+1 -1
View File
@@ -390,7 +390,7 @@ func (b *Builder) getBlobAccount(ctx context.Context, client *AzureClient, resou
func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey)
stateBag.Put(constants.ArmTags, packerAzureCommon.MapToAzureTags(b.config.AzureTags))
stateBag.Put(constants.ArmTags, b.config.AzureTags)
stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
+5 -6
View File
@@ -6,6 +6,7 @@ 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
@@ -150,7 +151,7 @@ const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
"build_resource_group_name" : "packer-acceptance-test",
"managed_image_resource_group_name": "packer-acceptance-test",
"managed_image_name": "testBuilderAccManagedDiskWindowsBuildResourceGroup-{{timestamp}}",
"managed_image_name": "testBuilderAccManagedDiskWindows-{{timestamp}}",
"os_type": "Windows",
"image_publisher": "MicrosoftWindowsServer",
@@ -222,11 +223,7 @@ const testBuilderAccManagedDiskLinux = `
"image_sku": "16.04-LTS",
"location": "South Central US",
"vm_size": "Standard_DS2_v2",
"azure_tags": {
"env": "testing",
"builder": "packer"
}
"vm_size": "Standard_DS2_v2"
}]
}
`
@@ -261,6 +258,7 @@ 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": [{
@@ -269,6 +267,7 @@ 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",
-32
View File
@@ -33,35 +33,3 @@ func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
}
}
}
func TestStateBagShouldPoluateExpectedTags(t *testing.T) {
var testSubject Builder
expectedTags := map[string]string{
"env": "test",
"builder": "packer",
}
armConfig := getArmBuilderConfiguration()
armConfig["azure_tags"] = expectedTags
_, _, err := testSubject.Prepare(armConfig, getPackerConfiguration())
if err != nil {
t.Fatalf("failed to prepare: %s", err)
}
tags, ok := testSubject.stateBag.Get(constants.ArmTags).(map[string]*string)
if !ok {
t.Errorf("Expected the builder's state bag to contain tags of type %T, but didn't.", testSubject.config.AzureTags)
}
if len(tags) != len(expectedTags) {
t.Errorf("expect tags from state to be the same length as tags from config")
}
for k, v := range tags {
if expectedTags[k] != *v {
t.Errorf("expect tag value of %s to be %s, but got %s", k, expectedTags[k], *v)
}
}
}
+2 -2
View File
@@ -2072,8 +2072,8 @@ func TestConfig_PrepareProvidedWinRMPassword(t *testing.T) {
}
}
func getArmBuilderConfiguration() map[string]interface{} {
m := make(map[string]interface{})
func getArmBuilderConfiguration() map[string]string {
m := make(map[string]string)
for _, v := range requiredConfigValues {
m[v] = "ignored00"
}
@@ -61,13 +61,7 @@ func (s *StepCreateResourceGroup) Run(ctx context.Context, state multistep.State
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var location = state.Get(constants.ArmLocation).(string)
tags, ok := state.Get(constants.ArmTags).(map[string]*string)
if !ok {
err := fmt.Errorf("failed to extract tags from state bag")
state.Put(constants.Error, err)
s.error(err)
return multistep.ActionHalt
}
var tags = state.Get(constants.ArmTags).(map[string]*string)
exists, err := s.exists(ctx, resourceGroupName)
if err != nil {
@@ -220,32 +220,3 @@ func createTestExistingStateBagStepCreateResourceGroup() multistep.StateBag {
stateBag.Put(constants.ArmTags, tags)
return stateBag
}
func TestStepCreateResourceGroupShouldFailIfTagsFailCast(t *testing.T) {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
value := "Unit Test: Tags"
tags := map[string]string{
"tag01": value,
}
stateBag.Put(constants.ArmTags, tags)
var testSubject = &StepCreateResourceGroup{
create: func(context.Context, string, string, map[string]*string) error { return nil },
say: func(message string) {},
error: func(e error) {},
exists: func(context.Context, string) (bool, error) { return false, nil },
}
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
@@ -16,9 +16,8 @@ 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:"exclude_from_latest"`
ExcludeFromLatestTypo bool `mapstructure:"exlude_from_latest" undocumented:"true"`
TargetRegions []TargetRegion `mapstructure:"target_regions"`
ExcludeFromLatest bool `mapstructure:"exlude_from_latest"`
}
// TargetRegion describes a region where the shared image should be replicated
@@ -62,10 +61,5 @@ 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,13 +9,12 @@ 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:"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"`
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"`
}
// FlatMapstructure returns a new FlatSharedImageGalleryDestination.
@@ -30,13 +29,12 @@ 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())},
"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},
"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},
}
return s
}
@@ -21,13 +21,12 @@ 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
ExcludeFromLatestTypo bool
ResourceGroup string
GalleryName string
ImageName string
ImageVersion string
TargetRegions []TargetRegion
ExcludeFromLatest bool
}
tests := []struct {
name string
@@ -67,29 +66,6 @@ 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{
@@ -129,13 +105,12 @@ 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,
ExcludeFromLatestTypo: tt.fields.ExcludeFromLatestTypo,
ResourceGroup: tt.fields.ResourceGroup,
GalleryName: tt.fields.GalleryName,
ImageName: tt.fields.ImageName,
ImageVersion: tt.fields.ImageVersion,
TargetRegions: tt.fields.TargetRegions,
ExcludeFromLatest: tt.fields.ExcludeFromLatest,
}
gotErrs, gotWarns := sigd.Validate("sigdest")
+11 -14
View File
@@ -30,22 +30,19 @@ func (a *ImportArtifact) Id() string {
}
func (a *ImportArtifact) String() string {
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)
}
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)
}
}
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())
return fmt.Sprintf("Imported Docker image: %s with tags %s",
a.Id(), strings.Join(names, " "))
}
func (a *ImportArtifact) State(name string) interface{} {
+19 -32
View File
@@ -9,39 +9,26 @@ import (
"golang.org/x/oauth2/jwt"
)
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) {
func ProcessAccountFile(text string) (*jwt.Config, 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)
conf, err := google.JWTConfigFromJSON([]byte(text), DriverScopes...)
if err != nil {
return nil, fmt.Errorf("Error reading account_file from path '%s': %s", text, err)
// 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)
}
}
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
return conf, nil
}
+6 -14
View File
@@ -36,15 +36,8 @@ 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) {
cfg := GCEDriverConfig{
Ui: ui,
ProjectId: b.config.ProjectId,
Account: b.config.account,
ImpersonateServiceAccountName: b.config.ImpersonateServiceAccount,
VaultOauthEngineName: b.config.VaultGCPOauthEngine,
}
driver, err := NewDriverGCE(cfg)
driver, err := NewDriverGCE(
ui, b.config.ProjectId, b.config.account, b.config.VaultGCPOauthEngine)
if err != nil {
return nil, err
}
@@ -77,11 +70,10 @@ 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,
ImpersonateAccount: b.config.ImpersonateServiceAccount,
ProjectId: b.config.ProjectId,
IAPConf: &b.config.IAPConfig,
CommConf: &b.config.Comm,
AccountFile: b.config.AccountFile,
ProjectId: b.config.ProjectId,
},
&communicator.StepConnect{
Config: &b.config.Comm,
+5 -8
View File
@@ -17,6 +17,7 @@ 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"
)
@@ -34,8 +35,6 @@ 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
@@ -76,8 +75,6 @@ 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"`
@@ -269,7 +266,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 Cloud. The value should be the path of the token generator
// Google's 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
@@ -283,7 +280,7 @@ type Config struct {
// Example: "us-central1-a"
Zone string `mapstructure:"zone" required:"true"`
account *ServiceAccount
account *jwt.Config
imageAlreadyExists bool
ctx interpolate.Context
}
@@ -484,9 +481,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
// Authenticating via an account file
if c.AccountFile != "" {
if c.VaultGCPOauthEngine != "" && c.ImpersonateServiceAccount != "" {
if c.VaultGCPOauthEngine != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("You cannot "+
"specify impersonate_service_account, account_file and vault_gcp_oauth_engine at the same time"))
"specify both account_file and vault_gcp_oauth_engine."))
}
cfg, err := ProcessAccountFile(c.AccountFile)
if err != nil {
-4
View File
@@ -64,7 +64,6 @@ 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"`
@@ -81,7 +80,6 @@ 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"`
@@ -184,7 +182,6 @@ 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},
@@ -201,7 +198,6 @@ 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())},
+20 -30
View File
@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"log"
"net/http"
"strings"
"time"
@@ -24,6 +25,7 @@ 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.
@@ -35,14 +37,6 @@ 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
@@ -78,34 +72,31 @@ func (ots OauthTokenSource) Token() (*oauth2.Token, error) {
}
func NewClientOptionGoogle(account *ServiceAccount, vaultOauth string, impersonatesa string) (option.ClientOption, error) {
func NewClientGCE(conf *jwt.Config, vaultOauth string) (*http.Client, error) {
var err error
var opts option.ClientOption
var client *http.Client
if vaultOauth != "" {
// Auth with Vault Oauth
log.Printf("Using Vault to generate Oauth token.")
ts := OauthTokenSource{vaultOauth}
opts = option.WithTokenSource(ts)
return oauth2.NewClient(context.TODO(), ts), nil
} else if impersonatesa != "" {
opts = option.ImpersonateCredentials(impersonatesa)
} else if account != nil && account.jwt != nil && len(account.jwt.PrivateKey) > 0 {
} else if conf != nil && len(conf.PrivateKey) > 0 {
// Auth with AccountFile if provided
log.Printf("[INFO] Requesting Google token via account_file...")
log.Printf("[INFO] -- Email: %s", account.jwt.Email)
log.Printf("[INFO] -- Email: %s", conf.Email)
log.Printf("[INFO] -- Scopes: %s", DriverScopes)
log.Printf("[INFO] -- Private Key Length: %d", len(account.jwt.PrivateKey))
log.Printf("[INFO] -- Private Key Length: %d", len(conf.PrivateKey))
opts = option.WithCredentialsJSON(account.jsonKey)
// 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())
} else {
log.Printf("[INFO] Requesting Google token via GCE API Default Client Token Source...")
ts, err := google.DefaultTokenSource(context.TODO(), "https://www.googleapis.com/auth/cloud-platform")
if err != nil {
return nil, err
}
opts = option.WithTokenSource(ts)
client, err = google.DefaultClient(context.TODO(), DriverScopes...)
// 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:
@@ -124,23 +115,22 @@ func NewClientOptionGoogle(account *ServiceAccount, vaultOauth string, impersona
return nil, err
}
return opts, nil
return client, nil
}
func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
opts, err := NewClientOptionGoogle(config.Account, config.VaultOauthEngineName, config.ImpersonateServiceAccountName)
func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config, vaultOauth string) (Driver, error) {
client, err := NewClientGCE(conf, vaultOauth)
if err != nil {
return nil, err
}
log.Printf("[INFO] Instantiating GCE client...")
service, err := compute.NewService(context.TODO(), opts)
service, err := compute.NewService(context.TODO(), option.WithHTTPClient(client))
if err != nil {
return nil, err
}
log.Printf("[INFO] Instantiating OS Login client...")
osLoginService, err := oslogin.NewService(context.TODO(), opts)
osLoginService, err := oslogin.NewService(context.TODO(), option.WithHTTPClient(client))
if err != nil {
return nil, err
}
@@ -149,10 +139,10 @@ func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
service.UserAgent = useragent.String()
return &driverGCE{
projectId: config.ProjectId,
projectId: p,
service: service,
osLoginService: osLoginService,
ui: config.Ui,
ui: ui,
}, nil
}
@@ -23,11 +23,6 @@ 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.jwt.Email
email = c.account.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.jwt.Email
s.accountEmail = config.account.Email
}
if s.accountEmail == "" {
+4 -9
View File
@@ -131,11 +131,10 @@ func (e RetryableTunnelError) Error() string {
}
type StepStartTunnel struct {
IAPConf *IAPConfig
CommConf *communicator.Config
AccountFile string
ImpersonateAccount string
ProjectId string
IAPConf *IAPConfig
CommConf *communicator.Config
AccountFile string
ProjectId string
tunnelDriver TunnelDriver
}
@@ -277,10 +276,6 @@ 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)
+6 -13
View File
@@ -252,21 +252,14 @@ 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 {
// 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."))
}
} 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."))
}
// if external_source_image_format is not set use qcow2 as default
-24
View File
@@ -141,7 +141,6 @@ 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"
@@ -150,7 +149,6 @@ 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 = ""
@@ -159,7 +157,6 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
t.Fatalf("err: %s", err)
}
c = testRunConfig()
// test setting only ExternalSourceImageURL passes
c.SourceImage = ""
c.SourceImageName = ""
@@ -177,27 +174,6 @@ 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
+6 -40
View File
@@ -1,4 +1,4 @@
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails
package oci
@@ -35,16 +35,6 @@ 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"`
@@ -79,17 +69,14 @@ type Config struct {
CompartmentID string `mapstructure:"compartment_ocid"`
// Image
BaseImageID string `mapstructure:"base_image_ocid"`
BaseImageFilter ListImagesRequest `mapstructure:"base_image_filter"`
ImageName string `mapstructure:"image_name"`
ImageCompartmentID string `mapstructure:"image_compartment_ocid"`
BaseImageID string `mapstructure:"base_image_ocid"`
ImageName string `mapstructure:"image_name"`
// 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
@@ -266,10 +253,6 @@ 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"))
@@ -280,17 +263,9 @@ func (c *Config) Prepare(raws ...interface{}) error {
errs, errors.New("'subnet_ocid' must be specified"))
}
if (c.BaseImageID == "") && (c.BaseImageFilter == ListImagesRequest{}) {
if c.BaseImageID == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'base_image_ocid' or 'base_image_filter' must be specified"))
}
if c.BaseImageFilter.CompartmentId == nil {
c.BaseImageFilter.CompartmentId = &c.CompartmentID
}
if c.BaseImageFilter.Shape == nil {
c.BaseImageFilter.Shape = &c.Shape
errs, errors.New("'base_image_ocid' must be specified"))
}
// Validate tag lengths. TODO (hlowndes) maximum number of tags allowed.
@@ -351,15 +326,6 @@ 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 -40
View File
@@ -1,4 +1,4 @@
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails"; DO NOT EDIT.
package oci
import (
@@ -76,14 +76,11 @@ 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"`
@@ -172,14 +169,11 @@ 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},
@@ -229,36 +223,3 @@ 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
}
+1 -32
View File
@@ -44,8 +44,7 @@ func testConfig(accessConfFile *os.File) map[string]interface{} {
"create_vnic_details": map[string]interface{}{
"nsg_ids": []string{"ocd1..."},
},
"shape": "VM.Standard1.1",
"disk_size": 60,
"shape": "VM.Standard1.1",
}
}
@@ -88,36 +87,6 @@ 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")
+19 -65
View File
@@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"regexp"
"time"
core "github.com/oracle/oci-go-sdk/core"
@@ -52,7 +51,23 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
metadata["user_data"] = d.cfg.UserData
}
// Create VNIC details for instance
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
CreateVnicDetails := core.CreateVnicDetails{
AssignPublicIp: d.cfg.CreateVnicDetails.AssignPublicIp,
DisplayName: d.cfg.CreateVnicDetails.DisplayName,
@@ -65,68 +80,7 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
FreeformTags: d.cfg.CreateVnicDetails.FreeformTags,
}
// Determine base image ID
var imageId *string
if d.cfg.BaseImageID != "" {
imageId = &d.cfg.BaseImageID
} else {
// Pull images and determine which image ID to use, if BaseImageId not specified
response, err := d.computeClient.ListImages(ctx, core.ListImagesRequest{
CompartmentId: d.cfg.BaseImageFilter.CompartmentId,
DisplayName: d.cfg.BaseImageFilter.DisplayName,
OperatingSystem: d.cfg.BaseImageFilter.OperatingSystem,
OperatingSystemVersion: d.cfg.BaseImageFilter.OperatingSystemVersion,
Shape: d.cfg.BaseImageFilter.Shape,
LifecycleState: "AVAILABLE",
SortBy: "TIMECREATED",
SortOrder: "DESC",
})
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,
}
instanceDetails.CreateVnicDetails = &CreateVnicDetails
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: instanceDetails})
@@ -140,7 +94,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.ImageCompartmentID,
CompartmentId: &d.cfg.CompartmentID,
InstanceId: &id,
DisplayName: &d.cfg.ImageName,
FreeformTags: d.cfg.Tags,
+22 -11
View File
@@ -9,7 +9,9 @@ package bsu
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"github.com/hashicorp/hcl/v2/hcldec"
osccommon "github.com/hashicorp/packer/builder/osc/common"
@@ -19,6 +21,7 @@ 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
@@ -83,13 +86,24 @@ 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) {
oscConn := b.config.NewOSCClient()
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)
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("osc", oscConn)
state.Put("accessConfig", &b.config.AccessConfig)
state.Put("oapi", oapiconn)
state.Put("clientConfig", clientConfig)
state.Put("hook", hook)
state.Put("ui", ui)
@@ -115,7 +129,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("osc_%s", b.config.PackerBuildName),
DebugKeyPath: fmt.Sprintf("oapi_%s", b.config.PackerBuildName),
},
&osccommon.StepPublicIp{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
@@ -147,7 +161,6 @@ 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,
@@ -157,8 +170,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: osccommon.OscSSHHost(
oscConn.VmApi,
Host: osccommon.SSHHost(
oapiconn,
b.config.SSHInterface),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
@@ -177,13 +190,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
OMIName: b.config.OMIName,
Regions: b.config.OMIRegions,
},
&stepCreateOMI{
RawRegion: b.config.RawRegion,
},
&stepCreateOMI{},
&osccommon.StepUpdateOMIAttributes{
AccountIds: b.config.OMIAccountIDs,
SnapshotAccountIds: b.config.SnapshotAccountIDs,
RawRegion: b.config.RawRegion,
Ctx: b.config.ctx,
},
&osccommon.StepCreateTags{
@@ -207,6 +217,7 @@ 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")},
}
+22 -4
View File
@@ -2,9 +2,13 @@
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) {
@@ -19,17 +23,31 @@ 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-abe953fa",
"source_omi": "ami-65efcc11",
"ssh_username": "outscale",
"omi_name": "packer-test",
"associate_public_ip_address": true,
"force_deregister": true
"omi_name": "packer-test {{timestamp}}"
}]
}
`
+27 -37
View File
@@ -5,68 +5,62 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
type stepCreateOMI struct {
image *osc.Image
RawRegion string
image *oapi.Image
}
func (s *stepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
oscconn := state.Get("osc").(*osc.APIClient)
vm := state.Get("vm").(osc.Vm)
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.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 := osc.CreateImageRequest{
createOpts := oapi.CreateImageRequest{
VmId: vm.VmId,
ImageName: omiName,
BlockDeviceMappings: config.BlockDevices.BuildOscOMIDevices(),
BlockDeviceMappings: config.BlockDevices.BuildOMIDevices(),
}
resp, _, err := oscconn.ImageApi.CreateImage(context.Background(), &osc.CreateImageOpts{
CreateImageRequest: optional.NewInterface(createOpts),
})
if err != nil || resp.Image.ImageId == "" {
resp, err := oapiconn.POST_CreateImage(createOpts)
if err != nil || resp.OK == nil {
err := fmt.Errorf("Error creating OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
image := resp.Image
image := resp.OK.Image
// Set the OMI ID in the state
ui.Message(fmt.Sprintf("OMI: %s", image.ImageId))
omis := make(map[string]string)
omis[s.RawRegion] = image.ImageId
omis[oapiconn.GetConfig().Region] = image.ImageId
state.Put("omis", omis)
// Wait for the image to become ready
ui.Say("Waiting for OMI to become ready...")
if err := osccommon.WaitUntilOscImageAvailable(oscconn, image.ImageId); err != nil {
if err := osccommon.WaitUntilImageAvailable(oapiconn, image.ImageId); err != nil {
log.Printf("Error waiting for OMI: %s", err)
imagesResp, _, err := oscconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageIds: []string{image.ImageId},
},
}),
imagesResp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.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.Images[0].StateComment
stateReason := imagesResp.OK.Images[0].StateComment
err = fmt.Errorf("Error waiting for OMI. Reason: %s", stateReason)
}
@@ -75,12 +69,10 @@ func (s *stepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
return multistep.ActionHalt
}
imagesResp, _, err := oscconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageIds: []string{image.ImageId},
},
}),
imagesResp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: []string{image.ImageId},
},
})
if err != nil {
err := fmt.Errorf("Error searching for OMI: %s", err)
@@ -88,12 +80,12 @@ func (s *stepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
ui.Error(err.Error())
return multistep.ActionHalt
}
s.image = &imagesResp.Images[0]
s.image = &imagesResp.OK.Images[0]
snapshots := make(map[string][]string)
for _, blockDeviceMapping := range imagesResp.Images[0].BlockDeviceMappings {
for _, blockDeviceMapping := range imagesResp.OK.Images[0].BlockDeviceMappings {
if blockDeviceMapping.Bsu.SnapshotId != "" {
snapshots[s.RawRegion] = append(snapshots[s.RawRegion], blockDeviceMapping.Bsu.SnapshotId)
snapshots[oapiconn.GetConfig().Region] = append(snapshots[oapiconn.GetConfig().Region], blockDeviceMapping.Bsu.SnapshotId)
}
}
state.Put("snapshots", snapshots)
@@ -112,14 +104,12 @@ func (s *stepCreateOMI) Cleanup(state multistep.StateBag) {
return
}
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deregistering the OMI because cancellation or error...")
DeleteOpts := osc.DeleteImageRequest{ImageId: s.image.ImageId}
if _, _, err := oscconn.ImageApi.DeleteImage(context.Background(), &osc.DeleteImageOpts{
DeleteImageRequest: optional.NewInterface(DeleteOpts),
}); err != nil {
DeleteOpts := oapi.DeleteImageRequest{ImageId: s.image.ImageId}
if _, err := oapiconn.POST_DeleteImage(DeleteOpts); err != nil {
ui.Error(fmt.Sprintf("Error Deleting OMI, may still be around: %s", err))
return
}
+24 -10
View File
@@ -6,8 +6,10 @@ package bsusurrogate
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/http"
"github.com/hashicorp/hcl/v2/hcldec"
osccommon "github.com/hashicorp/packer/builder/osc/common"
@@ -17,6 +19,7 @@ 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"
@@ -101,20 +104,31 @@ 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) {
oscConn := b.config.NewOSCClient()
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)
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("osc", oscConn)
state.Put("accessConfig", &b.config.AccessConfig)
state.Put("oapi", oapiconn)
state.Put("clientConfig", clientConfig)
state.Put("hook", hook)
state.Put("ui", ui)
//VMStep
omiDevices := b.config.BuildOscOMIDevices()
launchOSCDevices := b.config.BuildOSCLaunchDevices()
omiDevices := b.config.BuildOMIDevices()
launchDevices := b.config.BuildLaunchDevices()
steps := []multistep.Step{
&osccommon.StepPreValidate{
@@ -179,8 +193,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: osccommon.OscSSHHost(
oscConn.VmApi,
Host: osccommon.SSHHost(
oapiconn,
b.config.SSHInterface),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
@@ -193,7 +207,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
DisableStopVm: b.config.DisableStopVm,
},
&StepSnapshotVolumes{
LaunchDevices: launchOSCDevices,
LaunchDevices: launchDevices,
},
&osccommon.StepDeregisterOMI{
AccessConfig: &b.config.AccessConfig,
@@ -205,8 +219,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&StepRegisterOMI{
RootDevice: b.config.RootDevice,
OMIDevices: omiDevices,
LaunchDevices: launchOSCDevices,
RawRegion: b.config.RawRegion,
LaunchDevices: launchDevices,
},
&osccommon.StepUpdateOMIAttributes{
AccountIds: b.config.OMIAccountIDs,
@@ -234,6 +247,7 @@ 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")},
}
+1 -1
View File
@@ -24,7 +24,7 @@ const testBuilderAccBasic = `
"type": "test",
"region": "eu-west-2",
"vm_type": "t2.micro",
"source_omi": "ami-abe953fa",
"source_omi": "ami-65efcc11",
"ssh_username": "outscale",
"omi_name": "packer-test {{timestamp}}",
"omi_virtualization_type": "hvm",
+26 -36
View File
@@ -4,25 +4,23 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// StepRegisterOMI creates the OMI.
type StepRegisterOMI struct {
RootDevice RootBlockDevice
OMIDevices []osc.BlockDeviceMappingImage
LaunchDevices []osc.BlockDeviceMappingVmCreation
image *osc.Image
RawRegion string
OMIDevices []oapi.BlockDeviceMappingImage
LaunchDevices []oapi.BlockDeviceMappingVmCreation
image *oapi.Image
}
func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
snapshotIds := state.Get("snapshot_ids").(map[string]string)
ui := state.Get("ui").(packer.Ui)
@@ -30,16 +28,14 @@ func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) mul
blockDevices := s.combineDevices(snapshotIds)
registerOpts := osc.CreateImageRequest{
registerOpts := oapi.CreateImageRequest{
ImageName: config.OMIName,
Architecture: "x86_64",
RootDeviceName: s.RootDevice.DeviceName,
BlockDeviceMappings: blockDevices,
}
registerResp, _, err := oscconn.ImageApi.CreateImage(context.Background(), &osc.CreateImageOpts{
CreateImageRequest: optional.NewInterface(registerOpts),
})
registerResp, err := oapiconn.POST_CreateImage(registerOpts)
if err != nil {
state.Put("error", fmt.Errorf("Error registering OMI: %s", err))
ui.Error(state.Get("error").(error).Error())
@@ -47,26 +43,24 @@ 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.Image.ImageId))
ui.Say(fmt.Sprintf("OMI: %s", registerResp.OK.Image.ImageId))
omis := make(map[string]string)
omis[s.RawRegion] = registerResp.Image.ImageId
omis[oapiconn.GetConfig().Region] = registerResp.OK.Image.ImageId
state.Put("omis", omis)
// Wait for the image to become ready
ui.Say("Waiting for OMI to become ready...")
if err := osccommon.WaitUntilOscImageAvailable(oscconn, registerResp.Image.ImageId); err != nil {
if err := osccommon.WaitUntilImageAvailable(oapiconn, registerResp.OK.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 := oscconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageIds: []string{registerResp.Image.ImageId},
},
}),
imagesResp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: []string{registerResp.OK.Image.ImageId},
},
})
if err != nil {
@@ -75,12 +69,12 @@ func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) mul
ui.Error(err.Error())
return multistep.ActionHalt
}
s.image = &imagesResp.Images[0]
s.image = &imagesResp.OK.Images[0]
snapshots := make(map[string][]string)
for _, blockDeviceMapping := range imagesResp.Images[0].BlockDeviceMappings {
for _, blockDeviceMapping := range imagesResp.OK.Images[0].BlockDeviceMappings {
if blockDeviceMapping.Bsu.SnapshotId != "" {
snapshots[s.RawRegion] = append(snapshots[s.RawRegion], blockDeviceMapping.Bsu.SnapshotId)
snapshots[oapiconn.GetConfig().Region] = append(snapshots[oapiconn.GetConfig().Region], blockDeviceMapping.Bsu.SnapshotId)
}
}
state.Put("snapshots", snapshots)
@@ -99,23 +93,19 @@ func (s *StepRegisterOMI) Cleanup(state multistep.StateBag) {
return
}
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deregistering the OMI because cancellation or error...")
deregisterOpts := osc.DeleteImageRequest{ImageId: s.image.ImageId}
_, _, err := oscconn.ImageApi.DeleteImage(context.Background(), &osc.DeleteImageOpts{
DeleteImageRequest: optional.NewInterface(deregisterOpts),
})
if err != nil {
deregisterOpts := oapi.DeleteImageRequest{ImageId: s.image.ImageId}
if _, err := oapiconn.POST_DeleteImage(deregisterOpts); err != nil {
ui.Error(fmt.Sprintf("Error deregistering OMI, may still be around: %s", err))
return
}
}
func (s *StepRegisterOMI) combineDevices(snapshotIds map[string]string) []osc.BlockDeviceMappingImage {
devices := map[string]osc.BlockDeviceMappingImage{}
func (s *StepRegisterOMI) combineDevices(snapshotIds map[string]string) []oapi.BlockDeviceMappingImage {
devices := map[string]oapi.BlockDeviceMappingImage{}
for _, device := range s.OMIDevices {
devices[device.DeviceName] = device
@@ -135,18 +125,18 @@ func (s *StepRegisterOMI) combineDevices(snapshotIds map[string]string) []osc.Bl
devices[device.DeviceName] = copyToDeviceMappingImage(device)
}
blockDevices := []osc.BlockDeviceMappingImage{}
blockDevices := []oapi.BlockDeviceMappingImage{}
for _, device := range devices {
blockDevices = append(blockDevices, device)
}
return blockDevices
}
func copyToDeviceMappingImage(device osc.BlockDeviceMappingVmCreation) osc.BlockDeviceMappingImage {
deviceImage := osc.BlockDeviceMappingImage{
func copyToDeviceMappingImage(device oapi.BlockDeviceMappingVmCreation) oapi.BlockDeviceMappingImage {
deviceImage := oapi.BlockDeviceMappingImage{
DeviceName: device.DeviceName,
VirtualDeviceName: device.VirtualDeviceName,
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
DeleteOnVmDeletion: device.Bsu.DeleteOnVmDeletion,
Iops: device.Bsu.Iops,
SnapshotId: device.Bsu.SnapshotId,
@@ -6,12 +6,11 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// StepSnapshotVolumes creates snapshots of the created volumes.
@@ -19,14 +18,14 @@ import (
// Produces:
// snapshot_ids map[string]string - IDs of the created snapshots
type StepSnapshotVolumes struct {
LaunchDevices []osc.BlockDeviceMappingVmCreation
LaunchDevices []oapi.BlockDeviceMappingVmCreation
snapshotIds map[string]string
}
func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName string, state multistep.StateBag) error {
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(osc.Vm)
vm := state.Get("vm").(oapi.Vm)
var volumeId string
for _, volume := range vm.BlockDeviceMappings {
@@ -41,21 +40,19 @@ 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 := oscconn.SnapshotApi.CreateSnapshot(context.Background(), &osc.CreateSnapshotOpts{
CreateSnapshotRequest: optional.NewInterface(osc.CreateSnapshotRequest{
VolumeId: volumeId,
Description: description,
}),
createSnapResp, err := oapiconn.POST_CreateSnapshot(oapi.CreateSnapshotRequest{
VolumeId: volumeId,
Description: description,
})
if err != nil {
return err
}
// Set the snapshot ID so we can delete it later
s.snapshotIds[deviceName] = createSnapResp.Snapshot.SnapshotId
s.snapshotIds[deviceName] = createSnapResp.OK.Snapshot.SnapshotId
// Wait for snapshot to be created
err = osccommon.WaitUntilOscSnapshotCompleted(oscconn, createSnapResp.Snapshot.SnapshotId)
err = osccommon.WaitUntilSnapshotCompleted(oapiconn, createSnapResp.OK.Snapshot.SnapshotId)
return err
}
@@ -68,7 +65,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 osc.BlockDeviceMappingVmCreation) {
go func(device oapi.BlockDeviceMappingVmCreation) {
defer wg.Done()
if err := s.snapshotVolume(ctx, device.DeviceName, state); err != nil {
errs = multierror.Append(errs, err)
@@ -97,13 +94,11 @@ func (s *StepSnapshotVolumes) Cleanup(state multistep.StateBag) {
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
ui.Say("Removing snapshots since we cancelled or halted...")
for _, snapshotID := range s.snapshotIds {
_, _, err := oscconn.SnapshotApi.DeleteSnapshot(context.Background(), &osc.DeleteSnapshotOpts{
DeleteSnapshotRequest: optional.NewInterface(osc.DeleteSnapshotRequest{SnapshotId: snapshotID}),
})
for _, snapshotId := range s.snapshotIds {
_, err := oapiconn.POST_DeleteSnapshot(oapi.DeleteSnapshotRequest{SnapshotId: snapshotId})
if err != nil {
ui.Error(fmt.Sprintf("Error: %s", err))
}
+4 -8
View File
@@ -1,15 +1,13 @@
package bsuvolume
import (
"context"
"fmt"
"log"
"sort"
"strings"
"github.com/antihax/optional"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// map of region to list of volume IDs
@@ -24,7 +22,7 @@ type Artifact struct {
BuilderIdValue string
// Client connection for performing API stuff.
Conn *osc.APIClient
Conn *oapi.Client
// StateData should store data such as GeneratedData
// to be shared with post-processors
@@ -72,12 +70,10 @@ func (a *Artifact) Destroy() error {
for _, volumeID := range volumeIDs {
log.Printf("Deregistering Volume ID (%s) from region (%s)", volumeID, region)
input := osc.DeleteVolumeRequest{
input := oapi.DeleteVolumeRequest{
VolumeId: volumeID,
}
if _, _, err := a.Conn.VolumeApi.DeleteVolume(context.Background(), &osc.DeleteVolumeOpts{
DeleteVolumeRequest: optional.NewInterface(optional.NewInterface(input)),
}); err != nil {
if _, err := a.Conn.POST_DeleteVolume(input); err != nil {
errors = append(errors, err)
}
}
+18 -15
View File
@@ -6,8 +6,10 @@ package bsuvolume
import (
"context"
"crypto/tls"
"fmt"
"log"
"net/http"
"github.com/hashicorp/hcl/v2/hcldec"
osccommon "github.com/hashicorp/packer/builder/osc/common"
@@ -17,6 +19,7 @@ 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"
@@ -86,22 +89,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},
},
}
oscConn := b.config.NewOSCClient()
oapiconn := oapi.NewClient(clientConfig, skipClient)
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("osc", oscConn)
state.Put("oapi", oapiconn)
state.Put("hook", hook)
state.Put("ui", ui)
@@ -114,7 +117,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: "bsu",
ExpectedRootDevice: "ebs",
IamVmProfile: b.config.IamVmProfile,
VmInitiatedShutdownBehavior: b.config.VmInitiatedShutdownBehavior,
VmType: b.config.VmType,
@@ -167,8 +170,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: osccommon.OscSSHHost(
oscConn.VmApi,
Host: osccommon.SSHHost(
oapiconn,
b.config.SSHInterface),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
@@ -195,7 +198,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: oscConn,
Conn: oapiconn,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
ui.Say(fmt.Sprintf("Created Volumes: %s", artifact))
+21 -1
View File
@@ -2,9 +2,13 @@
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) {
@@ -19,6 +23,22 @@ 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": [
@@ -26,7 +46,7 @@ const testBuilderAccBasic = `
"type": "test",
"region": "eu-west-2",
"vm_type": "t2.micro",
"source_omi": "ami-abe953fa",
"source_omi": "ami-65efcc11",
"ssh_username": "outscale",
"bsu_volumes": [
{
+10 -14
View File
@@ -4,30 +4,28 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
type stepTagBSUVolumes struct {
VolumeMapping []BlockDevice
RawRegion string
Ctx interpolate.Context
}
func (s *stepTagBSUVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oscconn := state.Get("osc").(*osc.APIClient)
vm := state.Get("vm").(osc.Vm)
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.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[s.RawRegion] = append(
volumes[s.RawRegion],
volumes[oapiconn.GetConfig().Region] = append(
volumes[oapiconn.GetConfig().Region],
instanceBlockDevices.Bsu.VolumeId)
}
}
@@ -37,14 +35,14 @@ func (s *stepTagBSUVolumes) Run(_ context.Context, state multistep.StateBag) mul
if len(s.VolumeMapping) > 0 {
ui.Say("Tagging BSU volumes...")
toTag := map[string][]osc.ResourceTag{}
toTag := map[string][]oapi.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.OSCTags(s.Ctx, s.RawRegion, state)
tags, err := mapping.Tags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
err := fmt.Errorf("Error tagging device %s with %s", mapping.DeviceName, err)
state.Put("error", err)
@@ -61,11 +59,9 @@ func (s *stepTagBSUVolumes) Run(_ context.Context, state multistep.StateBag) mul
}
for volumeId, tags := range toTag {
_, _, err := oscconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
ResourceIds: []string{volumeId},
Tags: tags,
}),
_, err := oapiconn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: []string{volumeId},
Tags: tags,
})
if err != nil {
err := fmt.Errorf("Error tagging BSU Volume %s on %s: %s", volumeId, vm.VmId, err)
+20 -6
View File
@@ -8,7 +8,9 @@ package chroot
import (
"context"
"crypto/tls"
"errors"
"net/http"
"runtime"
"github.com/hashicorp/hcl/v2/hcldec"
@@ -18,6 +20,7 @@ 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
@@ -190,7 +193,19 @@ 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.")
}
oscConn := b.config.NewOSCClient()
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)
wrappedCommand := func(command string) (string, error) {
ctx := b.config.ctx
ctx.Data = &wrappedCommandTemplate{Command: command}
@@ -200,7 +215,8 @@ 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("osc", oscConn)
state.Put("oapi", oapiconn)
state.Put("clientConfig", clientConfig)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
@@ -250,9 +266,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&StepCopyFiles{},
&StepChrootProvision{},
&StepEarlyCleanup{},
&StepSnapshot{
RawRegion: b.config.RawRegion,
},
&StepSnapshot{},
&osccommon.StepDeregisterOMI{
AccessConfig: &b.config.AccessConfig,
ForceDeregister: b.config.OMIForceDeregister,
@@ -262,7 +276,6 @@ 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,
@@ -294,6 +307,7 @@ 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")},
}
+2 -2
View File
@@ -6,14 +6,14 @@ import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// 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").(osc.Image)
image := state.Get("source_image").(oapi.Image)
ui := state.Get("ui").(packer.Ui)
ui.Say("Checking the root device on source OMI...")
+19 -23
View File
@@ -4,44 +4,42 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// 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)
osconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
snapshotId := state.Get("snapshot_id").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating the OMI...")
var (
registerOpts osc.CreateImageRequest
mappings []osc.BlockDeviceMappingImage
image osc.Image
registerOpts oapi.CreateImageRequest
mappings []oapi.BlockDeviceMappingImage
image oapi.Image
rootDeviceName string
)
if config.FromScratch {
mappings = config.OMIBlockDevices.BuildOscOMIDevices()
mappings = config.OMIBlockDevices.BuildOMIDevices()
rootDeviceName = config.RootDeviceName
} else {
image = state.Get("source_image").(osc.Image)
image = state.Get("source_image").(oapi.Image)
mappings = image.BlockDeviceMappings
rootDeviceName = image.RootDeviceName
}
newMappings := make([]osc.BlockDeviceMappingImage, len(mappings))
newMappings := make([]oapi.BlockDeviceMappingImage, len(mappings))
for i, device := range mappings {
newDevice := device
@@ -50,14 +48,14 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
newDevice.Bsu.VolumeSize = gibSize
if newDevice.DeviceName == rootDeviceName {
if newDevice.Bsu != (osc.BsuToCreate{}) {
if newDevice.Bsu != (oapi.BsuToCreate{}) {
newDevice.Bsu.SnapshotId = snapshotId
} else {
newDevice.Bsu = osc.BsuToCreate{SnapshotId: snapshotId}
newDevice.Bsu = oapi.BsuToCreate{SnapshotId: snapshotId}
}
if config.FromScratch || int32(s.RootVolumeSize) > newDevice.Bsu.VolumeSize {
newDevice.Bsu.VolumeSize = int32(s.RootVolumeSize)
if config.FromScratch || s.RootVolumeSize > newDevice.Bsu.VolumeSize {
newDevice.Bsu.VolumeSize = s.RootVolumeSize
}
}
@@ -65,7 +63,7 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
}
if config.FromScratch {
registerOpts = osc.CreateImageRequest{
registerOpts = oapi.CreateImageRequest{
ImageName: config.OMIName,
Architecture: "x86_64",
RootDeviceName: rootDeviceName,
@@ -75,25 +73,23 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
registerOpts = buildRegisterOpts(config, image, newMappings)
}
registerResp, _, err := osconn.ImageApi.CreateImage(context.Background(), &osc.CreateImageOpts{
CreateImageRequest: optional.NewInterface(registerOpts),
})
registerResp, err := oapiconn.POST_CreateImage(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.Image.ImageId
imageID := registerResp.OK.Image.ImageId
// Set the OMI ID in the state
ui.Say(fmt.Sprintf("OMI: %s", imageID))
omis := make(map[string]string)
omis[s.RawRegion] = imageID
omis[oapiconn.GetConfig().Region] = imageID
state.Put("omis", omis)
ui.Say("Waiting for OMI to become ready...")
if err := osccommon.WaitUntilOscImageAvailable(osconn, imageID); err != nil {
if err := osccommon.WaitUntilImageAvailable(oapiconn, imageID); err != nil {
err := fmt.Errorf("Error waiting for OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@@ -105,8 +101,8 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
func (s *StepCreateOMI) Cleanup(state multistep.StateBag) {}
func buildRegisterOpts(config *Config, image osc.Image, mappings []osc.BlockDeviceMappingImage) osc.CreateImageRequest {
registerOpts := osc.CreateImageRequest{
func buildRegisterOpts(config *Config, image oapi.Image, mappings []oapi.BlockDeviceMappingImage) oapi.CreateImageRequest {
registerOpts := oapi.CreateImageRequest{
ImageName: config.OMIName,
Architecture: image.Architecture,
RootDeviceName: image.RootDeviceName,
+20 -26
View File
@@ -6,12 +6,11 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// StepCreateVolume creates a new volume from the snapshot of the root
@@ -24,19 +23,18 @@ 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)
oscconn := state.Get("osc").(*osc.APIClient)
vm := state.Get("vm").(osc.Vm)
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
ui := state.Get("ui").(packer.Ui)
var err error
volTags, err := s.RootVolumeTags.OSCTags(s.Ctx, s.RawRegion, state)
volTags, err := s.RootVolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
state.Put("error", err)
@@ -44,7 +42,7 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
return multistep.ActionHalt
}
var createVolume *osc.CreateVolumeRequest
var createVolume *oapi.CreateVolumeRequest
if config.FromScratch {
rootVolumeType := osccommon.VolumeTypeGp2
if s.RootVolumeType == "io1" {
@@ -55,17 +53,17 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
} else if s.RootVolumeType != "" {
rootVolumeType = s.RootVolumeType
}
createVolume = &osc.CreateVolumeRequest{
createVolume = &oapi.CreateVolumeRequest{
SubregionName: vm.Placement.SubregionName,
Size: int32(s.RootVolumeSize),
Size: s.RootVolumeSize,
VolumeType: rootVolumeType,
}
} else {
// Determine the root device snapshot
image := state.Get("source_image").(osc.Image)
image := state.Get("source_image").(oapi.Image)
log.Printf("Searching for root device of the image (%s)", image.RootDeviceName)
var rootDevice *osc.BlockDeviceMappingImage
var rootDevice *oapi.BlockDeviceMappingImage
for _, device := range image.BlockDeviceMappings {
if device.DeviceName == image.RootDeviceName {
rootDevice = &device
@@ -84,9 +82,7 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
log.Printf("Create args: %+v", createVolume)
createVolumeResp, _, err := oscconn.VolumeApi.CreateVolume(context.Background(), &osc.CreateVolumeOpts{
CreateVolumeRequest: optional.NewInterface(*createVolume),
})
createVolumeResp, err := oapiconn.POST_CreateVolume(*createVolume)
if err != nil {
err := fmt.Errorf("Error creating root volume: %s", err)
state.Put("error", err)
@@ -95,12 +91,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.Volume.VolumeId
s.volumeId = createVolumeResp.OK.Volume.VolumeId
log.Printf("Volume ID: %s", s.volumeId)
//Create tags for volume
if len(volTags) > 0 {
if err := osccommon.CreateOSCTags(oscconn, s.volumeId, ui, volTags); err != nil {
if err := osccommon.CreateTags(oapiconn, s.volumeId, ui, volTags); err != nil {
err := fmt.Errorf("Error creating tags for volume: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@@ -109,7 +105,7 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
}
// Wait for the volume to become ready
err = osccommon.WaitUntilOscVolumeAvailable(oscconn, s.volumeId)
err = osccommon.WaitUntilVolumeAvailable(oapiconn, s.volumeId)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
@@ -126,34 +122,32 @@ func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
return
}
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting the created BSU volume...")
_, _, err := oscconn.VolumeApi.DeleteVolume(context.Background(), &osc.DeleteVolumeOpts{
DeleteVolumeRequest: optional.NewInterface(osc.DeleteVolumeRequest{VolumeId: s.volumeId}),
})
_, err := oapiconn.POST_DeleteVolume(oapi.DeleteVolumeRequest{VolumeId: s.volumeId})
if err != nil {
ui.Error(fmt.Sprintf("Error deleting BSU volume: %s", err))
}
}
func (s *StepCreateVolume) buildCreateVolumeInput(suregionName string, rootDevice *osc.BlockDeviceMappingImage) (*osc.CreateVolumeRequest, error) {
func (s *StepCreateVolume) buildCreateVolumeInput(suregionName string, rootDevice *oapi.BlockDeviceMappingImage) (*oapi.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 := &osc.CreateVolumeRequest{
createVolumeInput := &oapi.CreateVolumeRequest{
SubregionName: suregionName,
Size: gibSize,
SnapshotId: rootDevice.Bsu.SnapshotId,
VolumeType: rootDevice.Bsu.VolumeType,
Iops: rootDevice.Bsu.Iops,
}
if int32(s.RootVolumeSize) > rootDevice.Bsu.VolumeSize {
createVolumeInput.Size = int32(s.RootVolumeSize)
if s.RootVolumeSize > rootDevice.Bsu.VolumeSize {
createVolumeInput.Size = s.RootVolumeSize
}
if s.RootVolumeType == "" || s.RootVolumeType == rootDevice.Bsu.VolumeType {
+11 -17
View File
@@ -4,11 +4,10 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// StepLinkVolume attaches the previously created volume to an
@@ -23,9 +22,9 @@ type StepLinkVolume struct {
}
func (s *StepLinkVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
device := state.Get("device").(string)
vm := state.Get("vm").(osc.Vm)
vm := state.Get("vm").(oapi.Vm)
ui := state.Get("ui").(packer.Ui)
volumeId := state.Get("volume_id").(string)
@@ -34,12 +33,10 @@ 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 := oscconn.VolumeApi.LinkVolume(context.Background(), &osc.LinkVolumeOpts{
LinkVolumeRequest: optional.NewInterface(osc.LinkVolumeRequest{
VmId: vm.VmId,
VolumeId: volumeId,
DeviceName: linkVolume,
}),
_, err := oapiconn.POST_LinkVolume(oapi.LinkVolumeRequest{
VmId: vm.VmId,
VolumeId: volumeId,
DeviceName: linkVolume,
})
if err != nil {
@@ -54,7 +51,7 @@ func (s *StepLinkVolume) Run(ctx context.Context, state multistep.StateBag) mult
s.volumeId = volumeId
// Wait for the volume to become attached
err = osccommon.WaitUntilOscVolumeIsLinked(oscconn, s.volumeId)
err = osccommon.WaitUntilVolumeIsLinked(oapiconn, s.volumeId)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
@@ -78,14 +75,11 @@ func (s *StepLinkVolume) CleanupFunc(state multistep.StateBag) error {
return nil
}
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
ui.Say("Detaching BSU volume...")
_, _, err := oscconn.VolumeApi.UnlinkVolume(context.Background(), &osc.UnlinkVolumeOpts{
UnlinkVolumeRequest: optional.NewInterface(osc.UnlinkVolumeRequest{VolumeId: s.volumeId}),
})
_, err := oapiconn.POST_UnlinkVolume(oapi.UnlinkVolumeRequest{VolumeId: s.volumeId})
if err != nil {
return fmt.Errorf("Error detaching BSU volume: %s", err)
}
@@ -93,7 +87,7 @@ func (s *StepLinkVolume) CleanupFunc(state multistep.StateBag) error {
s.attached = false
// Wait for the volume to detach
err = osccommon.WaitUntilOscVolumeIsUnlinked(oscconn, s.volumeId)
err = osccommon.WaitUntilVolumeIsUnlinked(oapiconn, s.volumeId)
if err != nil {
return fmt.Errorf("Error waiting for volume: %s", err)
}
+10 -16
View File
@@ -5,11 +5,10 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// StepSnapshot creates a snapshot of the created volume.
@@ -18,22 +17,19 @@ 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 {
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
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 := oscconn.SnapshotApi.CreateSnapshot(context.Background(), &osc.CreateSnapshotOpts{
CreateSnapshotRequest: optional.NewInterface(osc.CreateSnapshotRequest{
VolumeId: volumeId,
Description: description,
}),
createSnapResp, err := oapiconn.POST_CreateSnapshot(oapi.CreateSnapshotRequest{
VolumeId: volumeId,
Description: description,
})
if err != nil {
err := fmt.Errorf("Error creating snapshot: %s", err)
@@ -43,11 +39,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.Snapshot.SnapshotId
s.snapshotId = createSnapResp.OK.Snapshot.SnapshotId
ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId))
// Wait for the snapshot to be ready
err = osccommon.WaitUntilOscSnapshotDone(oscconn, s.snapshotId)
err = osccommon.WaitUntilSnapshotDone(oapiconn, s.snapshotId)
if err != nil {
err := fmt.Errorf("Error waiting for snapshot: %s", err)
state.Put("error", err)
@@ -58,7 +54,7 @@ func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
state.Put("snapshot_id", s.snapshotId)
snapshots := map[string][]string{
s.RawRegion: {s.snapshotId},
oapiconn.GetConfig().Region: {s.snapshotId},
}
state.Put("snapshots", snapshots)
@@ -74,12 +70,10 @@ func (s *StepSnapshot) Cleanup(state multistep.StateBag) {
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
ui.Say("Removing snapshot since we cancelled or halted...")
_, _, err := oscconn.SnapshotApi.DeleteSnapshot(context.Background(), &osc.DeleteSnapshotOpts{
DeleteSnapshotRequest: optional.NewInterface(osc.DeleteSnapshotRequest{SnapshotId: s.snapshotId}),
})
_, err := oapiconn.POST_DeleteSnapshot(oapi.DeleteSnapshotRequest{SnapshotId: s.snapshotId})
if err != nil {
ui.Error(fmt.Sprintf("Error: %s", err))
}
+7 -10
View File
@@ -5,17 +5,16 @@ import (
"fmt"
"log"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// 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 {
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
//session := state.Get("clientConfig").(*session.Session)
ui := state.Get("ui").(packer.Ui)
@@ -37,11 +36,9 @@ 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 := oscconn.VmApi.ReadVms(context.Background(), &osc.ReadVmsOpts{
ReadVmsRequest: optional.NewInterface(osc.ReadVmsRequest{Filters: osc.FiltersVm{
VmIds: []string{string(vmID)},
}}),
})
resp, err := oapiconn.POST_ReadVms(oapi.ReadVmsRequest{Filters: oapi.FiltersVm{
VmIds: []string{string(vmID)},
}})
if err != nil {
err := fmt.Errorf("Error getting vm data: %s", err)
state.Put("error", err)
@@ -49,10 +46,10 @@ func (s *StepVmInfo) Run(_ context.Context, state multistep.StateBag) multistep.
return multistep.ActionHalt
}
vmsResp := resp
vmsResp := resp.OK
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
+33 -19
View File
@@ -8,7 +8,7 @@ import (
"os"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// AccessConfig is for common configuration related to Outscale API access
@@ -23,10 +23,20 @@ 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
}
// NewOSCClient retrieves the Outscale OSC-SDK client
func (c *AccessConfig) NewOSCClient() *osc.APIClient {
// 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.
if c.AccessKey == "" {
c.AccessKey = os.Getenv("OUTSCALE_ACCESSKEYID")
}
@@ -47,32 +57,36 @@ func (c *AccessConfig) NewOSCClient() *osc.APIClient {
c.CustomEndpointOAPI = "outscale.com/oapi/latest"
}
return c.NewOSCClientByRegion(c.RawRegion)
config := &oapi.Config{
AccessKey: c.AccessKey,
SecretKey: c.SecretKey,
Region: c.RawRegion,
URL: c.CustomEndpointOAPI,
Service: "api",
}
return config, nil
}
// GetRegion retrieves the Outscale OSC-SDK Region set
func (c *AccessConfig) GetRegion() string {
return 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
}
// 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,
},
}
skipClient.Transport = NewTransport(c.AccessKey, c.SecretKey, c.RawRegion, skipClient.Transport)
oapiClient := oapi.NewClient(oapicfg, skipClient)
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,
})
return oapiClient, nil
}
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
+27 -6
View File
@@ -2,29 +2,50 @@ package common
import (
"testing"
"github.com/outscale/osc-go/oapi"
)
type mockOAPIClient struct {
oapi.OAPIClient
}
func testAccessConfig() *AccessConfig {
return &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
}
func TestAccessConfigPrepare_Region(t *testing.T) {
c := testAccessConfig()
c.RawRegion = "us-east-12"
err := c.ValidateOSCRegion(c.RawRegion)
err := c.ValidateRegion(c.RawRegion)
if err == nil {
t.Fatalf("should have region validation err: %s", c.RawRegion)
}
c.RawRegion = "us-east-1"
err = c.ValidateOSCRegion(c.RawRegion)
if err == nil {
t.Fatalf("should have region validation err: %s", c.RawRegion)
err = c.ValidateRegion(c.RawRegion)
if err != nil {
t.Fatalf("shouldn't have region validation err: %s", c.RawRegion)
}
c.RawRegion = "custom"
err = c.ValidateOSCRegion(c.RawRegion)
err = c.ValidateRegion(c.RawRegion)
if err == nil {
t.Fatalf("should have region validation err: %s", c.RawRegion)
}
+31 -17
View File
@@ -1,15 +1,15 @@
package common
import (
"context"
"crypto/tls"
"fmt"
"log"
"net/http"
"sort"
"strings"
"github.com/antihax/optional"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// Artifact is an artifact implementation that contains built OMIs.
@@ -20,6 +20,9 @@ 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{}
@@ -71,36 +74,47 @@ 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)
regionConn := config.NewOSCClientByRegion(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)
// Get image metadata
imageResp, _, err := regionConn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageIds: []string{imageId},
},
}),
imageResp, err := regionConn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: []string{imageId},
},
})
if err != nil {
errors = append(errors, err)
}
if len(imageResp.Images) == 0 {
if len(imageResp.OK.Images) == 0 {
err := fmt.Errorf("Error retrieving details for OMI (%s), no images found", imageId)
errors = append(errors, err)
}
// Deregister ami
input := osc.DeleteImageRequest{
input := oapi.DeleteImageRequest{
ImageId: imageId,
}
if _, _, err := regionConn.ImageApi.DeleteImage(context.Background(), &osc.DeleteImageOpts{
DeleteImageRequest: optional.NewInterface(input),
}); err != nil {
if _, err := regionConn.POST_DeleteImage(input); err != nil {
errors = append(errors, err)
}
+20 -19
View File
@@ -5,8 +5,9 @@ import (
"log"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// BlockDevice
@@ -34,11 +35,11 @@ type LaunchBlockDevices struct {
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"`
}
func buildOscBlockDevicesImage(b []BlockDevice) []osc.BlockDeviceMappingImage {
var blockDevices []osc.BlockDeviceMappingImage
func buildBlockDevicesImage(b []BlockDevice) []oapi.BlockDeviceMappingImage {
var blockDevices []oapi.BlockDeviceMappingImage
for _, blockDevice := range b {
mapping := osc.BlockDeviceMappingImage{
mapping := oapi.BlockDeviceMappingImage{
DeviceName: blockDevice.DeviceName,
}
@@ -47,8 +48,8 @@ func buildOscBlockDevicesImage(b []BlockDevice) []osc.BlockDeviceMappingImage {
mapping.VirtualDeviceName = blockDevice.VirtualName
}
} else {
bsu := osc.BsuToCreate{
DeleteOnVmDeletion: blockDevice.DeleteOnVmDeletion,
bsu := oapi.BsuToCreate{
DeleteOnVmDeletion: aws.Bool(blockDevice.DeleteOnVmDeletion),
}
if blockDevice.VolumeType != "" {
@@ -56,12 +57,12 @@ func buildOscBlockDevicesImage(b []BlockDevice) []osc.BlockDeviceMappingImage {
}
if blockDevice.VolumeSize > 0 {
bsu.VolumeSize = int32(blockDevice.VolumeSize)
bsu.VolumeSize = blockDevice.VolumeSize
}
// IOPS is only valid for io1 type
if blockDevice.VolumeType == "io1" {
bsu.Iops = int32(blockDevice.IOPS)
bsu.Iops = blockDevice.IOPS
}
if blockDevice.SnapshotId != "" {
@@ -76,13 +77,13 @@ func buildOscBlockDevicesImage(b []BlockDevice) []osc.BlockDeviceMappingImage {
return blockDevices
}
func buildOscBlockDevicesVmCreation(b []BlockDevice) []osc.BlockDeviceMappingVmCreation {
func buildBlockDevicesVmCreation(b []BlockDevice) []oapi.BlockDeviceMappingVmCreation {
log.Printf("[DEBUG] Launch Block Device %#v", b)
var blockDevices []osc.BlockDeviceMappingVmCreation
var blockDevices []oapi.BlockDeviceMappingVmCreation
for _, blockDevice := range b {
mapping := osc.BlockDeviceMappingVmCreation{
mapping := oapi.BlockDeviceMappingVmCreation{
DeviceName: blockDevice.DeviceName,
}
@@ -93,8 +94,8 @@ func buildOscBlockDevicesVmCreation(b []BlockDevice) []osc.BlockDeviceMappingVmC
mapping.VirtualDeviceName = blockDevice.VirtualName
}
} else {
bsu := osc.BsuToCreate{
DeleteOnVmDeletion: blockDevice.DeleteOnVmDeletion,
bsu := oapi.BsuToCreate{
DeleteOnVmDeletion: aws.Bool(blockDevice.DeleteOnVmDeletion),
}
if blockDevice.VolumeType != "" {
@@ -102,12 +103,12 @@ func buildOscBlockDevicesVmCreation(b []BlockDevice) []osc.BlockDeviceMappingVmC
}
if blockDevice.VolumeSize > 0 {
bsu.VolumeSize = int32(blockDevice.VolumeSize)
bsu.VolumeSize = blockDevice.VolumeSize
}
// IOPS is only valid for io1 type
if blockDevice.VolumeType == "io1" {
bsu.Iops = int32(blockDevice.IOPS)
bsu.Iops = blockDevice.IOPS
}
if blockDevice.SnapshotId != "" {
@@ -144,10 +145,10 @@ func (b *BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
return errs
}
func (b *OMIBlockDevices) BuildOscOMIDevices() []osc.BlockDeviceMappingImage {
return buildOscBlockDevicesImage(b.OMIMappings)
func (b *OMIBlockDevices) BuildOMIDevices() []oapi.BlockDeviceMappingImage {
return buildBlockDevicesImage(b.OMIMappings)
}
func (b *LaunchBlockDevices) BuildOSCLaunchDevices() []osc.BlockDeviceMappingVmCreation {
return buildOscBlockDevicesVmCreation(b.LaunchMappings)
func (b *LaunchBlockDevices) BuildLaunchDevices() []oapi.BlockDeviceMappingVmCreation {
return buildBlockDevicesVmCreation(b.LaunchMappings)
}
+63 -52
View File
@@ -4,13 +4,19 @@ import (
"reflect"
"testing"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
func TestBlockDevice_LaunchDevices(t *testing.T) {
tr := new(bool)
f := new(bool)
*tr = true
*f = false
cases := []struct {
Config *BlockDevice
Result osc.BlockDeviceMappingVmCreation
Result oapi.BlockDeviceMappingVmCreation
}{
{
Config: &BlockDevice{
@@ -21,13 +27,13 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: osc.BlockDeviceMappingVmCreation{
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
SnapshotId: "snap-1234",
VolumeType: "standard",
VolumeSize: 8,
DeleteOnVmDeletion: true,
DeleteOnVmDeletion: tr,
},
},
},
@@ -37,11 +43,11 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
VolumeSize: 8,
},
Result: osc.BlockDeviceMappingVmCreation{
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
VolumeSize: 8,
DeleteOnVmDeletion: false,
DeleteOnVmDeletion: f,
},
},
},
@@ -54,12 +60,12 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
IOPS: 1000,
},
Result: osc.BlockDeviceMappingVmCreation{
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
VolumeType: "io1",
VolumeSize: 8,
DeleteOnVmDeletion: true,
DeleteOnVmDeletion: tr,
Iops: 1000,
},
},
@@ -72,12 +78,12 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: osc.BlockDeviceMappingVmCreation{
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: true,
DeleteOnVmDeletion: tr,
},
},
},
@@ -89,12 +95,12 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: osc.BlockDeviceMappingVmCreation{
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: true,
DeleteOnVmDeletion: tr,
},
},
},
@@ -105,11 +111,11 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: osc.BlockDeviceMappingVmCreation{
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
VolumeType: "standard",
DeleteOnVmDeletion: true,
DeleteOnVmDeletion: tr,
},
},
},
@@ -119,7 +125,7 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
VirtualName: "ephemeral0",
},
Result: osc.BlockDeviceMappingVmCreation{
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
VirtualDeviceName: "ephemeral0",
},
@@ -130,7 +136,7 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
NoDevice: true,
},
Result: osc.BlockDeviceMappingVmCreation{
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
NoDevice: "",
},
@@ -143,9 +149,9 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
LaunchMappings: []BlockDevice{*tc.Config},
}
expected := []osc.BlockDeviceMappingVmCreation{tc.Result}
expected := []oapi.BlockDeviceMappingVmCreation{tc.Result}
launchResults := launchBlockDevices.BuildOSCLaunchDevices()
launchResults := launchBlockDevices.BuildLaunchDevices()
if !reflect.DeepEqual(expected, launchResults) {
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
expected, launchResults)
@@ -154,9 +160,15 @@ 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 osc.BlockDeviceMappingImage
Result oapi.BlockDeviceMappingImage
}{
{
Config: &BlockDevice{
@@ -167,28 +179,27 @@ func TestBlockDevice_OMI(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: osc.BlockDeviceMappingImage{
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
SnapshotId: "snap-1234",
VolumeType: "standard",
VolumeSize: 8,
DeleteOnVmDeletion: true,
DeleteOnVmDeletion: tr,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeSize: 8,
DeleteOnVmDeletion: true,
DeviceName: "/dev/sdb",
VolumeSize: 8,
},
Result: osc.BlockDeviceMappingImage{
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
VolumeSize: 8,
DeleteOnVmDeletion: true,
DeleteOnVmDeletion: f,
},
},
},
@@ -201,12 +212,12 @@ func TestBlockDevice_OMI(t *testing.T) {
IOPS: 1000,
},
Result: osc.BlockDeviceMappingImage{
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
VolumeType: "io1",
VolumeSize: 8,
DeleteOnVmDeletion: true,
DeleteOnVmDeletion: tr,
Iops: 1000,
},
},
@@ -219,12 +230,12 @@ func TestBlockDevice_OMI(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: osc.BlockDeviceMappingImage{
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: true,
DeleteOnVmDeletion: tr,
},
},
},
@@ -236,12 +247,12 @@ func TestBlockDevice_OMI(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: osc.BlockDeviceMappingImage{
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: true,
DeleteOnVmDeletion: tr,
},
},
},
@@ -252,11 +263,11 @@ func TestBlockDevice_OMI(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: osc.BlockDeviceMappingImage{
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: osc.BsuToCreate{
Bsu: oapi.BsuToCreate{
VolumeType: "standard",
DeleteOnVmDeletion: true,
DeleteOnVmDeletion: tr,
},
},
},
@@ -266,24 +277,24 @@ func TestBlockDevice_OMI(t *testing.T) {
VirtualName: "ephemeral0",
},
Result: osc.BlockDeviceMappingImage{
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
VirtualDeviceName: "ephemeral0",
},
},
}
for i, tc := range cases {
for _, tc := range cases {
omiBlockDevices := OMIBlockDevices{
OMIMappings: []BlockDevice{*tc.Config},
}
expected := []osc.BlockDeviceMappingImage{tc.Result}
expected := []oapi.BlockDeviceMappingImage{tc.Result}
omiResults := omiBlockDevices.BuildOscOMIDevices()
omiResults := omiBlockDevices.BuildOMIDevices()
if !reflect.DeepEqual(expected, omiResults) {
t.Fatalf("%d - Bad block device, \nexpected: %+#v\n\ngot: %+#v",
i, expected, omiResults)
t.Fatalf("Bad block device, \nexpected: %+#v\n\ngot: %+#v",
expected, omiResults)
}
}
}
+77 -12
View File
@@ -4,11 +4,11 @@ import (
"log"
"strconv"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
func buildOscNetFilters(input map[string]string) osc.FiltersNet {
var filters osc.FiltersNet
func buildNetFilters(input map[string]string) oapi.FiltersNet {
var filters oapi.FiltersNet
for k, v := range input {
filterValue := []string{v}
switch name := k; name {
@@ -33,14 +33,14 @@ func buildOscNetFilters(input map[string]string) osc.FiltersNet {
return filters
}
func buildOscSubnetFilters(input map[string]string) osc.FiltersSubnet {
var filters osc.FiltersSubnet
func buildSubnetFilters(input map[string]string) oapi.FiltersSubnet {
var filters oapi.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 = []int32{int32(ipCount)}
filters.AvailableIpsCounts = []int64{int64(ipCount)}
}
case "ip-ranges":
filters.IpRanges = filterValue
@@ -59,8 +59,8 @@ func buildOscSubnetFilters(input map[string]string) osc.FiltersSubnet {
return filters
}
func buildOSCOMIFilters(input map[string]string) osc.FiltersImage {
var filters osc.FiltersImage
func buildOMIFilters(input map[string]string) oapi.FiltersImage {
var filters oapi.FiltersImage
for k, v := range input {
filterValue := []string{v}
@@ -75,14 +75,14 @@ func buildOSCOMIFilters(input map[string]string) osc.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,3 +90,68 @@ func buildOSCOMIFilters(input map[string]string) osc.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 -2
View File
@@ -2,7 +2,7 @@ package common
import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
type BuildInfoTemplate struct {
@@ -20,7 +20,7 @@ func extractBuildInfo(region string, state multistep.StateBag) *BuildInfoTemplat
}
}
sourceOMI := rawSourceOMI.(osc.Image)
sourceOMI := rawSourceOMI.(oapi.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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
func testImage() osc.Image {
return osc.Image{
func testImage() oapi.Image {
return oapi.Image{
ImageId: "ami-abcd1234",
ImageName: "ami_test_name",
Tags: []osc.ResourceTag{
Tags: []oapi.ResourceTag{
{
Key: "key-1",
Value: "value-1",
+6 -2
View File
@@ -36,13 +36,17 @@ 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 = []string{"us-east-1", "us-west-1"}
c.OMIRegions, err = listOAPIRegions(mockConn)
if err != nil {
t.Fatalf("shouldn't have err: %s", err.Error())
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatalf("shouldn't have err: %#v", errs)
}
+11 -12
View File
@@ -1,23 +1,19 @@
package common
import (
"context"
"fmt"
"github.com/antihax/optional"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
func listOSCRegions(oscconn *osc.RegionApiService) ([]string, error) {
func listOAPIRegions(oapiconn oapi.OAPIClient) ([]string, error) {
var regions []string
resp, _, err := oscconn.ReadRegions(context.Background(), &osc.ReadRegionsOpts{
ReadRegionsRequest: optional.NewInterface(osc.ReadRegionsRequest{}),
})
if err != nil {
resp, err := oapiconn.POST_ReadRegions(oapi.ReadRegionsRequest{})
if resp.OK == nil || err != nil {
return []string{}, err
}
resultRegions := resp
resultRegions := resp.OK
for _, region := range resultRegions.Regions {
regions = append(regions, region.RegionName)
@@ -28,10 +24,13 @@ func listOSCRegions(oscconn *osc.RegionApiService) ([]string, error) {
// ValidateRegion returns true if the supplied region is a valid Outscale
// region and false if it's not.
func (c *AccessConfig) ValidateOSCRegion(regions ...string) error {
oscconn := c.NewOSCClient()
func (c *AccessConfig) ValidateRegion(regions ...string) error {
oapiconn, err := c.NewOAPIConnection()
if err != nil {
return err
}
validRegions, err := listOSCRegions(oscconn.RegionApi)
validRegions, err := listOAPIRegions(oapiconn)
if err != nil {
return err
}
+11 -86
View File
@@ -1,20 +1,16 @@
package common
import (
"context"
"errors"
"fmt"
"time"
"net/http"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
type oscDescriber interface {
ReadVms(ctx context.Context, localVarOptionals *osc.ReadVmsOpts) (osc.ReadVmsResponse, *http.Response, error)
type oapiDescriber interface {
POST_ReadVms(oapi.ReadVmsRequest) (*oapi.POST_ReadVmsResponses, error)
}
var (
@@ -24,13 +20,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 oscDescriber, sshInterface string) func(multistep.StateBag) (string, error) {
func SSHHost(e oapiDescriber, 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)
i := state.Get("vm").(oapi.Vm)
if sshInterface != "" {
switch sshInterface {
@@ -67,91 +63,20 @@ func SSHHost(e oscDescriber, sshInterface string) func(multistep.StateBag) (stri
return host, nil
}
r, _, err := e.ReadVms(context.Background(), &osc.ReadVmsOpts{
ReadVmsRequest: optional.NewInterface(osc.ReadVmsRequest{
Filters: osc.FiltersVm{
VmIds: []string{i.VmId},
},
}),
r, err := e.POST_ReadVms(oapi.ReadVmsRequest{
Filters: oapi.FiltersVm{
VmIds: []string{i.VmId},
},
})
if err != nil {
return "", err
}
if len(r.Vms) == 0 {
if len(r.OK.Vms) == 0 {
return "", fmt.Errorf("vm not found: %s", i.VmId)
}
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])
state.Put("vm", r.OK.Vms[0])
time.Sleep(sshHostSleepDuration)
}
+169 -113
View File
@@ -1,73 +1,78 @@
package common
import (
"context"
"fmt"
"log"
"github.com/antihax/optional"
"github.com/hashicorp/packer/common"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
type stateRefreshFunc func() (string, error)
func waitUntilForOscVmRunning(conn *osc.APIClient, vmID string) error {
func waitForSecurityGroup(conn *oapi.Client, securityGroupID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "running", waitUntilOscVmStateFunc(conn, vmID))
go waitForState(errCh, "exists", securityGroupWaitFunc(conn, securityGroupID))
err := <-errCh
return err
}
func waitUntilOscVmDeleted(conn *osc.APIClient, vmID string) error {
func waitUntilForVmRunning(conn *oapi.Client, vmID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "terminated", waitUntilOscVmStateFunc(conn, vmID))
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))
return <-errCh
}
func waitUntilOscVmStopped(conn *osc.APIClient, vmID string) error {
func waitUntilVmStopped(conn *oapi.Client, vmID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "stopped", waitUntilOscVmStateFunc(conn, vmID))
go waitForState(errCh, "stopped", waitUntilVmStateFunc(conn, vmID))
return <-errCh
}
func WaitUntilOscSnapshotCompleted(conn *osc.APIClient, id string) error {
func WaitUntilSnapshotCompleted(conn *oapi.Client, id string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "completed", waitUntilOscSnapshotStateFunc(conn, id))
go waitForState(errCh, "completed", waitUntilSnapshotStateFunc(conn, id))
return <-errCh
}
func WaitUntilOscImageAvailable(conn *osc.APIClient, imageID string) error {
func WaitUntilImageAvailable(conn *oapi.Client, imageID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "available", waitUntilOscImageStateFunc(conn, imageID))
go waitForState(errCh, "available", waitUntilImageStateFunc(conn, imageID))
return <-errCh
}
func WaitUntilOscVolumeAvailable(conn *osc.APIClient, volumeID string) error {
func WaitUntilVolumeAvailable(conn *oapi.Client, volumeID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "available", volumeOscWaitFunc(conn, volumeID))
go waitForState(errCh, "available", volumeWaitFunc(conn, volumeID))
return <-errCh
}
func WaitUntilOscVolumeIsLinked(conn *osc.APIClient, volumeID string) error {
func WaitUntilVolumeIsLinked(conn *oapi.Client, volumeID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "attached", waitUntilOscVolumeLinkedStateFunc(conn, volumeID))
go waitForState(errCh, "attached", waitUntilVolumeLinkedStateFunc(conn, volumeID))
return <-errCh
}
func WaitUntilOscVolumeIsUnlinked(conn *osc.APIClient, volumeID string) error {
func WaitUntilVolumeIsUnlinked(conn *oapi.Client, volumeID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "dettached", waitUntilOscVolumeUnLinkedStateFunc(conn, volumeID))
go waitForState(errCh, "dettached", waitUntilVolumeUnLinkedStateFunc(conn, volumeID))
return <-errCh
}
func WaitUntilOscSnapshotDone(conn *osc.APIClient, snapshotID string) error {
func WaitUntilSnapshotDone(conn *oapi.Client, snapshotID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "completed", waitUntilOscSnapshotDoneStateFunc(conn, snapshotID))
go waitForState(errCh, "completed", waitUntilSnapshotDoneStateFunc(conn, snapshotID))
return <-errCh
}
func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) {
func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error {
err := common.Retry(2, 2, 0, func(_ uint) (bool, error) {
state, err := refresh()
if err != nil {
@@ -78,83 +83,91 @@ func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) {
return false, nil
})
errCh <- err
return err
}
func waitUntilOscVmStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
func waitUntilVmStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Retrieving state for VM with id %s", id)
resp, _, err := conn.VmApi.ReadVms(context.Background(), &osc.ReadVmsOpts{
ReadVmsRequest: optional.NewInterface(osc.ReadVmsRequest{
Filters: osc.FiltersVm{
VmIds: []string{id},
},
}),
resp, err := conn.POST_ReadVms(oapi.ReadVmsRequest{
Filters: oapi.FiltersVm{
VmIds: []string{id},
},
})
log.Printf("[Debug] Read Response %+v", resp.OK)
if err != nil {
return "", err
}
//TODO: check if needed
// if resp == nil {
// return "", fmt.Errorf("Vm with ID %s not Found", id)
// }
if resp.OK == nil {
return "", fmt.Errorf("Vm with ID %s not Found", id)
}
if len(resp.Vms) == 0 {
if len(resp.OK.Vms) == 0 {
return "pending", nil
}
return resp.Vms[0].State, nil
return resp.OK.Vms[0].State, nil
}
}
func waitUntilOscVolumeLinkedStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
func waitUntilVolumeLinkedStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if volume with id %s exists", id)
resp, _, err := conn.VolumeApi.ReadVolumes(context.Background(), &osc.ReadVolumesOpts{
ReadVolumesRequest: optional.NewInterface(osc.ReadVolumesRequest{
Filters: osc.FiltersVolume{
VolumeIds: []string{id},
},
}),
resp, err := conn.POST_ReadVolumes(oapi.ReadVolumesRequest{
Filters: oapi.FiltersVolume{
VolumeIds: []string{id},
},
})
if err != nil {
return "", err
}
if len(resp.Volumes) == 0 {
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 {
return "pending", nil
}
if len(resp.Volumes[0].LinkedVolumes) == 0 {
if len(resp.OK.Volumes[0].LinkedVolumes) == 0 {
return "pending", nil
}
return resp.Volumes[0].LinkedVolumes[0].State, nil
return resp.OK.Volumes[0].LinkedVolumes[0].State, nil
}
}
func waitUntilOscVolumeUnLinkedStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
func waitUntilVolumeUnLinkedStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if volume with id %s exists", id)
resp, _, err := conn.VolumeApi.ReadVolumes(context.Background(), &osc.ReadVolumesOpts{
ReadVolumesRequest: optional.NewInterface(osc.ReadVolumesRequest{
Filters: osc.FiltersVolume{
VolumeIds: []string{id},
},
}),
resp, err := conn.POST_ReadVolumes(oapi.ReadVolumesRequest{
Filters: oapi.FiltersVolume{
VolumeIds: []string{id},
},
})
if err != nil {
return "", err
}
if len(resp.Volumes) == 0 {
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 {
return "pending", nil
}
if len(resp.Volumes[0].LinkedVolumes) == 0 {
if len(resp.OK.Volumes[0].LinkedVolumes) == 0 {
return "dettached", nil
}
@@ -162,106 +175,149 @@ func waitUntilOscVolumeUnLinkedStateFunc(conn *osc.APIClient, id string) stateRe
}
}
func waitUntilOscSnapshotStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
func waitUntilSnapshotStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if Snapshot with id %s exists", id)
resp, _, err := conn.SnapshotApi.ReadSnapshots(context.Background(), &osc.ReadSnapshotsOpts{
ReadSnapshotsRequest: optional.NewInterface(osc.ReadSnapshotsRequest{
Filters: osc.FiltersSnapshot{
SnapshotIds: []string{id},
},
}),
resp, err := conn.POST_ReadSnapshots(oapi.ReadSnapshotsRequest{
Filters: oapi.FiltersSnapshot{
SnapshotIds: []string{id},
},
})
log.Printf("[Debug] Read Response %+v", resp.OK)
if err != nil {
return "", err
}
if len(resp.Snapshots) == 0 {
if resp.OK == nil {
return "", fmt.Errorf("Vm with ID %s. Not Found", id)
}
if len(resp.OK.Snapshots) == 0 {
return "pending", nil
}
return resp.Snapshots[0].State, nil
return resp.OK.Snapshots[0].State, nil
}
}
func waitUntilOscImageStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
func waitUntilImageStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if Image with id %s exists", id)
resp, _, err := conn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageIds: []string{id},
},
}),
resp, err := conn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: []string{id},
},
})
log.Printf("[Debug] Read Response %+v", resp.OK)
if err != nil {
return "", err
}
if len(resp.Images) == 0 {
if resp.OK == nil {
return "", fmt.Errorf("Vm with ID %s. Not Found", id)
}
if len(resp.OK.Images) == 0 {
return "pending", nil
}
if resp.Images[0].State == "failed" {
return resp.Images[0].State, fmt.Errorf("Image (%s) creation is failed", id)
if resp.OK.Images[0].State == "failed" {
return resp.OK.Images[0].State, fmt.Errorf("Image (%s) creation is failed", id)
}
return resp.Images[0].State, nil
return resp.OK.Images[0].State, nil
}
}
func waitUntilOscSnapshotDoneStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
func securityGroupWaitFunc(conn *oapi.Client, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if Snapshot with id %s exists", id)
resp, _, err := conn.SnapshotApi.ReadSnapshots(context.Background(), &osc.ReadSnapshotsOpts{
ReadSnapshotsRequest: optional.NewInterface(osc.ReadSnapshotsRequest{
Filters: osc.FiltersSnapshot{
SnapshotIds: []string{id},
},
}),
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 len(resp.Snapshots) == 0 {
return "", fmt.Errorf("Snapshot with ID %s. Not Found", id)
if resp.OK == nil {
return "", fmt.Errorf("Security Group 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)
}
return resp.Snapshots[0].State, nil
}
}
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.VolumeApi.ReadVolumes(context.Background(), &osc.ReadVolumesOpts{
ReadVolumesRequest: optional.NewInterface(osc.ReadVolumesRequest{
Filters: osc.FiltersVolume{
VolumeIds: []string{id},
},
}),
})
if err != nil {
return "", err
}
if len(resp.Volumes) == 0 {
if len(resp.OK.SecurityGroups) == 0 {
return "waiting", nil
}
if resp.Volumes[0].State == "error" {
return resp.Volumes[0].State, fmt.Errorf("Volume (%s) creation is failed", id)
}
return resp.Volumes[0].State, nil
return "exists", nil
}
}
func waitUntilSnapshotDoneStateFunc(conn *oapi.Client, 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},
},
})
log.Printf("[Debug] Read Response %+v", resp.OK)
if err != nil {
return "", err
}
if resp.OK == nil {
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.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
}
}
func volumeWaitFunc(conn *oapi.Client, 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},
},
})
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 {
return "waiting", nil
}
if resp.OK.Volumes[0].State == "error" {
return resp.OK.Volumes[0].State, fmt.Errorf("Volume (%s) creation is failed", id)
}
return resp.OK.Volumes[0].State, nil
}
}
+14 -20
View File
@@ -5,32 +5,29 @@ import (
"fmt"
"reflect"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// 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) {
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
vmRaw := state.Get("vm")
var vm osc.Vm
var vm oapi.Vm
if vmRaw != nil {
vm = vmRaw.(osc.Vm)
vm = vmRaw.(oapi.Vm)
}
ui := state.Get("ui").(packer.Ui)
if vm.VmId == "" {
@@ -45,7 +42,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, osc.BsuCreated{}) {
if !reflect.DeepEqual(bdm.Bsu, oapi.BsuCreated{}) {
vl = append(vl, bdm.Bsu.VolumeId)
volList[bdm.Bsu.VolumeId] = bdm.DeviceName
}
@@ -53,12 +50,10 @@ 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 := oscconn.VolumeApi.ReadVolumes(context.Background(), &osc.ReadVolumesOpts{
ReadVolumesRequest: optional.NewInterface(osc.ReadVolumesRequest{
Filters: osc.FiltersVolume{
VolumeIds: vl,
},
}),
resp, err := oapiconn.POST_ReadVolumes(oapi.ReadVolumesRequest{
Filters: oapi.FiltersVolume{
VolumeIds: vl,
},
})
if err != nil {
@@ -68,13 +63,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.Volumes {
for _, v := range resp.OK.Volumes {
if v.State != "" && v.State != "available" {
delete(volList, v.VolumeId)
}
}
if len(resp.Volumes) == 0 {
if len(resp.OK.Volumes) == 0 {
ui.Say("No volumes to clean up, skipping")
return
}
@@ -92,11 +87,10 @@ func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
// Destroy remaining volumes
for k := range volList {
ui.Say(fmt.Sprintf("Destroying volume (%s)...", k))
_, _, err := oscconn.VolumeApi.DeleteVolume(context.Background(), &osc.DeleteVolumeOpts{
DeleteVolumeRequest: optional.NewInterface(osc.DeleteVolumeRequest{VolumeId: k}),
})
_, err := oapiconn.POST_DeleteVolume(oapi.DeleteVolumeRequest{VolumeId: k})
if err != nil {
ui.Say(fmt.Sprintf("Error deleting volume: %s", err))
}
}
}
+34 -24
View File
@@ -2,15 +2,16 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
type StepCreateTags struct {
@@ -20,7 +21,8 @@ type StepCreateTags struct {
}
func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("accessConfig").(*AccessConfig)
oapiconn := state.Get("oapi").(*oapi.Client)
config := state.Get("clientConfig").(*oapi.Config)
ui := state.Get("ui").(packer.Ui)
omis := state.Get("omis").(map[string]string)
@@ -32,16 +34,28 @@ 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))
regionconn := config.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)
// Retrieve image list for given OMI
resourceIds := []string{ami}
imageResp, _, err := regionconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageIds: resourceIds,
},
}),
imageResp, err := regionConn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: resourceIds,
},
})
if err != nil {
@@ -51,14 +65,14 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
return multistep.ActionHalt
}
if len(imageResp.Images) == 0 {
if len(imageResp.OK.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.Images[0]
image := imageResp.OK.Images[0]
snapshotIds := []string{}
// Add only those with a Snapshot ID, i.e. not Ephemeral
@@ -72,7 +86,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.OSCTags(s.Ctx, config.RawRegion, state)
amiTags, err := s.Tags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
@@ -81,7 +95,7 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
amiTags.Report(ui)
ui.Say("Creating snapshot tags")
snapshotTags, err := s.SnapshotTags.OSCTags(s.Ctx, config.RawRegion, state)
snapshotTags, err := s.SnapshotTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
@@ -92,11 +106,9 @@ 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.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
ResourceIds: resourceIds,
Tags: amiTags,
}),
_, err := regionConn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: resourceIds,
Tags: amiTags,
})
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidOMIID.NotFound" ||
@@ -107,11 +119,9 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
// Override tags on snapshots
if len(snapshotTags) > 0 {
_, _, err = regionconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
ResourceIds: snapshotIds,
Tags: snapshotTags,
}),
_, err = regionConn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: snapshotIds,
Tags: snapshotTags,
})
}
if err == nil {
+39 -36
View File
@@ -2,14 +2,13 @@ package common
import (
"context"
"crypto/tls"
"fmt"
"log"
"github.com/antihax/optional"
"github.com/outscale/osc-sdk-go/osc"
"net/http"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
type StepDeregisterOMI struct {
@@ -27,72 +26,76 @@ 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)
s.Regions = append(s.Regions, s.AccessConfig.GetRegion())
log.Printf("LOG_ s.Regions: %#+v\n", s.Regions)
for _, region := range s.Regions {
for _, region := range regions {
// get new connection for each region in which we need to deregister vms
conn := s.AccessConfig.NewOSCClientByRegion(region)
config, err := s.AccessConfig.Config()
if err != nil {
return multistep.ActionHalt
}
resp, _, err := conn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageNames: []string{s.OMIName},
//AccountAliases: []string{"self"},
},
}),
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"},
},
})
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.Images {
for _, i := range resp.OK.Images {
//We are supposing that DeleteImage does the same action as DeregisterImage
_, _, err := conn.ImageApi.DeleteImage(context.Background(), &osc.DeleteImageOpts{
DeleteImageRequest: optional.NewInterface(osc.DeleteImageRequest{
ImageId: resp.Images[i].ImageId,
}),
_, err := regionconn.POST_DeleteImage(oapi.DeleteImageRequest{
ImageId: 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, resp.Images[i].ImageId))
ui.Say(fmt.Sprintf("Deregistered OMI %s, id: %s", s.OMIName, i.ImageId))
// Delete snapshot(s) by image
if s.ForceDeleteSnapshot {
for _, b := range resp.Images[i].BlockDeviceMappings {
for _, b := range i.BlockDeviceMappings {
if b.Bsu.SnapshotId != "" {
_, _, err := conn.SnapshotApi.DeleteSnapshot(context.Background(), &osc.DeleteSnapshotOpts{
DeleteSnapshotRequest: optional.NewInterface(osc.DeleteSnapshotRequest{
SnapshotId: b.Bsu.SnapshotId,
}),
_, err := regionconn.POST_DeleteSnapshot(oapi.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))
}
}
+7 -10
View File
@@ -11,11 +11,10 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// StepGetPassword reads the password from a Windows server and sets it
@@ -103,8 +102,8 @@ WaitLoop:
func (s *StepGetPassword) Cleanup(multistep.StateBag) {}
func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) {
oscconn := state.Get("osc").(*osc.APIClient)
vm := state.Get("vm").(osc.Vm)
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
privateKey := s.Comm.SSHPrivateKey
for {
@@ -115,19 +114,17 @@ func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-cha
case <-time.After(5 * time.Second):
}
resp, _, err := oscconn.VmApi.ReadAdminPassword(context.Background(), &osc.ReadAdminPasswordOpts{
ReadAdminPasswordRequest: optional.NewInterface(osc.ReadAdminPasswordRequest{
VmId: vm.VmId,
}),
resp, err := oapiconn.POST_ReadAdminPassword(oapi.ReadAdminPasswordRequest{
VmId: vm.VmId,
})
if err != nil {
err := fmt.Errorf("Error retrieving auto-generated vm password: %s", err)
return "", err
}
if resp.AdminPassword != "" {
if resp.OK.AdminPassword != "" {
decryptedPassword, err := decryptPasswordDataWithPrivateKey(
resp.AdminPassword, privateKey)
resp.OK.AdminPassword, []byte(privateKey))
if err != nil {
err := fmt.Errorf("Error decrypting auto-generated vm password: %s", err)
return "", err
+9 -22
View File
@@ -6,11 +6,10 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
type StepKeyPair struct {
@@ -53,16 +52,11 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
return multistep.ActionContinue
}
conn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
resp, _, err := conn.KeypairApi.CreateKeypair(context.Background(), &osc.CreateKeypairOpts{
CreateKeypairRequest: optional.NewInterface(osc.CreateKeypairRequest{
KeypairName: s.Comm.SSHTemporaryKeyPairName,
}),
})
keyResp, err := oapiconn.POST_CreateKeypair(oapi.CreateKeypairRequest{
KeypairName: s.Comm.SSHTemporaryKeyPairName})
if err != nil {
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
return multistep.ActionHalt
@@ -72,7 +66,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(resp.Keypair.PrivateKey)
s.Comm.SSHPrivateKey = []byte(keyResp.OK.Keypair.PrivateKey)
// If we're in debug mode, output the private key to the working
// directory.
@@ -86,7 +80,7 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
defer f.Close()
// Write the key out
if _, err := f.Write([]byte(resp.Keypair.PrivateKey)); err != nil {
if _, err := f.Write([]byte(keyResp.OK.Keypair.PrivateKey)); err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
@@ -108,19 +102,12 @@ func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
return
}
var (
conn = state.Get("osc").(*osc.APIClient)
ui = state.Get("ui").(packer.Ui)
)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
// Remove the keypair
ui.Say("Deleting temporary keypair...")
_, _, err := conn.KeypairApi.DeleteKeypair(context.Background(), &osc.DeleteKeypairOpts{
DeleteKeypairRequest: optional.NewInterface(osc.DeleteKeypairRequest{
KeypairName: s.Comm.SSHTemporaryKeyPairName,
}),
})
_, err := oapiconn.POST_DeleteKeypair(oapi.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))
+32 -40
View File
@@ -7,10 +7,9 @@ import (
"math/rand"
"sort"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// StepNetworkInfo queries OUTSCALE for information about
@@ -30,37 +29,34 @@ type StepNetworkInfo struct {
SecurityGroupFilter SecurityGroupFilterOptions
}
type subnetsOscSort []osc.Subnet
type subnetsSort []oapi.Subnet
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 {
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 {
return a[i].AvailableIpsCount < a[j].AvailableIpsCount
}
// Returns the most recent OMI out of a slice of images.
func mostFreeOscSubnet(subnets []osc.Subnet) osc.Subnet {
func mostFreeSubnet(subnets []oapi.Subnet) oapi.Subnet {
sortedSubnets := subnets
sort.Sort(subnetsOscSort(sortedSubnets))
sort.Sort(subnetsSort(sortedSubnets))
return sortedSubnets[len(sortedSubnets)-1]
}
//Run ...
func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
// NET
if s.NetId == "" && !s.NetFilter.Empty() {
params := osc.ReadNetsRequest{}
params.Filters = buildOscNetFilters(s.NetFilter.Filters)
params := oapi.ReadNetsRequest{}
params.Filters = buildNetFilters(s.NetFilter.Filters)
s.NetFilter.Filters["state"] = "available"
log.Printf("Using NET Filters %v", params)
vpcResp, _, err := oscconn.NetApi.ReadNets(context.Background(), &osc.ReadNetsOpts{
ReadNetsRequest: optional.NewInterface(params),
})
vpcResp, err := oapiconn.POST_ReadNets(params)
if err != nil {
err := fmt.Errorf("Error querying NETs: %s", err)
state.Put("error", err)
@@ -68,20 +64,20 @@ func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multi
return multistep.ActionHalt
}
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)
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)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.NetId = vpcResp.Nets[0].NetId
s.NetId = vpcResp.OK.Nets[0].NetId
ui.Message(fmt.Sprintf("Found NET ID: %s", s.NetId))
}
// Subnet
if s.SubnetId == "" && !s.SubnetFilter.Empty() {
params := osc.ReadSubnetsRequest{}
params := oapi.ReadSubnetsRequest{}
s.SubnetFilter.Filters["state"] = "available"
if s.NetId != "" {
@@ -90,41 +86,39 @@ func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multi
if s.SubregionName != "" {
s.SubnetFilter.Filters["availability-zone"] = s.SubregionName
}
params.Filters = buildOscSubnetFilters(s.SubnetFilter.Filters)
params.Filters = buildSubnetFilters(s.SubnetFilter.Filters)
log.Printf("Using Subnet Filters %v", params)
subnetsResp, _, err := oscconn.SubnetApi.ReadSubnets(context.Background(), &osc.ReadSubnetsOpts{
ReadSubnetsRequest: optional.NewInterface(params),
})
subnetsResp, err := oapiconn.POST_ReadSubnets(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.Subnets) == 0 {
if len(subnetsResp.OK.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.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))
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))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
var subnet osc.Subnet
var subnet oapi.Subnet
switch {
case s.SubnetFilter.MostFree:
subnet = mostFreeOscSubnet(subnetsResp.Subnets)
subnet = mostFreeSubnet(subnetsResp.OK.Subnets)
case s.SubnetFilter.Random:
subnet = subnetsResp.Subnets[rand.Intn(len(subnetsResp.Subnets))]
subnet = subnetsResp.OK.Subnets[rand.Intn(len(subnetsResp.OK.Subnets))]
default:
subnet = subnetsResp.Subnets[0]
subnet = subnetsResp.OK.Subnets[0]
}
s.SubnetId = subnet.SubnetId
ui.Message(fmt.Sprintf("Found Subnet ID: %s", s.SubnetId))
@@ -133,25 +127,24 @@ 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 := oscconn.SubnetApi.ReadSubnets(context.Background(), &osc.ReadSubnetsOpts{
ReadSubnetsRequest: optional.NewInterface(osc.ReadSubnetsRequest{
Filters: osc.FiltersSubnet{
resp, err := oapiconn.POST_ReadSubnets(
oapi.ReadSubnetsRequest{
Filters: oapi.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.Subnets[0].SubregionName
s.SubregionName = resp.OK.Subnets[0].SubregionName
log.Printf("[INFO] SubregionName found: '%s'", s.SubregionName)
}
if s.NetId == "" {
s.NetId = resp.Subnets[0].NetId
s.NetId = resp.OK.Subnets[0].NetId
log.Printf("[INFO] NetId found: '%s'", s.NetId)
}
}
@@ -162,5 +155,4 @@ func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multi
return multistep.ActionContinue
}
//Cleanup ...
func (s *StepNetworkInfo) Cleanup(multistep.StateBag) {}
+10 -16
View File
@@ -4,10 +4,9 @@ import (
"context"
"fmt"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// StepPreValidate provides an opportunity to pre-validate any configuration for
@@ -16,7 +15,6 @@ import (
type StepPreValidate struct {
DestOmiName string
ForceDeregister bool
API string
}
func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
@@ -26,19 +24,11 @@ func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multi
return multistep.ActionContinue
}
var (
conn = state.Get("osc").(*osc.APIClient)
images []interface{}
)
oapiconn := state.Get("oapi").(*oapi.Client)
ui.Say(fmt.Sprintf("Prevalidating OMI Name: %s", s.DestOmiName))
resp, _, err := conn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageNames: []string{s.DestOmiName},
},
}),
resp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{ImageNames: []string{s.DestOmiName}},
})
if err != nil {
@@ -48,14 +38,18 @@ func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multi
return multistep.ActionHalt
}
for _, omi := range resp.Images {
//FIXME: Remove when the oAPI filters works
images := make([]oapi.Image, 0)
for _, omi := range resp.OK.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", s.DestOmiName)
err := fmt.Errorf("Error: name conflicts with an existing OMI: %s", resp.OK.Images[0].ImageId)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
+16 -26
View File
@@ -4,11 +4,10 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
type StepPublicIp struct {
@@ -21,24 +20,20 @@ type StepPublicIp struct {
}
func (s *StepPublicIp) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
var (
ui = state.Get("ui").(packer.Ui)
conn = state.Get("osc").(*osc.APIClient)
)
if !s.AssociatePublicIpAddress {
ui := state.Get("ui").(packer.Ui)
oapiconn := state.Get("oapi").(*oapi.Client)
netId := state.Get("net_id").(string)
subnetId := state.Get("subnet_id").(string)
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("Creating temporary PublicIp for instance ")
resp, _, err := conn.PublicIpApi.CreatePublicIp(context.Background(), &osc.CreatePublicIpOpts{
CreatePublicIpRequest: optional.NewInterface(osc.CreatePublicIpRequest{}),
})
ui.Say(fmt.Sprintf("Creating temporary PublicIp for instance in subnet %s (net %s)", subnetId, netId))
publicIpResp, err := oapiconn.POST_CreatePublicIp(oapi.CreatePublicIpRequest{})
if err != nil {
state.Put("error", fmt.Errorf("Error creating temporary PublicIp: %s", err))
return multistep.ActionHalt
@@ -48,8 +43,8 @@ func (s *StepPublicIp) Run(_ context.Context, state multistep.StateBag) multiste
s.doCleanup = true
// Set some data for use in future steps
s.publicIpId = resp.PublicIp.PublicIpId
state.Put("publicip_id", resp.PublicIp.PublicIpId)
s.publicIpId = publicIpResp.OK.PublicIp.PublicIpId
state.Put("publicip_id", publicIpResp.OK.PublicIp.PublicIpId)
return multistep.ActionContinue
}
@@ -59,20 +54,15 @@ func (s *StepPublicIp) Cleanup(state multistep.StateBag) {
return
}
var (
conn = state.Get("osc").(*osc.APIClient)
ui = state.Get("ui").(packer.Ui)
)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
// Remove the Public IP
ui.Say("Deleting temporary PublicIp...")
_, _, err := conn.PublicIpApi.DeletePublicIp(context.Background(), &osc.DeletePublicIpOpts{
DeletePublicIpRequest: optional.NewInterface(osc.DeletePublicIpRequest{
PublicIpId: s.publicIpId,
}),
})
_, 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))
ui.Error(fmt.Sprintf(
"Error cleaning up PublicIp. Please delete the PublicIp manually: %s", s.publicIpId))
}
}
+70 -51
View File
@@ -8,9 +8,8 @@ import (
"log"
"reflect"
"github.com/antihax/optional"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
retry "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
@@ -40,13 +39,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 {
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
securityGroupIds := state.Get("securityGroupIds").([]string)
ui := state.Get("ui").(packer.Ui)
@@ -68,7 +67,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").(osc.Image)
image, ok := state.Get("source_image").(oapi.Image)
if !ok {
state.Put("error", fmt.Errorf("source_image type assertion failed"))
return multistep.ActionHalt
@@ -90,9 +89,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
s.Tags["Name"] = "Packer Builder"
}
rawRegion := s.RawRegion
oscTags, err := s.Tags.OSCTags(s.Ctx, rawRegion, state)
oapiTags, err := s.Tags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
err := fmt.Errorf("Error tagging source vm: %s", err)
state.Put("error", err)
@@ -100,7 +97,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
return multistep.ActionHalt
}
volTags, err := s.VolumeTags.OSCTags(s.Ctx, rawRegion, state)
volTags, err := s.VolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
err := fmt.Errorf("Error tagging volumes: %s", err)
state.Put("error", err)
@@ -109,42 +106,73 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
subregion := state.Get("subregion_name").(string)
runOpts := osc.CreateVmsRequest{
runOpts := oapi.CreateVmsRequest{
ImageId: s.SourceOMI,
VmType: s.VmType,
UserData: userData,
MaxVmsCount: 1,
MinVmsCount: 1,
Placement: osc.Placement{SubregionName: subregion},
Placement: oapi.Placement{SubregionName: subregion},
BsuOptimized: s.BsuOptimized,
BlockDeviceMappings: s.BlockDevices.BuildOSCLaunchDevices(),
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
//IamVmProfile: oapi.IamVmProfileSpecification{Name: &s.IamVmProfile},
}
// 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 := oscconn.VmApi.CreateVms(context.Background(), &osc.CreateVmsOpts{
CreateVmsRequest: optional.NewInterface(runOpts),
})
runResp, err := oapiconn.POST_CreateVms(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.Vms[0].VmId
volumeId := runResp.Vms[0].BlockDeviceMappings[0].Bsu.VolumeId
vmId = runResp.OK.Vms[0].VmId
volumeId := runResp.OK.Vms[0].BlockDeviceMappings[0].Bsu.VolumeId
// Set the vm ID so that the cleanup works properly
s.vmId = vmId
@@ -152,12 +180,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 := osc.ReadVmsRequest{
Filters: osc.FiltersVm{
request := oapi.ReadVmsRequest{
Filters: oapi.FiltersVm{
VmIds: []string{vmId},
},
}
if err := waitUntilForOscVmRunning(oscconn, vmId); err != nil {
if err := waitUntilForVmRunning(oapiconn, 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())
@@ -165,8 +193,8 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
//Set Vm tags and vollume tags
if len(oscTags) > 0 {
if err := CreateOSCTags(oscconn, s.vmId, ui, oscTags); err != nil {
if len(oapiTags) > 0 {
if err := CreateTags(oapiconn, s.vmId, ui, oapiTags); err != nil {
err := fmt.Errorf("Error creating tags for vm (%s): %s", s.vmId, err)
state.Put("error", err)
ui.Error(err.Error())
@@ -175,7 +203,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
if len(volTags) > 0 {
if err := CreateOSCTags(oscconn, volumeId, ui, volTags); err != nil {
if err := CreateTags(oapiconn, 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())
@@ -185,9 +213,7 @@ 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 := oscconn.PublicIpApi.LinkPublicIp(context.Background(), &osc.LinkPublicIpOpts{
LinkPublicIpRequest: optional.NewInterface(osc.LinkPublicIpRequest{PublicIpId: publicip_id, VmId: vmId}),
})
_, err := oapiconn.POST_LinkPublicIp(oapi.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())
@@ -195,11 +221,9 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
}
resp, _, err := oscconn.VmApi.ReadVms(context.Background(), &osc.ReadVmsOpts{
ReadVmsRequest: optional.NewInterface(request),
})
resp, err := oapiconn.POST_ReadVms(request)
r := resp
r := resp.OK
if err != nil || len(r.Vms) == 0 {
err := fmt.Errorf("Error finding source vm.")
@@ -232,14 +256,12 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
// do that now.
if s.IsRestricted {
oscTags.Report(ui)
oapiTags.Report(ui)
// Retry creating tags for about 2.5 minutes
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
_, _, err := oscconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
Tags: oscTags,
ResourceIds: []string{vmId},
}),
_, err := oapiconn.POST_CreateTags(oapi.CreateTagsRequest{
Tags: oapiTags,
ResourceIds: []string{vmId},
})
if err == nil {
return true, nil
@@ -264,7 +286,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, osc.BsuCreated{}) {
if bsu := v.Bsu; !reflect.DeepEqual(bsu, oapi.BsuCreated{}) {
volumeIds = append(volumeIds, bsu.VolumeId)
}
}
@@ -272,7 +294,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.OSCTags(s.Ctx, rawRegion, state)
volumeTags, err := s.VolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
err := fmt.Errorf("Error tagging source BSU Volumes on %s: %s", vm.VmId, err)
state.Put("error", err)
@@ -281,11 +303,9 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
volumeTags.Report(ui)
_, _, err = oscconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
ResourceIds: volumeIds,
Tags: volumeTags,
}),
_, err = oapiconn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: volumeIds,
Tags: volumeTags,
})
if err != nil {
@@ -301,20 +321,19 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
func (s *StepRunSourceVm) Cleanup(state multistep.StateBag) {
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
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 := oscconn.VmApi.DeleteVms(context.Background(), &osc.DeleteVmsOpts{
DeleteVmsRequest: optional.NewInterface(osc.DeleteVmsRequest{VmIds: []string{s.vmId}}),
}); err != nil {
if _, err := oapiconn.POST_DeleteVms(oapi.DeleteVmsRequest{VmIds: []string{s.vmId}}); err != nil {
ui.Error(fmt.Sprintf("Error terminating vm, may still be around: %s", err))
return
}
if err := waitUntilOscVmDeleted(oscconn, s.vmId); err != nil {
if err := waitUntilVmDeleted(oapiconn, s.vmId); err != nil {
ui.Error(err.Error())
}
}
+64 -88
View File
@@ -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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
type StepSecurityGroup struct {
@@ -24,56 +24,50 @@ type StepSecurityGroup struct {
}
func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
var (
ui = state.Get("ui").(packer.Ui)
conn = state.Get("osc").(*osc.APIClient)
netID = state.Get("net_id").(string)
)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
netId := state.Get("net_id").(string)
if len(s.SecurityGroupIds) > 0 {
resp, _, err := conn.SecurityGroupApi.ReadSecurityGroups(context.Background(), &osc.ReadSecurityGroupsOpts{
ReadSecurityGroupsRequest: optional.NewInterface(osc.ReadSecurityGroupsRequest{
Filters: osc.FiltersSecurityGroup{
resp, err := oapiconn.POST_ReadSecurityGroups(
oapi.ReadSecurityGroupsRequest{
Filters: oapi.FiltersSecurityGroup{
SecurityGroupIds: s.SecurityGroupIds,
},
}),
})
if err != nil || len(resp.SecurityGroups) == 0 {
},
)
if err != nil || resp.OK == nil || len(resp.OK.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)
log.Printf("Using SecurityGroup Filters %v", filterReq)
params := oapi.ReadSecurityGroupsRequest{}
if netId != "" {
s.SecurityGroupFilter.Filters["net-id"] = netId
}
params.Filters = buildSecurityGroupFilters(s.SecurityGroupFilter.Filters)
resp, _, err := conn.SecurityGroupApi.ReadSecurityGroups(context.Background(), &osc.ReadSecurityGroupsOpts{
ReadSecurityGroupsRequest: optional.NewInterface(osc.ReadSecurityGroupsRequest{
Filters: filterReq,
}),
})
log.Printf("Using SecurityGroup Filters %v", params)
if err != nil || len(resp.SecurityGroups) == 0 {
sgResp, err := oapiconn.POST_ReadSecurityGroups(params)
if err != nil || sgResp.OK == nil {
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 resp.SecurityGroups {
for _, sg := range sgResp.OK.SecurityGroups {
securityGroupIds = append(securityGroupIds, sg.SecurityGroupId)
}
@@ -83,20 +77,24 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
return multistep.ActionContinue
}
/* Create the group */
groupName := fmt.Sprintf("packer_osc_%s", uuid.TimeOrderedUUID())
ui.Say(fmt.Sprintf("Creating temporary security group for this instance: %s", groupName))
port := s.CommConfig.Port()
if port == 0 {
if s.CommConfig.Type != "none" {
panic("port must be set to a non-zero value.")
}
}
createSGReq := osc.CreateSecurityGroupRequest{
// Create the group
groupName := fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
ui.Say(fmt.Sprintf("Creating temporary security group for this instance: %s", groupName))
group := oapi.CreateSecurityGroupRequest{
SecurityGroupName: groupName,
NetId: netID,
Description: "Temporary group for Packer",
}
resp, _, err := conn.SecurityGroupApi.CreateSecurityGroup(context.Background(), &osc.CreateSecurityGroupOpts{
CreateSecurityGroupRequest: optional.NewInterface(createSGReq),
})
group.NetId = netId
groupResp, err := oapiconn.POST_CreateSecurityGroup(group)
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
@@ -104,36 +102,38 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
}
// Set the group ID so we can delete it later
s.createdGroupId = resp.SecurityGroup.SecurityGroupId
s.createdGroupId = groupResp.OK.SecurityGroup.SecurityGroupId
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
}
// 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
}
// Authorize the SSH access for the security group
createSGRReq := osc.CreateSecurityGroupRuleRequest{
SecurityGroupId: resp.SecurityGroup.SecurityGroupId,
groupRules := oapi.CreateSecurityGroupRuleRequest{
SecurityGroupId: groupResp.OK.SecurityGroup.SecurityGroupId,
Flow: "Inbound",
Rules: []osc.SecurityGroupRule{
Rules: []oapi.SecurityGroupRule{
{
FromPortRange: int32(port),
ToPortRange: int32(port),
FromPortRange: int64(port),
ToPortRange: int64(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 = conn.SecurityGroupRuleApi.CreateSecurityGroupRule(context.Background(), &osc.CreateSecurityGroupRuleOpts{
CreateSecurityGroupRuleRequest: optional.NewInterface(createSGRReq),
})
ui.Say(fmt.Sprintf(
"Authorizing access to port %d from %s in the temporary security group...",
port, s.TemporarySGSourceCidr))
_, err = oapiconn.POST_CreateSecurityGroupRule(groupRules)
if err != nil {
err := fmt.Errorf("Error authorizing temporary security group: %s", err)
state.Put("error", err)
@@ -152,48 +152,24 @@ func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) {
return
}
var (
ui = state.Get("ui").(packer.Ui)
conn = state.Get("osc").(*osc.APIClient)
)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting temporary security group...")
_, _, err := conn.SecurityGroupApi.DeleteSecurityGroup(context.Background(), &osc.DeleteSecurityGroupOpts{
DeleteSecurityGroupRequest: optional.NewInterface(osc.DeleteSecurityGroupRequest{
SecurityGroupId: s.createdGroupId,
}),
})
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)
}
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
}
+19 -22
View File
@@ -7,46 +7,45 @@ import (
"sort"
"time"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
// StepSourceOMIInfo extracts critical information from the source OMI
// that is used throughout the OMI creation process.
//
// Produces:
// source_image *osc.Image - the source OMI info
// source_image *oapi.Image - the source OMI info
type StepSourceOMIInfo struct {
SourceOmi string
OMIVirtType string
OmiFilters OmiFilterOptions
}
type imageOscSort []osc.Image
type imageSort []oapi.Image
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 {
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 {
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 mostRecentOscOmi(images []osc.Image) osc.Image {
func mostRecentOmi(images []oapi.Image) oapi.Image {
sortedImages := images
sort.Sort(imageOscSort(sortedImages))
sort.Sort(imageSort(sortedImages))
return sortedImages[len(sortedImages)-1]
}
func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oscconn := state.Get("osc").(*osc.APIClient)
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
params := osc.ReadImagesRequest{
Filters: osc.FiltersImage{},
params := oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{},
}
if s.SourceOmi != "" {
@@ -55,7 +54,7 @@ func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) mul
// We have filters to apply
if len(s.OmiFilters.Filters) > 0 {
params.Filters = buildOSCOMIFilters(s.OmiFilters.Filters)
params.Filters = buildOMIFilters(s.OmiFilters.Filters)
}
//TODO:Check if AccountIds correspond to Owners.
if len(s.OmiFilters.Owners) > 0 {
@@ -63,9 +62,7 @@ func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) mul
}
log.Printf("Using OMI Filters %#v", params)
imageResp, _, err := oscconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(params),
})
imageResp, err := oapiconn.POST_ReadImages(params)
if err != nil {
err := fmt.Errorf("Error querying OMI: %s", err)
state.Put("error", err)
@@ -73,25 +70,25 @@ func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) mul
return multistep.ActionHalt
}
if len(imageResp.Images) == 0 {
if len(imageResp.OK.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.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.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.")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
var image osc.Image
var image oapi.Image
if s.OmiFilters.MostRecent {
image = mostRecentOscOmi(imageResp.Images)
image = mostRecentOmi(imageResp.OK.Images)
} else {
image = imageResp.Images[0]
image = imageResp.OK.Images[0]
}
ui.Message(fmt.Sprintf("Found Image ID: %s", image.ImageId))
@@ -4,12 +4,11 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
type StepStopBSUBackedVm struct {
@@ -18,8 +17,8 @@ type StepStopBSUBackedVm struct {
}
func (s *StepStopBSUBackedVm) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
oscconn := state.Get("osc").(*osc.APIClient)
vm := state.Get("vm").(osc.Vm)
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
ui := state.Get("ui").(packer.Ui)
// Skip when it is a spot vm
@@ -44,10 +43,8 @@ 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 = oscconn.VmApi.StopVms(context.Background(), &osc.StopVmsOpts{
StopVmsRequest: optional.NewInterface(osc.StopVmsRequest{
VmIds: []string{vm.VmId},
}),
_, err = oapiconn.POST_StopVms(oapi.StopVmsRequest{
VmIds: []string{vm.VmId},
})
if err == nil {
@@ -81,7 +78,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 = waitUntilOscVmStopped(oscconn, vm.VmId)
err = waitUntilVmStopped(oapiconn, vm.VmId)
if err != nil {
err := fmt.Errorf("Error waiting for vm to stop: %s", err)
+44 -35
View File
@@ -2,24 +2,25 @@ 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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
type StepUpdateOMIAttributes struct {
AccountIds []string
SnapshotAccountIds []string
RawRegion string
Ctx interpolate.Context
}
func (s *StepUpdateOMIAttributes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("accessConfig").(*AccessConfig)
oapiconn := state.Get("oapi").(*oapi.Client)
config := state.Get("clientConfig").(*oapi.Config)
ui := state.Get("ui").(packer.Ui)
omis := state.Get("omis").(map[string]string)
snapshots := state.Get("snapshots").(map[string][]string)
@@ -33,20 +34,20 @@ func (s *StepUpdateOMIAttributes) Run(_ context.Context, state multistep.StateBa
return multistep.ActionContinue
}
s.Ctx.Data = extractBuildInfo(s.RawRegion, state)
s.Ctx.Data = extractBuildInfo(oapiconn.GetConfig().Region, state)
updateSnapshoptRequest := osc.UpdateSnapshotRequest{
PermissionsToCreateVolume: osc.PermissionsOnResourceCreation{
Additions: osc.PermissionsOnResource{
updateSnapshoptRequest := oapi.UpdateSnapshotRequest{
PermissionsToCreateVolume: oapi.PermissionsOnResourceCreation{
Additions: oapi.PermissionsOnResource{
AccountIds: s.AccountIds,
GlobalPermission: false,
},
},
}
updateImageRequest := osc.UpdateImageRequest{
PermissionsToLaunch: osc.PermissionsOnResourceCreation{
Additions: osc.PermissionsOnResource{
updateImageRequest := oapi.UpdateImageRequest{
PermissionsToLaunch: oapi.PermissionsOnResourceCreation{
Additions: oapi.PermissionsOnResource{
AccountIds: s.AccountIds,
GlobalPermission: false,
},
@@ -56,31 +57,26 @@ 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))
regionconn := config.NewOSCClientByRegion(region)
newConfig := &oapi.Config{
UserAgent: config.UserAgent,
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
Service: config.Service,
Region: region, //New region
URL: config.URL,
}
// newConfig := &osc.Configuration{
// 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},
},
}
// skipClient := &http.Client{
// Transport: &http.Transport{
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
// },
// }
//regionconn := oapi.NewClient(newConfig, skipClient)
regionconn := oapi.NewClient(newConfig, skipClient)
ui.Message(fmt.Sprintf("Updating: %s", omi))
updateImageRequest.ImageId = omi
_, _, err := regionconn.ImageApi.UpdateImage(context.Background(), &osc.UpdateImageOpts{
UpdateImageRequest: optional.NewInterface(updateImageRequest),
})
_, err := regionconn.POST_UpdateImage(updateImageRequest)
if err != nil {
err := fmt.Errorf("Error updating OMI: %s", err)
state.Put("error", err)
@@ -93,13 +89,26 @@ 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))
regionconn := config.NewOSCClientByRegion(region)
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)
ui.Message(fmt.Sprintf("Updating: %s", snapshot))
updateSnapshoptRequest.SnapshotId = snapshot
_, _, err := regionconn.SnapshotApi.UpdateSnapshot(context.Background(), &osc.UpdateSnapshotOpts{
UpdateSnapshotRequest: optional.NewInterface(updateSnapshoptRequest),
})
_, err := regionconn.POST_UpdateSnapshot(updateSnapshoptRequest)
if err != nil {
err := fmt.Errorf("Error updating snapshot: %s", err)
state.Put("error", err)
+11 -15
View File
@@ -1,20 +1,18 @@
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-sdk-go/osc"
"github.com/outscale/osc-go/oapi"
)
type TagMap map[string]string
type OSCTags []osc.ResourceTag
type OAPITags []oapi.ResourceTag
func (t OSCTags) Report(ui packer.Ui) {
func (t OAPITags) Report(ui packer.Ui) {
for _, tag := range t {
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"",
tag.Key, tag.Value))
@@ -25,8 +23,8 @@ func (t TagMap) IsSet() bool {
return len(t) > 0
}
func (t TagMap) OSCTags(ctx interpolate.Context, region string, state multistep.StateBag) (OSCTags, error) {
var oscTags []osc.ResourceTag
func (t TagMap) OAPITags(ctx interpolate.Context, region string, state multistep.StateBag) (OAPITags, error) {
var oapiTags []oapi.ResourceTag
ctx.Data = extractBuildInfo(region, state)
for key, value := range t {
@@ -38,22 +36,20 @@ func (t TagMap) OSCTags(ctx interpolate.Context, region string, state multistep.
if err != nil {
return nil, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
}
oscTags = append(oscTags, osc.ResourceTag{
oapiTags = append(oapiTags, oapi.ResourceTag{
Key: interpolatedKey,
Value: interpolatedValue,
})
}
return oscTags, nil
return oapiTags, nil
}
func CreateOSCTags(conn *osc.APIClient, resourceID string, ui packer.Ui, tags OSCTags) error {
func CreateTags(conn *oapi.Client, resourceID string, ui packer.Ui, tags OAPITags) error {
tags.Report(ui)
_, _, err := conn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
ResourceIds: []string{resourceID},
Tags: tags,
}),
_, err := conn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: []string{resourceID},
Tags: tags,
})
return err
-60
View File
@@ -1,60 +0,0 @@
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}
}
-2
View File
@@ -192,8 +192,6 @@ 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
+8 -16
View File
@@ -208,27 +208,19 @@ 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 qemu-img).
// Each array of strings makes up a command line switch
// 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
// 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 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.
// 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.
//
// The following shows a sample usage:
//
+170 -211
View File
@@ -16,45 +16,41 @@ import (
// stepRun runs the virtual machine
type stepRun struct {
DiskImage bool
}
atLeastVersion2 bool
ui packer.Ui
type qemuArgsTemplateData struct {
HTTPIP string
HTTPPort int
HTTPDir string
OutputDir string
Name string
SSHHostPort int
}
func (s *stepRun) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
s.ui = state.Get("ui").(packer.Ui)
ui := state.Get("ui").(packer.Ui)
// 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
// 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"
}
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"))
ui.Say(message)
s.atLeastVersion2 = qemuVersion.GreaterThanOrEqual(v2)
// Generate the qemu command
command, err := s.getCommandArgs(config, state)
command, err := getCommandArgs(bootDrive, state)
if err != nil {
err := fmt.Errorf("Error processing QemuArgs: %s", err)
s.ui.Error(err.Error())
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)
s.ui.Error(err.Error())
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -70,187 +66,140 @@ func (s *stepRun) Cleanup(state multistep.StateBag) {
}
}
func (s *stepRun) getDefaultArgs(config *Config, state multistep.StateBag) map[string]interface{} {
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)
defaultArgs := make(map[string]interface{})
// 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
var deviceArgs []string
var driveArgs []string
var commHostPort int
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
// Track the connection for the user
vncPass, _ := state.Get("vnc_password").(string)
message = getVncConnectionMessage(config.Headless, vnc, vncPass)
if message != "" {
s.ui.Message(message)
if config.QMPEnable {
defaultArgs["-qmp"] = fmt.Sprintf("unix:%s,server,nowait", config.QMPSocketPath)
}
// Configure "-m" memory argument
defaultArgs["-m"] = fmt.Sprintf("%dM", config.MemorySize)
defaultArgs["-name"] = vmName
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
// 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)
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")
}
} else {
log.Println("Qemu Builder has no floppy files, not attaching a floppy.")
defaultArgs["-netdev"] = fmt.Sprintf("bridge,id=user.0,br=%s", config.NetBridge)
}
// 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"
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 {
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))
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)
}
// 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
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
}
} else if !config.UseDefaultDisplay {
defaultArgs["-display"] = "gtk"
}
} else {
ui.Message("WARNING: The version of qemu on your host doesn't support display mode.\n" +
"The display parameter will be ignored.")
}
}
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
@@ -271,48 +220,64 @@ func (s *stepRun) getDeviceAndDriveArgs(config *Config, state multistep.StateBag
}
}
return deviceArgs, driveArgs
}
defaultArgs["-device"] = deviceArgs
defaultArgs["-drive"] = driveArgs
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
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.")
}
inArgs := make(map[string][]string)
if len(config.QemuArgs) > 0 {
s.ui.Say("Overriding default Qemu arguments with qemuargs template option...")
ui.Say("Overriding default Qemu arguments with QemuArgs...")
commHostPort := state.Get("commHostPort").(int)
httpIp := state.Get("http_ip").(string)
httpPort := state.Get("http_port").(int)
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,
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,
}
}
// Interpolate each string in qemuargs
newQemuArgs, err := processArgs(config.QemuArgs, &ictx)
if err != nil {
return nil, err
}
// Qemu supports multiple appearances of the same switch. This means
// each key in the args hash will have an array of string values
// 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
for _, qemuArgs := range newQemuArgs {
key := qemuArgs[0]
val := strings.Join(qemuArgs[1:], "")
@@ -361,12 +326,6 @@ func (s *stepRun) applyUserOverrides(defaultArgs map[string]interface{}, config
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
+73 -632
View File
@@ -4,8 +4,6 @@ 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"
@@ -13,6 +11,8 @@ 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,396 +31,86 @@ func runTestState(t *testing.T, config *Config) multistep.StateBag {
return state
}
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",
},
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)
}
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))
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",
}
assert.ElementsMatch(t, args, expected, "unexpected generated args")
}
func Test_DriveAndDeviceArgs(t *testing.T) {
type testCase struct {
Config *Config
ExtraState map[string]interface{}
Step *stepRun
Expected []string
Reason string
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)
}
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",
},
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",
}
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)
}
assert.ElementsMatch(t, args, expected, fmt.Sprintf("unexpected generated args: %#v", args))
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))
// cd_path is set and DiskImage is true
config := &Config{
DiskImage: true,
DiskInterface: "virtio-scsi",
}
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) {
@@ -434,11 +124,8 @@ func Test_OptionalConfigOptionsGetSet(t *testing.T) {
}
state := runTestState(t, c)
step := &stepRun{
atLeastVersion2: true,
ui: packer.TestUi(t),
}
args, err := step.getCommandArgs(c, state)
args, err := getCommandArgs("once=d", state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
}
@@ -457,251 +144,5 @@ 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: %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
assert.ElementsMatch(t, args, expected, "password flag should be set, and d drive should be set.")
}
+4 -3
View File
@@ -44,11 +44,12 @@ 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:
// C1, C2L, C2M, C2S, DEV1-S, DEV1-M, DEV1-L, DEV1-XL,
// GP1-XS, GP1-S, GP1-M, GP1-L, GP1-XL, RENDER-S
// 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
CommercialType string `mapstructure:"commercial_type" required:"true"`
// The name of the resulting snapshot that will
// appear in your account. Default packer-TIMESTAMP
+1 -22
View File
@@ -5,11 +5,9 @@ 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"
)
@@ -24,27 +22,8 @@ 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: imageID,
ImageID: c.Image,
})
if err != nil {
err := fmt.Errorf("Error getting initial image info: %s", err)
+5 -8
View File
@@ -14,18 +14,15 @@ 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 the
// [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
// section above for more details.
// ESXi cluster where target VM is created. See
// [Working with Clusters](#working-with-clusters).
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
// [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
// section above for more details.
// `Specifying 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.
-95
View File
@@ -1,95 +0,0 @@
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
View File
@@ -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-processors other than these.
-except=foo,bar,baz Run all builds and post-procesors 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 or HCL2 file containing user variables.
-var-file=path JSON file containing user variables.
`
return strings.TrimSpace(helpText)

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