Compare commits

..

1 Commits

Author SHA1 Message Date
Wilken Rivera 44eda339d3 docs/powershell: Add struct-markdown to provisioner config 2020-08-24 16:27:29 -04:00
684 changed files with 33226 additions and 128215 deletions
+2 -3
View File
@@ -13,6 +13,5 @@ coverage:
project: off
patch: off
ignore: # ignore hcl2spec generated code for coverage and mocks
- "**/*.hcl2spec.go"
- "**/*_mock.go"
ignore: # ignore hcl2spec generated code for coverage
- "**/*.hcl2spec.go"
+1 -113
View File
@@ -1,97 +1,4 @@
## 1.6.4 (Upcoming)
## 1.6.3 (September 25, 2020)
### IMPROVEMENTS:
* builder/amazon: Add `pause_before_ssm` option to pause for some time before
establishing a Session Manager session; defaults to 10s. [GH-9988]
* builder/amazon: Implement assume_role option that matches Terraform behavior.
[GH-9981]
* builder/azure: Support publishing to a Shared Image Gallery with a different
subscription id [GH-9875]
* builder/openstack: Add `external_source_image_url` and
`external_source_image_format` to support building images from external
source URLs. [GH-9992]
* builder/openstack: Include API requests and responses as part of the debug
log output. [GH-9972]
* builder/oracle-oci: Add `create_vnic_details` option for launch details.
[GH-9856]
* builder/oracle-oci: Allow freeform and defined tags to be added to an instance.
[GH-9802]
* 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_directroy and VM.
[GH-9874]
* builder/proxmox: Allow the mounting of multiple ISOs via the `cd_drive`
option. [GH-9653]
* builder/proxmox: Fix boot command special keys. [GH-9885]
* builder/qemu: Add `qemu_img_args` option to set special cli flags for calls
to qemu-img [GH-9956]
* builder/qemu: Add `skip_resize_disk` option to skip the resizing of QCOW2
images. [GH-9896] [GH-9860]
* builder/qemu: Skip qemu-img convert on MacOS to prevent the creation of
corrupt images [QEMU
#1776920](https://bugs.launchpad.net/qemu/+bug/1776920) [GH-9949]
* builder/scaleway: Change default boottype to local. [GH-9853]
* builder/scaleway: Update scaleway to use non-deprecated sdk. [GH-9902]
* builder/vmware: Add `vnc_over_websocket` to allow the sending of a
`boot_command` to hosts running ESXi 6.7 and above. [GH-9938]
* builder/vmware: Allow user to set vmware tools source path. [GH-9983]
* builder/vsphere-clone: Add ability to set `mac_address` [GH-9930]
* builder/vsphere-clone: Add floppy_files, cd_files, and iso_paths options.
[GH-9963]
* builder/vsphere-iso: Add NVMe controller support. [GH-9880]
* builder/vsphere: Look for a default resource pool when root resource pool is
not found. [GH-9809]
* core: Add support for running cygwin/msys2 based cd/iso creation tool
[GH-9954]
* core: New `cd_files` option to mount iso for modern OSes which don't support
floppies. [GH-9796] [GH-9919] [GH-9928] [GH-9932] [GH-9941]
* HCL2: When the type of a variable is not known evaluate setting as a literal
string instead of a variable name. [GH-9863]
* post-processor/vagrant: Support the use of template variables within
Vagrantfile templates. [GH-9923]
* post-processor/yandex-import: Allow custom API endpoint. [GH-9850]
* provisioner/ansible: Add support for Ansible Galaxy Collections. [GH-9903]
### BUG FIXES:
* builder/amazon-ebs: Fix issue where retrying on invalid IAM instance profile
error was creating multiple spot instances. [GH-9946]
* builder/amazon-ebssurrogate: Fix issue where builder defaults to AWS managed
key even when custom `kms_key_id` is set. [GH-9959]
* builder/amazon: Update ssm_driver log polling logic to prevent infinite loops
when SSM driver is terminated outside of Packer. [GH-9991]
* builder/azure: Fix crash when using HCL2 configs. [GH-9984] [GH-9985]
* builder/qemu: Fix hardcoded lowerbound causing negative ports [GH-9905]
* builder/qemu: Skip compaction when backing file is used. [GH-9918]
* builder/scaleway: Add pre validate step to prevent the creation of multiple
images with the same name. [GH-9840]
* builder/vmware-iso: Prevent the use of reserved SCSI ID 0:7 when attaching
multiple disks. [GH-9940]
* builder/vsphere: Fix overly strict iso_path validation regex. [GH-9855]
* command/console: Prevent failure when there are unknown vars. [GH-9864]
* command/inspect: Allow unset variables in HCL2 and JSON. [GH-9832]
* core: Prevent the UI progressbar from hanging and crashing when there is no
TTY available. [GH-9974]
* core: Use $APPDATA over $HOME on Windows hosts when determining homedir.
[GH-9830]
* post-processor/digitalocean-import: Fix crash caused by empty artifact.Files
slice. [GH-9857]
* post-processor/yandex-export: Check for error after runner completes.
[GH-9925]
* post-processor/yandex-export: Set metadata key to expected value on error.
[GH-9849]
* post-processor/yandex-import: Fix S3 URL construct process. [GH-9931]
## 1.6.2 (August 28, 2020)
### FEATURES:
* **New command** `hcl2_upgrade` is a JSON to HCL2 transpiler that allows users
to transform an existing JSON configuration template into its HCL2 template
equivalent. Please see [hcl2_upgrade command
docs](https://packer.io/docs/commands/hcl2_upgrade) for more details.
[GH-9659]
## 1.6.2 (Upcoming)
### IMPROVEMENTS:
* builder/amazon: Add all of the custom AWS template engines to `build`
@@ -101,22 +8,14 @@
* builder/azure: Add FreeBSD support to azure/chroot builder. [GH-9697]
* builder/vmware-esx: Add `network_name` option to vmware so that users can set
a network without using vmx data. [GH-9718]
* builder/vmware-vmx: Add additional disk configuration option. Previously
only implemented for vmware-iso builder [GH-9815]
* builder/vmware: Add a `remote_output_directory option` so users can tell
Packer where on a datastore to create a vm. [GH-9784]
* builder/vmware: Add option to export to ovf or ova from a local vmware build
[GH-9825]
* builder/vmware: Add progress tracker to vmware-esx5 iso upload. [GH-9779]
* builder/vsphere-iso: Add support for building on a single ESXi host
[GH-9793]
* builder/vsphere: Add new `directory_permission` config export option.
[GH-9704]
* builder/vsphere: Add option to import OVF templates to the Content Library
[GH-9755]
* builder/vsphere: Add step and options to customize cloned VMs. [GH-9665]
* builder/vsphere: Update `iso_paths` to support reading ISOs from Content
Library paths [GH-9801]
* core/hcl: Add provisioner "override" option to HCL2 templates. [GH-9764]
* core/hcl: Add vault integration as an HCL2 function function. [GH-9746]
* core: Add colored prefix to progress bar so it's clearer what build each
@@ -128,8 +27,6 @@
[GH-9773]
* post-processor/vsphere: Improve UI to catch bad credentials and print errors.
[GH-9649]
* provisioner/ansible-remote: Add `ansible_ssh_extra_args` so users can specify
extra arguments to pass to ssh [GH-9821]
* provisioner/file: Clean up, bugfix, and document previously-hidden `sources`
option. [GH-9725] [GH-9735]
* provisioner/salt-masterless: Add option to option to download community
@@ -142,11 +39,6 @@
binaries. [GH-9706]
* builder/amazon-ebssurrogate: Make skip_save_build_region option work in the
ebssurrogate builder, not just the ebs builder. [GH-9666]
* builder/amazon: Add retry logic to the spot instance creation step to handle
"Invalid IAM Instance Profile name" errors [GH-9810]
* builder/amazon: Update the `aws_secretsmanager` function to read from the AWS
credentials file for obtaining default region information; fixes the
'MissingRegion' error when AWS_REGION is not set [GH-9781]
* builder/file: Make sure that UploadDir receives the interpolated destination.
[GH-9698]
* builder/googlecompute: Fix bug where startup script hang would cause export
@@ -169,10 +61,6 @@
interpolation. [GH-9673]
* post-processor/vsphere-template: Fix ReregisterVM to default to true instead
of false. [GH-9736]
* post-processor/yandex-export: Fix issue when validating region_name [GH-9814]
* provisioner/inspec: Fix the 'Unsupported argument; An argument named
"command"' error when using the inspec provisioner in an HCL2 configuration
[GH-9800]
## 1.6.1 (July 30, 2020)
+2 -2
View File
@@ -49,8 +49,8 @@
/builder/proxmox/ @carlpett
/website/pages/docs/builders/proxmox* @carlpett
/builder/scaleway/ @scaleway/devtools
/website/pages/docs/builders/scaleway* @scaleway/devtools
/builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
/website/pages/docs/builders/scaleway* @sieben @mvaude @jqueuniet @fflorens @brmzkw
/builder/hcloud/ @LKaemmerling
/website/pages/docs/builders/hcloud* @LKaemmerling
+1 -1
View File
@@ -25,7 +25,7 @@ export GOLDFLAGS
.PHONY: bin checkversion ci ci-lint default install-build-deps install-gen-deps fmt fmt-docs fmt-examples generate install-lint-deps lint \
releasebin test testacc testrace
default: install-build-deps install-gen-deps generate dev
default: install-build-deps install-gen-deps generate bin
ci: testrace ## Test in continuous integration
+1
View File
@@ -422,6 +422,7 @@ func TestBuilderAcc_ECSImageSharing(t *testing.T) {
})
}
// share with catsby
const testBuilderAccSharing = `
{
"builders": [{
@@ -41,9 +41,7 @@ type FlatConfig struct {
SnapshotUsers []string `mapstructure:"snapshot_users" required:"false" cty:"snapshot_users" hcl:"snapshot_users"`
SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false" cty:"snapshot_groups" hcl:"snapshot_groups"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
AssumeRole *common.FlatAssumeRoleConfig `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"`
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2" hcl:"custom_endpoint_ec2"`
CredsFilename *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages" hcl:"decode_authorization_messages"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
MaxRetries *int `mapstructure:"max_retries" required:"false" cty:"max_retries" hcl:"max_retries"`
@@ -119,9 +117,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"snapshot_users": &hcldec.AttrSpec{Name: "snapshot_users", Type: cty.List(cty.String), Required: false},
"snapshot_groups": &hcldec.AttrSpec{Name: "snapshot_groups", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
+10 -256
View File
@@ -1,5 +1,5 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type VaultAWSEngineOptions,AssumeRoleConfig
//go:generate mapstructure-to-hcl2 -type VaultAWSEngineOptions
package common
@@ -9,72 +9,17 @@ 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/credentials"
"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"
"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
// role when executing Packer.
//
// Usage example:
//
// HCL config example:
//
// ```HCL
// source "example" "amazon-ebs"{
// assume_role {
// role_arn = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME"
// session_name = "SESSION_NAME"
// external_id = "EXTERNAL_ID"
// }
// }
// ```
//
// JSON config example:
//
// ```json
// builder{
// "type": "amazon-ebs",
// "assume_role": {
// "role_arn" : "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME",
// "session_name": "SESSION_NAME",
// "external_id" : "EXTERNAL_ID"
// }
// }
// ```
type AssumeRoleConfig struct {
// Amazon Resource Name (ARN) of the IAM Role to assume.
AssumeRoleARN string `mapstructure:"role_arn" required:"false"`
// Number of seconds to restrict the assume role session duration.
AssumeRoleDurationSeconds int `mapstructure:"duration_seconds" required:"false"`
// The external ID to use when assuming the role. If omitted, no external
// ID is passed to the AssumeRole call.
AssumeRoleExternalID string `mapstructure:"external_id" required:"false"`
// IAM Policy JSON describing further restricting permissions for the IAM
// Role being assumed.
AssumeRolePolicy string `mapstructure:"policy" required:"false"`
// Set of Amazon Resource Names (ARNs) of IAM Policies describing further
// restricting permissions for the IAM Role being
AssumeRolePolicyARNs []string `mapstructure:"policy_arns" required:"false"`
// Session name to use when assuming the role.
AssumeRoleSessionName string `mapstructure:"session_name" required:"false"`
// Map of assume role session tags.
AssumeRoleTags map[string]string `mapstructure:"tags" required:"false"`
// Set of assume role session tag keys to pass to any subsequent sessions.
AssumeRoleTransitiveTagKeys []string `mapstructure:"transitive_tag_keys" required:"false"`
}
type VaultAWSEngineOptions struct {
Name string `mapstructure:"name"`
RoleARN string `mapstructure:"role_arn"`
@@ -103,17 +48,10 @@ type AccessConfig struct {
// is not required if you are using `use_vault_aws_engine` for
// authentication instead.
AccessKey string `mapstructure:"access_key" required:"true"`
// If provided with a role ARN, Packer will attempt to assume this role
// using the supplied credentials. See
// [AssumeRoleConfig](#assume-role-configuration) below for more
// details on all of the options available, and for a usage example.
AssumeRole AssumeRoleConfig `mapstructure:"assume_role" required:"false"`
// This option is useful if you use a cloud
// provider whose API is compatible with aws EC2. Specify another endpoint
// like this https://ec2.custom.endpoint.com.
CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2" required:"false"`
// Path to a credentials file to load credentials from
CredsFilename string `mapstructure:"shared_credentials_file" required:"false"`
// Enable automatic decoding of any encoded authorization (error) messages
// using the `sts:DecodeAuthorizationMessage` API. Note: requires that the
// effective user/role have permissions to `sts:DecodeAuthorizationMessage`
@@ -214,13 +152,16 @@ func (c *AccessConfig) Session() (*session.Session, error) {
return c.session, nil
}
// Create new AWS config
config := aws.NewConfig().WithCredentialsChainVerboseErrors(true)
if c.MaxRetries > 0 {
config = config.WithMaxRetries(c.MaxRetries)
}
// Set AWS config defaults.
staticCreds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
if _, err := staticCreds.Get(); err != credentials.ErrStaticCredentialsEmpty {
config.WithCredentials(staticCreds)
}
if c.RawRegion != "" {
config = config.WithRegion(c.RawRegion)
}
@@ -238,16 +179,6 @@ func (c *AccessConfig) Session() (*session.Session, error) {
}
transport.Proxy = http.ProxyFromEnvironment
// Figure out which possible credential providers are valid; test that we
// can get credentials via the selected providers, and set the providers in
// the config.
creds, err := c.GetCredentials(config)
if err != nil {
return nil, err
}
config.WithCredentials(creds)
// Create session options based on our AWS config
opts := session.Options{
SharedConfigState: session.SharedConfigEnable,
Config: *config,
@@ -273,7 +204,9 @@ func (c *AccessConfig) Session() (*session.Session, error) {
cp, err := c.session.Config.Credentials.Get()
if IsAWSErr(err, "NoCredentialProviders", "") {
return nil, c.NewNoValidCredentialSourcesError(err)
return nil, fmt.Errorf("No valid credential sources found for AWS Builder. " +
"Please see https://www.packer.io/docs/builders/amazon#specifying-amazon-credentials " +
"for more information on providing credentials for the AWS Builder.")
}
if err != nil {
@@ -304,178 +237,6 @@ func (c *AccessConfig) IsChinaCloud() bool {
return strings.HasPrefix(c.SessionRegion(), "cn-")
}
// GetCredentials gets credentials from the environment, shared credentials,
// the session (which may include a credential process), or ECS/EC2 metadata
// 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) {
sharedCredentialsFilename, err := homedir.Expand(c.CredsFilename)
if err != nil {
return nil, fmt.Errorf("error expanding shared credentials filename: %v", err)
}
// 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 {
// const EnvVaultAddress = "VAULT_ADDR"
// const EnvVaultToken = "VAULT_TOKEN"
@@ -545,13 +306,6 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
return errs
}
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: %v", err)
}
func (c *AccessConfig) NewEC2Connection() (ec2iface.EC2API, error) {
if c.getEC2Connection != nil {
return c.getEC2Connection(), nil
@@ -1,4 +1,4 @@
// Code generated by "mapstructure-to-hcl2 -type VaultAWSEngineOptions,AssumeRoleConfig"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type VaultAWSEngineOptions"; DO NOT EDIT.
package common
import (
@@ -6,43 +6,6 @@ import (
"github.com/zclconf/go-cty/cty"
)
// FlatAssumeRoleConfig is an auto-generated flat version of AssumeRoleConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatAssumeRoleConfig struct {
AssumeRoleARN *string `mapstructure:"role_arn" required:"false" cty:"role_arn" hcl:"role_arn"`
AssumeRoleDurationSeconds *int `mapstructure:"duration_seconds" required:"false" cty:"duration_seconds" hcl:"duration_seconds"`
AssumeRoleExternalID *string `mapstructure:"external_id" required:"false" cty:"external_id" hcl:"external_id"`
AssumeRolePolicy *string `mapstructure:"policy" required:"false" cty:"policy" hcl:"policy"`
AssumeRolePolicyARNs []string `mapstructure:"policy_arns" required:"false" cty:"policy_arns" hcl:"policy_arns"`
AssumeRoleSessionName *string `mapstructure:"session_name" required:"false" cty:"session_name" hcl:"session_name"`
AssumeRoleTags map[string]string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"`
AssumeRoleTransitiveTagKeys []string `mapstructure:"transitive_tag_keys" required:"false" cty:"transitive_tag_keys" hcl:"transitive_tag_keys"`
}
// FlatMapstructure returns a new FlatAssumeRoleConfig.
// FlatAssumeRoleConfig is an auto-generated flat version of AssumeRoleConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*AssumeRoleConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatAssumeRoleConfig)
}
// HCL2Spec returns the hcl spec of a AssumeRoleConfig.
// This spec is used by HCL to read the fields of AssumeRoleConfig.
// The decoded values from this spec will then be applied to a FlatAssumeRoleConfig.
func (*FlatAssumeRoleConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"role_arn": &hcldec.AttrSpec{Name: "role_arn", Type: cty.String, Required: false},
"duration_seconds": &hcldec.AttrSpec{Name: "duration_seconds", Type: cty.Number, Required: false},
"external_id": &hcldec.AttrSpec{Name: "external_id", Type: cty.String, Required: false},
"policy": &hcldec.AttrSpec{Name: "policy", Type: cty.String, Required: false},
"policy_arns": &hcldec.AttrSpec{Name: "policy_arns", Type: cty.List(cty.String), Required: false},
"session_name": &hcldec.AttrSpec{Name: "session_name", Type: cty.String, Required: false},
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.Map(cty.String), Required: false},
"transitive_tag_keys": &hcldec.AttrSpec{Name: "transitive_tag_keys", Type: cty.List(cty.String), Required: false},
}
return s
}
// FlatVaultAWSEngineOptions is an auto-generated flat version of VaultAWSEngineOptions.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatVaultAWSEngineOptions struct {
+5 -16
View File
@@ -53,14 +53,14 @@ type VpcFilterOptions struct {
}
type Statement struct {
Effect string `mapstructure:"Effect" required:"false"`
Action []string `mapstructure:"Action" required:"false"`
Resource []string `mapstructure:"Resource" required:"false"`
Effect string
Action []string
Resource []string
}
type PolicyDocument struct {
Version string `mapstructure:"Version" required:"false"`
Statement []Statement `mapstructure:"Statement" required:"false"`
Version string
Statement []Statement
}
type SecurityGroupFilterOptions struct {
@@ -473,13 +473,6 @@ type RunConfig struct {
// terminating the tunnel it will automatically terminate itself after 20 minutes of inactivity.
SSHInterface string `mapstructure:"ssh_interface"`
// The time to wait before establishing the Session Manager session.
// The value of this should be a duration. Examples are
// `5s` and `1m30s` which will cause Packer to wait five seconds and one
// minute 30 seconds, respectively. If no set, defaults to 10 seconds.
// This option is useful when the remote port takes longer to become available.
PauseBeforeSSM time.Duration `mapstructure:"pause_before_ssm"`
// Which port to connect the local end of the session tunnel to. If
// left blank, Packer will choose a port for you from available ports.
// This option is only used when `ssh_interface` is set `session_manager`.
@@ -542,10 +535,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
msg := fmt.Errorf(`no iam_instance_profile defined; session_manager connectivity requires a valid instance profile with AmazonSSMManagedInstanceCore permissions. Alternatively a temporary_iam_instance_profile_policy_document can be used.`)
errs = append(errs, msg)
}
if c.PauseBeforeSSM == 0 {
c.PauseBeforeSSM = 10 * time.Second
}
}
if c.Comm.SSHKeyPairName != "" {
+10 -10
View File
@@ -39,8 +39,8 @@ func (*FlatAmiFilterOptions) HCL2Spec() map[string]hcldec.Spec {
// FlatPolicyDocument is an auto-generated flat version of PolicyDocument.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatPolicyDocument struct {
Version *string `mapstructure:"Version" required:"false" cty:"Version" hcl:"Version"`
Statement []FlatStatement `mapstructure:"Statement" required:"false" cty:"Statement" hcl:"Statement"`
Version *string `cty:"version" hcl:"version"`
Statement []FlatStatement `cty:"statement" hcl:"statement"`
}
// FlatMapstructure returns a new FlatPolicyDocument.
@@ -55,8 +55,8 @@ func (*PolicyDocument) FlatMapstructure() interface{ HCL2Spec() map[string]hclde
// The decoded values from this spec will then be applied to a FlatPolicyDocument.
func (*FlatPolicyDocument) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"Version": &hcldec.AttrSpec{Name: "Version", Type: cty.String, Required: false},
"Statement": &hcldec.BlockListSpec{TypeName: "Statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())},
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
"statement": &hcldec.BlockListSpec{TypeName: "statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())},
}
return s
}
@@ -89,9 +89,9 @@ func (*FlatSecurityGroupFilterOptions) HCL2Spec() map[string]hcldec.Spec {
// FlatStatement is an auto-generated flat version of Statement.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatStatement struct {
Effect *string `mapstructure:"Effect" required:"false" cty:"Effect" hcl:"Effect"`
Action []string `mapstructure:"Action" required:"false" cty:"Action" hcl:"Action"`
Resource []string `mapstructure:"Resource" required:"false" cty:"Resource" hcl:"Resource"`
Effect *string `cty:"effect" hcl:"effect"`
Action []string `cty:"action" hcl:"action"`
Resource []string `cty:"resource" hcl:"resource"`
}
// FlatMapstructure returns a new FlatStatement.
@@ -106,9 +106,9 @@ func (*Statement) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spe
// The decoded values from this spec will then be applied to a FlatStatement.
func (*FlatStatement) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"Effect": &hcldec.AttrSpec{Name: "Effect", Type: cty.String, Required: false},
"Action": &hcldec.AttrSpec{Name: "Action", Type: cty.List(cty.String), Required: false},
"Resource": &hcldec.AttrSpec{Name: "Resource", Type: cty.List(cty.String), Required: false},
"effect": &hcldec.AttrSpec{Name: "effect", Type: cty.String, Required: false},
"action": &hcldec.AttrSpec{Name: "action", Type: cty.List(cty.String), Required: false},
"resource": &hcldec.AttrSpec{Name: "resource", Type: cty.List(cty.String), Required: false},
}
return s
}
+3 -18
View File
@@ -46,7 +46,7 @@ func NewSSMDriver(config SSMDriverConfig) *SSMDriver {
// not wish to manage the session manually calling StopSession on a instance of this driver will terminate the active session
// created from calling StartSession.
func (d *SSMDriver) StartSession(ctx context.Context, input ssm.StartSessionInput) (*ssm.StartSessionOutput, error) {
log.Printf("Starting PortForwarding session to instance %q", aws.StringValue(input.Target))
log.Printf("Starting PortForwarding session to instance %q with following params %v", aws.StringValue(input.Target), input.Parameters)
var output *ssm.StartSessionOutput
err := retry.Config{
@@ -110,30 +110,15 @@ func (d *SSMDriver) openTunnelForSession(ctx context.Context) error {
select {
case <-ctx.Done():
return
case output, ok := <-stderrCh:
if !ok {
stderrCh = nil
break
}
case output := <-stderrCh:
if output != "" {
log.Printf("[ERROR] %s: %s", prefix, output)
}
case output, ok := <-stdoutCh:
if !ok {
stdoutCh = nil
break
}
case output := <-stdoutCh:
if output != "" {
log.Printf("[DEBUG] %s: %s", prefix, output)
}
}
if stdoutCh == nil && stderrCh == nil {
log.Printf("[DEBUG] %s: %s", prefix, "active session has been terminated; stopping all log polling processes.")
return
}
}
}(ctx, sessionManagerPluginName)
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"strconv"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
@@ -22,7 +21,6 @@ type StepCreateSSMTunnel struct {
RemotePortNumber int
SSMAgentEnabled bool
instanceId string
PauseBeforeSSM time.Duration
driver *SSMDriver
}
@@ -34,17 +32,6 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
return multistep.ActionContinue
}
// Wait for the remote port to become available
if s.PauseBeforeSSM > 0 {
ui.Say(fmt.Sprintf("Waiting %s for establishing the SSM session...", s.PauseBeforeSSM))
select {
case <-time.After(s.PauseBeforeSSM):
break
case <-ctx.Done():
return multistep.ActionHalt
}
}
// Configure local port number
if err := s.ConfigureLocalHostPort(ctx); err != nil {
err := fmt.Errorf("error finding an available port to initiate a session tunnel: %s", err)
+19 -37
View File
@@ -6,7 +6,6 @@ import (
"fmt"
"io/ioutil"
"log"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
@@ -279,53 +278,36 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
Type: aws.String("instant"),
}
var createOutput *ec2.CreateFleetOutput
err = retry.Config{
Tries: 11,
ShouldRetry: func(err error) bool {
if strings.Contains(err.Error(), "Invalid IAM Instance Profile name") {
// eventual consistency of the profile. PutRolePolicy &
// AddRoleToInstanceProfile are eventually consistent and once
// we can wait on those operations, this can be removed.
return true
}
return false
},
RetryDelay: (&retry.Backoff{InitialBackoff: 500 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
createOutput, err = ec2conn.CreateFleet(createFleetInput)
if err == nil && createOutput.Errors != nil {
err = fmt.Errorf("errors: %v", createOutput.Errors)
}
// We can end up with errors because one of the allowed availability
// zones doesn't have one of the allowed instance types; as long as
// an instance is launched, these errors aren't important.
if len(createOutput.Instances) > 0 {
if err != nil {
log.Printf("create request failed for some instances %v", err.Error())
}
return nil
}
if err != nil {
log.Printf("create request failed %v", err)
}
return err
})
// Create the request for the spot instance.
req, createOutput := ec2conn.CreateFleetRequest(createFleetInput)
ui.Message(fmt.Sprintf("Sending spot request (%s)...", req.RequestID))
// Actually send the spot connection request.
err = req.Send()
if err != nil {
if createOutput.FleetId != nil {
err = fmt.Errorf("Error waiting for fleet request (%s): %s", *createOutput.FleetId, err)
} else {
err = fmt.Errorf("Error waiting for fleet request: %s", err)
}
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(createOutput.Instances) == 0 {
// We can end up with errors because one of the allowed availability
// zones doesn't have one of the allowed instance types; as long as
// an instance is launched, these errors aren't important.
if len(createOutput.Errors) > 0 {
errString := fmt.Sprintf("Error waiting for fleet request (%s) to become ready:", *createOutput.FleetId)
for _, outErr := range createOutput.Errors {
errString = errString + aws.StringValue(outErr.ErrorMessage)
}
err = fmt.Errorf(errString)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instanceId = *createOutput.Instances[0].InstanceIds[0]
-1
View File
@@ -269,7 +269,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepCreateSSMTunnel{
AWSSession: session,
Region: *ec2conn.Config.Region,
PauseBeforeSSM: b.config.PauseBeforeSSM,
LocalPortNumber: b.config.SessionManagerPort,
RemotePortNumber: b.config.Comm.Port(),
SSMAgentEnabled: b.config.SSMAgentEnabled(),
-6
View File
@@ -19,9 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
AssumeRole *common.FlatAssumeRoleConfig `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"`
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2" hcl:"custom_endpoint_ec2"`
CredsFilename *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages" hcl:"decode_authorization_messages"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
MaxRetries *int `mapstructure:"max_retries" required:"false" cty:"max_retries" hcl:"max_retries"`
@@ -134,7 +132,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"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" required:"false" cty:"ami_block_device_mappings" hcl:"ami_block_device_mappings"`
LaunchMappings []common.FlatBlockDevice `mapstructure:"launch_block_device_mappings" required:"false" cty:"launch_block_device_mappings" hcl:"launch_block_device_mappings"`
@@ -163,9 +160,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@@ -278,7 +273,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},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
"launch_block_device_mappings": &hcldec.BlockListSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
+5 -15
View File
@@ -6,7 +6,6 @@ package ebs
import (
"fmt"
"os"
"testing"
"github.com/aws/aws-sdk-go/aws"
@@ -105,10 +104,10 @@ func checkSnapshotsDeleted(snapshotIds []*string) builderT.TestCheckFunc {
func TestBuilderAcc_amiSharing(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccSharingPreCheck(t) },
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: buildSharingConfig(os.Getenv("TESTACC_AWS_ACCOUNT_ID")),
Check: checkAMISharing(2, os.Getenv("TESTACC_AWS_ACCOUNT_ID"), "all"),
Template: testBuilderAccSharing,
Check: checkAMISharing(2, "932021504756", "all"),
})
}
@@ -249,12 +248,6 @@ func checkBootEncrypted() builderT.TestCheckFunc {
func testAccPreCheck(t *testing.T) {
}
func testAccSharingPreCheck(t *testing.T) {
if v := os.Getenv("TESTACC_AWS_ACCOUNT_ID"); v == "" {
t.Fatal(fmt.Sprintf("TESTACC_AWS_ACCOUNT_ID must be set for acceptance tests"))
}
}
func testEC2Conn() (*ec2.EC2, error) {
access := &common.AccessConfig{RawRegion: "us-east-1"}
session, err := access.Session()
@@ -321,6 +314,7 @@ const testBuilderAccForceDeleteSnapshot = `
}
`
// share with catsby
const testBuilderAccSharing = `
{
"builders": [{
@@ -330,7 +324,7 @@ const testBuilderAccSharing = `
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"ami_name": "packer-test {{timestamp}}",
"ami_users":["%s"],
"ami_users":["932021504756"],
"ami_groups":["all"]
}]
}
@@ -357,7 +351,3 @@ func buildForceDeregisterConfig(val, name string) string {
func buildForceDeleteSnapshotConfig(val, name string) string {
return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name)
}
func buildSharingConfig(val string) string {
return fmt.Sprintf(testBuilderAccSharing, val)
}
@@ -3,6 +3,9 @@
package ebssurrogate
import (
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/template/interpolate"
@@ -38,6 +41,50 @@ func (bds BlockDevices) BuildEC2BlockDeviceMappings() []*ec2.BlockDeviceMapping
return blockDevices
}
func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapping {
mapping := &ec2.BlockDeviceMapping{
DeviceName: aws.String(blockDevice.DeviceName),
}
if blockDevice.NoDevice {
mapping.NoDevice = aws.String("")
return mapping
} else if blockDevice.VirtualName != "" {
if strings.HasPrefix(blockDevice.VirtualName, "ephemeral") {
mapping.VirtualName = aws.String(blockDevice.VirtualName)
}
return mapping
}
ebsBlockDevice := &ec2.EbsBlockDevice{
DeleteOnTermination: aws.Bool(blockDevice.DeleteOnTermination),
}
if blockDevice.VolumeType != "" {
ebsBlockDevice.VolumeType = aws.String(blockDevice.VolumeType)
}
if blockDevice.VolumeSize > 0 {
ebsBlockDevice.VolumeSize = aws.Int64(blockDevice.VolumeSize)
}
// IOPS is only valid for io1 type
if blockDevice.VolumeType == "io1" {
ebsBlockDevice.Iops = aws.Int64(blockDevice.IOPS)
}
// You cannot specify Encrypted if you specify a Snapshot ID
if blockDevice.SnapshotId != "" {
ebsBlockDevice.SnapshotId = aws.String(blockDevice.SnapshotId)
}
ebsBlockDevice.Encrypted = blockDevice.Encrypted.ToBoolPointer()
mapping.Ebs = ebsBlockDevice
return mapping
}
func (bds BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
for _, block := range bds {
if err := block.Prepare(ctx); err != nil {
-1
View File
@@ -293,7 +293,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepCreateSSMTunnel{
AWSSession: session,
Region: *ec2conn.Config.Region,
PauseBeforeSSM: b.config.PauseBeforeSSM,
LocalPortNumber: b.config.SessionManagerPort,
RemotePortNumber: b.config.Comm.Port(),
SSMAgentEnabled: b.config.SSMAgentEnabled(),
@@ -62,9 +62,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
AssumeRole *common.FlatAssumeRoleConfig `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"`
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2" hcl:"custom_endpoint_ec2"`
CredsFilename *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages" hcl:"decode_authorization_messages"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
MaxRetries *int `mapstructure:"max_retries" required:"false" cty:"max_retries" hcl:"max_retries"`
@@ -156,7 +154,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"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name" hcl:"ami_name"`
AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description" hcl:"ami_description"`
@@ -207,9 +204,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@@ -301,7 +296,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},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},
-1
View File
@@ -263,7 +263,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepCreateSSMTunnel{
AWSSession: session,
Region: *ec2conn.Config.Region,
PauseBeforeSSM: b.config.PauseBeforeSSM,
LocalPortNumber: b.config.SessionManagerPort,
RemotePortNumber: b.config.Comm.Port(),
SSMAgentEnabled: b.config.SSMAgentEnabled(),
@@ -64,9 +64,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
AssumeRole *common.FlatAssumeRoleConfig `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"`
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2" hcl:"custom_endpoint_ec2"`
CredsFilename *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages" hcl:"decode_authorization_messages"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
MaxRetries *int `mapstructure:"max_retries" required:"false" cty:"max_retries" hcl:"max_retries"`
@@ -158,7 +156,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"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMIENASupport *bool `mapstructure:"ena_support" required:"false" cty:"ena_support" hcl:"ena_support"`
AMISriovNetSupport *bool `mapstructure:"sriov_support" required:"false" cty:"sriov_support" hcl:"sriov_support"`
@@ -187,9 +184,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@@ -281,7 +276,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},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"ena_support": &hcldec.AttrSpec{Name: "ena_support", Type: cty.Bool, Required: false},
"sriov_support": &hcldec.AttrSpec{Name: "sriov_support", Type: cty.Bool, Required: false},
-1
View File
@@ -343,7 +343,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepCreateSSMTunnel{
AWSSession: session,
Region: *ec2conn.Config.Region,
PauseBeforeSSM: b.config.PauseBeforeSSM,
LocalPortNumber: b.config.SessionManagerPort,
RemotePortNumber: b.config.Comm.Port(),
SSMAgentEnabled: b.config.SSMAgentEnabled(),
@@ -19,9 +19,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
AssumeRole *common.FlatAssumeRoleConfig `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"`
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2" hcl:"custom_endpoint_ec2"`
CredsFilename *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"`
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages" hcl:"decode_authorization_messages"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
MaxRetries *int `mapstructure:"max_retries" required:"false" cty:"max_retries" hcl:"max_retries"`
@@ -134,7 +132,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"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" required:"false" cty:"ami_block_device_mappings" hcl:"ami_block_device_mappings"`
LaunchMappings []common.FlatBlockDevice `mapstructure:"launch_block_device_mappings" required:"false" cty:"launch_block_device_mappings" hcl:"launch_block_device_mappings"`
@@ -169,9 +166,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@@ -284,7 +279,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},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
"launch_block_device_mappings": &hcldec.BlockListSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
+18 -20
View File
@@ -128,8 +128,8 @@ func byConcatDecorators(decorators ...autorest.RespondDecorator) autorest.Respon
}
}
func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storageAccountName string,
cloud *azure.Environment, sharedGalleryTimeout time.Duration, pollingDuration time.Duration,
func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string,
cloud *azure.Environment, SharedGalleryTimeout time.Duration, PollingDuration time.Duration,
servicePrincipalToken, servicePrincipalTokenVault *adal.ServicePrincipalToken) (*AzureClient, error) {
var azureClient = &AzureClient{}
@@ -141,56 +141,56 @@ func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storag
azureClient.DeploymentsClient.RequestInspector = withInspection(maxlen)
azureClient.DeploymentsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.DeploymentsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DeploymentsClient.UserAgent)
azureClient.DeploymentsClient.Client.PollingDuration = pollingDuration
azureClient.DeploymentsClient.Client.PollingDuration = PollingDuration
azureClient.DeploymentOperationsClient = resources.NewDeploymentOperationsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.DeploymentOperationsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.DeploymentOperationsClient.RequestInspector = withInspection(maxlen)
azureClient.DeploymentOperationsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.DeploymentOperationsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DeploymentOperationsClient.UserAgent)
azureClient.DeploymentOperationsClient.Client.PollingDuration = pollingDuration
azureClient.DeploymentOperationsClient.Client.PollingDuration = PollingDuration
azureClient.DisksClient = compute.NewDisksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.DisksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.DisksClient.RequestInspector = withInspection(maxlen)
azureClient.DisksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.DisksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DisksClient.UserAgent)
azureClient.DisksClient.Client.PollingDuration = pollingDuration
azureClient.DisksClient.Client.PollingDuration = PollingDuration
azureClient.GroupsClient = resources.NewGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.GroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.GroupsClient.RequestInspector = withInspection(maxlen)
azureClient.GroupsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.GroupsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GroupsClient.UserAgent)
azureClient.GroupsClient.Client.PollingDuration = pollingDuration
azureClient.GroupsClient.Client.PollingDuration = PollingDuration
azureClient.ImagesClient = compute.NewImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.ImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.ImagesClient.RequestInspector = withInspection(maxlen)
azureClient.ImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.ImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.ImagesClient.UserAgent)
azureClient.ImagesClient.Client.PollingDuration = pollingDuration
azureClient.ImagesClient.Client.PollingDuration = PollingDuration
azureClient.InterfacesClient = network.NewInterfacesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.InterfacesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.InterfacesClient.RequestInspector = withInspection(maxlen)
azureClient.InterfacesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.InterfacesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.InterfacesClient.UserAgent)
azureClient.InterfacesClient.Client.PollingDuration = pollingDuration
azureClient.InterfacesClient.Client.PollingDuration = PollingDuration
azureClient.SubnetsClient = network.NewSubnetsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.SubnetsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.SubnetsClient.RequestInspector = withInspection(maxlen)
azureClient.SubnetsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.SubnetsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SubnetsClient.UserAgent)
azureClient.SubnetsClient.Client.PollingDuration = pollingDuration
azureClient.SubnetsClient.Client.PollingDuration = PollingDuration
azureClient.VirtualNetworksClient = network.NewVirtualNetworksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.VirtualNetworksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.VirtualNetworksClient.RequestInspector = withInspection(maxlen)
azureClient.VirtualNetworksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.VirtualNetworksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualNetworksClient.UserAgent)
azureClient.VirtualNetworksClient.Client.PollingDuration = pollingDuration
azureClient.VirtualNetworksClient.Client.PollingDuration = PollingDuration
azureClient.SecurityGroupsClient = network.NewSecurityGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.SecurityGroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
@@ -203,44 +203,42 @@ func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storag
azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen)
azureClient.PublicIPAddressesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.PublicIPAddressesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.PublicIPAddressesClient.UserAgent)
azureClient.PublicIPAddressesClient.Client.PollingDuration = pollingDuration
azureClient.PublicIPAddressesClient.Client.PollingDuration = PollingDuration
azureClient.VirtualMachinesClient = compute.NewVirtualMachinesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.VirtualMachinesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.VirtualMachinesClient.RequestInspector = withInspection(maxlen)
azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient), errorCapture(azureClient))
azureClient.VirtualMachinesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualMachinesClient.UserAgent)
azureClient.VirtualMachinesClient.Client.PollingDuration = pollingDuration
azureClient.VirtualMachinesClient.Client.PollingDuration = PollingDuration
azureClient.SnapshotsClient = compute.NewSnapshotsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.SnapshotsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.SnapshotsClient.RequestInspector = withInspection(maxlen)
azureClient.SnapshotsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.SnapshotsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SnapshotsClient.UserAgent)
azureClient.SnapshotsClient.Client.PollingDuration = pollingDuration
azureClient.SnapshotsClient.Client.PollingDuration = PollingDuration
azureClient.AccountsClient = armStorage.NewAccountsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.AccountsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.AccountsClient.RequestInspector = withInspection(maxlen)
azureClient.AccountsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.AccountsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.AccountsClient.UserAgent)
azureClient.AccountsClient.Client.PollingDuration = pollingDuration
azureClient.AccountsClient.Client.PollingDuration = PollingDuration
azureClient.GalleryImageVersionsClient = newCompute.NewGalleryImageVersionsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.GalleryImageVersionsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.GalleryImageVersionsClient.RequestInspector = withInspection(maxlen)
azureClient.GalleryImageVersionsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.GalleryImageVersionsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GalleryImageVersionsClient.UserAgent)
azureClient.GalleryImageVersionsClient.Client.PollingDuration = sharedGalleryTimeout
azureClient.GalleryImageVersionsClient.SubscriptionID = sigSubscriptionID
azureClient.GalleryImageVersionsClient.Client.PollingDuration = SharedGalleryTimeout
azureClient.GalleryImagesClient = newCompute.NewGalleryImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.GalleryImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.GalleryImagesClient.RequestInspector = withInspection(maxlen)
azureClient.GalleryImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.GalleryImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GalleryImagesClient.UserAgent)
azureClient.GalleryImagesClient.Client.PollingDuration = pollingDuration
azureClient.GalleryImagesClient.SubscriptionID = sigSubscriptionID
azureClient.GalleryImagesClient.Client.PollingDuration = PollingDuration
keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint)
if err != nil {
@@ -252,7 +250,7 @@ func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storag
azureClient.VaultClient.RequestInspector = withInspection(maxlen)
azureClient.VaultClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.VaultClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VaultClient.UserAgent)
azureClient.VaultClient.Client.PollingDuration = pollingDuration
azureClient.VaultClient.Client.PollingDuration = PollingDuration
// This client is different than the above because it manages the vault
// itself rather than the contents of the vault.
@@ -261,7 +259,7 @@ func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storag
azureClient.VaultClientDelete.RequestInspector = withInspection(maxlen)
azureClient.VaultClientDelete.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.VaultClientDelete.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VaultClientDelete.UserAgent)
azureClient.VaultClientDelete.Client.PollingDuration = pollingDuration
azureClient.VaultClientDelete.Client.PollingDuration = PollingDuration
// If this is a managed disk build, this should be ignored.
if resourceGroupName != "" && storageAccountName != "" {
-1
View File
@@ -84,7 +84,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
ui.Message("Creating Azure Resource Manager (ARM) client ...")
azureClient, err := NewAzureClient(
b.config.ClientConfig.SubscriptionID,
b.config.SharedGalleryDestination.SigDestinationSubscription,
b.config.ResourceGroupName,
b.config.StorageAccount,
b.config.ClientConfig.CloudEnvironment(),
+32 -66
View File
@@ -90,7 +90,6 @@ type SharedImageGallery struct {
}
type SharedImageGalleryDestination struct {
SigDestinationSubscription string `mapstructure:"subscription"`
SigDestinationResourceGroup string `mapstructure:"resource_group"`
SigDestinationGalleryName string `mapstructure:"gallery_name"`
SigDestinationImageName string `mapstructure:"image_name"`
@@ -119,64 +118,31 @@ type Config struct {
// Use a [Shared Gallery
// image](https://azure.microsoft.com/en-us/blog/announcing-the-public-preview-of-shared-image-gallery/)
// as the source for this build. *VHD targets are incompatible with this
// build type* - the target must be a *Managed Image*. When using shared_image_gallery as a source, image_publisher,
// image_offer, image_sku, image_version, and custom_managed_image_name should not be set.
// build type* - the target must be a *Managed Image*.
//
// In JSON
// ```json
// "shared_image_gallery": {
// "subscription": "00000000-0000-0000-0000-00000000000",
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0"
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
// ```
// In HCL2
// ```hcl
// shared_image_gallery {
// subscription = "00000000-0000-0000-0000-00000000000"
// resource_group = "ResourceGroup"
// gallery_name = "GalleryName"
// image_name = "ImageName"
// image_version = "1.0.0"
// }
// managed_image_name = "TargetImageName"
// managed_image_resource_group_name = "TargetResourceGroup"
// ```
// "shared_image_gallery": {
// "subscription": "00000000-0000-0000-0000-00000000000",
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0"
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
SharedGallery SharedImageGallery `mapstructure:"shared_image_gallery" required:"false"`
// The name of the Shared Image Gallery under which the managed image will be published as Shared Gallery Image version.
//
// Following is an example.
//
// In JSON
// ```json
// "shared_image_gallery_destination": {
// "subscription": "00000000-0000-0000-0000-00000000000",
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0",
// "replication_regions": ["regionA", "regionB", "regionC"]
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
// ```
// In HCL2
// ```hcl
// shared_image_gallery_destination {
// subscription = "00000000-0000-0000-0000-00000000000"
// resource_group = "ResourceGroup"
// gallery_name = "GalleryName"
// image_name = "ImageName"
// image_version = "1.0.0"
// replication_regions = ["regionA", "regionB", "regionC"]
// }
// managed_image_name = "TargetImageName"
// managed_image_resource_group_name = "TargetResourceGroup"
// ```
// "shared_image_gallery_destination": {
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0",
// "replication_regions": ["regionA", "regionB", "regionC"]
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
SharedGalleryDestination SharedImageGalleryDestination `mapstructure:"shared_image_gallery_destination"`
// How long to wait for an image to be published to the shared image
// gallery before timing out. If your Packer build is failing on the
@@ -288,7 +254,7 @@ type Config struct {
// Group, VM, NIC, VNET, Public IP, KeyVault, etc. The user can define up
// to 15 tags. Tag names cannot exceed 512 characters, and tag values
// cannot exceed 256 characters.
AzureTags map[string]string `mapstructure:"azure_tags" required:"false"`
AzureTags map[string]*string `mapstructure:"azure_tags" required:"false"`
// Same as [`azure_tags`](#azure_tags) but defined as a singular repeatable block
// containing a `name` and a `value` field. In HCL2 mode the
// [`dynamic_block`](/docs/configuration/from-1.5/expressions#dynamic-blocks)
@@ -513,7 +479,7 @@ func (c *Config) toImageParameters() *compute.Image {
},
},
Location: to.StringPtr(c.Location),
Tags: azcommon.MapToAzureTags(c.AzureTags),
Tags: c.AzureTags,
}
}
@@ -596,7 +562,10 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
// copy singular blocks
c.AzureTag.CopyOn(&c.AzureTags)
for _, kv := range c.AzureTag {
v := kv.Value
c.AzureTags[kv.Name] = &v
}
err = c.ClientConfig.SetDefaultValues()
if err != nil {
@@ -804,8 +773,8 @@ func assertTagProperties(c *Config, errs *packer.MultiError) {
if len(k) > 512 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 512 character limit", k, len(k)))
}
if len(v) > 256 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", v, len(v)))
if len(*v) > 256 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", *v, len(*v)))
}
}
}
@@ -1009,9 +978,6 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
if len(c.SharedGalleryDestination.SigDestinationReplicationRegions) == 0 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A list of replication_regions must be specified for shared_image_gallery_destination"))
}
if c.SharedGalleryDestination.SigDestinationSubscription == "" {
c.SharedGalleryDestination.SigDestinationSubscription = c.ClientConfig.SubscriptionID
}
}
if c.SharedGalleryTimeout == 0 {
// default to a one-hour timeout. In the sdk, the default is 15 m.
@@ -1060,13 +1026,13 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("if either plan_name, plan_product, plan_publisher, or plan_promotion_code are defined then plan_name, plan_product, and plan_publisher must be defined"))
} else {
if c.AzureTags == nil {
c.AzureTags = make(map[string]string)
c.AzureTags = make(map[string]*string)
}
c.AzureTags["PlanInfo"] = c.PlanInfo.PlanName
c.AzureTags["PlanProduct"] = c.PlanInfo.PlanProduct
c.AzureTags["PlanPublisher"] = c.PlanInfo.PlanPublisher
c.AzureTags["PlanPromotionCode"] = c.PlanInfo.PlanPromotionCode
c.AzureTags["PlanInfo"] = &c.PlanInfo.PlanName
c.AzureTags["PlanProduct"] = &c.PlanInfo.PlanProduct
c.AzureTags["PlanPublisher"] = &c.PlanInfo.PlanPublisher
c.AzureTags["PlanPromotionCode"] = &c.PlanInfo.PlanPromotionCode
}
}
+1 -3
View File
@@ -49,7 +49,7 @@ type FlatConfig struct {
ManagedImageOSDiskSnapshotName *string `mapstructure:"managed_image_os_disk_snapshot_name" required:"false" cty:"managed_image_os_disk_snapshot_name" hcl:"managed_image_os_disk_snapshot_name"`
ManagedImageDataDiskSnapshotPrefix *string `mapstructure:"managed_image_data_disk_snapshot_prefix" required:"false" cty:"managed_image_data_disk_snapshot_prefix" hcl:"managed_image_data_disk_snapshot_prefix"`
ManagedImageZoneResilient *bool `mapstructure:"managed_image_zone_resilient" required:"false" cty:"managed_image_zone_resilient" hcl:"managed_image_zone_resilient"`
AzureTags map[string]string `mapstructure:"azure_tags" required:"false" cty:"azure_tags" hcl:"azure_tags"`
AzureTags map[string]*string `mapstructure:"azure_tags" required:"false" cty:"azure_tags" hcl:"azure_tags"`
AzureTag []hcl2template.FlatNameValue `mapstructure:"azure_tag" required:"false" cty:"azure_tag" hcl:"azure_tag"`
ResourceGroupName *string `mapstructure:"resource_group_name" cty:"resource_group_name" hcl:"resource_group_name"`
StorageAccount *string `mapstructure:"storage_account" cty:"storage_account" hcl:"storage_account"`
@@ -311,7 +311,6 @@ func (*FlatSharedImageGallery) HCL2Spec() map[string]hcldec.Spec {
// 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 {
SigDestinationSubscription *string `mapstructure:"subscription" cty:"subscription" hcl:"subscription"`
SigDestinationResourceGroup *string `mapstructure:"resource_group" cty:"resource_group" hcl:"resource_group"`
SigDestinationGalleryName *string `mapstructure:"gallery_name" cty:"gallery_name" hcl:"gallery_name"`
SigDestinationImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
@@ -331,7 +330,6 @@ 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{
"subscription": &hcldec.AttrSpec{Name: "subscription", Type: cty.String, 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},
+16 -41
View File
@@ -6,9 +6,7 @@ import (
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer/builder/azure/common/constants"
"github.com/hashicorp/packer/hcl2template"
)
// List of configuration parameters that are required by the ARM builder.
@@ -912,55 +910,32 @@ func TestConfigShouldAcceptTags(t *testing.T) {
},
}
c := Config{
AzureTag: hcl2template.NameValues{
{Name: "tag03", Value: "value03"},
},
}
var c Config
_, err := c.Prepare(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(c.AzureTags, map[string]string{
"tag01": "value01",
"tag02": "value02",
"tag03": "value03",
}); diff != "" {
t.Fatalf("unexpected azure tags: %s", diff)
}
}
func TestConfigShouldAcceptTag(t *testing.T) {
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
if len(c.AzureTags) != 2 {
t.Fatalf("expected to find 2 tags, but got %d", len(c.AzureTags))
}
c := Config{
AzureTag: hcl2template.NameValues{
{Name: "tag03", Value: "value03"},
},
if _, ok := c.AzureTags["tag01"]; !ok {
t.Error("expected to find key=\"tag01\", but did not")
}
_, err := c.Prepare(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
if _, ok := c.AzureTags["tag02"]; !ok {
t.Error("expected to find key=\"tag02\", but did not")
}
if diff := cmp.Diff(c.AzureTags, map[string]string{
"tag03": "value03",
}); diff != "" {
t.Fatalf("unexpected azure tags: %s", diff)
value := c.AzureTags["tag01"]
if *value != "value01" {
t.Errorf("expected AzureTags[\"tag01\"] to have value \"value01\", but got %q", *value)
}
value = c.AzureTags["tag02"]
if *value != "value02" {
t.Errorf("expected AzureTags[\"tag02\"] to have value \"value02\", but got %q", *value)
}
}
-10
View File
@@ -1,10 +0,0 @@
package common
func MapToAzureTags(in map[string]string) map[string]*string {
res := map[string]*string{}
for k := range in {
v := in[k]
res[k] = &v
}
return res
}
+10 -10
View File
@@ -25,16 +25,16 @@ type Parameters struct {
/////////////////////////////////////////////////
// Template > Resource
type Resource struct {
ApiVersion *string `json:"apiVersion"`
Name *string `json:"name"`
Type *string `json:"type"`
Location *string `json:"location,omitempty"`
DependsOn *[]string `json:"dependsOn,omitempty"`
Plan *Plan `json:"plan,omitempty"`
Properties *Properties `json:"properties,omitempty"`
Tags *map[string]string `json:"tags,omitempty"`
Resources *[]Resource `json:"resources,omitempty"`
Identity *Identity `json:"identity,omitempty"`
ApiVersion *string `json:"apiVersion"`
Name *string `json:"name"`
Type *string `json:"type"`
Location *string `json:"location,omitempty"`
DependsOn *[]string `json:"dependsOn,omitempty"`
Plan *Plan `json:"plan,omitempty"`
Properties *Properties `json:"properties,omitempty"`
Tags *map[string]*string `json:"tags,omitempty"`
Resources *[]Resource `json:"resources,omitempty"`
Identity *Identity `json:"identity,omitempty"`
}
type Plan struct {
@@ -374,7 +374,7 @@ func (s *TemplateBuilder) SetNetworkSecurityGroup(ipAddresses []string, port int
return nil
}
func (s *TemplateBuilder) SetTags(tags *map[string]string) error {
func (s *TemplateBuilder) SetTags(tags *map[string]*string) error {
if tags == nil || len(*tags) == 0 {
return nil
}
-2
View File
@@ -20,7 +20,6 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
@@ -133,7 +132,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
+11 -19
View File
@@ -5,7 +5,6 @@ import (
"log"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
@@ -30,9 +29,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
return nil, warnings, errs
}
return []string{
"ImageSha256",
}, warnings, nil
return nil, warnings, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
@@ -47,16 +44,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
}
log.Printf("[DEBUG] Docker version: %s", version.String())
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("hook", hook)
state.Put("ui", ui)
generatedData := &builder.GeneratedData{State: state}
// Setup the driver that will talk to Docker
state.Put("driver", driver)
steps := []multistep.Step{
&StepTempDir{},
&StepPull{},
@@ -80,11 +67,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
log.Print("[DEBUG] Container will be discarded")
} else if b.config.Commit {
log.Print("[DEBUG] Container will be committed")
steps = append(steps,
new(StepCommit),
&StepSetGeneratedData{ // Adds ImageSha256 variable available after StepCommit
GeneratedData: generatedData,
})
steps = append(steps, new(StepCommit))
} else if b.config.ExportPath != "" {
log.Printf("[DEBUG] Container will be exported to %s", b.config.ExportPath)
steps = append(steps, new(StepExport))
@@ -92,6 +75,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
return nil, errArtifactNotUsed
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("hook", hook)
state.Put("ui", ui)
// Setup the driver that will talk to Docker
state.Put("driver", driver)
// Run!
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
-3
View File
@@ -26,9 +26,6 @@ type Driver interface {
// for external access.
IPAddress(id string) (string, error)
// Sha256 returns the sha256 id of the image
Sha256(id string) (string, error)
// Login. This will lock the driver from performing another Login
// until Logout is called. Therefore, any users MUST call Logout.
Login(repo, username, password string) error
-17
View File
@@ -159,23 +159,6 @@ func (d *DockerDriver) IPAddress(id string) (string, error) {
return strings.TrimSpace(stdout.String()), nil
}
func (d *DockerDriver) Sha256(id string) (string, error) {
var stderr, stdout bytes.Buffer
cmd := exec.Command(
"docker",
"inspect",
"--format",
"{{ .Id }}",
id)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("Error: %s\n\nStderr: %s", err, stderr.String())
}
return strings.TrimSpace(stdout.String()), nil
}
func (d *DockerDriver) Login(repo, user, pass string) error {
d.l.Lock()
-11
View File
@@ -28,11 +28,6 @@ type MockDriver struct {
IPAddressResult string
IPAddressErr error
Sha256Called bool
Sha256Id string
Sha256Result string
Sha256Err error
KillCalled bool
KillID string
KillError error
@@ -123,12 +118,6 @@ func (d *MockDriver) IPAddress(id string) (string, error) {
return d.IPAddressResult, d.IPAddressErr
}
func (d *MockDriver) Sha256(id string) (string, error) {
d.Sha256Called = true
d.Sha256Id = id
return d.Sha256Result, d.Sha256Err
}
func (d *MockDriver) Login(r, u, p string) error {
d.LoginCalled = true
d.LoginRepo = r
+93 -5
View File
@@ -1,26 +1,114 @@
package docker
import (
"fmt"
"io"
"log"
"os/exec"
"regexp"
"strings"
"sync"
"syscall"
"github.com/hashicorp/packer/helper/builder/localexec"
"github.com/hashicorp/packer/common/iochan"
"github.com/hashicorp/packer/packer"
)
func runAndStream(cmd *exec.Cmd, ui packer.Ui) error {
stdout_r, stdout_w := io.Pipe()
stderr_r, stderr_w := io.Pipe()
defer stdout_w.Close()
defer stderr_w.Close()
args := make([]string, len(cmd.Args)-1)
copy(args, cmd.Args[1:])
// Scrub password from the log output.
capturedPassword := ""
for i, v := range args {
if v == "-p" || v == "--password" {
capturedPassword = args[i+1]
args[i+1] = "<Filtered>"
break
}
}
// run local command and stream output to UI.
return localexec.RunAndStream(cmd, ui, []string{capturedPassword})
log.Printf("Executing: %s %v", cmd.Path, args)
cmd.Stdout = stdout_w
cmd.Stderr = stderr_w
if err := cmd.Start(); err != nil {
return err
}
// Create the channels we'll use for data
exitCh := make(chan int, 1)
stdoutCh := iochan.LineReader(stdout_r)
stderrCh := iochan.LineReader(stderr_r)
// Start the goroutine to watch for the exit
go func() {
defer stdout_w.Close()
defer stderr_w.Close()
exitStatus := 0
err := cmd.Wait()
if exitErr, ok := err.(*exec.ExitError); ok {
exitStatus = 1
// There is no process-independent way to get the REAL
// exit status so we just try to go deeper.
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
}
}
exitCh <- exitStatus
}()
// This waitgroup waits for the streaming to end
var streamWg sync.WaitGroup
streamWg.Add(2)
streamFunc := func(ch <-chan string) {
defer streamWg.Done()
for data := range ch {
data = cleanOutputLine(data)
if data != "" {
ui.Message(data)
}
}
}
// Stream stderr/stdout
go streamFunc(stderrCh)
go streamFunc(stdoutCh)
// Wait for the process to end and then wait for the streaming to end
exitStatus := <-exitCh
streamWg.Wait()
if exitStatus != 0 {
return fmt.Errorf("Bad exit status: %d", exitStatus)
}
return nil
}
// cleanOutputLine cleans up a line so that '\r' don't muck up the
// UI output when we're reading from a remote command.
func cleanOutputLine(line string) string {
// Build a regular expression that will get rid of shell codes
re := regexp.MustCompile("(?i)\x1b\\[([0-9]{1,2}(;[0-9]{1,2})?)?[a|b|m|k]")
line = re.ReplaceAllString(line, "")
// Trim surrounding whitespace
line = strings.TrimSpace(line)
// Trim up to the first carriage return, since that text would be
// lost anyways.
idx := strings.LastIndex(line, "\r")
if idx > -1 {
line = line[idx+1:]
}
return line
}
@@ -1,4 +1,4 @@
package localexec
package docker
import (
"testing"
-30
View File
@@ -1,30 +0,0 @@
package docker
import (
"context"
"github.com/hashicorp/packer/builder"
"github.com/hashicorp/packer/helper/multistep"
)
type StepSetGeneratedData struct {
GeneratedData *builder.GeneratedData
}
func (s *StepSetGeneratedData) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
sha256 := "ERR_IMAGE_SHA256_NOT_FOUND"
if imageId, ok := state.GetOk("image_id"); ok {
s256, err := driver.Sha256(imageId.(string))
if err == nil {
sha256 = s256
}
}
s.GeneratedData.Put("ImageSha256", sha256)
return multistep.ActionContinue
}
func (s *StepSetGeneratedData) Cleanup(_ multistep.StateBag) {
// No cleanup...
}
@@ -1,51 +0,0 @@
package docker
import (
"context"
"testing"
"github.com/hashicorp/packer/builder"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepSetGeneratedData_Run(t *testing.T) {
state := testState(t)
step := new(StepSetGeneratedData)
step.GeneratedData = &builder.GeneratedData{State: state}
driver := state.Get("driver").(*MockDriver)
driver.Sha256Result = "80B3BB1B1696E73A9B19DEEF92F664F8979F948DF348088B61F9A3477655AF64"
state.Put("image_id", "12345")
if action := step.Run(context.TODO(), state); action != multistep.ActionContinue {
t.Fatalf("Should not halt")
}
if !driver.Sha256Called {
t.Fatalf("driver.SHA256 should be called")
}
if driver.Sha256Id != "12345" {
t.Fatalf("driver.SHA256 got wrong image it: %s", driver.Sha256Id)
}
genData := state.Get("generated_data").(map[string]interface{})
imgSha256 := genData["ImageSha256"].(string)
if imgSha256 != driver.Sha256Result {
t.Fatalf("Expected ImageSha256 to be %s but was %s", driver.Sha256Result, imgSha256)
}
// Image ID not implement
state = testState(t)
step.GeneratedData = &builder.GeneratedData{State: state}
driver = state.Get("driver").(*MockDriver)
notImplementedMsg := "ERR_IMAGE_SHA256_NOT_FOUND"
if action := step.Run(context.TODO(), state); action != multistep.ActionContinue {
t.Fatalf("Should not halt")
}
if driver.Sha256Called {
t.Fatalf("driver.SHA256 should not be called")
}
genData = state.Get("generated_data").(map[string]interface{})
imgSha256 = genData["ImageSha256"].(string)
if imgSha256 != notImplementedMsg {
t.Fatalf("Expected ImageSha256 to be %s but was %s", notImplementedMsg, imgSha256)
}
}
+2 -3
View File
@@ -15,7 +15,6 @@ import (
"time"
compute "google.golang.org/api/compute/v1"
"google.golang.org/api/option"
oslogin "google.golang.org/api/oslogin/v1"
"github.com/hashicorp/packer/common/retry"
@@ -125,12 +124,12 @@ func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config, vaultOauth string) (
}
log.Printf("[INFO] Instantiating GCE client...")
service, err := compute.NewService(context.TODO(), option.WithHTTPClient(client))
service, err := compute.New(client)
if err != nil {
return nil, err
}
osLoginService, err := oslogin.NewService(context.TODO(), option.WithHTTPClient(client))
osLoginService, err := oslogin.New(client)
if err != nil {
return nil, err
}
+2 -3
View File
@@ -37,7 +37,6 @@ const (
type CommonConfig struct {
common.FloppyConfig `mapstructure:",squash"`
common.CDConfig `mapstructure:",squash"`
// The block size of the VHD to be created.
// Recommended disk block size for Linux hyper-v guests is 1 MiB. This
// defaults to "32" MiB.
@@ -211,8 +210,8 @@ func (c *CommonConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig
}
// Errors
errs = append(errs, c.FloppyConfig.Prepare(ctx)...)
errs = append(errs, c.CDConfig.Prepare(ctx)...)
floppyerrs := c.FloppyConfig.Prepare(ctx)
errs = append(errs, floppyerrs...)
if c.GuestAdditionsMode == "" {
if c.GuestAdditionsPath != "" {
c.GuestAdditionsMode = "attach"
@@ -34,17 +34,7 @@ func (s *StepMountSecondaryDvdImages) Run(ctx context.Context, state multistep.S
// For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1)
var dvdProperties []DvdControllerProperties
isoPaths := s.IsoPaths
// Add our custom CD, if it exists
cd_path, ok := state.Get("cd_path").(string)
if ok {
if cd_path != "" {
isoPaths = append(isoPaths, cd_path)
}
}
for _, isoPath := range isoPaths {
for _, isoPath := range s.IsoPaths {
var properties DvdControllerProperties
controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, isoPath, s.Generation)
+1 -4
View File
@@ -254,10 +254,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
GuestAdditionsPath: b.config.GuestAdditionsPath,
Generation: b.config.Generation,
},
&common.StepCreateCD{
Files: b.config.CDConfig.CDFiles,
Label: b.config.CDConfig.CDLabel,
},
&hypervcommon.StepMountSecondaryDvdImages{
IsoPaths: b.config.SecondaryDvdImages,
Generation: b.config.Generation,
-6
View File
@@ -20,7 +20,6 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
@@ -80,8 +79,6 @@ type FlatConfig struct {
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"`
CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"`
DiskBlockSize *uint `mapstructure:"disk_block_size" required:"false" cty:"disk_block_size" hcl:"disk_block_size"`
RamSize *uint `mapstructure:"memory" required:"false" cty:"memory" hcl:"memory"`
SecondaryDvdImages []string `mapstructure:"secondary_iso_images" required:"false" cty:"secondary_iso_images" hcl:"secondary_iso_images"`
@@ -139,7 +136,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
@@ -199,8 +195,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false},
"cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false},
"disk_block_size": &hcldec.AttrSpec{Name: "disk_block_size", Type: cty.Number, Required: false},
"memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false},
"secondary_iso_images": &hcldec.AttrSpec{Name: "secondary_iso_images", Type: cty.List(cty.String), Required: false},
+1 -4
View File
@@ -294,10 +294,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
GuestAdditionsPath: b.config.GuestAdditionsPath,
Generation: b.config.Generation,
},
&common.StepCreateCD{
Files: b.config.CDConfig.CDFiles,
Label: b.config.CDConfig.CDLabel,
},
&hypervcommon.StepMountSecondaryDvdImages{
IsoPaths: b.config.SecondaryDvdImages,
Generation: b.config.Generation,
-6
View File
@@ -20,7 +20,6 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
@@ -80,8 +79,6 @@ type FlatConfig struct {
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"`
CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"`
DiskBlockSize *uint `mapstructure:"disk_block_size" required:"false" cty:"disk_block_size" hcl:"disk_block_size"`
RamSize *uint `mapstructure:"memory" required:"false" cty:"memory" hcl:"memory"`
SecondaryDvdImages []string `mapstructure:"secondary_iso_images" required:"false" cty:"secondary_iso_images" hcl:"secondary_iso_images"`
@@ -141,7 +138,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
@@ -201,8 +197,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false},
"cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false},
"disk_block_size": &hcldec.AttrSpec{Name: "disk_block_size", Type: cty.Number, Required: false},
"memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false},
"secondary_iso_images": &hcldec.AttrSpec{Name: "secondary_iso_images", Type: cty.List(cty.String), Required: false},
+1 -1
View File
@@ -94,7 +94,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs, err)
}
for _, dc := range dcs {
if strings.EqualFold(dc.CountryCode, c.DataCenterName) {
if strings.ToLower(dc.CountryCode) == strings.ToLower(c.DataCenterName) {
c.DataCenterId = dc.Id
break
}
-59
View File
@@ -3,20 +3,16 @@
package openstack
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/utils/openstack/clientconfig"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
@@ -240,15 +236,6 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
return nil
}
func (c *AccessConfig) enableDebug(ui packer.Ui) {
c.osClient.HTTPClient = http.Client{
Transport: &DebugRoundTripper{
ui: ui,
rt: c.osClient.HTTPClient.Transport,
},
}
}
func (c *AccessConfig) computeV2Client() (*gophercloud.ServiceClient, error) {
return openstack.NewComputeV2(c.osClient, gophercloud.EndpointOpts{
Region: c.Region,
@@ -286,49 +273,3 @@ func (c *AccessConfig) getEndpointType() gophercloud.Availability {
}
return gophercloud.AvailabilityPublic
}
type DebugRoundTripper struct {
ui packer.Ui
rt http.RoundTripper
numReauthAttempts int
}
// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
func (drt *DebugRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
defer func() {
if request.Body != nil {
request.Body.Close()
}
}()
var response *http.Response
var err error
response, err = drt.rt.RoundTrip(request)
if response == nil {
return nil, err
}
if response.StatusCode == http.StatusUnauthorized {
if drt.numReauthAttempts == 3 {
return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.")
}
drt.numReauthAttempts++
}
drt.DebugMessage(fmt.Sprintf("Request %s %s %d", request.Method, request.URL, response.StatusCode))
if response.StatusCode >= 400 {
buf := bytes.NewBuffer([]byte{})
body, _ := ioutil.ReadAll(io.TeeReader(response.Body, buf))
drt.DebugMessage(fmt.Sprintf("Response Error: %+v\n", string(body)))
bufWithClose := ioutil.NopCloser(buf)
response.Body = bufWithClose
}
return response, err
}
func (drt *DebugRoundTripper) DebugMessage(message string) {
drt.ui.Message(fmt.Sprintf("[DEBUG] %s", message))
}
+5 -11
View File
@@ -71,10 +71,6 @@ 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) {
if b.config.PackerDebug {
b.config.enableDebug(ui)
}
computeClient, err := b.config.computeV2Client()
if err != nil {
return nil, fmt.Errorf("Error initializing compute client: %s", err)
@@ -102,13 +98,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
DebugKeyPath: fmt.Sprintf("os_%s.pem", b.config.PackerBuildName),
},
&StepSourceImageInfo{
SourceImage: b.config.RunConfig.SourceImage,
SourceImageName: b.config.RunConfig.SourceImageName,
ExternalSourceImageURL: b.config.RunConfig.ExternalSourceImageURL,
ExternalSourceImageFormat: b.config.RunConfig.ExternalSourceImageFormat,
SourceImageOpts: b.config.RunConfig.sourceImageOpts,
SourceMostRecent: b.config.SourceImageFilters.MostRecent,
SourceProperties: b.config.SourceImageFilters.Filters.Properties,
SourceImage: b.config.RunConfig.SourceImage,
SourceImageName: b.config.RunConfig.SourceImageName,
SourceImageOpts: b.config.RunConfig.sourceImageOpts,
SourceMostRecent: b.config.SourceImageFilters.MostRecent,
SourceProperties: b.config.SourceImageFilters.Filters.Properties,
},
&StepDiscoverNetwork{
Networks: b.config.Networks,
-4
View File
@@ -95,8 +95,6 @@ type FlatConfig struct {
SSHIPVersion *string `mapstructure:"ssh_ip_version" required:"false" cty:"ssh_ip_version" hcl:"ssh_ip_version"`
SourceImage *string `mapstructure:"source_image" required:"true" cty:"source_image" hcl:"source_image"`
SourceImageName *string `mapstructure:"source_image_name" required:"true" cty:"source_image_name" hcl:"source_image_name"`
ExternalSourceImageURL *string `mapstructure:"external_source_image_url" required:"true" cty:"external_source_image_url" hcl:"external_source_image_url"`
ExternalSourceImageFormat *string `mapstructure:"external_source_image_format" required:"false" cty:"external_source_image_format" hcl:"external_source_image_format"`
SourceImageFilters *FlatImageFilter `mapstructure:"source_image_filter" required:"true" cty:"source_image_filter" hcl:"source_image_filter"`
Flavor *string `mapstructure:"flavor" required:"true" cty:"flavor" hcl:"flavor"`
AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone" hcl:"availability_zone"`
@@ -222,8 +220,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_ip_version": &hcldec.AttrSpec{Name: "ssh_ip_version", Type: cty.String, Required: false},
"source_image": &hcldec.AttrSpec{Name: "source_image", Type: cty.String, Required: false},
"source_image_name": &hcldec.AttrSpec{Name: "source_image_name", Type: cty.String, Required: false},
"external_source_image_url": &hcldec.AttrSpec{Name: "external_source_image_url", Type: cty.String, Required: false},
"external_source_image_format": &hcldec.AttrSpec{Name: "external_source_image_format", Type: cty.String, Required: false},
"source_image_filter": &hcldec.BlockSpec{TypeName: "source_image_filter", Nested: hcldec.ObjectSpec((*FlatImageFilter)(nil).HCL2Spec())},
"flavor": &hcldec.AttrSpec{Name: "flavor", Type: cty.String, Required: false},
"availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false},
+7 -26
View File
@@ -33,11 +33,6 @@ type RunConfig struct {
// The name of the base image to use. This is an alternative way of
// providing source_image and only either of them can be specified.
SourceImageName string `mapstructure:"source_image_name" required:"true"`
// The URL of an external base image to use. This is an alternative way of
// providing source_image and only either of them can be specified.
ExternalSourceImageURL string `mapstructure:"external_source_image_url" required:"true"`
// The format of the external source image to use, e.g. qcow2, raw.
ExternalSourceImageFormat string `mapstructure:"external_source_image_format" required:"false"`
// Filters used to populate filter options. Example:
//
// ```json
@@ -252,19 +247,10 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
}
}
hasOnlySourceImage := len(c.SourceImage) > 0 && len(c.SourceImageName) == 0 && len(c.ExternalSourceImageURL) == 0
hasOnlySourceImageName := len(c.SourceImageName) > 0 && len(c.SourceImage) == 0 && len(c.ExternalSourceImageURL) == 0
hasOnlyExternalSourceImageURL := len(c.ExternalSourceImageURL) > 0 && len(c.SourceImage) == 0 && len(c.SourceImageName) == 0
if c.SourceImage == "" && c.SourceImageName == "" && c.ExternalSourceImageURL == "" && c.SourceImageFilters.Filters.Empty() {
errs = append(errs, errors.New("Either a source_image, a source_image_name, an external_source_image_url or source_image_filter must be specified"))
} else if !(hasOnlySourceImage || hasOnlySourceImageName || hasOnlyExternalSourceImageURL) {
errs = append(errs, errors.New("Only a source_image, a source_image_name or an external_source_image_url can be specified, not multiple."))
}
// if external_source_image_format is not set use qcow2 as default
if c.ExternalSourceImageFormat == "" {
c.ExternalSourceImageFormat = "qcow2"
if c.SourceImage == "" && c.SourceImageName == "" && c.SourceImageFilters.Filters.Empty() {
errs = append(errs, errors.New("Either a source_image, a source_image_name, or source_image_filter must be specified"))
} else if len(c.SourceImage) > 0 && len(c.SourceImageName) > 0 {
errs = append(errs, errors.New("Only a source_image or a source_image_name can be specified, not both."))
}
if c.Flavor == "" {
@@ -297,9 +283,9 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
}
}
// if neither ID, image name or external image URL is provided outside the filter,
// build the filter
if len(c.SourceImage) == 0 && len(c.SourceImageName) == 0 && len(c.ExternalSourceImageURL) == 0 {
// if neither ID or image name is provided outside the filter, build the
// filter
if len(c.SourceImage) == 0 && len(c.SourceImageName) == 0 {
listOpts, filterErr := c.SourceImageFilters.Filters.Build()
@@ -309,11 +295,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
c.sourceImageOpts = *listOpts
}
// if c.ExternalSourceImageURL is set use a generated source image name
if c.ExternalSourceImageURL != "" {
c.SourceImageName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
}
return errs
}
-44
View File
@@ -2,7 +2,6 @@ package openstack
import (
"os"
"regexp"
"testing"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
@@ -133,49 +132,6 @@ func TestRunConfigPrepare_FloatingIPPoolCompat(t *testing.T) {
}
}
func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
c := testRunConfig()
// test setting both ExternalSourceImageURL and SourceImage causes an error
c.ExternalSourceImageURL = "http://example.com/image.qcow2"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
// test setting both ExternalSourceImageURL and SourceImageName causes an error
c.SourceImage = ""
c.SourceImageName = "abcd"
c.ExternalSourceImageURL = "http://example.com/image.qcow2"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
// test neither setting SourceImage, SourceImageName or ExternalSourceImageURL causes an error
c.SourceImage = ""
c.SourceImageName = ""
c.ExternalSourceImageURL = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
// test setting only ExternalSourceImageURL passes
c.SourceImage = ""
c.SourceImageName = ""
c.ExternalSourceImageURL = "http://example.com/image.qcow2"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
// test default values
if c.ExternalSourceImageFormat != "qcow2" {
t.Fatalf("ExternalSourceImageFormat should have been set to default: qcow2")
}
p := `packer_[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}`
if matches, _ := regexp.MatchString(p, c.SourceImageName); !matches {
t.Fatalf("invalid format for SourceImageName: %s", c.SourceImageName)
}
}
// This test case confirms that only allowed fields will be set to values
// The checked values are non-nil for their target type
func TestBuildImageFilter(t *testing.T) {
+12 -94
View File
@@ -4,9 +4,7 @@ import (
"context"
"fmt"
"log"
"time"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/gophercloud/gophercloud/pagination"
"github.com/hashicorp/packer/helper/multistep"
@@ -14,13 +12,11 @@ import (
)
type StepSourceImageInfo struct {
SourceImage string
SourceImageName string
ExternalSourceImageURL string
ExternalSourceImageFormat string
SourceImageOpts images.ListOpts
SourceMostRecent bool
SourceProperties map[string]string
SourceImage string
SourceImageName string
SourceImageOpts images.ListOpts
SourceMostRecent bool
SourceProperties map[string]string
}
func PropertiesSatisfied(image *images.Image, props *map[string]string) bool {
@@ -37,6 +33,12 @@ func (s *StepSourceImageInfo) Run(ctx context.Context, state multistep.StateBag)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
if s.SourceImage != "" {
state.Put("source_image", s.SourceImage)
return multistep.ActionContinue
}
client, err := config.imageV2Client()
if err != nil {
err := fmt.Errorf("error creating image client: %s", err)
@@ -45,70 +47,6 @@ func (s *StepSourceImageInfo) Run(ctx context.Context, state multistep.StateBag)
return multistep.ActionHalt
}
if s.ExternalSourceImageURL != "" {
createOpts := images.CreateOpts{
Name: s.SourceImageName,
ContainerFormat: "bare",
DiskFormat: s.ExternalSourceImageFormat,
Properties: map[string]string{
"packer_external_source_image_url": s.ExternalSourceImageURL,
"packer_external_source_image_format": s.ExternalSourceImageFormat,
},
}
ui.Say("Creating image using external source image with name " + s.SourceImageName)
ui.Say("Using disk format " + s.ExternalSourceImageFormat)
image, err := images.Create(client, createOpts).Extract()
if err != nil {
err := fmt.Errorf("Error creating source image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Created image with ID " + image.ID)
importOpts := imageimport.CreateOpts{
Name: imageimport.WebDownloadMethod,
URI: s.ExternalSourceImageURL,
}
ui.Say("Importing External Source Image from URL " + s.ExternalSourceImageURL)
err = imageimport.Create(client, image.ID, importOpts).ExtractErr()
if err != nil {
err := fmt.Errorf("Error importing source image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for image.Status != images.ImageStatusActive {
ui.Message("Image not Active, retrying in 10 seconds")
time.Sleep(10 * time.Second)
img, err := images.Get(client, image.ID).Extract()
if err != nil {
err := fmt.Errorf("Error querying image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
image = img
}
s.SourceImage = image.ID
}
if s.SourceImage != "" {
state.Put("source_image", s.SourceImage)
return multistep.ActionContinue
}
if s.SourceImageName != "" {
s.SourceImageOpts = images.ListOpts{
Name: s.SourceImageName,
@@ -179,25 +117,5 @@ func (s *StepSourceImageInfo) Run(ctx context.Context, state multistep.StateBag)
}
func (s *StepSourceImageInfo) Cleanup(state multistep.StateBag) {
if s.ExternalSourceImageURL != "" {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
client, err := config.imageV2Client()
if err != nil {
err := fmt.Errorf("error creating image client: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
ui.Say(fmt.Sprintf("Deleting temporary external source image: %s ...", s.SourceImageName))
err = images.Delete(client, s.SourceImage).ExtractErr()
if err != nil {
err := fmt.Errorf("error cleaning up external source image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
}
// No cleanup required for backout
}
+4 -20
View File
@@ -1,4 +1,4 @@
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails
//go:generate mapstructure-to-hcl2 -type Config
package oci
@@ -22,19 +22,6 @@ import (
ociauth "github.com/oracle/oci-go-sdk/common/auth"
)
type CreateVNICDetails struct {
// fields that can be specified under "create_vnic_details"
AssignPublicIp *bool `mapstructure:"assign_public_ip" required:"false"`
DefinedTags map[string]map[string]interface{} `mapstructure:"defined_tags" required:"false"`
DisplayName *string `mapstructure:"display_name" required:"false"`
FreeformTags map[string]string `mapstructure:"tags" required:"false"`
HostnameLabel *string `mapstructure:"hostname_label" required:"false"`
NsgIds []string `mapstructure:"nsg_ids" required:"false"`
PrivateIp *string `mapstructure:"private_ip" required:"false"`
SkipSourceDestCheck *bool `mapstructure:"skip_source_dest_check" required:"false"`
SubnetId *string `mapstructure:"subnet_id" required:"false"`
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
@@ -70,13 +57,11 @@ type Config struct {
// Image
BaseImageID string `mapstructure:"base_image_ocid"`
Shape string `mapstructure:"shape"`
ImageName string `mapstructure:"image_name"`
// Instance
InstanceName string `mapstructure:"instance_name"`
InstanceTags map[string]string `mapstructure:"instance_tags"`
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
Shape string `mapstructure:"shape"`
InstanceName string `mapstructure:"instance_name"`
// Metadata optionally contains custom metadata key/value pairs provided in the
// configuration. While this can be used to set metadata["user_data"] the explicit
@@ -90,8 +75,7 @@ type Config struct {
UserDataFile string `mapstructure:"user_data_file"`
// Networking
SubnetID string `mapstructure:"subnet_ocid"`
CreateVnicDetails CreateVNICDetails `mapstructure:"create_vnic_details"`
SubnetID string `mapstructure:"subnet_ocid"`
// Tagging
Tags map[string]string `mapstructure:"tags"`
+3 -48
View File
@@ -1,4 +1,4 @@
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
package oci
import (
@@ -76,16 +76,13 @@ 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"`
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
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"`
Metadata map[string]string `mapstructure:"metadata" cty:"metadata" hcl:"metadata"`
UserData *string `mapstructure:"user_data" cty:"user_data" hcl:"user_data"`
UserDataFile *string `mapstructure:"user_data_file" cty:"user_data_file" hcl:"user_data_file"`
SubnetID *string `mapstructure:"subnet_ocid" cty:"subnet_ocid" hcl:"subnet_ocid"`
CreateVnicDetails *FlatCreateVNICDetails `mapstructure:"create_vnic_details" cty:"create_vnic_details" hcl:"create_vnic_details"`
Tags map[string]string `mapstructure:"tags" cty:"tags" hcl:"tags"`
DefinedTags map[string]map[string]interface{} `mapstructure:"defined_tags" cty:"defined_tags" hcl:"defined_tags"`
}
@@ -169,57 +166,15 @@ 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},
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", 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},
"metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
"subnet_ocid": &hcldec.AttrSpec{Name: "subnet_ocid", Type: cty.String, Required: false},
"create_vnic_details": &hcldec.BlockSpec{TypeName: "create_vnic_details", Nested: hcldec.ObjectSpec((*FlatCreateVNICDetails)(nil).HCL2Spec())},
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.Map(cty.String), Required: false},
"defined_tags": &hcldec.AttrSpec{Name: "defined_tags", Type: cty.Map(cty.String), Required: false},
}
return s
}
// FlatCreateVNICDetails is an auto-generated flat version of CreateVNICDetails.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatCreateVNICDetails struct {
AssignPublicIp *bool `mapstructure:"assign_public_ip" required:"false" cty:"assign_public_ip" hcl:"assign_public_ip"`
DefinedTags map[string]map[string]interface{} `mapstructure:"defined_tags" required:"false" cty:"defined_tags" hcl:"defined_tags"`
DisplayName *string `mapstructure:"display_name" required:"false" cty:"display_name" hcl:"display_name"`
FreeformTags map[string]string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"`
HostnameLabel *string `mapstructure:"hostname_label" required:"false" cty:"hostname_label" hcl:"hostname_label"`
NsgIds []string `mapstructure:"nsg_ids" required:"false" cty:"nsg_ids" hcl:"nsg_ids"`
PrivateIp *string `mapstructure:"private_ip" required:"false" cty:"private_ip" hcl:"private_ip"`
SkipSourceDestCheck *bool `mapstructure:"skip_source_dest_check" required:"false" cty:"skip_source_dest_check" hcl:"skip_source_dest_check"`
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
}
// FlatMapstructure returns a new FlatCreateVNICDetails.
// FlatCreateVNICDetails is an auto-generated flat version of CreateVNICDetails.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*CreateVNICDetails) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatCreateVNICDetails)
}
// HCL2Spec returns the hcl spec of a CreateVNICDetails.
// This spec is used by HCL to read the fields of CreateVNICDetails.
// The decoded values from this spec will then be applied to a FlatCreateVNICDetails.
func (*FlatCreateVNICDetails) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"assign_public_ip": &hcldec.AttrSpec{Name: "assign_public_ip", Type: cty.Bool, Required: false},
"defined_tags": &hcldec.AttrSpec{Name: "defined_tags", Type: cty.Map(cty.String), Required: false},
"display_name": &hcldec.AttrSpec{Name: "display_name", Type: cty.String, Required: false},
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.Map(cty.String), Required: false},
"hostname_label": &hcldec.AttrSpec{Name: "hostname_label", Type: cty.String, Required: false},
"nsg_ids": &hcldec.AttrSpec{Name: "nsg_ids", Type: cty.List(cty.String), Required: false},
"private_ip": &hcldec.AttrSpec{Name: "private_ip", Type: cty.String, Required: false},
"skip_source_dest_check": &hcldec.AttrSpec{Name: "skip_source_dest_check", Type: cty.Bool, Required: false},
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
}
return s
}
+1 -10
View File
@@ -21,6 +21,7 @@ func testConfig(accessConfFile *os.File) map[string]interface{} {
// Image
"base_image_ocid": "ocd1...",
"shape": "VM.Standard1.1",
"image_name": "HelloWorld",
// Networking
@@ -35,16 +36,6 @@ func testConfig(accessConfFile *os.File) map[string]interface{} {
"defined_tags": map[string]map[string]interface{}{
"namespace": {"key": "value"},
},
// Instance Details
"instance_name": "hello-world",
"instance_tags": map[string]string{
"key": "value",
},
"create_vnic_details": map[string]interface{}{
"nsg_ids": []string{"ocd1..."},
},
"shape": "VM.Standard1.1",
}
}
-17
View File
@@ -54,8 +54,6 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
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,
@@ -67,21 +65,6 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
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,
HostnameLabel: d.cfg.CreateVnicDetails.HostnameLabel,
NsgIds: d.cfg.CreateVnicDetails.NsgIds,
PrivateIp: d.cfg.CreateVnicDetails.PrivateIp,
SkipSourceDestCheck: d.cfg.CreateVnicDetails.SkipSourceDestCheck,
SubnetId: d.cfg.CreateVnicDetails.SubnetId,
DefinedTags: d.cfg.CreateVnicDetails.DefinedTags,
FreeformTags: d.cfg.CreateVnicDetails.FreeformTags,
}
instanceDetails.CreateVnicDetails = &CreateVnicDetails
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: instanceDetails})
if err != nil {
@@ -20,7 +20,6 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
@@ -124,7 +123,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
+1 -1
View File
@@ -251,7 +251,7 @@ func (d *stepCreateServer) getImageAlias(imageAlias string, location string, ui
if i != "" {
alias = i
}
if alias != "" && strings.EqualFold(alias, imageAlias) {
if alias != "" && strings.ToLower(alias) == strings.ToLower(imageAlias) {
return alias
}
}
+28 -77
View File
@@ -2,7 +2,6 @@ package proxmox
import (
"fmt"
"strings"
"time"
"unicode"
@@ -11,33 +10,23 @@ import (
)
type proxmoxDriver struct {
client commandTyper
vmRef *proxmox.VmRef
specialMap map[string]string
runeMap map[rune]string
interval time.Duration
specialBuffer []string
normalBuffer []string
client commandTyper
vmRef *proxmox.VmRef
specialMap map[string]string
runeMap map[rune]string
interval time.Duration
}
func NewProxmoxDriver(c commandTyper, vmRef *proxmox.VmRef, interval time.Duration) *proxmoxDriver {
// Mappings for packer shorthand to qemu qkeycodes
sMap := map[string]string{
"spacebar": "spc",
"bs": "backspace",
"del": "delete",
"return": "ret",
"enter": "ret",
"pageUp": "pgup",
"pageDown": "pgdn",
"leftshift": "shift",
"rightshift": "shift",
"leftalt": "alt",
"rightalt": "alt_r",
"leftctrl": "ctrl",
"rightctrl": "ctrl_r",
"leftsuper": "meta_l",
"rightsuper": "meta_r",
"spacebar": "spc",
"bs": "backspace",
"del": "delete",
"return": "ret",
"enter": "ret",
"pageUp": "pgup",
"pageDown": "pgdn",
}
// Mappings for runes that need to be translated to special qkeycodes
// Taken from https://github.com/qemu/qemu/blob/master/pc-bios/keymaps/en-us
@@ -89,26 +78,18 @@ func NewProxmoxDriver(c commandTyper, vmRef *proxmox.VmRef, interval time.Durati
}
func (p *proxmoxDriver) SendKey(key rune, action bootcommand.KeyAction) error {
switch action.String() {
case "Press":
if special, ok := p.runeMap[key]; ok {
return p.send(special)
}
var keys string
if unicode.IsUpper(key) {
keys = fmt.Sprintf("shift-%c", unicode.ToLower(key))
} else {
keys = fmt.Sprintf("%c", key)
}
return p.send(keys)
case "On":
key := fmt.Sprintf("%c", key)
p.normalBuffer = addKeyToBuffer(p.normalBuffer, key)
case "Off":
key := fmt.Sprintf("%c", key)
p.normalBuffer = removeKeyFromBuffer(p.normalBuffer, key)
if special, ok := p.runeMap[key]; ok {
return p.send(special)
}
return nil
var keys string
if unicode.IsUpper(key) {
keys = fmt.Sprintf("shift-%c", unicode.ToLower(key))
} else {
keys = fmt.Sprintf("%c", key)
}
return p.send(keys)
}
func (p *proxmoxDriver) SendSpecial(special string, action bootcommand.KeyAction) error {
@@ -116,48 +97,18 @@ func (p *proxmoxDriver) SendSpecial(special string, action bootcommand.KeyAction
if replacement, ok := p.specialMap[special]; ok {
keys = replacement
}
switch action.String() {
case "Press":
return p.send(keys)
case "On":
p.specialBuffer = addKeyToBuffer(p.specialBuffer, keys)
case "Off":
p.specialBuffer = removeKeyFromBuffer(p.specialBuffer, keys)
}
return nil
return p.send(keys)
}
func (p *proxmoxDriver) send(key string) error {
keys := append(p.specialBuffer, p.normalBuffer...)
keys = append(keys, key)
keyEventString := bufferToKeyEvent(keys)
err := p.client.Sendkey(p.vmRef, keyEventString)
func (p *proxmoxDriver) send(keys string) error {
err := p.client.Sendkey(p.vmRef, keys)
if err != nil {
return err
}
time.Sleep(p.interval)
return nil
}
func (p *proxmoxDriver) Flush() error { return nil }
func bufferToKeyEvent(keys []string) string {
return strings.Join(keys, "-")
}
func addKeyToBuffer(buffer []string, key string) []string {
for _, value := range buffer {
if value == key {
return buffer
}
}
return append(buffer, key)
}
func removeKeyFromBuffer(buffer []string, key string) []string {
for index, value := range buffer {
if value == key {
buffer[index] = buffer[len(buffer)-1]
return buffer[:len(buffer)-1]
}
}
return buffer
}
+6 -36
View File
@@ -71,21 +71,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
ResultKey: downloadPathKey,
TargetPath: b.config.TargetPath,
Url: b.config.ISOUrls,
}}
for idx := range b.config.AdditionalISOFiles {
steps = append(steps, &common.StepDownload{
Checksum: b.config.AdditionalISOFiles[idx].ISOChecksum,
Description: "additional ISO",
Extension: b.config.AdditionalISOFiles[idx].TargetExtension,
ResultKey: b.config.AdditionalISOFiles[idx].downloadPathKey,
TargetPath: b.config.AdditionalISOFiles[idx].downloadPathKey,
Url: b.config.AdditionalISOFiles[idx].ISOUrls,
})
}
steps = append(steps,
},
&stepUploadISO{},
&stepUploadAdditionalISOs{},
&stepStartVM{},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
@@ -109,8 +96,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&stepConvertToTemplate{},
&stepFinalizeTemplateConfig{},
&stepSuccess{},
)
}
// Run the steps
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
@@ -152,32 +138,16 @@ func commHost(host string) func(state multistep.StateBag) (string, error) {
// Reads the first non-loopback interface's IP address from the VM.
// qemu-guest-agent package must be installed on the VM
func getVMIP(state multistep.StateBag) (string, error) {
client := state.Get("proxmoxClient").(*proxmox.Client)
config := state.Get("config").(*Config)
c := state.Get("proxmoxClient").(*proxmox.Client)
vmRef := state.Get("vmRef").(*proxmox.VmRef)
ifs, err := client.GetVmAgentNetworkInterfaces(vmRef)
ifs, err := c.GetVmAgentNetworkInterfaces(vmRef)
if err != nil {
return "", err
}
if config.VMInterface != "" {
for _, iface := range ifs {
if config.VMInterface != iface.Name {
continue
}
for _, addr := range iface.IPAddresses {
if addr.IsLoopback() {
continue
}
return addr.String(), nil
}
return "", fmt.Errorf("Interface %s only has loopback addresses", config.VMInterface)
}
return "", fmt.Errorf("Interface %s not found in VM", config.VMInterface)
}
// TODO: Do something smarter here? Allow specifying interface? Or address family?
// For now, just go for first non-loopback
for _, iface := range ifs {
for _, addr := range iface.IPAddresses {
if addr.IsLoopback() {
+1 -81
View File
@@ -1,4 +1,4 @@
//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig
//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig
package proxmox
@@ -8,7 +8,6 @@ import (
"log"
"net/url"
"os"
"strconv"
"strings"
"time"
@@ -65,9 +64,6 @@ type Config struct {
shouldUploadISO bool
AdditionalISOFiles []storageConfig `mapstructure:"additional_iso_files"`
VMInterface string `mapstructure:"vm_interface"`
ctx interpolate.Context
}
@@ -86,21 +82,11 @@ type diskConfig struct {
Size string `mapstructure:"disk_size"`
CacheMode string `mapstructure:"cache_mode"`
DiskFormat string `mapstructure:"format"`
IOThread bool `mapstructure:"io_thread"`
}
type vgaConfig struct {
Type string `mapstructure:"type"`
Memory int `mapstructure:"memory"`
}
type storageConfig struct {
common.ISOConfig `mapstructure:",squash"`
Device string `mapstructure:"device"`
ISOFile string `mapstructure:"iso_file"`
ISOStoragePool string `mapstructure:"iso_storage_pool"`
Unmount bool `mapstructure:"unmount"`
shouldUploadISO bool
downloadPathKey string
}
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
// Agent defaults to true
@@ -190,17 +176,6 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
log.Printf("Disk %d cache mode not set, using default 'none'", idx)
c.Disks[idx].CacheMode = "none"
}
if c.Disks[idx].IOThread {
// io thread is only supported by virtio-scsi-single controller
if c.SCSIController != "virtio-scsi-single" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("io thread option requires virtio-scsi-single controller"))
} else {
// ... and only for virtio and scsi disks
if !(c.Disks[idx].Type == "scsi" || c.Disks[idx].Type == "virtio") {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("io thread option requires scsi or a virtio disk"))
}
}
}
// For any storage pool types which aren't in rxStorageTypes in proxmox-api/proxmox/config_qemu.go:890
// (currently zfspool|lvm|rbd|cephfs), the format parameter is mandatory. Make sure this is still up to date
// when updating the vendored code!
@@ -208,61 +183,6 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("disk format must be specified for pool type %q", c.Disks[idx].StoragePoolType))
}
}
for idx := range c.AdditionalISOFiles {
// Check AdditionalISO config
// Either a pre-uploaded ISO should be referenced in iso_file, OR a URL
// (possibly to a local file) to an ISO file that will be downloaded and
// then uploaded to Proxmox.
if c.AdditionalISOFiles[idx].ISOFile != "" {
c.AdditionalISOFiles[idx].shouldUploadISO = false
} else {
c.AdditionalISOFiles[idx].downloadPathKey = "downloaded_additional_iso_path_" + strconv.Itoa(idx)
isoWarnings, isoErrors := c.AdditionalISOFiles[idx].ISOConfig.Prepare(&c.ctx)
errs = packer.MultiErrorAppend(errs, isoErrors...)
warnings = append(warnings, isoWarnings...)
c.AdditionalISOFiles[idx].shouldUploadISO = true
}
if c.AdditionalISOFiles[idx].Device == "" {
log.Printf("AdditionalISOFile %d Device not set, using default 'ide3'", idx)
c.AdditionalISOFiles[idx].Device = "ide3"
}
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "ide") {
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[3:])
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[3:]))
}
if busnumber == 2 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("IDE bus 2 is used by boot ISO"))
}
if busnumber > 3 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("IDE bus index can't be higher than 3"))
}
}
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "sata") {
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[4:])
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[4:]))
}
if busnumber > 5 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("SATA bus index can't be higher than 5"))
}
}
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "scsi") {
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[4:])
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[4:]))
}
if busnumber > 30 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("SCSI bus index can't be higher than 30"))
}
}
if (c.AdditionalISOFiles[idx].ISOFile == "" && len(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls) == 0) || (c.AdditionalISOFiles[idx].ISOFile != "" && len(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls) != 0) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("either iso_file or iso_url, but not both, must be specified for AdditionalISO file %s", c.AdditionalISOFiles[idx].Device))
}
if len(c.ISOConfig.ISOUrls) != 0 && c.ISOStoragePool == "" {
errs = packer.MultiErrorAppend(errs, errors.New("when specifying iso_url, iso_storage_pool must also be specified"))
}
}
if c.SCSIController == "" {
log.Printf("SCSI controller not set, using default 'lsi'")
c.SCSIController = "lsi"
+95 -142
View File
@@ -1,4 +1,4 @@
// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig"; DO NOT EDIT.
package proxmox
import (
@@ -9,103 +9,100 @@ import (
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"`
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"`
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
ProxmoxURLRaw *string `mapstructure:"proxmox_url" cty:"proxmox_url" hcl:"proxmox_url"`
SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"`
Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"`
Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"`
CPUType *string `mapstructure:"cpu_type" cty:"cpu_type" hcl:"cpu_type"`
Sockets *int `mapstructure:"sockets" cty:"sockets" hcl:"sockets"`
OS *string `mapstructure:"os" cty:"os" hcl:"os"`
VGA *FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"`
NICs []FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
Disks []FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"`
ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"`
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"`
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent" hcl:"qemu_agent"`
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"`
Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"`
DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"`
TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"`
TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"`
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"`
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"`
AdditionalISOFiles []FlatstorageConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"`
VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"`
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"`
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"`
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
ProxmoxURLRaw *string `mapstructure:"proxmox_url" cty:"proxmox_url" hcl:"proxmox_url"`
SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"`
Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"`
Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"`
CPUType *string `mapstructure:"cpu_type" cty:"cpu_type" hcl:"cpu_type"`
Sockets *int `mapstructure:"sockets" cty:"sockets" hcl:"sockets"`
OS *string `mapstructure:"os" cty:"os" hcl:"os"`
VGA *FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"`
NICs []FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
Disks []FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"`
ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"`
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"`
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent" hcl:"qemu_agent"`
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"`
Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"`
DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"`
TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"`
TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"`
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"`
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"`
}
// FlatMapstructure returns a new FlatConfig.
@@ -131,7 +128,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
@@ -215,8 +211,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"unmount_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false},
"cloud_init": &hcldec.AttrSpec{Name: "cloud_init", Type: cty.Bool, Required: false},
"cloud_init_storage_pool": &hcldec.AttrSpec{Name: "cloud_init_storage_pool", Type: cty.String, Required: false},
"additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*FlatstorageConfig)(nil).HCL2Spec())},
"vm_interface": &hcldec.AttrSpec{Name: "vm_interface", Type: cty.String, Required: false},
}
return s
}
@@ -230,7 +224,6 @@ type FlatdiskConfig struct {
Size *string `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
CacheMode *string `mapstructure:"cache_mode" cty:"cache_mode" hcl:"cache_mode"`
DiskFormat *string `mapstructure:"format" cty:"format" hcl:"format"`
IOThread *bool `mapstructure:"io_thread" cty:"io_thread" hcl:"io_thread"`
}
// FlatMapstructure returns a new FlatdiskConfig.
@@ -251,7 +244,6 @@ func (*FlatdiskConfig) HCL2Spec() map[string]hcldec.Spec {
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.String, Required: false},
"cache_mode": &hcldec.AttrSpec{Name: "cache_mode", Type: cty.String, Required: false},
"format": &hcldec.AttrSpec{Name: "format", Type: cty.String, Required: false},
"io_thread": &hcldec.AttrSpec{Name: "io_thread", Type: cty.Bool, Required: false},
}
return s
}
@@ -289,45 +281,6 @@ func (*FlatnicConfig) HCL2Spec() map[string]hcldec.Spec {
return s
}
// FlatstorageConfig is an auto-generated flat version of storageConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatstorageConfig struct {
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"`
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"`
Device *string `mapstructure:"device" cty:"device" hcl:"device"`
ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"`
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"`
Unmount *bool `mapstructure:"unmount" cty:"unmount" hcl:"unmount"`
}
// FlatMapstructure returns a new FlatstorageConfig.
// FlatstorageConfig is an auto-generated flat version of storageConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*storageConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatstorageConfig)
}
// HCL2Spec returns the hcl spec of a storageConfig.
// This spec is used by HCL to read the fields of storageConfig.
// The decoded values from this spec will then be applied to a FlatstorageConfig.
func (*FlatstorageConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
"iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false},
"iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false},
"device": &hcldec.AttrSpec{Name: "device", Type: cty.String, Required: false},
"iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false},
"iso_storage_pool": &hcldec.AttrSpec{Name: "iso_storage_pool", Type: cty.String, Required: false},
"unmount": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false},
}
return s
}
// FlatvgaConfig is an auto-generated flat version of vgaConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatvgaConfig struct {
-49
View File
@@ -208,52 +208,3 @@ func TestPacketQueueSupportForNetworkAdapters(t *testing.T) {
}
}
}
func TestHardDiskControllerIOThreadSupport(t *testing.T) {
drivertests := []struct {
expectedToFail bool
controller string
disk_type string
}{
// io thread is only supported by virtio-scsi-single controller
// and only for virtio and scsi disks
{expectedToFail: false, controller: "virtio-scsi-single", disk_type: "scsi"},
{expectedToFail: false, controller: "virtio-scsi-single", disk_type: "virtio"},
{expectedToFail: true, controller: "virtio-scsi-single", disk_type: "sata"},
{expectedToFail: true, controller: "lsi", disk_type: "scsi"},
{expectedToFail: true, controller: "lsi53c810", disk_type: "virtio"},
}
for _, tt := range drivertests {
nic := make(map[string]interface{})
nic["bridge"] = "vmbr0"
nics := make([]map[string]interface{}, 0)
nics = append(nics, nic)
disk := make(map[string]interface{})
disk["type"] = tt.disk_type
disk["io_thread"] = true
disk["storage_pool"] = "local-lvm"
disk["storage_pool_type"] = "lvm"
disks := make([]map[string]interface{}, 0)
disks = append(disks, disk)
cfg := mandatoryConfig(t)
cfg["network_adapters"] = nics
cfg["disks"] = disks
cfg["scsi_controller"] = tt.controller
var c Config
_, err := c.Prepare(cfg)
if tt.expectedToFail == true && err == nil {
t.Error("expected config preparation to fail, but no error occured")
}
if tt.expectedToFail == false && err != nil {
t.Errorf("expected config preparation to succeed, but %s", err.Error())
}
}
}
@@ -93,29 +93,6 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St
}
}
if len(c.AdditionalISOFiles) > 0 {
vmParams, err := client.GetVmConfig(vmRef)
if err != nil {
err := fmt.Errorf("Error fetching template config: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for idx := range c.AdditionalISOFiles {
cdrom := c.AdditionalISOFiles[idx].Device
if c.AdditionalISOFiles[idx].Unmount {
if vmParams[cdrom] == nil || !strings.Contains(vmParams[cdrom].(string), "media=cdrom") {
err := fmt.Errorf("Cannot eject ISO from cdrom drive, %s is not present or not a cdrom media", cdrom)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
changes[cdrom] = "none,media=cdrom"
} else {
changes[cdrom] = c.AdditionalISOFiles[idx].ISOFile + ",media=cdrom"
}
}
}
if len(changes) > 0 {
_, err := client.SetVmConfig(vmRef, changes)
if err != nil {
-17
View File
@@ -91,19 +91,6 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist
// instance id inside of the provisioners, used in step_provision.
state.Put("instance_id", vmRef)
for idx := range c.AdditionalISOFiles {
params := map[string]interface{}{
c.AdditionalISOFiles[idx].Device: c.AdditionalISOFiles[idx].ISOFile + ",media=cdrom",
}
_, err = client.SetVmConfig(vmRef, params)
if err != nil {
err := fmt.Errorf("Error configuring VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
ui.Say("Starting VM")
_, err = client.StartVm(vmRef)
if err != nil {
@@ -142,10 +129,6 @@ func generateProxmoxDisks(disks []diskConfig) proxmox.QemuDevices {
setDeviceParamIfDefined(devs[idx], "storage_type", disks[idx].StoragePoolType)
setDeviceParamIfDefined(devs[idx], "cache", disks[idx].CacheMode)
setDeviceParamIfDefined(devs[idx], "format", disks[idx].DiskFormat)
if devs[idx]["type"] == "scsi" || devs[idx]["type"] == "virtio" {
setDeviceParamIfDefined(devs[idx], "iothread", strconv.FormatBool(disks[idx].IOThread))
}
}
return devs
}
+13 -31
View File
@@ -53,20 +53,14 @@ func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
return multistep.ActionHalt
}
}
var httpIP string
var err error
if c.HTTPAddress != "0.0.0.0" {
httpIP = c.HTTPAddress
} else {
httpIP, err = hostIP(c.HTTPInterface)
if err != nil {
err := fmt.Errorf("Failed to determine host IP: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
httpIP, err := hostIP()
if err != nil {
err := fmt.Errorf("Failed to determine host IP: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("http_ip", httpIP)
s.Ctx.Data = &bootCommandTemplateData{
HTTPIP: httpIP,
@@ -103,25 +97,12 @@ func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}
func hostIP(ifname string) (string, error) {
var addrs []net.Addr
var err error
if ifname != "" {
iface, err := net.InterfaceByName(ifname)
if err != nil {
return "", err
}
addrs, err = iface.Addrs()
if err != nil {
return "", err
}
} else {
addrs, err = net.InterfaceAddrs()
if err != nil {
return "", err
}
func hostIP() (string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
@@ -129,5 +110,6 @@ func hostIP(ifname string) (string, error) {
}
}
}
return "", errors.New("No host IP found")
}
@@ -52,34 +52,6 @@ func TestTypeBootCommand(t *testing.T) {
expectedKeysSent: "shift-hellospcshift-worldspc2dot0fooshift-1barshift-2baz",
expectedAction: multistep.ActionContinue,
},
{
name: "holding and releasing keys",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"<leftShiftOn>hello<rightAltOn>world<leftShiftOff><rightAltOff>"}}},
expectCallSendkey: true,
expectedKeysSent: "shift-hshift-eshift-lshift-lshift-oshift-alt_r-wshift-alt_r-oshift-alt_r-rshift-alt_r-lshift-alt_r-d",
expectedAction: multistep.ActionContinue,
},
{
name: "holding multiple alphabetical keys and shift",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"<cOn><leftShiftOn>n<leftShiftOff><cOff>"}}},
expectCallSendkey: true,
expectedKeysSent: "shift-c-n",
expectedAction: multistep.ActionContinue,
},
{
name: "noop keystrokes",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"<cOn><leftShiftOn><cOff><leftAltOn><leftShiftOff><leftAltOff>"}}},
expectCallSendkey: true,
expectedKeysSent: "",
expectedAction: multistep.ActionContinue,
},
{
name: "noop keystrokes mixed",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"<cOn><leftShiftOn><cOff>h<leftShiftOff>"}}},
expectCallSendkey: true,
expectedKeysSent: "shift-h",
expectedAction: multistep.ActionContinue,
},
{
name: "without boot command sendkey should not be called",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{}}},
@@ -1,68 +0,0 @@
package proxmox
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// stepUploadAdditionalISOs uploads all additional ISO files that are mountet
// to the VM
type stepUploadAdditionalISOs struct{}
type uploader interface {
Upload(node string, storage string, contentType string, filename string, file io.Reader) error
}
var _ uploader = &proxmox.Client{}
func (s *stepUploadAdditionalISOs) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
client := state.Get("proxmoxClient").(uploader)
c := state.Get("config").(*Config)
for idx := range c.AdditionalISOFiles {
if !c.AdditionalISOFiles[idx].shouldUploadISO {
state.Put("additional_iso_files", c.AdditionalISOFiles)
continue
}
p := state.Get(c.AdditionalISOFiles[idx].downloadPathKey).(string)
if p == "" {
err := fmt.Errorf("Path to downloaded ISO was empty")
state.Put("erroe", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
isoPath, _ := filepath.EvalSymlinks(p)
r, err := os.Open(isoPath)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
filename := filepath.Base(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls[0])
err = client.Upload(c.Node, c.AdditionalISOFiles[idx].ISOStoragePool, "iso", filename, r)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
isoStoragePath := fmt.Sprintf("%s:iso/%s", c.AdditionalISOFiles[idx].ISOStoragePool, filename)
c.AdditionalISOFiles[idx].ISOFile = isoStoragePath
state.Put("additional_iso_files", c.AdditionalISOFiles)
}
return multistep.ActionContinue
}
func (s *stepUploadAdditionalISOs) Cleanup(state multistep.StateBag) {
}
+6
View File
@@ -3,6 +3,7 @@ package proxmox
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
@@ -14,6 +15,10 @@ import (
// stepUploadISO uploads an ISO file to Proxmox so we can boot from it
type stepUploadISO struct{}
type uploader interface {
Upload(node string, storage string, contentType string, filename string, file io.Reader) error
}
var _ uploader = &proxmox.Client{}
func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@@ -53,6 +58,7 @@ func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multi
isoStoragePath := fmt.Sprintf("%s:iso/%s", c.ISOStoragePool, filename)
state.Put("iso_file", isoStoragePath)
return multistep.ActionContinue
}
+598 -62
View File
@@ -1,3 +1,6 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type Config
package qemu
import (
@@ -8,26 +11,561 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/common/shutdowncommand"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
const BuilderId = "transcend.qemu"
var accels = map[string]struct{}{
"none": {},
"kvm": {},
"tcg": {},
"xen": {},
"hax": {},
"hvf": {},
"whpx": {},
}
var diskInterface = map[string]bool{
"ide": true,
"scsi": true,
"virtio": true,
"virtio-scsi": true,
}
var diskCache = map[string]bool{
"writethrough": true,
"writeback": true,
"none": true,
"unsafe": true,
"directsync": true,
}
var diskDiscard = map[string]bool{
"unmap": true,
"ignore": true,
}
var diskDZeroes = map[string]bool{
"unmap": true,
"on": true,
"off": true,
}
type Builder struct {
config Config
runner multistep.Runner
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
common.HTTPConfig `mapstructure:",squash"`
common.ISOConfig `mapstructure:",squash"`
bootcommand.VNCConfig `mapstructure:",squash"`
shutdowncommand.ShutdownConfig `mapstructure:",squash"`
CommConfig CommConfig `mapstructure:",squash"`
common.FloppyConfig `mapstructure:",squash"`
// Use iso from provided url. Qemu must support
// curl block device. This defaults to `false`.
ISOSkipCache bool `mapstructure:"iso_skip_cache" required:"false"`
// The accelerator type to use when running the VM.
// This may be `none`, `kvm`, `tcg`, `hax`, `hvf`, `whpx`, or `xen`. The appropriate
// software must have already been installed on your build machine to use the
// accelerator you specified. When no accelerator is specified, Packer will try
// to use `kvm` if it is available but will default to `tcg` otherwise.
//
// ~> The `hax` accelerator has issues attaching CDROM ISOs. This is an
// upstream issue which can be tracked
// [here](https://github.com/intel/haxm/issues/20).
//
// ~> The `hvf` and `whpx` accelerator are new and experimental as of
// [QEMU 2.12.0](https://wiki.qemu.org/ChangeLog/2.12#Host_support).
// You may encounter issues unrelated to Packer when using these. You may need to
// add [ "-global", "virtio-pci.disable-modern=on" ] to `qemuargs` depending on the
// guest operating system.
//
// ~> For `whpx`, note that [Stefan Weil's QEMU for Windows distribution](https://qemu.weilnetz.de/w64/)
// does not include WHPX support and users may need to compile or source a
// build of QEMU for Windows themselves with WHPX support.
Accelerator string `mapstructure:"accelerator" required:"false"`
// Additional disks to create. Uses `vm_name` as the disk name template and
// appends `-#` where `#` is the position in the array. `#` starts at 1 since 0
// is the default disk. Each string represents the disk image size in bytes.
// Optional suffixes 'k' or 'K' (kilobyte, 1024), 'M' (megabyte, 1024k), 'G'
// (gigabyte, 1024M), 'T' (terabyte, 1024G), 'P' (petabyte, 1024T) and 'E'
// (exabyte, 1024P) are supported. 'b' is ignored. Per qemu-img documentation.
// Each additional disk uses the same disk parameters as the default disk.
// Unset by default.
AdditionalDiskSize []string `mapstructure:"disk_additional_size" required:"false"`
// The number of cpus to use when building the VM.
// The default is `1` CPU.
CpuCount int `mapstructure:"cpus" required:"false"`
// The interface to use for the disk. Allowed values include any of `ide`,
// `scsi`, `virtio` or `virtio-scsi`^\*. Note also that any boot commands
// or kickstart type scripts must have proper adjustments for resulting
// device names. The Qemu builder uses `virtio` by default.
//
// ^\* Please be aware that use of the `scsi` disk interface has been
// disabled by Red Hat due to a bug described
// [here](https://bugzilla.redhat.com/show_bug.cgi?id=1019220). If you are
// running Qemu on RHEL or a RHEL variant such as CentOS, you *must* choose
// one of the other listed interfaces. Using the `scsi` interface under
// these circumstances will cause the build to fail.
DiskInterface string `mapstructure:"disk_interface" required:"false"`
// The size in bytes of the hard disk of the VM. Suffix with the first
// letter of common byte types. Use "k" or "K" for kilobytes, "M" for
// megabytes, G for gigabytes, and T for terabytes. If no value is provided
// for disk_size, Packer uses a default of `40960M` (40 GB). If a disk_size
// number is provided with no units, Packer will default to Megabytes.
DiskSize string `mapstructure:"disk_size" required:"false"`
// The cache mode to use for disk. Allowed values include any of
// `writethrough`, `writeback`, `none`, `unsafe` or `directsync`. By
// default, this is set to `writeback`.
DiskCache string `mapstructure:"disk_cache" required:"false"`
// The discard mode to use for disk. Allowed values
// include any of unmap or ignore. By default, this is set to ignore.
DiskDiscard string `mapstructure:"disk_discard" required:"false"`
// The detect-zeroes mode to use for disk.
// Allowed values include any of unmap, on or off. Defaults to off.
// When the value is "off" we don't set the flag in the qemu command, so that
// Packer still works with old versions of QEMU that don't have this option.
DetectZeroes string `mapstructure:"disk_detect_zeroes" required:"false"`
// Packer compacts the QCOW2 image using
// qemu-img convert. Set this option to true to disable compacting.
// Defaults to false.
SkipCompaction bool `mapstructure:"skip_compaction" required:"false"`
// Apply compression to the QCOW2 disk file
// using qemu-img convert. Defaults to false.
DiskCompression bool `mapstructure:"disk_compression" required:"false"`
// Either `qcow2` or `raw`, this specifies the output format of the virtual
// machine image. This defaults to `qcow2`.
Format string `mapstructure:"format" required:"false"`
// Packer defaults to building QEMU virtual machines by
// launching a GUI that shows the console of the machine being built. When this
// value is set to `true`, the machine will start without a console.
//
// You can still see the console if you make a note of the VNC display
// number chosen, and then connect using `vncviewer -Shared <host>:<display>`
Headless bool `mapstructure:"headless" required:"false"`
// Packer defaults to building from an ISO file, this parameter controls
// whether the ISO URL supplied is actually a bootable QEMU image. When
// this value is set to `true`, the machine will either clone the source or
// use it as a backing file (if `use_backing_file` is `true`); then, it
// will resize the image according to `disk_size` and boot it.
DiskImage bool `mapstructure:"disk_image" required:"false"`
// Only applicable when disk_image is true
// and format is qcow2, set this option to true to create a new QCOW2
// file that uses the file located at iso_url as a backing file. The new file
// will only contain blocks that have changed compared to the backing file, so
// enabling this option can significantly reduce disk usage.
UseBackingFile bool `mapstructure:"use_backing_file" required:"false"`
// The type of machine emulation to use. Run your qemu binary with the
// flags `-machine help` to list available types for your system. This
// defaults to `pc`.
MachineType string `mapstructure:"machine_type" required:"false"`
// The amount of memory to use when building the VM
// in megabytes. This defaults to 512 megabytes.
MemorySize int `mapstructure:"memory" required:"false"`
// The driver to use for the network interface. Allowed values `ne2k_pci`,
// `i82551`, `i82557b`, `i82559er`, `rtl8139`, `e1000`, `pcnet`, `virtio`,
// `virtio-net`, `virtio-net-pci`, `usb-net`, `i82559a`, `i82559b`,
// `i82559c`, `i82550`, `i82562`, `i82557a`, `i82557c`, `i82801`,
// `vmxnet3`, `i82558a` or `i82558b`. The Qemu builder uses `virtio-net` by
// default.
NetDevice string `mapstructure:"net_device" required:"false"`
// Connects the network to this bridge instead of using the user mode
// networking.
//
// **NB** This bridge must already exist. You can use the `virbr0` bridge
// as created by vagrant-libvirt.
//
// **NB** This will automatically enable the QMP socket (see QMPEnable).
//
// **NB** This only works in Linux based OSes.
NetBridge string `mapstructure:"net_bridge" required:"false"`
// This is the path to the directory where the
// resulting virtual machine will be created. This may be relative or absolute.
// If relative, the path is relative to the working directory when packer
// is executed. This directory must not exist or be empty prior to running
// the builder. By default this is output-BUILDNAME where "BUILDNAME" is the
// name of the build.
OutputDir string `mapstructure:"output_directory" required:"false"`
// Allows complete control over the qemu command line (though not, at this
// time, qemu-img). Each array of strings makes up a command line switch
// that overrides matching default switch/value pairs. Any value specified
// as an empty string is ignored. All values after the switch are
// concatenated with no separator.
//
// ~> **Warning:** The qemu command line allows extreme flexibility, so
// beware of conflicting arguments causing failures of your run. For
// instance, using --no-acpi could break the ability to send power signal
// type commands (e.g., shutdown -P now) to the virtual machine, thus
// preventing proper shutdown. To see the defaults, look in the packer.log
// file and search for the qemu-system-x86 command. The arguments are all
// printed for review.
//
// The following shows a sample usage:
//
// In JSON:
// ```json
// "qemuargs": [
// [ "-m", "1024M" ],
// [ "--no-acpi", "" ],
// [
// "-netdev",
// "user,id=mynet0,",
// "hostfwd=hostip:hostport-guestip:guestport",
// ""
// ],
// [ "-device", "virtio-net,netdev=mynet0" ]
// ]
// ```
//
// In HCL2:
// ```hcl
// qemuargs = [
// [ "-m", "1024M" ],
// [ "--no-acpi", "" ],
// [
// "-netdev",
// "user,id=mynet0,",
// "hostfwd=hostip:hostport-guestip:guestport",
// ""
// ],
// [ "-device", "virtio-net,netdev=mynet0" ]
// ]
// ```
//
// would produce the following (not including other defaults supplied by
// the builder and not otherwise conflicting with the qemuargs):
//
// ```text
// qemu-system-x86 -m 1024m --no-acpi -netdev
// user,id=mynet0,hostfwd=hostip:hostport-guestip:guestport -device
// virtio-net,netdev=mynet0"
// ```
//
// ~> **Windows Users:** [QEMU for Windows](https://qemu.weilnetz.de/)
// builds are available though an environmental variable does need to be
// set for QEMU for Windows to redirect stdout to the console instead of
// stdout.txt.
//
// The following shows the environment variable that needs to be set for
// Windows QEMU support:
//
// ```text
// setx SDL_STDIO_REDIRECT=0
// ```
//
// You can also use the `SSHHostPort` template variable to produce a packer
// template that can be invoked by `make` in parallel:
//
// In JSON:
// ```json
// "qemuargs": [
// [ "-netdev", "user,hostfwd=tcp::{{ .SSHHostPort }}-:22,id=forward"],
// [ "-device", "virtio-net,netdev=forward,id=net0"]
// ]
// ```
//
// In HCL2:
// ```hcl
// qemuargs = [
// [ "-netdev", "user,hostfwd=tcp::{{ .SSHHostPort }}-:22,id=forward"],
// [ "-device", "virtio-net,netdev=forward,id=net0"]
// ]
//
// `make -j 3 my-awesome-packer-templates` spawns 3 packer processes, each
// of which will bind to their own SSH port as determined by each process.
// This will also work with WinRM, just change the port forward in
// `qemuargs` to map to WinRM's default port of `5985` or whatever value
// you have the service set to listen on.
//
// This is a template engine and allows access to the following variables:
// `{{ .HTTPIP }}`, `{{ .HTTPPort }}`, `{{ .HTTPDir }}`,
// `{{ .OutputDir }}`, `{{ .Name }}`, and `{{ .SSHHostPort }}`
QemuArgs [][]string `mapstructure:"qemuargs" required:"false"`
// The name of the Qemu binary to look for. This
// defaults to qemu-system-x86_64, but may need to be changed for
// some platforms. For example qemu-kvm, or qemu-system-i386 may be a
// better choice for some systems.
QemuBinary string `mapstructure:"qemu_binary" required:"false"`
// Enable QMP socket. Location is specified by `qmp_socket_path`. Defaults
// to false.
QMPEnable bool `mapstructure:"qmp_enable" required:"false"`
// QMP Socket Path when `qmp_enable` is true. Defaults to
// `output_directory`/`vm_name`.monitor.
QMPSocketPath string `mapstructure:"qmp_socket_path" required:"false"`
// If true, do not pass a -display option
// to qemu, allowing it to choose the default. This may be needed when running
// under macOS, and getting errors about sdl not being available.
UseDefaultDisplay bool `mapstructure:"use_default_display" required:"false"`
// What QEMU -display option to use. Defaults to gtk, use none to not pass the
// -display option allowing QEMU to choose the default. This may be needed when
// running under macOS, and getting errors about sdl not being available.
Display string `mapstructure:"display" required:"false"`
// The IP address that should be
// binded to for VNC. By default packer will use 127.0.0.1 for this. If you
// wish to bind to all interfaces use 0.0.0.0.
VNCBindAddress string `mapstructure:"vnc_bind_address" required:"false"`
// Whether or not to set a password on the VNC server. This option
// automatically enables the QMP socket. See `qmp_socket_path`. Defaults to
// `false`.
VNCUsePassword bool `mapstructure:"vnc_use_password" required:"false"`
// The minimum and maximum port
// to use for VNC access to the virtual machine. The builder uses VNC to type
// the initial boot_command. Because Packer generally runs in parallel,
// Packer uses a randomly chosen port in this range that appears available. By
// default this is 5900 to 6000. The minimum and maximum ports are inclusive.
VNCPortMin int `mapstructure:"vnc_port_min" required:"false"`
VNCPortMax int `mapstructure:"vnc_port_max"`
// This is the name of the image (QCOW2 or IMG) file for
// the new virtual machine. By default this is packer-BUILDNAME, where
// "BUILDNAME" is the name of the build. Currently, no file extension will be
// used unless it is specified in this option.
VMName string `mapstructure:"vm_name" required:"false"`
// The interface to use for the CDROM device which contains the ISO image.
// Allowed values include any of `ide`, `scsi`, `virtio` or
// `virtio-scsi`. The Qemu builder uses `virtio` by default.
// Some ARM64 images require `virtio-scsi`.
CDROMInterface string `mapstructure:"cdrom_interface" required:"false"`
// TODO(mitchellh): deprecate
RunOnce bool `mapstructure:"run_once"`
ctx interpolate.Context
}
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
warnings, errs := b.config.Prepare(raws...)
if errs != nil {
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"boot_command",
"qemuargs",
},
},
}, raws...)
if err != nil {
return nil, nil, err
}
var errs *packer.MultiError
warnings := make([]string, 0)
errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...)
if b.config.DiskSize == "" || b.config.DiskSize == "0" {
b.config.DiskSize = "40960M"
} else {
// Make sure supplied disk size is valid
// (digits, plus an optional valid unit character). e.g. 5000, 40G, 1t
re := regexp.MustCompile(`^[\d]+(b|k|m|g|t){0,1}$`)
matched := re.MatchString(strings.ToLower(b.config.DiskSize))
if !matched {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Invalid disk size."))
} else {
// Okay, it's valid -- if it doesn't alreay have a suffix, then
// append "M" as the default unit.
re = regexp.MustCompile(`^[\d]+$`)
matched = re.MatchString(strings.ToLower(b.config.DiskSize))
if matched {
// Needs M added.
b.config.DiskSize = fmt.Sprintf("%sM", b.config.DiskSize)
}
}
}
if b.config.DiskCache == "" {
b.config.DiskCache = "writeback"
}
if b.config.DiskDiscard == "" {
b.config.DiskDiscard = "ignore"
}
if b.config.DetectZeroes == "" {
b.config.DetectZeroes = "off"
}
if b.config.Accelerator == "" {
if runtime.GOOS == "windows" {
b.config.Accelerator = "tcg"
} else {
// /dev/kvm is a kernel module that may be loaded if kvm is
// installed and the host supports VT-x extensions. To make sure
// this will actually work we need to os.Open() it. If os.Open fails
// the kernel module was not installed or loaded correctly.
if fp, err := os.Open("/dev/kvm"); err != nil {
b.config.Accelerator = "tcg"
} else {
fp.Close()
b.config.Accelerator = "kvm"
}
}
log.Printf("use detected accelerator: %s", b.config.Accelerator)
} else {
log.Printf("use specified accelerator: %s", b.config.Accelerator)
}
if b.config.MachineType == "" {
b.config.MachineType = "pc"
}
if b.config.OutputDir == "" {
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
}
if b.config.QemuBinary == "" {
b.config.QemuBinary = "qemu-system-x86_64"
}
if b.config.MemorySize < 10 {
log.Printf("MemorySize %d is too small, using default: 512", b.config.MemorySize)
b.config.MemorySize = 512
}
if b.config.CpuCount < 1 {
log.Printf("CpuCount %d too small, using default: 1", b.config.CpuCount)
b.config.CpuCount = 1
}
if b.config.VNCBindAddress == "" {
b.config.VNCBindAddress = "127.0.0.1"
}
if b.config.VNCPortMin == 0 {
b.config.VNCPortMin = 5900
}
if b.config.VNCPortMax == 0 {
b.config.VNCPortMax = 6000
}
if b.config.VMName == "" {
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
}
if b.config.Format == "" {
b.config.Format = "qcow2"
}
errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.VNCConfig.Prepare(&b.config.ctx)...)
if b.config.NetDevice == "" {
b.config.NetDevice = "virtio-net"
}
if b.config.DiskInterface == "" {
b.config.DiskInterface = "virtio"
}
if b.config.ISOSkipCache {
b.config.ISOChecksum = "none"
}
isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx)
warnings = append(warnings, isoWarnings...)
errs = packer.MultiErrorAppend(errs, isoErrs...)
errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...)
commConfigWarnings, es := b.config.CommConfig.Prepare(&b.config.ctx)
if len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
warnings = append(warnings, commConfigWarnings...)
if !(b.config.Format == "qcow2" || b.config.Format == "raw") {
errs = packer.MultiErrorAppend(
errs, errors.New("invalid format, only 'qcow2' or 'raw' are allowed"))
}
if b.config.Format != "qcow2" {
b.config.SkipCompaction = true
b.config.DiskCompression = false
}
if b.config.UseBackingFile && !(b.config.DiskImage && b.config.Format == "qcow2") {
errs = packer.MultiErrorAppend(
errs, errors.New("use_backing_file can only be enabled for QCOW2 images and when disk_image is true"))
}
if b.config.DiskImage && len(b.config.AdditionalDiskSize) > 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("disk_additional_size can only be used when disk_image is false"))
}
if _, ok := accels[b.config.Accelerator]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("invalid accelerator, only 'kvm', 'tcg', 'xen', 'hax', 'hvf', 'whpx', or 'none' are allowed"))
}
if _, ok := diskInterface[b.config.DiskInterface]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("unrecognized disk interface type"))
}
if _, ok := diskCache[b.config.DiskCache]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("unrecognized disk cache type"))
}
if _, ok := diskDiscard[b.config.DiskDiscard]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("unrecognized disk discard type"))
}
if _, ok := diskDZeroes[b.config.DetectZeroes]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("unrecognized disk detect zeroes setting"))
}
if !b.config.PackerForce {
if _, err := os.Stat(b.config.OutputDir); err == nil {
errs = packer.MultiErrorAppend(
errs,
fmt.Errorf("Output directory '%s' already exists. It must not exist.", b.config.OutputDir))
}
}
if b.config.VNCPortMin > b.config.VNCPortMax {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
}
if b.config.NetBridge != "" && runtime.GOOS != "linux" {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("net_bridge is only supported in Linux based OSes"))
}
if b.config.NetBridge != "" || b.config.VNCUsePassword {
b.config.QMPEnable = true
}
if b.config.QMPEnable && b.config.QMPSocketPath == "" {
socketName := fmt.Sprintf("%s.monitor", b.config.VMName)
b.config.QMPSocketPath = filepath.Join(b.config.OutputDir, socketName)
}
if b.config.QemuArgs == nil {
b.config.QemuArgs = make([][]string, 0)
}
if errs != nil && len(errs.Errors) > 0 {
return nil, warnings, errs
}
@@ -41,6 +579,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
return nil, fmt.Errorf("Failed creating Qemu driver: %s", err)
}
steprun := &stepRun{}
if !b.config.DiskImage {
steprun.BootDrive = "once=d"
steprun.Message = "Starting VM, booting from CD-ROM"
} else {
steprun.BootDrive = "c"
steprun.Message = "Starting VM, booting disk image"
}
steps := []multistep.Step{}
if !b.config.ISOSkipCache {
steps = append(steps, &common.StepDownload{
@@ -50,12 +597,14 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
ResultKey: "iso_path",
TargetPath: b.config.TargetPath,
Url: b.config.ISOUrls,
})
},
)
} else {
steps = append(steps, &stepSetISO{
ResultKey: "iso_path",
Url: b.config.ISOUrls,
})
},
)
}
steps = append(steps, new(stepPrepareOutputDir),
@@ -64,37 +613,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Directories: b.config.FloppyConfig.FloppyDirectories,
Label: b.config.FloppyConfig.FloppyLabel,
},
&common.StepCreateCD{
Files: b.config.CDConfig.CDFiles,
Label: b.config.CDConfig.CDLabel,
},
&stepCreateDisk{
AdditionalDiskSize: b.config.AdditionalDiskSize,
DiskImage: b.config.DiskImage,
DiskSize: b.config.DiskSize,
Format: b.config.Format,
OutputDir: b.config.OutputDir,
UseBackingFile: b.config.UseBackingFile,
VMName: b.config.VMName,
QemuImgArgs: b.config.QemuImgArgs,
},
&stepCopyDisk{
DiskImage: b.config.DiskImage,
Format: b.config.Format,
OutputDir: b.config.OutputDir,
UseBackingFile: b.config.UseBackingFile,
VMName: b.config.VMName,
},
&stepResizeDisk{
DiskCompression: b.config.DiskCompression,
DiskImage: b.config.DiskImage,
Format: b.config.Format,
OutputDir: b.config.OutputDir,
SkipResizeDisk: b.config.SkipResizeDisk,
VMName: b.config.VMName,
DiskSize: b.config.DiskSize,
QemuImgArgs: b.config.QemuImgArgs,
},
new(stepCreateDisk),
new(stepCopyDisk),
new(stepResizeDisk),
new(stepHTTPIPDiscover),
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
@@ -102,43 +623,58 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
HTTPPortMax: b.config.HTTPPortMax,
HTTPAddress: b.config.HTTPAddress,
},
&stepPortForward{
CommunicatorType: b.config.CommConfig.Comm.Type,
NetBridge: b.config.NetBridge,
},
)
if b.config.CommConfig.Comm.Type != "none" && b.config.NetBridge == "" {
steps = append(steps,
new(stepPortForward),
)
}
steps = append(steps,
new(stepConfigureVNC),
&stepRun{
DiskImage: b.config.DiskImage,
},
steprun,
&stepConfigureQMP{
QMPSocketPath: b.config.QMPSocketPath,
},
&stepTypeBootCommand{},
&stepWaitGuestAddress{
CommunicatorType: b.config.CommConfig.Comm.Type,
NetBridge: b.config.NetBridge,
timeout: b.config.CommConfig.Comm.SSHTimeout,
},
&communicator.StepConnect{
Config: &b.config.CommConfig.Comm,
Host: commHost(b.config.CommConfig.Comm.Host()),
SSHConfig: b.config.CommConfig.Comm.SSHConfigFunc(),
SSHPort: commPort,
WinRMPort: commPort,
},
)
if b.config.CommConfig.Comm.Type != "none" && b.config.NetBridge != "" {
steps = append(steps,
&stepWaitGuestAddress{
timeout: b.config.CommConfig.Comm.SSHTimeout,
},
)
}
if b.config.CommConfig.Comm.Type != "none" {
steps = append(steps,
&communicator.StepConnect{
Config: &b.config.CommConfig.Comm,
Host: commHost(b.config.CommConfig.Comm.Host()),
SSHConfig: b.config.CommConfig.Comm.SSHConfigFunc(),
SSHPort: commPort,
WinRMPort: commPort,
},
)
}
steps = append(steps,
new(common.StepProvision),
)
steps = append(steps,
&common.StepCleanupTempKeys{
Comm: &b.config.CommConfig.Comm,
},
)
steps = append(steps,
new(stepShutdown),
&stepConvertDisk{
DiskCompression: b.config.DiskCompression,
Format: b.config.Format,
OutputDir: b.config.OutputDir,
SkipCompaction: b.config.SkipCompaction,
VMName: b.config.VMName,
QemuImgArgs: b.config.QemuImgArgs,
},
)
steps = append(steps,
new(stepConvertDisk),
)
// Setup the state bag
@@ -1,4 +1,4 @@
// Code generated by "mapstructure-to-hcl2 -type Config,QemuImgArgs"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
package qemu
import (
@@ -20,7 +20,6 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
@@ -88,15 +87,12 @@ type FlatConfig struct {
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"`
CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"`
ISOSkipCache *bool `mapstructure:"iso_skip_cache" required:"false" cty:"iso_skip_cache" hcl:"iso_skip_cache"`
Accelerator *string `mapstructure:"accelerator" required:"false" cty:"accelerator" hcl:"accelerator"`
AdditionalDiskSize []string `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
CpuCount *int `mapstructure:"cpus" required:"false" cty:"cpus" hcl:"cpus"`
DiskInterface *string `mapstructure:"disk_interface" required:"false" cty:"disk_interface" hcl:"disk_interface"`
DiskSize *string `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"`
SkipResizeDisk *bool `mapstructure:"skip_resize_disk" required:"false" cty:"skip_resize_disk" hcl:"skip_resize_disk"`
DiskCache *string `mapstructure:"disk_cache" required:"false" cty:"disk_cache" hcl:"disk_cache"`
DiskDiscard *string `mapstructure:"disk_discard" required:"false" cty:"disk_discard" hcl:"disk_discard"`
DetectZeroes *string `mapstructure:"disk_detect_zeroes" required:"false" cty:"disk_detect_zeroes" hcl:"disk_detect_zeroes"`
@@ -112,7 +108,6 @@ type FlatConfig struct {
NetBridge *string `mapstructure:"net_bridge" required:"false" cty:"net_bridge" hcl:"net_bridge"`
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"`
QemuArgs [][]string `mapstructure:"qemuargs" required:"false" cty:"qemuargs" hcl:"qemuargs"`
QemuImgArgs *FlatQemuImgArgs `mapstructure:"qemu_img_args" required:"false" cty:"qemu_img_args" hcl:"qemu_img_args"`
QemuBinary *string `mapstructure:"qemu_binary" required:"false" cty:"qemu_binary" hcl:"qemu_binary"`
QMPEnable *bool `mapstructure:"qmp_enable" required:"false" cty:"qmp_enable" hcl:"qmp_enable"`
QMPSocketPath *string `mapstructure:"qmp_socket_path" required:"false" cty:"qmp_socket_path" hcl:"qmp_socket_path"`
@@ -150,7 +145,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
@@ -218,15 +212,12 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false},
"cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false},
"iso_skip_cache": &hcldec.AttrSpec{Name: "iso_skip_cache", Type: cty.Bool, Required: false},
"accelerator": &hcldec.AttrSpec{Name: "accelerator", Type: cty.String, Required: false},
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.String), Required: false},
"cpus": &hcldec.AttrSpec{Name: "cpus", Type: cty.Number, Required: false},
"disk_interface": &hcldec.AttrSpec{Name: "disk_interface", Type: cty.String, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.String, Required: false},
"skip_resize_disk": &hcldec.AttrSpec{Name: "skip_resize_disk", Type: cty.Bool, Required: false},
"disk_cache": &hcldec.AttrSpec{Name: "disk_cache", Type: cty.String, Required: false},
"disk_discard": &hcldec.AttrSpec{Name: "disk_discard", Type: cty.String, Required: false},
"disk_detect_zeroes": &hcldec.AttrSpec{Name: "disk_detect_zeroes", Type: cty.String, Required: false},
@@ -242,7 +233,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"net_bridge": &hcldec.AttrSpec{Name: "net_bridge", Type: cty.String, Required: false},
"output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false},
"qemuargs": &hcldec.AttrSpec{Name: "qemuargs", Type: cty.List(cty.List(cty.String)), Required: false},
"qemu_img_args": &hcldec.BlockSpec{TypeName: "qemu_img_args", Nested: hcldec.ObjectSpec((*FlatQemuImgArgs)(nil).HCL2Spec())},
"qemu_binary": &hcldec.AttrSpec{Name: "qemu_binary", Type: cty.String, Required: false},
"qmp_enable": &hcldec.AttrSpec{Name: "qmp_enable", Type: cty.Bool, Required: false},
"qmp_socket_path": &hcldec.AttrSpec{Name: "qmp_socket_path", Type: cty.String, Required: false},
@@ -258,30 +248,3 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
}
return s
}
// FlatQemuImgArgs is an auto-generated flat version of QemuImgArgs.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatQemuImgArgs struct {
Convert []string `mapstructure:"convert" required:"false" cty:"convert" hcl:"convert"`
Create []string `mapstructure:"create" required:"false" cty:"create" hcl:"create"`
Resize []string `mapstructure:"resize" required:"false" cty:"resize" hcl:"resize"`
}
// FlatMapstructure returns a new FlatQemuImgArgs.
// FlatQemuImgArgs is an auto-generated flat version of QemuImgArgs.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*QemuImgArgs) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatQemuImgArgs)
}
// HCL2Spec returns the hcl spec of a QemuImgArgs.
// This spec is used by HCL to read the fields of QemuImgArgs.
// The decoded values from this spec will then be applied to a FlatQemuImgArgs.
func (*FlatQemuImgArgs) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"convert": &hcldec.AttrSpec{Name: "convert", Type: cty.List(cty.String), Required: false},
"create": &hcldec.AttrSpec{Name: "create", Type: cty.List(cty.String), Required: false},
"resize": &hcldec.AttrSpec{Name: "resize", Type: cty.List(cty.String), Required: false},
}
return s
}
+643
View File
@@ -1,11 +1,56 @@
package qemu
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/hashicorp/packer/packer"
)
var testPem = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu
hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW
LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN
AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD
2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH
uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3
5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV
BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG
E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko
9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF
K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3
/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+
2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa
nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn
kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6
hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC
v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl
b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR
v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3
uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1
9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR
lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc
eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa
1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG
3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4=
-----END RSA PRIVATE KEY-----
`
func testConfig() map[string]interface{} {
return map[string]interface{}{
"iso_checksum": "md5:0B0F137F17AC10944716020B018F8126",
"iso_url": "http://www.google.com/",
"ssh_username": "foo",
packer.BuildNameConfigKey: "foo",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
@@ -13,3 +58,601 @@ func TestBuilder_ImplementsBuilder(t *testing.T) {
t.Error("Builder must implement builder.")
}
}
func TestBuilderPrepare_Defaults(t *testing.T) {
var b Builder
config := testConfig()
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.OutputDir != "output-foo" {
t.Errorf("bad output dir: %s", b.config.OutputDir)
}
if b.config.CommConfig.HostPortMin != 2222 {
t.Errorf("bad min ssh host port: %d", b.config.CommConfig.HostPortMin)
}
if b.config.CommConfig.HostPortMax != 4444 {
t.Errorf("bad max ssh host port: %d", b.config.CommConfig.HostPortMax)
}
if b.config.CommConfig.Comm.SSHPort != 22 {
t.Errorf("bad ssh port: %d", b.config.CommConfig.Comm.SSHPort)
}
if b.config.VMName != "packer-foo" {
t.Errorf("bad vm name: %s", b.config.VMName)
}
if b.config.Format != "qcow2" {
t.Errorf("bad format: %s", b.config.Format)
}
}
func TestBuilderPrepare_VNCBindAddress(t *testing.T) {
var b Builder
config := testConfig()
// Test a default boot_wait
delete(config, "vnc_bind_address")
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
if b.config.VNCBindAddress != "127.0.0.1" {
t.Fatalf("bad value: %s", b.config.VNCBindAddress)
}
}
func TestBuilderPrepare_DiskCompaction(t *testing.T) {
var b Builder
config := testConfig()
// Bad
config["skip_compaction"] = false
config["disk_compression"] = true
config["format"] = "img"
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
if b.config.SkipCompaction != true {
t.Fatalf("SkipCompaction should be true")
}
if b.config.DiskCompression != false {
t.Fatalf("DiskCompression should be false")
}
// Good
config["skip_compaction"] = false
config["disk_compression"] = true
config["format"] = "qcow2"
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.SkipCompaction != false {
t.Fatalf("SkipCompaction should be false")
}
if b.config.DiskCompression != true {
t.Fatalf("DiskCompression should be true")
}
}
func TestBuilderPrepare_DiskSize(t *testing.T) {
type testcase struct {
InputSize string
OutputSize string
ErrExpected bool
}
testCases := []testcase{
{"", "40960M", false}, // not provided
{"12345", "12345M", false}, // no unit given, defaults to M
{"12345x", "12345x", true}, // invalid unit
{"12345T", "12345T", false}, // terabytes
{"12345b", "12345b", false}, // bytes get preserved when set.
{"60000M", "60000M", false}, // Original test case
}
for _, tc := range testCases {
// Set input disk size
var b Builder
config := testConfig()
delete(config, "disk_size")
config["disk_size"] = tc.InputSize
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if (err == nil) == tc.ErrExpected {
t.Fatalf("bad: error when providing disk size %s; Err expected: %t; err recieved: %v", tc.InputSize, tc.ErrExpected, err)
}
if b.config.DiskSize != tc.OutputSize {
t.Fatalf("bad size: received: %s but expected %s", b.config.DiskSize, tc.OutputSize)
}
}
}
func TestBuilderPrepare_AdditionalDiskSize(t *testing.T) {
var b Builder
config := testConfig()
config["disk_additional_size"] = []string{"1M"}
config["disk_image"] = true
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatalf("should have error")
}
delete(config, "disk_image")
config["disk_additional_size"] = []string{"1M"}
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.AdditionalDiskSize[0] != "1M" {
t.Fatalf("bad size: %s", b.config.AdditionalDiskSize)
}
}
func TestBuilderPrepare_Format(t *testing.T) {
var b Builder
config := testConfig()
// Bad
config["format"] = "illegal value"
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good
config["format"] = "qcow2"
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Good
config["format"] = "raw"
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_UseBackingFile(t *testing.T) {
var b Builder
config := testConfig()
config["use_backing_file"] = true
// Bad: iso_url is not a disk_image
config["disk_image"] = false
config["format"] = "qcow2"
b = Builder{}
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Bad: format is not 'qcow2'
config["disk_image"] = true
config["format"] = "raw"
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good: iso_url is a disk image and format is 'qcow2'
config["disk_image"] = true
config["format"] = "qcow2"
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "floppy_files")
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("bad err: %s", err)
}
if len(b.config.FloppyFiles) != 0 {
t.Fatalf("bad: %#v", b.config.FloppyFiles)
}
floppies_path := "../../common/test-fixtures/floppies"
config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
t.Fatalf("bad: %#v", b.config.FloppyFiles)
}
}
func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
var b Builder
config := testConfig()
config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"}
b = Builder{}
_, _, errs := b.Prepare(config)
if errs == nil {
t.Fatalf("Nonexistent floppies should trigger multierror")
}
if len(errs.(*packer.MultiError).Errors) != 2 {
t.Fatalf("Multierror should work and report 2 errors")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_OutputDir(t *testing.T) {
var b Builder
config := testConfig()
// Test with existing dir
dir, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(dir)
config["output_directory"] = dir
b = Builder{}
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test with a good one
config["output_directory"] = "i-hope-i-dont-exist"
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_ShutdownTimeout(t *testing.T) {
var b Builder
config := testConfig()
// Test with a bad value
config["shutdown_timeout"] = "this is not good"
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test with a good one
config["shutdown_timeout"] = "5s"
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_SSHHostPort(t *testing.T) {
var b Builder
config := testConfig()
// Bad
config["host_port_min"] = 1000
config["host_port_max"] = 500
b = Builder{}
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Bad
config["host_port_min"] = -500
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good
config["host_port_min"] = 500
config["host_port_max"] = 1000
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_SSHPrivateKey(t *testing.T) {
var b Builder
config := testConfig()
config["ssh_private_key_file"] = ""
b = Builder{}
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
config["ssh_private_key_file"] = "/i/dont/exist"
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test bad contents
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
if _, err := tf.Write([]byte("HELLO!")); err != nil {
t.Fatalf("err: %s", err)
}
config["ssh_private_key_file"] = tf.Name()
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test good contents
tf.Seek(0, 0)
tf.Truncate(0)
tf.Write([]byte(testPem))
config["ssh_private_key_file"] = tf.Name()
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) {
var b Builder
config := testConfig()
// Test a default boot_wait
delete(config, "ssh_timeout")
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
// Test with a bad value
config["ssh_timeout"] = "this is not good"
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test with a good one
config["ssh_timeout"] = "5s"
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_QemuArgs(t *testing.T) {
var b Builder
config := testConfig()
// Test with empty
delete(config, "qemuargs")
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(b.config.QemuArgs, [][]string{}) {
t.Fatalf("bad: %#v", b.config.QemuArgs)
}
// Test with a good one
config["qemuargs"] = [][]interface{}{
{"foo", "bar", "baz"},
}
b = Builder{}
_, warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
expected := [][]string{
{"foo", "bar", "baz"},
}
if !reflect.DeepEqual(b.config.QemuArgs, expected) {
t.Fatalf("bad: %#v", b.config.QemuArgs)
}
}
func TestBuilderPrepare_VNCPassword(t *testing.T) {
var b Builder
config := testConfig()
config["vnc_use_password"] = true
config["output_directory"] = "not-a-real-directory"
b = Builder{}
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
expected := filepath.Join("not-a-real-directory", "packer-foo.monitor")
if !reflect.DeepEqual(b.config.QMPSocketPath, expected) {
t.Fatalf("Bad QMP socket Path: %s", b.config.QMPSocketPath)
}
}
func TestCommConfigPrepare_BackwardsCompatibility(t *testing.T) {
var b Builder
config := testConfig()
hostPortMin := 1234
hostPortMax := 4321
sshTimeout := 2 * time.Minute
config["ssh_wait_timeout"] = sshTimeout
config["ssh_host_port_min"] = hostPortMin
config["ssh_host_port_max"] = hostPortMax
_, warns, err := b.Prepare(config)
if len(warns) == 0 {
t.Fatalf("should have deprecation warn")
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.CommConfig.Comm.SSHTimeout != sshTimeout {
t.Fatalf("SSHTimeout should be %s for backwards compatibility, but it was %s", sshTimeout.String(), b.config.CommConfig.Comm.SSHTimeout.String())
}
if b.config.CommConfig.HostPortMin != hostPortMin {
t.Fatalf("HostPortMin should be %d for backwards compatibility, but it was %d", hostPortMin, b.config.CommConfig.HostPortMin)
}
if b.config.CommConfig.HostPortMax != hostPortMax {
t.Fatalf("HostPortMax should be %d for backwards compatibility, but it was %d", hostPortMax, b.config.CommConfig.HostPortMax)
}
}
-619
View File
@@ -1,619 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type Config,QemuImgArgs
package qemu
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/common/shutdowncommand"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
var accels = map[string]struct{}{
"none": {},
"kvm": {},
"tcg": {},
"xen": {},
"hax": {},
"hvf": {},
"whpx": {},
}
var diskInterface = map[string]bool{
"ide": true,
"scsi": true,
"virtio": true,
"virtio-scsi": true,
}
var diskCache = map[string]bool{
"writethrough": true,
"writeback": true,
"none": true,
"unsafe": true,
"directsync": true,
}
var diskDiscard = map[string]bool{
"unmap": true,
"ignore": true,
}
var diskDZeroes = map[string]bool{
"unmap": true,
"on": true,
"off": true,
}
type QemuImgArgs struct {
Convert []string `mapstructure:"convert" required:"false"`
Create []string `mapstructure:"create" required:"false"`
Resize []string `mapstructure:"resize" required:"false"`
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
common.HTTPConfig `mapstructure:",squash"`
common.ISOConfig `mapstructure:",squash"`
bootcommand.VNCConfig `mapstructure:",squash"`
shutdowncommand.ShutdownConfig `mapstructure:",squash"`
CommConfig CommConfig `mapstructure:",squash"`
common.FloppyConfig `mapstructure:",squash"`
common.CDConfig `mapstructure:",squash"`
// Use iso from provided url. Qemu must support
// curl block device. This defaults to `false`.
ISOSkipCache bool `mapstructure:"iso_skip_cache" required:"false"`
// The accelerator type to use when running the VM.
// This may be `none`, `kvm`, `tcg`, `hax`, `hvf`, `whpx`, or `xen`. The appropriate
// software must have already been installed on your build machine to use the
// accelerator you specified. When no accelerator is specified, Packer will try
// to use `kvm` if it is available but will default to `tcg` otherwise.
//
// ~> The `hax` accelerator has issues attaching CDROM ISOs. This is an
// upstream issue which can be tracked
// [here](https://github.com/intel/haxm/issues/20).
//
// ~> The `hvf` and `whpx` accelerator are new and experimental as of
// [QEMU 2.12.0](https://wiki.qemu.org/ChangeLog/2.12#Host_support).
// You may encounter issues unrelated to Packer when using these. You may need to
// add [ "-global", "virtio-pci.disable-modern=on" ] to `qemuargs` depending on the
// guest operating system.
//
// ~> For `whpx`, note that [Stefan Weil's QEMU for Windows distribution](https://qemu.weilnetz.de/w64/)
// does not include WHPX support and users may need to compile or source a
// build of QEMU for Windows themselves with WHPX support.
Accelerator string `mapstructure:"accelerator" required:"false"`
// Additional disks to create. Uses `vm_name` as the disk name template and
// appends `-#` where `#` is the position in the array. `#` starts at 1 since 0
// is the default disk. Each string represents the disk image size in bytes.
// Optional suffixes 'k' or 'K' (kilobyte, 1024), 'M' (megabyte, 1024k), 'G'
// (gigabyte, 1024M), 'T' (terabyte, 1024G), 'P' (petabyte, 1024T) and 'E'
// (exabyte, 1024P) are supported. 'b' is ignored. Per qemu-img documentation.
// Each additional disk uses the same disk parameters as the default disk.
// Unset by default.
AdditionalDiskSize []string `mapstructure:"disk_additional_size" required:"false"`
// The number of cpus to use when building the VM.
// The default is `1` CPU.
CpuCount int `mapstructure:"cpus" required:"false"`
// The interface to use for the disk. Allowed values include any of `ide`,
// `scsi`, `virtio` or `virtio-scsi`^\*. Note also that any boot commands
// or kickstart type scripts must have proper adjustments for resulting
// device names. The Qemu builder uses `virtio` by default.
//
// ^\* Please be aware that use of the `scsi` disk interface has been
// disabled by Red Hat due to a bug described
// [here](https://bugzilla.redhat.com/show_bug.cgi?id=1019220). If you are
// running Qemu on RHEL or a RHEL variant such as CentOS, you *must* choose
// one of the other listed interfaces. Using the `scsi` interface under
// these circumstances will cause the build to fail.
DiskInterface string `mapstructure:"disk_interface" required:"false"`
// The size in bytes of the hard disk of the VM. Suffix with the first
// letter of common byte types. Use "k" or "K" for kilobytes, "M" for
// megabytes, G for gigabytes, and T for terabytes. If no value is provided
// for disk_size, Packer uses a default of `40960M` (40 GB). If a disk_size
// number is provided with no units, Packer will default to Megabytes.
DiskSize string `mapstructure:"disk_size" required:"false"`
// Packer resizes the QCOW2 image using
// qemu-img resize. Set this option to true to disable resizing.
// Defaults to false.
SkipResizeDisk bool `mapstructure:"skip_resize_disk" required:"false"`
// The cache mode to use for disk. Allowed values include any of
// `writethrough`, `writeback`, `none`, `unsafe` or `directsync`. By
// default, this is set to `writeback`.
DiskCache string `mapstructure:"disk_cache" required:"false"`
// The discard mode to use for disk. Allowed values
// include any of unmap or ignore. By default, this is set to ignore.
DiskDiscard string `mapstructure:"disk_discard" required:"false"`
// The detect-zeroes mode to use for disk.
// Allowed values include any of unmap, on or off. Defaults to off.
// When the value is "off" we don't set the flag in the qemu command, so that
// Packer still works with old versions of QEMU that don't have this option.
DetectZeroes string `mapstructure:"disk_detect_zeroes" required:"false"`
// Packer compacts the QCOW2 image using
// qemu-img convert. Set this option to true to disable compacting.
// Defaults to false.
SkipCompaction bool `mapstructure:"skip_compaction" required:"false"`
// Apply compression to the QCOW2 disk file
// using qemu-img convert. Defaults to false.
DiskCompression bool `mapstructure:"disk_compression" required:"false"`
// Either `qcow2` or `raw`, this specifies the output format of the virtual
// machine image. This defaults to `qcow2`. Due to a long-standing bug with
// `qemu-img convert` on OSX, sometimes the qemu-img convert call will
// create a corrupted image. If this is an issue for you, make sure that the
// the output format matches the input file's format, and Packer will
// perform a simple copy operation instead. See
// https://bugs.launchpad.net/qemu/+bug/1776920 for more details.
Format string `mapstructure:"format" required:"false"`
// Packer defaults to building QEMU virtual machines by
// launching a GUI that shows the console of the machine being built. When this
// value is set to `true`, the machine will start without a console.
//
// You can still see the console if you make a note of the VNC display
// number chosen, and then connect using `vncviewer -Shared <host>:<display>`
Headless bool `mapstructure:"headless" required:"false"`
// Packer defaults to building from an ISO file, this parameter controls
// whether the ISO URL supplied is actually a bootable QEMU image. When
// this value is set to `true`, the machine will either clone the source or
// use it as a backing file (if `use_backing_file` is `true`); then, it
// will resize the image according to `disk_size` and boot it.
DiskImage bool `mapstructure:"disk_image" required:"false"`
// Only applicable when disk_image is true
// and format is qcow2, set this option to true to create a new QCOW2
// file that uses the file located at iso_url as a backing file. The new file
// will only contain blocks that have changed compared to the backing file, so
// enabling this option can significantly reduce disk usage. If true, Packer
// will force the `skip_compaction` also to be true as well to skip disk
// conversion which would render the backing file feature useless.
UseBackingFile bool `mapstructure:"use_backing_file" required:"false"`
// The type of machine emulation to use. Run your qemu binary with the
// flags `-machine help` to list available types for your system. This
// defaults to `pc`.
MachineType string `mapstructure:"machine_type" required:"false"`
// The amount of memory to use when building the VM
// in megabytes. This defaults to 512 megabytes.
MemorySize int `mapstructure:"memory" required:"false"`
// The driver to use for the network interface. Allowed values `ne2k_pci`,
// `i82551`, `i82557b`, `i82559er`, `rtl8139`, `e1000`, `pcnet`, `virtio`,
// `virtio-net`, `virtio-net-pci`, `usb-net`, `i82559a`, `i82559b`,
// `i82559c`, `i82550`, `i82562`, `i82557a`, `i82557c`, `i82801`,
// `vmxnet3`, `i82558a` or `i82558b`. The Qemu builder uses `virtio-net` by
// default.
NetDevice string `mapstructure:"net_device" required:"false"`
// Connects the network to this bridge instead of using the user mode
// networking.
//
// **NB** This bridge must already exist. You can use the `virbr0` bridge
// as created by vagrant-libvirt.
//
// **NB** This will automatically enable the QMP socket (see QMPEnable).
//
// **NB** This only works in Linux based OSes.
NetBridge string `mapstructure:"net_bridge" required:"false"`
// This is the path to the directory where the
// resulting virtual machine will be created. This may be relative or absolute.
// If relative, the path is relative to the working directory when packer
// is executed. This directory must not exist or be empty prior to running
// the builder. By default this is output-BUILDNAME where "BUILDNAME" is the
// name of the build.
OutputDir string `mapstructure:"output_directory" required:"false"`
// Allows complete control over the qemu command line (though not, at this
// time, qemu-img). Each array of strings makes up a command line switch
// that overrides matching default switch/value pairs. Any value specified
// as an empty string is ignored. All values after the switch are
// concatenated with no separator.
//
// ~> **Warning:** The qemu command line allows extreme flexibility, so
// beware of conflicting arguments causing failures of your run. For
// instance, using --no-acpi could break the ability to send power signal
// type commands (e.g., shutdown -P now) to the virtual machine, thus
// preventing proper shutdown. To see the defaults, look in the packer.log
// file and search for the qemu-system-x86 command. The arguments are all
// printed for review.
//
// The following shows a sample usage:
//
// In JSON:
// ```json
// "qemuargs": [
// [ "-m", "1024M" ],
// [ "--no-acpi", "" ],
// [
// "-netdev",
// "user,id=mynet0,",
// "hostfwd=hostip:hostport-guestip:guestport",
// ""
// ],
// [ "-device", "virtio-net,netdev=mynet0" ]
// ]
// ```
//
// In HCL2:
// ```hcl
// qemuargs = [
// [ "-m", "1024M" ],
// [ "--no-acpi", "" ],
// [
// "-netdev",
// "user,id=mynet0,",
// "hostfwd=hostip:hostport-guestip:guestport",
// ""
// ],
// [ "-device", "virtio-net,netdev=mynet0" ]
// ]
// ```
//
// would produce the following (not including other defaults supplied by
// the builder and not otherwise conflicting with the qemuargs):
//
// ```text
// qemu-system-x86 -m 1024m --no-acpi -netdev
// user,id=mynet0,hostfwd=hostip:hostport-guestip:guestport -device
// virtio-net,netdev=mynet0"
// ```
//
// ~> **Windows Users:** [QEMU for Windows](https://qemu.weilnetz.de/)
// builds are available though an environmental variable does need to be
// set for QEMU for Windows to redirect stdout to the console instead of
// stdout.txt.
//
// The following shows the environment variable that needs to be set for
// Windows QEMU support:
//
// ```text
// setx SDL_STDIO_REDIRECT=0
// ```
//
// You can also use the `SSHHostPort` template variable to produce a packer
// template that can be invoked by `make` in parallel:
//
// In JSON:
// ```json
// "qemuargs": [
// [ "-netdev", "user,hostfwd=tcp::{{ .SSHHostPort }}-:22,id=forward"],
// [ "-device", "virtio-net,netdev=forward,id=net0"]
// ]
// ```
//
// In HCL2:
// ```hcl
// qemuargs = [
// [ "-netdev", "user,hostfwd=tcp::{{ .SSHHostPort }}-:22,id=forward"],
// [ "-device", "virtio-net,netdev=forward,id=net0"]
// ]
//
// `make -j 3 my-awesome-packer-templates` spawns 3 packer processes, each
// of which will bind to their own SSH port as determined by each process.
// This will also work with WinRM, just change the port forward in
// `qemuargs` to map to WinRM's default port of `5985` or whatever value
// you have the service set to listen on.
//
// This is a template engine and allows access to the following variables:
// `{{ .HTTPIP }}`, `{{ .HTTPPort }}`, `{{ .HTTPDir }}`,
// `{{ .OutputDir }}`, `{{ .Name }}`, and `{{ .SSHHostPort }}`
QemuArgs [][]string `mapstructure:"qemuargs" required:"false"`
// A map of custom arguments to pass to qemu-img commands, where the key
// is the subcommand, and the values are lists of strings for each flag.
// Example:
//
// In JSON:
// ```json
// {
// "qemu_img_args": {
// "convert": ["-o", "preallocation=full"],
// "resize": ["-foo", "bar"]
// }
// ```
// Please note
// that unlike qemuargs, these commands are not split into switch-value
// sub-arrays, because the basic elements in qemu-img calls are unlikely
// to need an actual override.
// The arguments will be constructed as follows:
// - Convert:
// Default is `qemu-img convert -O $format $sourcepath $targetpath`. Adding
// arguments ["-foo", "bar"] to qemu_img_args.convert will change this to
// `qemu-img convert -foo bar -O $format $sourcepath $targetpath`
// - Create:
// Default is `create -f $format $targetpath $size`. Adding arguments
// ["-foo", "bar"] to qemu_img_args.create will change this to
// "create -f qcow2 -foo bar target.qcow2 1234M"
// - Resize:
// Default is `qemu-img resize -f $format $sourcepath $size`. Adding
// arguments ["-foo", "bar"] to qemu_img_args.resize will change this to
// `qemu-img resize -f $format -foo bar $sourcepath $size`
QemuImgArgs QemuImgArgs `mapstructure:"qemu_img_args" required:"false"`
// The name of the Qemu binary to look for. This
// defaults to qemu-system-x86_64, but may need to be changed for
// some platforms. For example qemu-kvm, or qemu-system-i386 may be a
// better choice for some systems.
QemuBinary string `mapstructure:"qemu_binary" required:"false"`
// Enable QMP socket. Location is specified by `qmp_socket_path`. Defaults
// to false.
QMPEnable bool `mapstructure:"qmp_enable" required:"false"`
// QMP Socket Path when `qmp_enable` is true. Defaults to
// `output_directory`/`vm_name`.monitor.
QMPSocketPath string `mapstructure:"qmp_socket_path" required:"false"`
// If true, do not pass a -display option
// to qemu, allowing it to choose the default. This may be needed when running
// under macOS, and getting errors about sdl not being available.
UseDefaultDisplay bool `mapstructure:"use_default_display" required:"false"`
// What QEMU -display option to use. Defaults to gtk, use none to not pass the
// -display option allowing QEMU to choose the default. This may be needed when
// running under macOS, and getting errors about sdl not being available.
Display string `mapstructure:"display" required:"false"`
// The IP address that should be
// binded to for VNC. By default packer will use 127.0.0.1 for this. If you
// wish to bind to all interfaces use 0.0.0.0.
VNCBindAddress string `mapstructure:"vnc_bind_address" required:"false"`
// Whether or not to set a password on the VNC server. This option
// automatically enables the QMP socket. See `qmp_socket_path`. Defaults to
// `false`.
VNCUsePassword bool `mapstructure:"vnc_use_password" required:"false"`
// The minimum and maximum port
// to use for VNC access to the virtual machine. The builder uses VNC to type
// the initial boot_command. Because Packer generally runs in parallel,
// Packer uses a randomly chosen port in this range that appears available. By
// default this is 5900 to 6000. The minimum and maximum ports are inclusive.
VNCPortMin int `mapstructure:"vnc_port_min" required:"false"`
VNCPortMax int `mapstructure:"vnc_port_max"`
// This is the name of the image (QCOW2 or IMG) file for
// the new virtual machine. By default this is packer-BUILDNAME, where
// "BUILDNAME" is the name of the build. Currently, no file extension will be
// used unless it is specified in this option.
VMName string `mapstructure:"vm_name" required:"false"`
// The interface to use for the CDROM device which contains the ISO image.
// Allowed values include any of `ide`, `scsi`, `virtio` or
// `virtio-scsi`. The Qemu builder uses `virtio` by default.
// Some ARM64 images require `virtio-scsi`.
CDROMInterface string `mapstructure:"cdrom_interface" required:"false"`
// TODO(mitchellh): deprecate
RunOnce bool `mapstructure:"run_once"`
ctx interpolate.Context
}
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
err := config.Decode(c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"boot_command",
"qemuargs",
},
},
}, raws...)
if err != nil {
return nil, err
}
// Accumulate any errors and warnings
var errs *packer.MultiError
warnings := make([]string, 0)
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(&c.ctx)...)
if c.DiskSize == "" || c.DiskSize == "0" {
c.DiskSize = "40960M"
} else {
// Make sure supplied disk size is valid
// (digits, plus an optional valid unit character). e.g. 5000, 40G, 1t
re := regexp.MustCompile(`^[\d]+(b|k|m|g|t){0,1}$`)
matched := re.MatchString(strings.ToLower(c.DiskSize))
if !matched {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Invalid disk size."))
} else {
// Okay, it's valid -- if it doesn't alreay have a suffix, then
// append "M" as the default unit.
re = regexp.MustCompile(`^[\d]+$`)
matched = re.MatchString(strings.ToLower(c.DiskSize))
if matched {
// Needs M added.
c.DiskSize = fmt.Sprintf("%sM", c.DiskSize)
}
}
}
if c.DiskCache == "" {
c.DiskCache = "writeback"
}
if c.DiskDiscard == "" {
c.DiskDiscard = "ignore"
}
if c.DetectZeroes == "" {
c.DetectZeroes = "off"
}
if c.Accelerator == "" {
if runtime.GOOS == "windows" {
c.Accelerator = "tcg"
} else {
// /dev/kvm is a kernel module that may be loaded if kvm is
// installed and the host supports VT-x extensions. To make sure
// this will actually work we need to os.Open() it. If os.Open fails
// the kernel module was not installed or loaded correctly.
if fp, err := os.Open("/dev/kvm"); err != nil {
c.Accelerator = "tcg"
} else {
fp.Close()
c.Accelerator = "kvm"
}
}
log.Printf("use detected accelerator: %s", c.Accelerator)
} else {
log.Printf("use specified accelerator: %s", c.Accelerator)
}
if c.MachineType == "" {
c.MachineType = "pc"
}
if c.OutputDir == "" {
c.OutputDir = fmt.Sprintf("output-%s", c.PackerBuildName)
}
if c.QemuBinary == "" {
c.QemuBinary = "qemu-system-x86_64"
}
if c.MemorySize < 10 {
log.Printf("MemorySize %d is too small, using default: 512", c.MemorySize)
c.MemorySize = 512
}
if c.CpuCount < 1 {
log.Printf("CpuCount %d too small, using default: 1", c.CpuCount)
c.CpuCount = 1
}
if c.VNCBindAddress == "" {
c.VNCBindAddress = "127.0.0.1"
}
if c.VNCPortMin == 0 {
c.VNCPortMin = 5900
}
if c.VNCPortMax == 0 {
c.VNCPortMax = 6000
}
if c.VMName == "" {
c.VMName = fmt.Sprintf("packer-%s", c.PackerBuildName)
}
if c.Format == "" {
c.Format = "qcow2"
}
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.CDConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...)
if c.NetDevice == "" {
c.NetDevice = "virtio-net"
}
if c.DiskInterface == "" {
c.DiskInterface = "virtio"
}
if c.ISOSkipCache {
c.ISOChecksum = "none"
}
isoWarnings, isoErrs := c.ISOConfig.Prepare(&c.ctx)
warnings = append(warnings, isoWarnings...)
errs = packer.MultiErrorAppend(errs, isoErrs...)
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
commConfigWarnings, es := c.CommConfig.Prepare(&c.ctx)
if len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
warnings = append(warnings, commConfigWarnings...)
if !(c.Format == "qcow2" || c.Format == "raw") {
errs = packer.MultiErrorAppend(
errs, errors.New("invalid format, only 'qcow2' or 'raw' are allowed"))
}
if c.Format != "qcow2" {
c.SkipCompaction = true
c.DiskCompression = false
}
if c.UseBackingFile {
c.SkipCompaction = true
if !(c.DiskImage && c.Format == "qcow2") {
errs = packer.MultiErrorAppend(
errs, errors.New("use_backing_file can only be enabled for QCOW2 images and when disk_image is true"))
}
}
if c.DiskImage && len(c.AdditionalDiskSize) > 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("disk_additional_size can only be used when disk_image is false"))
}
if c.SkipResizeDisk && !(c.DiskImage) {
errs = packer.MultiErrorAppend(
errs, errors.New("skip_resize_disk can only be used when disk_image is true"))
}
if _, ok := accels[c.Accelerator]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("invalid accelerator, only 'kvm', 'tcg', 'xen', 'hax', 'hvf', 'whpx', or 'none' are allowed"))
}
if _, ok := diskInterface[c.DiskInterface]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("unrecognized disk interface type"))
}
if _, ok := diskCache[c.DiskCache]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("unrecognized disk cache type"))
}
if _, ok := diskDiscard[c.DiskDiscard]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("unrecognized disk discard type"))
}
if _, ok := diskDZeroes[c.DetectZeroes]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("unrecognized disk detect zeroes setting"))
}
if !c.PackerForce {
if _, err := os.Stat(c.OutputDir); err == nil {
errs = packer.MultiErrorAppend(
errs,
fmt.Errorf("Output directory '%s' already exists. It must not exist.", c.OutputDir))
}
}
if c.VNCPortMin > c.VNCPortMax {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
}
if c.NetBridge != "" && runtime.GOOS != "linux" {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("net_bridge is only supported in Linux based OSes"))
}
if c.NetBridge != "" || c.VNCUsePassword {
c.QMPEnable = true
}
if c.QMPEnable && c.QMPSocketPath == "" {
socketName := fmt.Sprintf("%s.monitor", c.VMName)
c.QMPSocketPath = filepath.Join(c.OutputDir, socketName)
}
if c.QemuArgs == nil {
c.QemuArgs = make([][]string, 0)
}
if errs != nil && len(errs.Errors) > 0 {
return warnings, errs
}
return warnings, nil
}
-694
View File
@@ -1,694 +0,0 @@
package qemu
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/hashicorp/packer/packer"
"github.com/stretchr/testify/assert"
)
var testPem = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu
hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW
LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN
AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD
2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH
uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3
5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV
BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG
E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko
9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF
K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3
/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+
2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa
nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn
kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6
hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC
v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl
b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR
v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3
uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1
9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR
lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc
eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa
1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG
3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4=
-----END RSA PRIVATE KEY-----
`
func testConfig() map[string]interface{} {
return map[string]interface{}{
"iso_checksum": "md5:0B0F137F17AC10944716020B018F8126",
"iso_url": "http://www.google.com/",
"ssh_username": "foo",
packer.BuildNameConfigKey: "foo",
}
}
func TestBuilderPrepare_Defaults(t *testing.T) {
var c Config
config := testConfig()
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if c.OutputDir != "output-foo" {
t.Errorf("bad output dir: %s", c.OutputDir)
}
if c.CommConfig.HostPortMin != 2222 {
t.Errorf("bad min ssh host port: %d", c.CommConfig.HostPortMin)
}
if c.CommConfig.HostPortMax != 4444 {
t.Errorf("bad max ssh host port: %d", c.CommConfig.HostPortMax)
}
if c.CommConfig.Comm.SSHPort != 22 {
t.Errorf("bad ssh port: %d", c.CommConfig.Comm.SSHPort)
}
if c.VMName != "packer-foo" {
t.Errorf("bad vm name: %s", c.VMName)
}
if c.Format != "qcow2" {
t.Errorf("bad format: %s", c.Format)
}
}
func TestBuilderPrepare_VNCBindAddress(t *testing.T) {
var c Config
config := testConfig()
// Test a default boot_wait
delete(config, "vnc_bind_address")
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
if c.VNCBindAddress != "127.0.0.1" {
t.Fatalf("bad value: %s", c.VNCBindAddress)
}
}
func TestBuilderPrepare_DiskCompaction(t *testing.T) {
var c Config
config := testConfig()
// Bad
config["skip_compaction"] = false
config["disk_compression"] = true
config["format"] = "img"
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
if c.SkipCompaction != true {
t.Fatalf("SkipCompaction should be true")
}
if c.DiskCompression != false {
t.Fatalf("DiskCompression should be false")
}
// Good
config["skip_compaction"] = false
config["disk_compression"] = true
config["format"] = "qcow2"
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if c.SkipCompaction != false {
t.Fatalf("SkipCompaction should be false")
}
if c.DiskCompression != true {
t.Fatalf("DiskCompression should be true")
}
}
func TestBuilderPrepare_DiskSize(t *testing.T) {
type testcase struct {
InputSize string
OutputSize string
ErrExpected bool
}
testCases := []testcase{
{"", "40960M", false}, // not provided
{"12345", "12345M", false}, // no unit given, defaults to M
{"12345x", "12345x", true}, // invalid unit
{"12345T", "12345T", false}, // terabytes
{"12345b", "12345b", false}, // bytes get preserved when set.
{"60000M", "60000M", false}, // Original test case
}
for _, tc := range testCases {
// Set input disk size
var c Config
config := testConfig()
delete(config, "disk_size")
config["disk_size"] = tc.InputSize
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if (err == nil) == tc.ErrExpected {
t.Fatalf("bad: error when providing disk size %s; Err expected: %t; err recieved: %v", tc.InputSize, tc.ErrExpected, err)
}
if c.DiskSize != tc.OutputSize {
t.Fatalf("bad size: received: %s but expected %s", c.DiskSize, tc.OutputSize)
}
}
}
func TestBuilderPrepare_AdditionalDiskSize(t *testing.T) {
var c Config
config := testConfig()
config["disk_additional_size"] = []string{"1M"}
config["disk_image"] = true
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatalf("should have error")
}
delete(config, "disk_image")
config["disk_additional_size"] = []string{"1M"}
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if c.AdditionalDiskSize[0] != "1M" {
t.Fatalf("bad size: %s", c.AdditionalDiskSize)
}
}
func TestBuilderPrepare_Format(t *testing.T) {
var c Config
config := testConfig()
// Bad
config["format"] = "illegal value"
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good
config["format"] = "qcow2"
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Good
config["format"] = "raw"
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_UseBackingFile(t *testing.T) {
var c Config
config := testConfig()
config["use_backing_file"] = true
// Bad: iso_url is not a disk_image
config["disk_image"] = false
config["format"] = "qcow2"
c = Config{}
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Bad: format is not 'qcow2'
config["disk_image"] = true
config["format"] = "raw"
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good: iso_url is a disk image and format is 'qcow2'
config["disk_image"] = true
config["format"] = "qcow2"
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_SkipResizeDisk(t *testing.T) {
config := testConfig()
config["skip_resize_disk"] = true
config["disk_image"] = false
var c Config
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Errorf("unexpected warns when calling prepare with skip_resize_disk set to true: %#v", warns)
}
if err == nil {
t.Errorf("setting skip_resize_disk to true when disk_image is false should have error")
}
}
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
var c Config
config := testConfig()
delete(config, "floppy_files")
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("bad err: %s", err)
}
if len(c.FloppyFiles) != 0 {
t.Fatalf("bad: %#v", c.FloppyFiles)
}
floppies_path := "../../common/test-fixtures/floppies"
config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
if !reflect.DeepEqual(c.FloppyFiles, expected) {
t.Fatalf("bad: %#v", c.FloppyFiles)
}
}
func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
var c Config
config := testConfig()
config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"}
c = Config{}
_, errs := c.Prepare(config)
if errs == nil {
t.Fatalf("Nonexistent floppies should trigger multierror")
}
if len(errs.(*packer.MultiError).Errors) != 2 {
t.Fatalf("Multierror should work and report 2 errors")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var c Config
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_OutputDir(t *testing.T) {
var c Config
config := testConfig()
// Test with existing dir
dir, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(dir)
config["output_directory"] = dir
c = Config{}
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test with a good one
config["output_directory"] = "i-hope-i-dont-exist"
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_ShutdownTimeout(t *testing.T) {
var c Config
config := testConfig()
// Test with a bad value
config["shutdown_timeout"] = "this is not good"
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test with a good one
config["shutdown_timeout"] = "5s"
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_SSHHostPort(t *testing.T) {
var c Config
config := testConfig()
// Bad
config["host_port_min"] = 1000
config["host_port_max"] = 500
c = Config{}
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Bad
config["host_port_min"] = -500
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good
config["host_port_min"] = 500
config["host_port_max"] = 1000
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_SSHPrivateKey(t *testing.T) {
var c Config
config := testConfig()
config["ssh_private_key_file"] = ""
c = Config{}
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
config["ssh_private_key_file"] = "/i/dont/exist"
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test bad contents
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
if _, err := tf.Write([]byte("HELLO!")); err != nil {
t.Fatalf("err: %s", err)
}
config["ssh_private_key_file"] = tf.Name()
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test good contents
if _, err := tf.Seek(0, 0); err != nil {
t.Fatalf("errorf getting key")
}
if err := tf.Truncate(0); err != nil {
t.Fatalf("errorf getting key")
}
if _, err := tf.Write([]byte(testPem)); err != nil {
t.Fatalf("errorf getting key")
}
config["ssh_private_key_file"] = tf.Name()
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) {
var c Config
config := testConfig()
// Test a default boot_wait
delete(config, "ssh_timeout")
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
// Test with a bad value
config["ssh_timeout"] = "this is not good"
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test with a good one
config["ssh_timeout"] = "5s"
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_QemuArgs(t *testing.T) {
var c Config
config := testConfig()
// Test with empty
delete(config, "qemuargs")
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(c.QemuArgs, [][]string{}) {
t.Fatalf("bad: %#v", c.QemuArgs)
}
// Test with a good one
config["qemuargs"] = [][]interface{}{
{"foo", "bar", "baz"},
}
c = Config{}
warns, err = c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
expected := [][]string{
{"foo", "bar", "baz"},
}
if !reflect.DeepEqual(c.QemuArgs, expected) {
t.Fatalf("bad: %#v", c.QemuArgs)
}
}
func TestBuilderPrepare_VNCPassword(t *testing.T) {
var c Config
config := testConfig()
config["vnc_use_password"] = true
config["output_directory"] = "not-a-real-directory"
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
expected := filepath.Join("not-a-real-directory", "packer-foo.monitor")
if !reflect.DeepEqual(c.QMPSocketPath, expected) {
t.Fatalf("Bad QMP socket Path: %s", c.QMPSocketPath)
}
}
func TestCommConfigPrepare_BackwardsCompatibility(t *testing.T) {
var c Config
config := testConfig()
hostPortMin := 1234
hostPortMax := 4321
sshTimeout := 2 * time.Minute
config["ssh_wait_timeout"] = sshTimeout
config["ssh_host_port_min"] = hostPortMin
config["ssh_host_port_max"] = hostPortMax
warns, err := c.Prepare(config)
if len(warns) == 0 {
t.Fatalf("should have deprecation warn")
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if c.CommConfig.Comm.SSHTimeout != sshTimeout {
t.Fatalf("SSHTimeout should be %s for backwards compatibility, but it was %s", sshTimeout.String(), c.CommConfig.Comm.SSHTimeout.String())
}
if c.CommConfig.HostPortMin != hostPortMin {
t.Fatalf("HostPortMin should be %d for backwards compatibility, but it was %d", hostPortMin, c.CommConfig.HostPortMin)
}
if c.CommConfig.HostPortMax != hostPortMax {
t.Fatalf("HostPortMax should be %d for backwards compatibility, but it was %d", hostPortMax, c.CommConfig.HostPortMax)
}
}
func TestBuilderPrepare_LoadQemuImgArgs(t *testing.T) {
var c Config
config := testConfig()
config["qemu_img_args"] = map[string][]string{
"convert": []string{"-o", "preallocation=full"},
"resize": []string{"-foo", "bar"},
"create": []string{"-baz", "bang"},
}
warns, err := c.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
assert.Equal(t, []string{"-o", "preallocation=full"},
c.QemuImgArgs.Convert, "Convert args not loaded properly")
assert.Equal(t, []string{"-foo", "bar"},
c.QemuImgArgs.Resize, "Resize args not loaded properly")
assert.Equal(t, []string{"-baz", "bang"},
c.QemuImgArgs.Create, "Create args not loaded properly")
}
-32
View File
@@ -6,7 +6,6 @@ import (
"fmt"
"io"
"log"
"os"
"os/exec"
"regexp"
"strings"
@@ -23,10 +22,6 @@ type DriverCancelCallback func(state multistep.StateBag) bool
// A driver is able to talk to qemu-system-x86_64 and perform certain
// operations with it.
type Driver interface {
// Copy bypasses qemu-img convert and directly copies an image
// that doesn't need converting.
Copy(string, string) error
// Stop stops a running machine, forcefully.
Stop() error
@@ -70,33 +65,6 @@ func (d *QemuDriver) Stop() error {
return nil
}
func (d *QemuDriver) Copy(sourceName, targetName string) error {
source, err := os.Open(sourceName)
if err != nil {
err = fmt.Errorf("Error opening iso for copy: %s", err)
return err
}
defer source.Close()
// Create will truncate an existing file
target, err := os.Create(targetName)
if err != nil {
err = fmt.Errorf("Error creating hard drive in output dir: %s", err)
return err
}
defer target.Close()
log.Printf("Copying %s to %s", source.Name(), target.Name())
bytes, err := io.Copy(target, source)
if err != nil {
err = fmt.Errorf("Error copying iso to output dir: %s", err)
return err
}
log.Printf(fmt.Sprintf("Copied %d bytes", bytes))
return nil
}
func (d *QemuDriver) Qemu(qemuArgs ...string) error {
d.lock.Lock()
defer d.lock.Unlock()
-74
View File
@@ -1,74 +0,0 @@
package qemu
import "sync"
type DriverMock struct {
sync.Mutex
CopyCalled bool
CopyErr error
StopCalled bool
StopErr error
QemuCalls [][]string
QemuErrs []error
WaitForShutdownCalled bool
WaitForShutdownState bool
QemuImgCalled bool
QemuImgCalls []string
QemuImgErrs []error
VerifyCalled bool
VerifyErr error
VersionCalled bool
VersionResult string
VersionErr error
}
func (d *DriverMock) Copy(source, dst string) error {
d.CopyCalled = true
return d.CopyErr
}
func (d *DriverMock) Stop() error {
d.StopCalled = true
return d.StopErr
}
func (d *DriverMock) Qemu(args ...string) error {
d.QemuCalls = append(d.QemuCalls, args)
if len(d.QemuErrs) >= len(d.QemuCalls) {
return d.QemuErrs[len(d.QemuCalls)-1]
}
return nil
}
func (d *DriverMock) WaitForShutdown(cancelCh <-chan struct{}) bool {
d.WaitForShutdownCalled = true
return d.WaitForShutdownState
}
func (d *DriverMock) QemuImg(args ...string) error {
d.QemuImgCalled = true
d.QemuImgCalls = append(d.QemuImgCalls, args...)
if len(d.QemuImgErrs) >= len(d.QemuImgCalls) {
return d.QemuImgErrs[len(d.QemuImgCalls)-1]
}
return nil
}
func (d *DriverMock) Verify() error {
d.VerifyCalled = true
return d.VerifyErr
}
func (d *DriverMock) Version() (string, error) {
d.VersionCalled = true
return d.VersionResult, d.VersionErr
}
+20 -31
View File
@@ -17,32 +17,37 @@ import (
// This step converts the virtual disk that was used as the
// hard drive for the virtual machine.
type stepConvertDisk struct {
DiskCompression bool
Format string
OutputDir string
SkipCompaction bool
VMName string
QemuImgArgs QemuImgArgs
}
type stepConvertDisk struct{}
func (s *stepConvertDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
diskName := config.VMName
ui := state.Get("ui").(packer.Ui)
diskName := s.VMName
if s.SkipCompaction && !s.DiskCompression {
if config.SkipCompaction && !config.DiskCompression {
return multistep.ActionContinue
}
name := diskName + ".convert"
sourcePath := filepath.Join(s.OutputDir, diskName)
targetPath := filepath.Join(s.OutputDir, name)
sourcePath := filepath.Join(config.OutputDir, diskName)
targetPath := filepath.Join(config.OutputDir, name)
command := s.buildConvertCommand(sourcePath, targetPath)
command := []string{
"convert",
}
if config.DiskCompression {
command = append(command, "-c")
}
command = append(command, []string{
"-O", config.Format,
sourcePath,
targetPath,
}...,
)
ui.Say("Converting hard drive...")
// Retry the conversion a few times in case it takes the qemu process a
@@ -85,20 +90,4 @@ func (s *stepConvertDisk) Run(ctx context.Context, state multistep.StateBag) mul
return multistep.ActionContinue
}
func (s *stepConvertDisk) buildConvertCommand(sourcePath, targetPath string) []string {
command := []string{"convert"}
if s.DiskCompression {
command = append(command, "-c")
}
// Add user-provided convert args
command = append(command, s.QemuImgArgs.Convert...)
// Add format, and paths.
command = append(command, "-O", s.Format, sourcePath, targetPath)
return command
}
func (s *stepConvertDisk) Cleanup(state multistep.StateBag) {}
-52
View File
@@ -1,52 +0,0 @@
package qemu
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_buildConvertCommand(t *testing.T) {
type testCase struct {
Step *stepConvertDisk
Expected []string
Reason string
}
testcases := []testCase{
{
&stepConvertDisk{
Format: "qcow2",
DiskCompression: false,
},
[]string{"convert", "-O", "qcow2", "source.qcow", "target.qcow2"},
"Basic, happy path, no compression, no extra args",
},
{
&stepConvertDisk{
Format: "qcow2",
DiskCompression: true,
},
[]string{"convert", "-c", "-O", "qcow2", "source.qcow", "target.qcow2"},
"Basic, happy path, with compression, no extra args",
},
{
&stepConvertDisk{
Format: "qcow2",
DiskCompression: true,
QemuImgArgs: QemuImgArgs{
Convert: []string{"-o", "preallocation=full"},
},
},
[]string{"convert", "-c", "-o", "preallocation=full", "-O", "qcow2", "source.qcow", "target.qcow2"},
"Basic, happy path, with compression, one set of extra args",
},
}
for _, tc := range testcases {
command := tc.Step.buildConvertCommand("source.qcow", "target.qcow2")
assert.Equal(t, command, tc.Expected,
fmt.Sprintf("%s. Expected %#v", tc.Reason, tc.Expected))
}
}
+9 -37
View File
@@ -11,42 +11,26 @@ import (
// This step copies the virtual disk that will be used as the
// hard drive for the virtual machine.
type stepCopyDisk struct {
DiskImage bool
Format string
OutputDir string
UseBackingFile bool
VMName string
QemuImgArgs QemuImgArgs
}
type stepCopyDisk struct{}
func (s *stepCopyDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packer.Ui)
path := filepath.Join(s.OutputDir, s.VMName)
path := filepath.Join(config.OutputDir, config.VMName)
if !s.DiskImage || s.UseBackingFile {
return multistep.ActionContinue
command := []string{
"convert",
"-O", config.Format,
isoPath,
path,
}
// isoPath extention is:
ext := filepath.Ext(isoPath)
if ext[1:] == s.Format {
ui.Message("File extension already matches desired output format. " +
"Skipping qemu-img convert step")
err := driver.Copy(isoPath, path)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if !config.DiskImage || config.UseBackingFile {
return multistep.ActionContinue
}
command := s.buildConvertCommand(isoPath, path)
ui.Say("Copying hard drive...")
if err := driver.QemuImg(command...); err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
@@ -58,16 +42,4 @@ func (s *stepCopyDisk) Run(ctx context.Context, state multistep.StateBag) multis
return multistep.ActionContinue
}
func (s *stepCopyDisk) buildConvertCommand(sourcePath, targetPath string) []string {
command := []string{"convert"}
// Add user-provided convert args
command = append(command, s.QemuImgArgs.Convert...)
// Add format, and paths.
command = append(command, "-O", s.Format, sourcePath, targetPath)
return command
}
func (s *stepCopyDisk) Cleanup(state multistep.StateBag) {}
-122
View File
@@ -1,122 +0,0 @@
package qemu
import (
"context"
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/stretchr/testify/assert"
)
func copyTestState(t *testing.T, d *DriverMock) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", packer.TestUi(t))
state.Put("driver", d)
state.Put("iso_path", "example_source.qcow2")
return state
}
func Test_StepCopySkip(t *testing.T) {
testcases := []stepCopyDisk{
stepCopyDisk{
DiskImage: false,
UseBackingFile: false,
},
stepCopyDisk{
DiskImage: true,
UseBackingFile: true,
},
stepCopyDisk{
DiskImage: false,
UseBackingFile: true,
},
}
for _, tc := range testcases {
d := new(DriverMock)
state := copyTestState(t, d)
action := tc.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have gotten an ActionContinue")
}
if d.CopyCalled || d.QemuImgCalled {
t.Fatalf("Should have skipped step since DiskImage and UseBackingFile are not set")
}
}
}
func Test_StepCopyCalled(t *testing.T) {
step := stepCopyDisk{
DiskImage: true,
Format: "qcow2",
VMName: "output.qcow2",
}
d := new(DriverMock)
state := copyTestState(t, d)
action := step.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have gotten an ActionContinue")
}
if !d.CopyCalled {
t.Fatalf("Should have copied since all extensions are qcow2")
}
if d.QemuImgCalled {
t.Fatalf("Should not have called qemu-img when formats match")
}
}
func Test_StepQemuImgCalled(t *testing.T) {
step := stepCopyDisk{
DiskImage: true,
Format: "raw",
VMName: "output.qcow2",
}
d := new(DriverMock)
state := copyTestState(t, d)
action := step.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have gotten an ActionContinue")
}
if d.CopyCalled {
t.Fatalf("Should not have copied since extensions don't match")
}
if !d.QemuImgCalled {
t.Fatalf("Should have called qemu-img since extensions don't match")
}
}
func Test_StepQemuImgCalledWithExtraArgs(t *testing.T) {
step := &stepCopyDisk{
DiskImage: true,
Format: "raw",
VMName: "output.qcow2",
QemuImgArgs: QemuImgArgs{
Convert: []string{"-o", "preallocation=full"},
},
}
d := new(DriverMock)
state := copyTestState(t, d)
action := step.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have gotten an ActionContinue")
}
if d.CopyCalled {
t.Fatalf("Should not have copied since extensions don't match")
}
if !d.QemuImgCalled {
t.Fatalf("Should have called qemu-img since extensions don't match")
}
assert.Equal(
t,
d.QemuImgCalls,
[]string{"convert", "-o", "preallocation=full", "-O", "raw",
"example_source.qcow2", "output.qcow2"},
"should have added user extra args")
}
+21 -35
View File
@@ -12,23 +12,15 @@ import (
// This step creates the virtual disk that will be used as the
// hard drive for the virtual machine.
type stepCreateDisk struct {
AdditionalDiskSize []string
DiskImage bool
DiskSize string
Format string
OutputDir string
UseBackingFile bool
VMName string
QemuImgArgs QemuImgArgs
}
type stepCreateDisk struct{}
func (s *stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
name := s.VMName
name := config.VMName
if s.DiskImage && !s.UseBackingFile {
if config.DiskImage && !config.UseBackingFile {
return multistep.ActionContinue
}
@@ -36,12 +28,12 @@ func (s *stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) mult
ui.Say("Creating required virtual machine disks")
// The 'main' or 'default' disk
diskFullPaths = append(diskFullPaths, filepath.Join(s.OutputDir, name))
diskSizes = append(diskSizes, s.DiskSize)
diskFullPaths = append(diskFullPaths, filepath.Join(config.OutputDir, name))
diskSizes = append(diskSizes, config.DiskSize)
// Additional disks
if len(s.AdditionalDiskSize) > 0 {
for i, diskSize := range s.AdditionalDiskSize {
path := filepath.Join(s.OutputDir, fmt.Sprintf("%s-%d", name, i+1))
if len(config.AdditionalDiskSize) > 0 {
for i, diskSize := range config.AdditionalDiskSize {
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s-%d", name, i+1))
diskFullPaths = append(diskFullPaths, path)
size := diskSize
diskSizes = append(diskSizes, size)
@@ -51,8 +43,19 @@ func (s *stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) mult
// Create all required disks
for i, diskFullPath := range diskFullPaths {
log.Printf("[INFO] Creating disk with Path: %s and Size: %s", diskFullPath, diskSizes[i])
command := []string{
"create",
"-f", config.Format,
}
command := s.buildCreateCommand(diskFullPath, diskSizes[i], i, state)
if config.UseBackingFile && i == 0 {
isoPath := state.Get("iso_path").(string)
command = append(command, "-b", isoPath)
}
command = append(command,
diskFullPath,
diskSizes[i])
if err := driver.QemuImg(command...); err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
@@ -68,21 +71,4 @@ func (s *stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) mult
return multistep.ActionContinue
}
func (s *stepCreateDisk) buildCreateCommand(path string, size string, i int, state multistep.StateBag) []string {
command := []string{"create", "-f", s.Format}
if s.UseBackingFile && i == 0 {
isoPath := state.Get("iso_path").(string)
command = append(command, "-b", isoPath)
}
// add user-provided convert args
command = append(command, s.QemuImgArgs.Create...)
// add target path and size.
command = append(command, path, size)
return command
}
func (s *stepCreateDisk) Cleanup(state multistep.StateBag) {}
-80
View File
@@ -1,80 +0,0 @@
package qemu
import (
"fmt"
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/stretchr/testify/assert"
)
func Test_buildCreateCommand(t *testing.T) {
type testCase struct {
Step *stepCreateDisk
I int
Expected []string
Reason string
}
testcases := []testCase{
{
&stepCreateDisk{
Format: "qcow2",
UseBackingFile: false,
},
0,
[]string{"create", "-f", "qcow2", "target.qcow2", "1234M"},
"Basic, happy path, no backing store, no extra args",
},
{
&stepCreateDisk{
Format: "qcow2",
UseBackingFile: true,
},
0,
[]string{"create", "-f", "qcow2", "-b", "source.qcow2", "target.qcow2", "1234M"},
"Basic, happy path, backing store, no extra args",
},
{
&stepCreateDisk{
Format: "qcow2",
UseBackingFile: true,
},
1,
[]string{"create", "-f", "qcow2", "target.qcow2", "1234M"},
"Basic, happy path, backing store set but not at first index, no extra args",
},
{
&stepCreateDisk{
Format: "qcow2",
UseBackingFile: true,
QemuImgArgs: QemuImgArgs{
Create: []string{"-foo", "bar"},
},
},
0,
[]string{"create", "-f", "qcow2", "-b", "source.qcow2", "-foo", "bar", "target.qcow2", "1234M"},
"Basic, happy path, backing store set, extra args",
},
{
&stepCreateDisk{
Format: "qcow2",
UseBackingFile: true,
QemuImgArgs: QemuImgArgs{
Create: []string{"-foo", "bar"},
},
},
1,
[]string{"create", "-f", "qcow2", "-foo", "bar", "target.qcow2", "1234M"},
"Basic, happy path, backing store set but not at first index, extra args",
},
}
for _, tc := range testcases {
state := new(multistep.BasicStateBag)
state.Put("iso_path", "source.qcow2")
command := tc.Step.buildCreateCommand("target.qcow2", "1234M", tc.I, state)
assert.Equal(t, command, tc.Expected,
fmt.Sprintf("%s. Expected %#v", tc.Reason, tc.Expected))
}
}
-12
View File
@@ -13,9 +13,6 @@ import (
// This step adds a NAT port forwarding definition so that SSH or WinRM is available
// on the guest machine.
type stepPortForward struct {
CommunicatorType string
NetBridge string
l *net.Listener
}
@@ -23,15 +20,6 @@ func (s *stepPortForward) Run(ctx context.Context, state multistep.StateBag) mul
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
if s.CommunicatorType == "none" {
ui.Message("No communicator is set; skipping port forwarding setup.")
return multistep.ActionContinue
}
if s.NetBridge != "" {
ui.Message("net_bridge is set; skipping port forwarding setup.")
return multistep.ActionContinue
}
commHostPort := config.CommConfig.Comm.Port()
if config.CommConfig.SkipNatMapping {
+10 -27
View File
@@ -11,26 +11,21 @@ import (
// This step resizes the virtual disk that will be used as the
// hard drive for the virtual machine.
type stepResizeDisk struct {
DiskCompression bool
DiskImage bool
Format string
OutputDir string
SkipResizeDisk bool
VMName string
DiskSize string
QemuImgArgs QemuImgArgs
}
type stepResizeDisk struct{}
func (s *stepResizeDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
path := filepath.Join(s.OutputDir, s.VMName)
path := filepath.Join(config.OutputDir, config.VMName)
command := s.buildResizeCommand(path)
if s.DiskImage == false || s.SkipResizeDisk == true {
command := []string{
"resize",
"-f", config.Format,
path,
config.DiskSize,
}
if config.DiskImage == false {
return multistep.ActionContinue
}
@@ -45,16 +40,4 @@ func (s *stepResizeDisk) Run(ctx context.Context, state multistep.StateBag) mult
return multistep.ActionContinue
}
func (s *stepResizeDisk) buildResizeCommand(path string) []string {
command := []string{"resize", "-f", s.Format}
// add user-provided convert args
command = append(command, s.QemuImgArgs.Resize...)
// Add file and size
command = append(command, path, s.DiskSize)
return command
}
func (s *stepResizeDisk) Cleanup(state multistep.StateBag) {}
-77
View File
@@ -1,77 +0,0 @@
package qemu
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/stretchr/testify/assert"
)
func TestStepResizeDisk_Skips(t *testing.T) {
testConfigs := []*Config{
&Config{
DiskImage: false,
SkipResizeDisk: false,
},
&Config{
DiskImage: false,
SkipResizeDisk: true,
},
}
for _, config := range testConfigs {
state := testState(t)
driver := state.Get("driver").(*DriverMock)
state.Put("config", config)
step := new(stepResizeDisk)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
if len(driver.QemuImgCalls) > 0 {
t.Fatal("should NOT have called qemu-img")
}
}
}
func Test_buildResizeCommand(t *testing.T) {
type testCase struct {
Step *stepResizeDisk
Expected []string
Reason string
}
testcases := []testCase{
{
&stepResizeDisk{
Format: "qcow2",
DiskSize: "1234M",
},
[]string{"resize", "-f", "qcow2", "source.qcow", "1234M"},
"no extra args",
},
{
&stepResizeDisk{
Format: "qcow2",
DiskSize: "1234M",
QemuImgArgs: QemuImgArgs{
Resize: []string{"-foo", "bar"},
},
},
[]string{"resize", "-f", "qcow2", "-foo", "bar", "source.qcow", "1234M"},
"one set of extra args",
},
}
for _, tc := range testcases {
command := tc.Step.buildResizeCommand("source.qcow")
assert.Equal(t, command, tc.Expected,
fmt.Sprintf("%s. Expected %#v", tc.Reason, tc.Expected))
}
}
+14 -32
View File
@@ -15,7 +15,8 @@ import (
// stepRun runs the virtual machine
type stepRun struct {
DiskImage bool
BootDrive string
Message string
}
type qemuArgsTemplateData struct {
@@ -31,17 +32,9 @@ func (s *stepRun) Run(ctx context.Context, state multistep.StateBag) multistep.S
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
// Run command is different depending whether we're booting from an
// installation CD or a pre-baked image
bootDrive := "once=d"
message := "Starting VM, booting from CD-ROM"
if s.DiskImage {
bootDrive = "c"
message = "Starting VM, booting disk image"
}
ui.Say(message)
ui.Say(s.Message)
command, err := getCommandArgs(bootDrive, state)
command, err := getCommandArgs(s.BootDrive, state)
if err != nil {
err := fmt.Errorf("Error processing QemuArgs: %s", err)
ui.Error(err.Error())
@@ -80,11 +73,12 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
var deviceArgs []string
var driveArgs []string
var commHostPort int
var vnc string
vncPort = vncPort - config.VNCPortMin
vnc := fmt.Sprintf("%s:%d", vncIP, vncPort)
if config.VNCUsePassword {
vnc = fmt.Sprintf("%s:%d,password", vncIP, vncPort)
if !config.VNCUsePassword {
vnc = fmt.Sprintf("%s:%d", vncIP, vncPort-5900)
} else {
vnc = fmt.Sprintf("%s:%d,password", vncIP, vncPort-5900)
}
if config.QMPEnable {
@@ -197,26 +191,14 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
}
}
cdPaths := []string{}
// Add the installation CD to the run command
if !config.DiskImage {
cdPaths = append(cdPaths, isoPath)
}
// Add our custom CD created from cd_files, if it exists
cdFilesPath, ok := state.Get("cd_path").(string)
if ok {
if cdFilesPath != "" {
cdPaths = append(cdPaths, cdFilesPath)
}
}
for i, cdPath := range cdPaths {
if config.CDROMInterface == "" {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,index=%d,media=cdrom", cdPath, i))
defaultArgs["-cdrom"] = isoPath
} else if config.CDROMInterface == "virtio-scsi" {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=none,index=%d,id=cdrom%d,media=cdrom", cdPath, i, i))
deviceArgs = append(deviceArgs, "virtio-scsi-device", fmt.Sprintf("scsi-cd,drive=cdrom%d", i))
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=none,id=cdrom,media=cdrom", isoPath))
deviceArgs = append(deviceArgs, "virtio-scsi-device", "scsi-cd,drive=cdrom")
} else {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,index=%d,id=cdrom%d,media=cdrom", cdPath, config.CDROMInterface, i, i))
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,id=cdrom,media=cdrom", isoPath, config.CDROMInterface))
}
}
@@ -247,7 +229,7 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
inArgs := make(map[string][]string)
if len(config.QemuArgs) > 0 {
ui.Say("Overriding default Qemu arguments with QemuArgs...")
ui.Say("Overriding defaults Qemu arguments with QemuArgs...")
httpIp := state.Get("http_ip").(string)
httpPort := state.Get("http_port").(int)
-148
View File
@@ -1,148 +0,0 @@
package qemu
import (
"fmt"
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/stretchr/testify/assert"
)
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)
d.VersionResult = "3.0.0"
state.Put("driver", d)
state.Put("commHostPort", 5000)
state.Put("floppy_path", "fake_floppy_path")
state.Put("http_ip", "127.0.0.1")
state.Put("http_port", 1234)
state.Put("iso_path", "/path/to/test.iso")
state.Put("qemu_disk_paths", []string{})
state.Put("vnc_port", 5905)
state.Put("vnc_password", "fake_vnc_password")
return state
}
func Test_getCommandArgs(t *testing.T) {
state := runTestState(t, &Config{})
args, err := getCommandArgs("", state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
}
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_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)
}
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",
}
assert.ElementsMatch(t, args, expected, fmt.Sprintf("unexpected generated args: %#v", 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) {
c := &Config{
VNCUsePassword: true,
QMPEnable: true,
QMPSocketPath: "qmp_path",
VMName: "MyFancyName",
MachineType: "pc",
Accelerator: "hvf",
}
state := runTestState(t, c)
args, err := getCommandArgs("once=d", state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
}
expected := []string{
"-display", "gtk",
"-m", "0M",
"-boot", "once=d",
"-fda", "fake_floppy_path",
"-name", "MyFancyName",
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
"-vnc", ":5905,password",
"-machine", "type=pc,accel=hvf",
"-device", ",netdev=user.0",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-qmp", "unix:qmp_path,server,nowait",
}
assert.ElementsMatch(t, args, expected, "password flag should be set, and d drive should be set.")
}
-19
View File
@@ -1,19 +0,0 @@
package qemu
import (
"bytes"
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func testState(t *testing.T) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("driver", new(DriverMock))
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}
+3 -15
View File
@@ -19,31 +19,19 @@ import (
// This step waits for the guest address to become available in the network
// bridge, then it sets the guestAddress state property.
type stepWaitGuestAddress struct {
CommunicatorType string
NetBridge string
timeout time.Duration
}
func (s *stepWaitGuestAddress) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
if s.CommunicatorType == "none" {
ui.Message("No communicator is configured -- skipping StepWaitGuestAddress")
return multistep.ActionContinue
}
if s.NetBridge == "" {
ui.Message("Not using a NetBridge -- skipping StepWaitGuestAddress")
return multistep.ActionContinue
}
qmpMonitor := state.Get("qmp_monitor").(*qmp.SocketMonitor)
ctx, cancel := context.WithTimeout(ctx, s.timeout)
defer cancel()
ui.Say(fmt.Sprintf("Waiting for the guest address to become available in the %s network bridge...", s.NetBridge))
ui.Say(fmt.Sprintf("Waiting for the guest address to become available in the %s network bridge...", config.NetBridge))
for {
guestAddress := getGuestAddress(qmpMonitor, s.NetBridge, "user.0")
guestAddress := getGuestAddress(qmpMonitor, config.NetBridge, "user.0")
if guestAddress != "" {
log.Printf("Found guest address %s", guestAddress)
state.Put("guestAddress", guestAddress)
+9 -18
View File
@@ -4,8 +4,7 @@ import (
"fmt"
"log"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/scaleway-cli/pkg/api"
)
type Artifact struct {
@@ -21,11 +20,11 @@ type Artifact struct {
// The ID of the snapshot
snapshotID string
// The name of the zone
zoneName string
// The name of the region
regionName string
// The client for making API calls
client *scw.Client
client *api.ScalewayAPI
// StateData should store data such as GeneratedData
// to be shared with post-processors
@@ -42,12 +41,12 @@ func (*Artifact) Files() []string {
}
func (a *Artifact) Id() string {
return fmt.Sprintf("%s:%s", a.zoneName, a.imageID)
return fmt.Sprintf("%s:%s", a.regionName, a.imageID)
}
func (a *Artifact) String() string {
return fmt.Sprintf("An image was created: '%v' (ID: %v) in zone '%v' based on snapshot '%v' (ID: %v)",
a.imageName, a.imageID, a.zoneName, a.snapshotName, a.snapshotID)
return fmt.Sprintf("An image was created: '%v' (ID: %v) in region '%v' based on snapshot '%v' (ID: %v)",
a.imageName, a.imageID, a.regionName, a.snapshotName, a.snapshotID)
}
func (a *Artifact) State(name string) interface{} {
@@ -56,19 +55,11 @@ func (a *Artifact) State(name string) interface{} {
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %s (%s)", a.imageID, a.imageName)
instanceAPI := instance.NewAPI(a.client)
err := instanceAPI.DeleteImage(&instance.DeleteImageRequest{
ImageID: a.imageID,
})
if err != nil {
if err := a.client.DeleteImage(a.imageID); err != nil {
return err
}
log.Printf("Destroying snapshot: %s (%s)", a.snapshotID, a.snapshotName)
err = instanceAPI.DeleteSnapshot(&instance.DeleteSnapshotRequest{
SnapshotID: a.snapshotID,
})
if err != nil {
if err := a.client.DeleteSnapshot(a.snapshotID); err != nil {
return err
}
return nil
+1 -1
View File
@@ -27,7 +27,7 @@ func TestArtifactId(t *testing.T) {
func TestArtifactString(t *testing.T) {
generatedData := make(map[string]interface{})
a := &Artifact{"packer-foobar-image", "cc586e45-5156-4f71-b223-cf406b10dd1d", "packer-foobar-snapshot", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil, generatedData}
expected := "An image was created: 'packer-foobar-image' (ID: cc586e45-5156-4f71-b223-cf406b10dd1d) in zone 'ams1' based on snapshot 'packer-foobar-snapshot' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c)"
expected := "An image was created: 'packer-foobar-image' (ID: cc586e45-5156-4f71-b223-cf406b10dd1d) in region 'ams1' based on snapshot 'packer-foobar-snapshot' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c)"
if a.String() != expected {
t.Fatalf("artifact string should match: %v", expected)
+4 -24
View File
@@ -13,7 +13,7 @@ import (
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/scaleway-cli/pkg/api"
)
// The unique id for the builder
@@ -32,27 +32,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
return nil, warnings, errs
}
return nil, warnings, nil
return nil, nil, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
scwZone, err := scw.ParseZone(b.config.Zone)
if err != nil {
ui.Error(err.Error())
return nil, err
}
client, err := api.NewScalewayAPI(b.config.Organization, b.config.Token, b.config.UserAgent, b.config.Region)
clientOpts := []scw.ClientOption{
scw.WithDefaultProjectID(b.config.ProjectID),
scw.WithAuth(b.config.AccessKey, b.config.SecretKey),
scw.WithDefaultZone(scwZone),
}
if b.config.APIURL != "" {
clientOpts = append(clientOpts, scw.WithAPIURL(b.config.APIURL))
}
client, err := scw.NewClient(clientOpts...)
if err != nil {
ui.Error(err.Error())
return nil, err
@@ -65,11 +50,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
state.Put("ui", ui)
steps := []multistep.Step{
&stepPreValidate{
Force: b.config.PackerForce,
ImageName: b.config.ImageName,
SnapshotName: b.config.SnapshotName,
},
&stepCreateSSHKey{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("scw_%s.pem", b.config.PackerBuildName),
@@ -116,7 +96,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
imageID: state.Get("image_id").(string),
snapshotName: state.Get("snapshot_name").(string),
snapshotID: state.Get("snapshot_id").(string),
zoneName: b.config.Zone,
regionName: state.Get("region").(string),
client: client,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
+13 -11
View File
@@ -9,10 +9,9 @@ import (
func testConfig() map[string]interface{} {
return map[string]interface{}{
"project_id": "00000000-1111-2222-3333-444444444444",
"access_key": "SCWABCXXXXXXXXXXXXXX",
"secret_key": "00000000-1111-2222-3333-444444444444",
"zone": "fr-par-1",
"organization_id": "foo",
"api_token": "bar",
"region": "ams1",
"commercial_type": "START1-S",
"ssh_username": "root",
"image": "image-uuid",
@@ -33,7 +32,10 @@ func TestBuilder_Prepare_BadType(t *testing.T) {
"api_token": []string{},
}
_, _, err := b.Prepare(c)
_, warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("prepare should fail")
}
@@ -66,11 +68,11 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
}
}
func TestBuilderPrepare_Zone(t *testing.T) {
func TestBuilderPrepare_Region(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "zone")
delete(config, "region")
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
@@ -79,9 +81,9 @@ func TestBuilderPrepare_Zone(t *testing.T) {
t.Fatalf("should error")
}
expected := "fr-par-1"
expected := "ams1"
config["zone"] = expected
config["region"] = expected
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
@@ -91,8 +93,8 @@ func TestBuilderPrepare_Zone(t *testing.T) {
t.Fatalf("should not have error: %s", err)
}
if b.config.Zone != expected {
t.Errorf("found %s, expected %s", b.config.Zone, expected)
if b.config.Region != expected {
t.Errorf("found %s, expected %s", b.config.Region, expected)
}
}
+25 -90
View File
@@ -17,29 +17,27 @@ import (
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/mapstructure"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
// The AccessKey corresponding to the secret key.
// It can also be specified via the environment variable SCW_ACCESS_KEY.
AccessKey string `mapstructure:"access_key" required:"true"`
// The SecretKey to authenticate against the Scaleway API.
// It can also be specified via the environment variable SCW_SECRET_KEY.
SecretKey string `mapstructure:"secret_key" required:"true"`
// The Project ID in which the instances, volumes and snapshots will be created.
// It can also be specified via the environment variable SCW_DEFAULT_PROJECT_ID.
ProjectID string `mapstructure:"project_id" required:"true"`
// The Zone in which the instances, volumes and snapshots will be created.
// It can also be specified via the environment variable SCW_DEFAULT_ZONE
Zone string `mapstructure:"zone" required:"true"`
// The Scaleway API URL to use
// It can also be specified via the environment variable SCW_API_URL
APIURL string `mapstructure:"api_url"`
// The token to use to authenticate with your account.
// It can also be specified via environment variable SCALEWAY_API_TOKEN. You
// can see and generate tokens in the "Credentials"
// section of the control panel.
Token string `mapstructure:"api_token" required:"true"`
// The organization id to use to identify your
// organization. It can also be specified via environment variable
// SCALEWAY_ORGANIZATION. Your organization id is available in the
// "Account" section of the
// control panel.
// Previously named: api_access_key with environment variable: SCALEWAY_API_ACCESS_KEY
Organization string `mapstructure:"organization_id" required:"true"`
// The name of the region to launch the server in (par1
// or ams1). Consequently, this is the region where the snapshot will be
// available.
Region string `mapstructure:"region" required:"true"`
// The UUID of the base image to use. This is the image
// that will be used to launch a new server and provision it. See
// the images list
@@ -71,28 +69,6 @@ type Config struct {
UserAgent string `mapstructure-to-hcl2:",skip"`
ctx interpolate.Context
// Deprecated configs
// The token to use to authenticate with your account.
// It can also be specified via environment variable SCALEWAY_API_TOKEN. You
// can see and generate tokens in the "Credentials"
// section of the control panel.
// Deprecated, use SecretKey instead
Token string `mapstructure:"api_token" required:"false"`
// The organization id to use to identify your
// organization. It can also be specified via environment variable
// SCALEWAY_ORGANIZATION. Your organization id is available in the
// "Account" section of the
// control panel.
// Previously named: api_access_key with environment variable: SCALEWAY_API_ACCESS_KEY
// Deprecated, use ProjectID instead
Organization string `mapstructure:"organization_id" required:"false"`
// The name of the region to launch the server in (par1
// or ams1). Consequently, this is the region where the snapshot will be
// available.
// Deprecated, use Zone instead
Region string `mapstructure:"region" required:"false"`
}
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
@@ -112,11 +88,8 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
return nil, err
}
var warnings []string
c.UserAgent = useragent.String()
// Deprecated variables
if c.Organization == "" {
if os.Getenv("SCALEWAY_ORGANIZATION") != "" {
c.Organization = os.Getenv("SCALEWAY_ORGANIZATION")
@@ -125,43 +98,10 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
c.Organization = os.Getenv("SCALEWAY_API_ACCESS_KEY")
}
}
if c.Organization != "" {
warnings = append(warnings, "organization_id is deprecated in favor of project_id")
c.ProjectID = c.Organization
}
if c.Token == "" {
c.Token = os.Getenv("SCALEWAY_API_TOKEN")
}
if c.Token != "" {
warnings = append(warnings, "token is deprecated in favor of secret_key")
c.SecretKey = c.Token
}
if c.Region != "" {
warnings = append(warnings, "region is deprecated in favor of zone")
c.Zone = c.Region
}
if c.AccessKey == "" {
c.AccessKey = os.Getenv(scw.ScwAccessKeyEnv)
}
if c.SecretKey == "" {
c.SecretKey = os.Getenv(scw.ScwSecretKeyEnv)
}
if c.ProjectID == "" {
c.ProjectID = os.Getenv(scw.ScwDefaultProjectIDEnv)
}
if c.Zone == "" {
c.Zone = os.Getenv(scw.ScwDefaultZoneEnv)
}
if c.APIURL == "" {
c.APIURL = os.Getenv(scw.ScwAPIURLEnv)
}
if c.SnapshotName == "" {
def, err := interpolate.Render("snapshot-packer-{{timestamp}}", nil)
@@ -187,31 +127,26 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
if c.BootType == "" {
c.BootType = instance.BootTypeLocal.String()
c.BootType = "bootscript"
}
var errs *packer.MultiError
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
if c.ProjectID == "" {
if c.Organization == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Scaleway Project ID must be specified"))
errs, errors.New("Scaleway Organization ID must be specified"))
}
if c.SecretKey == "" {
if c.Token == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Scaleway Secret Key must be specified"))
errs, errors.New("Scaleway Token must be specified"))
}
if c.AccessKey == "" {
warnings = append(warnings, "access_key will be required in future versions")
c.AccessKey = "SCWXXXXXXXXXXXXXXXXX"
}
if c.Zone == "" {
if c.Region == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Scaleway Zone is required"))
errs, errors.New("region is required"))
}
if c.CommercialType == "" {
@@ -225,9 +160,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
if errs != nil && len(errs.Errors) > 0 {
return warnings, errs
return nil, errs
}
packer.LogSecretFilter.Set(c.Token)
return warnings, nil
return nil, nil
}
+6 -16
View File
@@ -63,11 +63,9 @@ type FlatConfig struct {
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
ProjectID *string `mapstructure:"project_id" required:"true" cty:"project_id" hcl:"project_id"`
Zone *string `mapstructure:"zone" required:"true" cty:"zone" hcl:"zone"`
APIURL *string `mapstructure:"api_url" cty:"api_url" hcl:"api_url"`
Token *string `mapstructure:"api_token" required:"true" cty:"api_token" hcl:"api_token"`
Organization *string `mapstructure:"organization_id" required:"true" cty:"organization_id" hcl:"organization_id"`
Region *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"`
Image *string `mapstructure:"image" required:"true" cty:"image" hcl:"image"`
CommercialType *string `mapstructure:"commercial_type" required:"true" cty:"commercial_type" hcl:"commercial_type"`
SnapshotName *string `mapstructure:"snapshot_name" required:"false" cty:"snapshot_name" hcl:"snapshot_name"`
@@ -76,9 +74,6 @@ type FlatConfig struct {
Bootscript *string `mapstructure:"bootscript" required:"false" cty:"bootscript" hcl:"bootscript"`
BootType *string `mapstructure:"boottype" required:"false" cty:"boottype" hcl:"boottype"`
RemoveVolume *bool `mapstructure:"remove_volume" cty:"remove_volume" hcl:"remove_volume"`
Token *string `mapstructure:"api_token" required:"false" cty:"api_token" hcl:"api_token"`
Organization *string `mapstructure:"organization_id" required:"false" cty:"organization_id" hcl:"organization_id"`
Region *string `mapstructure:"region" required:"false" cty:"region" hcl:"region"`
}
// FlatMapstructure returns a new FlatConfig.
@@ -147,11 +142,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false},
"zone": &hcldec.AttrSpec{Name: "zone", Type: cty.String, Required: false},
"api_url": &hcldec.AttrSpec{Name: "api_url", Type: cty.String, Required: false},
"api_token": &hcldec.AttrSpec{Name: "api_token", Type: cty.String, Required: false},
"organization_id": &hcldec.AttrSpec{Name: "organization_id", Type: cty.String, Required: false},
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
"image": &hcldec.AttrSpec{Name: "image", Type: cty.String, Required: false},
"commercial_type": &hcldec.AttrSpec{Name: "commercial_type", Type: cty.String, Required: false},
"snapshot_name": &hcldec.AttrSpec{Name: "snapshot_name", Type: cty.String, Required: false},
@@ -160,9 +153,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"bootscript": &hcldec.AttrSpec{Name: "bootscript", Type: cty.String, Required: false},
"boottype": &hcldec.AttrSpec{Name: "boottype", Type: cty.String, Required: false},
"remove_volume": &hcldec.AttrSpec{Name: "remove_volume", Type: cty.Bool, Required: false},
"api_token": &hcldec.AttrSpec{Name: "api_token", Type: cty.String, Required: false},
"organization_id": &hcldec.AttrSpec{Name: "organization_id", Type: cty.String, Required: false},
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
}
return s
}
+9 -18
View File
@@ -7,14 +7,13 @@ import (
"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/scw"
"github.com/scaleway/scaleway-cli/pkg/api"
)
type stepImage struct{}
func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
client := state.Get("client").(*api.ScalewayAPI)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
snapshotID := state.Get("snapshot_id").(string)
@@ -22,9 +21,7 @@ func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep
ui.Say(fmt.Sprintf("Creating image: %v", c.ImageName))
imageResp, err := instanceAPI.GetImage(&instance.GetImageRequest{
ImageID: c.Image,
})
image, err := client.GetImage(c.Image)
if err != nil {
err := fmt.Errorf("Error getting initial image info: %s", err)
state.Put("error", err)
@@ -32,16 +29,11 @@ func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep
return multistep.ActionHalt
}
if imageResp.Image.DefaultBootscript != nil {
bootscriptID = imageResp.Image.DefaultBootscript.ID
if image.DefaultBootscript != nil {
bootscriptID = image.DefaultBootscript.Identifier
}
createImageResp, err := instanceAPI.CreateImage(&instance.CreateImageRequest{
Arch: imageResp.Image.Arch,
DefaultBootscript: bootscriptID,
Name: c.ImageName,
RootVolume: snapshotID,
})
imageID, err := client.PostImage(snapshotID, c.ImageName, bootscriptID, image.Arch)
if err != nil {
err := fmt.Errorf("Error creating image: %s", err)
state.Put("error", err)
@@ -49,11 +41,10 @@ func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep
return multistep.ActionHalt
}
log.Printf("Image ID: %s", createImageResp.Image.ID)
state.Put("image_id", createImageResp.Image.ID)
log.Printf("Image ID: %s", imageID)
state.Put("image_id", imageID)
state.Put("image_name", c.ImageName)
state.Put("region", c.Zone) // Deprecated
state.Put("zone", c.Zone)
state.Put("region", c.Region)
return multistep.ActionContinue
}
+19 -28
View File
@@ -7,8 +7,7 @@ import (
"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/scw"
"github.com/scaleway/scaleway-cli/pkg/api"
)
type stepCreateServer struct {
@@ -16,7 +15,7 @@ type stepCreateServer struct {
}
func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
client := state.Get("client").(*api.ScalewayAPI)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
tags := []string{}
@@ -32,16 +31,16 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
tags = []string{fmt.Sprintf("AUTHORIZED_KEY=%s", strings.Replace(strings.TrimSpace(string(c.Comm.SSHPublicKey)), " ", "_", -1))}
}
bootType := instance.BootType(c.BootType)
createServerResp, err := instanceAPI.CreateServer(&instance.CreateServerRequest{
BootType: &bootType,
Bootscript: bootscript,
CommercialType: c.CommercialType,
server, err := client.PostServer(api.ScalewayServerDefinition{
Name: c.ServerName,
Image: c.Image,
Image: &c.Image,
Organization: c.Organization,
CommercialType: c.CommercialType,
Tags: tags,
Bootscript: bootscript,
BootType: c.BootType,
})
if err != nil {
err := fmt.Errorf("Error creating server: %s", err)
state.Put("error", err)
@@ -49,10 +48,8 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
return multistep.ActionHalt
}
_, err = instanceAPI.ServerAction(&instance.ServerActionRequest{
Action: instance.ServerActionPoweron,
ServerID: createServerResp.Server.ID,
})
err = client.PostServerAction(server, "poweron")
if err != nil {
err := fmt.Errorf("Error starting server: %s", err)
state.Put("error", err)
@@ -60,9 +57,9 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
return multistep.ActionHalt
}
s.serverID = createServerResp.Server.ID
s.serverID = server
state.Put("server_id", createServerResp.Server.ID)
state.Put("server_id", server)
// instance_id is the generic term used so that users can have access to the
// instance id inside of the provisioners, used in step_provision.
state.Put("instance_id", s.serverID)
@@ -75,22 +72,16 @@ func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
return
}
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
client := state.Get("client").(*api.ScalewayAPI)
ui := state.Get("ui").(packer.Ui)
ui.Say("Destroying server...")
err := instanceAPI.DeleteServer(&instance.DeleteServerRequest{
ServerID: s.serverID,
})
err := client.DeleteServerForce(s.serverID)
if err != nil {
_, err = instanceAPI.ServerAction(&instance.ServerActionRequest{
Action: instance.ServerActionTerminate,
ServerID: s.serverID,
})
if err != nil {
ui.Error(fmt.Sprintf(
"Error destroying server. Please destroy it manually: %s", err))
}
ui.Error(fmt.Sprintf(
"Error destroying server. Please destroy it manually: %s", err))
}
}
-80
View File
@@ -1,80 +0,0 @@
package scaleway
import (
"context"
"fmt"
"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/scw"
)
// StepPreValidate provides an opportunity to pre-validate any configuration for
// the build before actually doing any time consuming work
//
type stepPreValidate struct {
Force bool
ImageName string
SnapshotName string
}
func (s *stepPreValidate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if s.Force {
ui.Say("Force flag found, skipping prevalidating image name")
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Prevalidating image name: %s", s.ImageName))
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
images, err := instanceAPI.ListImages(
&instance.ListImagesRequest{Name: &s.ImageName},
scw.WithAllPages())
if err != nil {
err := fmt.Errorf("Error: getting image list: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for _, im := range images.Images {
if im.Name == s.ImageName {
err := fmt.Errorf("Error: image name: '%s' is used by existing image with ID %s",
s.ImageName, im.ID)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
ui.Say(fmt.Sprintf("Prevalidating snapshot name: %s", s.SnapshotName))
snapshots, err := instanceAPI.ListSnapshots(
&instance.ListSnapshotsRequest{Name: &s.SnapshotName},
scw.WithAllPages())
if err != nil {
err := fmt.Errorf("Error: getting snapshot list: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for _, sn := range snapshots.Snapshots {
if sn.Name == s.SnapshotName {
err := fmt.Errorf("Error: snapshot name: '%s' is used by existing snapshot with ID %s",
s.SnapshotName, sn.ID)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *stepPreValidate) Cleanup(multistep.StateBag) {
}

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