From acd71a5c43bbd66e28a2b6d5c8a1e3afd142c211 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Wed, 25 Mar 2015 09:19:54 -0400 Subject: [PATCH 01/17] Refactor WinRMConfig and use it in VMWare builder In order to allow the build pipeline to modify the WinRM configuration it's necessary to pass around a reference to the actual WinRMConfig object rather than extracting the username and password while constructing the pipeline itself. To achieve this we also make the VMWare builder use the common WinRMConfig structure rather than the identical but internal one it was previously using. --- builder/amazon-windows/common/connect_step.go | 5 +- builder/amazon-windows/ebs/builder.go | 2 +- builder/amazon-windows/instance/builder.go | 2 +- .../parallels-windows/common/connect_step.go | 7 +- builder/parallels-windows/iso/builder.go | 2 +- builder/parallels-windows/pvm/builder.go | 2 +- .../virtualbox-windows/common/connect_step.go | 7 +- builder/virtualbox-windows/iso/builder.go | 2 +- builder/virtualbox-windows/ovf/builder.go | 2 +- builder/vmware-windows/common/connect_step.go | 7 +- builder/vmware-windows/common/winrm.go | 3 +- builder/vmware-windows/common/winrm_config.go | 65 ---------------- .../common/winrm_config_test.go | 74 ------------------- builder/vmware-windows/iso/builder.go | 3 +- builder/vmware-windows/vmx/config.go | 3 +- common/step_connect_winrm.go | 8 +- 16 files changed, 25 insertions(+), 169 deletions(-) delete mode 100644 builder/vmware-windows/common/winrm_config.go delete mode 100644 builder/vmware-windows/common/winrm_config_test.go diff --git a/builder/amazon-windows/common/connect_step.go b/builder/amazon-windows/common/connect_step.go index 028d690..af2566f 100644 --- a/builder/amazon-windows/common/connect_step.go +++ b/builder/amazon-windows/common/connect_step.go @@ -50,11 +50,10 @@ func WinRMAddress(e *ec2.EC2, port uint, private bool) func(multistep.StateBag) } // Creates a WinRM connect step for an EC2 instance -func NewConnectStep(ec2 *ec2.EC2, private bool, winrmConfig wincommon.WinRMConfig) multistep.Step { +func NewConnectStep(ec2 *ec2.EC2, private bool, winrmConfig *wincommon.WinRMConfig) multistep.Step { return &wincommon.StepConnectWinRM{ WinRMAddress: WinRMAddress(ec2, winrmConfig.WinRMPort, private), - WinRMUser: winrmConfig.WinRMUser, - WinRMPassword: winrmConfig.WinRMPassword, + WinRMConfig: winrmConfig, WinRMWaitTimeout: winrmConfig.WinRMWaitTimeout, } } diff --git a/builder/amazon-windows/ebs/builder.go b/builder/amazon-windows/ebs/builder.go index ff499d4..626ffef 100644 --- a/builder/amazon-windows/ebs/builder.go +++ b/builder/amazon-windows/ebs/builder.go @@ -114,7 +114,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe BlockDevices: b.config.BlockDevices, Tags: b.config.RunTags, }, - winawscommon.NewConnectStep(ec2conn, b.config.WinRMPrivateIp, b.config.WinRMConfig), + winawscommon.NewConnectStep(ec2conn, b.config.WinRMPrivateIp, &b.config.WinRMConfig), &common.StepProvision{}, &stepStopInstance{SpotPrice: b.config.SpotPrice}, // TODO(mitchellh): verify works with spots diff --git a/builder/amazon-windows/instance/builder.go b/builder/amazon-windows/instance/builder.go index f1da772..06cffde 100644 --- a/builder/amazon-windows/instance/builder.go +++ b/builder/amazon-windows/instance/builder.go @@ -30,7 +30,7 @@ type Config struct { awscommon.AMIConfig `mapstructure:",squash"` awscommon.BlockDevices `mapstructure:",squash"` winawscommon.RunConfig `mapstructure:",squash"` - wincommon.WinRMConfig `mapstructure:",squash"` + *wincommon.WinRMConfig `mapstructure:",squash"` AccountId string `mapstructure:"account_id"` BundleDestination string `mapstructure:"bundle_destination"` diff --git a/builder/parallels-windows/common/connect_step.go b/builder/parallels-windows/common/connect_step.go index ddbcf2b..1b69342 100644 --- a/builder/parallels-windows/common/connect_step.go +++ b/builder/parallels-windows/common/connect_step.go @@ -9,7 +9,7 @@ import ( wincommon "github.com/packer-community/packer-windows-plugins/common" ) -func WinRMAddressFunc(config wincommon.WinRMConfig) func(state multistep.StateBag) (string, error) { +func WinRMAddressFunc(config *wincommon.WinRMConfig) func(state multistep.StateBag) (string, error) { return func(state multistep.StateBag) (string, error) { log.Printf("Determining WinRM remote IP address...") @@ -37,11 +37,10 @@ func WinRMAddressFunc(config wincommon.WinRMConfig) func(state multistep.StateBa } // Creates a generic WinRM connect step from a Parallels builder config -func NewConnectStep(winrmConfig wincommon.WinRMConfig) multistep.Step { +func NewConnectStep(winrmConfig *wincommon.WinRMConfig) multistep.Step { return &wincommon.StepConnectWinRM{ WinRMAddress: WinRMAddressFunc(winrmConfig), - WinRMUser: winrmConfig.WinRMUser, - WinRMPassword: winrmConfig.WinRMPassword, + WinRMConfig: winrmConfig, WinRMWaitTimeout: winrmConfig.WinRMWaitTimeout, } } diff --git a/builder/parallels-windows/iso/builder.go b/builder/parallels-windows/iso/builder.go index d1a5b2d..6d7be88 100644 --- a/builder/parallels-windows/iso/builder.go +++ b/builder/parallels-windows/iso/builder.go @@ -278,7 +278,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe VMName: b.config.VMName, Tpl: b.config.tpl, }, - winparallelscommon.NewConnectStep(b.config.WinRMConfig), + winparallelscommon.NewConnectStep(&b.config.WinRMConfig), ¶llelscommon.StepUploadVersion{ Path: b.config.PrlctlVersionFile, }, diff --git a/builder/parallels-windows/pvm/builder.go b/builder/parallels-windows/pvm/builder.go index b720235..a7b2026 100644 --- a/builder/parallels-windows/pvm/builder.go +++ b/builder/parallels-windows/pvm/builder.go @@ -81,7 +81,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe VMName: b.config.VMName, Tpl: b.config.tpl, }, - winparallelscommon.NewConnectStep(b.config.WinRMConfig), + winparallelscommon.NewConnectStep(&b.config.WinRMConfig), ¶llelscommon.StepUploadVersion{ Path: b.config.PrlctlVersionFile, }, diff --git a/builder/virtualbox-windows/common/connect_step.go b/builder/virtualbox-windows/common/connect_step.go index b3bf892..6b6d8ff 100644 --- a/builder/virtualbox-windows/common/connect_step.go +++ b/builder/virtualbox-windows/common/connect_step.go @@ -8,7 +8,7 @@ import ( wincommon "github.com/packer-community/packer-windows-plugins/common" ) -func WinRMAddressFunc(config wincommon.WinRMConfig) func(state multistep.StateBag) (string, error) { +func WinRMAddressFunc(config *wincommon.WinRMConfig) func(state multistep.StateBag) (string, error) { if config.WinRMHost == "" { log.Printf("No WinRM Host provided, using default host 127.0.0.1") config.WinRMHost = "127.0.0.1" @@ -27,11 +27,10 @@ func WinRMAddressFunc(config wincommon.WinRMConfig) func(state multistep.StateBa } // Creates a generic WinRM connect step from a Virtualbox builder config -func NewConnectStep(winrmConfig wincommon.WinRMConfig) multistep.Step { +func NewConnectStep(winrmConfig *wincommon.WinRMConfig) multistep.Step { return &wincommon.StepConnectWinRM{ WinRMAddress: WinRMAddressFunc(winrmConfig), - WinRMUser: winrmConfig.WinRMUser, - WinRMPassword: winrmConfig.WinRMPassword, + WinRMConfig: winrmConfig, WinRMWaitTimeout: winrmConfig.WinRMWaitTimeout, } } diff --git a/builder/virtualbox-windows/iso/builder.go b/builder/virtualbox-windows/iso/builder.go index 171b636..d3e15b4 100644 --- a/builder/virtualbox-windows/iso/builder.go +++ b/builder/virtualbox-windows/iso/builder.go @@ -314,7 +314,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe VMName: b.config.VMName, Tpl: b.config.tpl, }, - winvboxcommon.NewConnectStep(b.config.WinRMConfig), + winvboxcommon.NewConnectStep(&b.config.WinRMConfig), &vboxcommon.StepUploadVersion{ Path: b.config.VBoxVersionFile, }, diff --git a/builder/virtualbox-windows/ovf/builder.go b/builder/virtualbox-windows/ovf/builder.go index c03beae..826b339 100644 --- a/builder/virtualbox-windows/ovf/builder.go +++ b/builder/virtualbox-windows/ovf/builder.go @@ -101,7 +101,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe VMName: b.config.VMName, Tpl: b.config.tpl, }, - winvboxcommon.NewConnectStep(b.config.WinRMConfig), + winvboxcommon.NewConnectStep(&b.config.WinRMConfig), &vboxcommon.StepUploadVersion{ Path: b.config.VBoxVersionFile, }, diff --git a/builder/vmware-windows/common/connect_step.go b/builder/vmware-windows/common/connect_step.go index 410a80d..64bf0c4 100644 --- a/builder/vmware-windows/common/connect_step.go +++ b/builder/vmware-windows/common/connect_step.go @@ -1,19 +1,18 @@ package common import ( - wincommon "github.com/packer-community/packer-windows-plugins/common" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/common" + wincommon "github.com/packer-community/packer-windows-plugins/common" ) // Creates a generic SSH or WinRM connect step from a VMWare builder config -func NewConnectStep(communicatorType string, driver Driver, sshConfig *SSHConfig, winrmConfig *WinRMConfig) multistep.Step { +func NewConnectStep(communicatorType string, driver Driver, sshConfig *SSHConfig, winrmConfig *wincommon.WinRMConfig) multistep.Step { //if communicatorType == packer.WinRMCommunicatorType { if communicatorType == "winrm" { return &wincommon.StepConnectWinRM{ WinRMAddress: WinRMAddressFunc(winrmConfig, driver), - WinRMUser: winrmConfig.WinRMUser, - WinRMPassword: winrmConfig.WinRMPassword, + WinRMConfig: winrmConfig, WinRMWaitTimeout: winrmConfig.WinRMWaitTimeout, } } else { diff --git a/builder/vmware-windows/common/winrm.go b/builder/vmware-windows/common/winrm.go index 275ed7e..421153c 100644 --- a/builder/vmware-windows/common/winrm.go +++ b/builder/vmware-windows/common/winrm.go @@ -4,9 +4,10 @@ import ( "fmt" "github.com/mitchellh/multistep" + wincommon "github.com/packer-community/packer-windows-plugins/common" ) -func WinRMAddressFunc(config *WinRMConfig, driver Driver) func(multistep.StateBag) (string, error) { +func WinRMAddressFunc(config *wincommon.WinRMConfig, driver Driver) func(multistep.StateBag) (string, error) { return func(state multistep.StateBag) (string, error) { if config.WinRMHost != "" { return fmt.Sprintf("%s:%d", config.WinRMHost, config.WinRMPort), nil diff --git a/builder/vmware-windows/common/winrm_config.go b/builder/vmware-windows/common/winrm_config.go deleted file mode 100644 index d4fba28..0000000 --- a/builder/vmware-windows/common/winrm_config.go +++ /dev/null @@ -1,65 +0,0 @@ -package common - -import ( - "errors" - "fmt" - "net" - "time" - - "github.com/mitchellh/packer/packer" -) - -type WinRMConfig struct { - WinRMUser string `mapstructure:"winrm_username"` - WinRMPassword string `mapstructure:"winrm_password"` - WinRMHost string `mapstructure:"winrm_host"` - WinRMPort uint `mapstructure:"winrm_port"` - RawWinRMWaitTimeout string `mapstructure:"winrm_wait_timeout"` - - WinRMWaitTimeout time.Duration -} - -func (c *WinRMConfig) Prepare(t *packer.ConfigTemplate) []error { - if c.WinRMPort == 0 { - c.WinRMPort = 5985 - } - - if c.RawWinRMWaitTimeout == "" { - c.RawWinRMWaitTimeout = "20m" - } - - templates := map[string]*string{ - "winrm_password": &c.WinRMPassword, - "winrm_username": &c.WinRMUser, - "winrm_wait_timeout": &c.RawWinRMWaitTimeout, - } - - errs := make([]error, 0) - for n, ptr := range templates { - var err error - *ptr, err = t.Process(*ptr, nil) - if err != nil { - errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) - } - } - - if c.WinRMHost != "" { - if ip := net.ParseIP(c.WinRMHost); ip == nil { - if _, err := net.LookupHost(c.WinRMHost); err != nil { - errs = append(errs, errors.New("winrm_host is an invalid IP or hostname")) - } - } - } - - if c.WinRMUser == "" { - errs = append(errs, errors.New("winrm_username must be specified.")) - } - - var err error - c.WinRMWaitTimeout, err = time.ParseDuration(c.RawWinRMWaitTimeout) - if err != nil { - errs = append(errs, fmt.Errorf("Failed parsing winrm_wait_timeout: %s", err)) - } - - return errs -} diff --git a/builder/vmware-windows/common/winrm_config_test.go b/builder/vmware-windows/common/winrm_config_test.go deleted file mode 100644 index 332d1df..0000000 --- a/builder/vmware-windows/common/winrm_config_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package common - -import ( - "testing" -) - -func testWinRMConfig() *WinRMConfig { - return &WinRMConfig{ - WinRMUser: "admin", - } -} - -func TestWinRMConfigPrepare(t *testing.T) { - c := testWinRMConfig() - errs := c.Prepare(testConfigTemplate(t)) - if len(errs) > 0 { - t.Fatalf("err: %#v", errs) - } - - if c.WinRMPort != 5985 { - t.Errorf("bad winrm port: %d", c.WinRMPort) - } -} - -func TestWinRMConfigPrepare_WinRMUser(t *testing.T) { - var c *WinRMConfig - var errs []error - - c = testWinRMConfig() - c.WinRMUser = "" - errs = c.Prepare(testConfigTemplate(t)) - if len(errs) == 0 { - t.Fatalf("should have error") - } - - c = testWinRMConfig() - c.WinRMUser = "exists" - errs = c.Prepare(testConfigTemplate(t)) - if len(errs) > 0 { - t.Fatalf("should not have error: %#v", errs) - } -} - -func TestWinRMConfigPrepare_WinRMWaitTimeout(t *testing.T) { - var c *WinRMConfig - var errs []error - - // Defaults - c = testWinRMConfig() - c.RawWinRMWaitTimeout = "" - errs = c.Prepare(testConfigTemplate(t)) - if len(errs) > 0 { - t.Fatalf("should not have error: %#v", errs) - } - if c.RawWinRMWaitTimeout != "20m" { - t.Fatalf("bad value: %s", c.RawWinRMWaitTimeout) - } - - // Test with a bad value - c = testWinRMConfig() - c.RawWinRMWaitTimeout = "this is not good" - errs = c.Prepare(testConfigTemplate(t)) - if len(errs) == 0 { - t.Fatal("should have error") - } - - // Test with a good one - c = testWinRMConfig() - c.RawWinRMWaitTimeout = "5s" - errs = c.Prepare(testConfigTemplate(t)) - if len(errs) > 0 { - t.Fatalf("should not have error: %#v", errs) - } -} diff --git a/builder/vmware-windows/iso/builder.go b/builder/vmware-windows/iso/builder.go index 79224be..3b98f1c 100644 --- a/builder/vmware-windows/iso/builder.go +++ b/builder/vmware-windows/iso/builder.go @@ -14,6 +14,7 @@ import ( "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" vmwcommon "github.com/packer-community/packer-windows-plugins/builder/vmware-windows/common" + wincommon "github.com/packer-community/packer-windows-plugins/common" ) const BuilderIdESX = "mitchellh.vmware-esx" @@ -32,7 +33,7 @@ type config struct { vmwcommon.SSHConfig `mapstructure:",squash"` vmwcommon.ToolsConfig `mapstructure:",squash"` vmwcommon.VMXConfig `mapstructure:",squash"` - vmwcommon.WinRMConfig `mapstructure:",squash"` + wincommon.WinRMConfig `mapstructure:",squash"` AdditionalDiskSize []uint `mapstructure:"additionaldisk_size"` DiskName string `mapstructure:"vmdk_name"` diff --git a/builder/vmware-windows/vmx/config.go b/builder/vmware-windows/vmx/config.go index c13df14..a4e3f84 100644 --- a/builder/vmware-windows/vmx/config.go +++ b/builder/vmware-windows/vmx/config.go @@ -7,6 +7,7 @@ import ( "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" vmwcommon "github.com/packer-community/packer-windows-plugins/builder/vmware-windows/common" + wincommon "github.com/packer-community/packer-windows-plugins/common" ) // Config is the configuration structure for the builder. @@ -19,7 +20,7 @@ type Config struct { vmwcommon.SSHConfig `mapstructure:",squash"` vmwcommon.ToolsConfig `mapstructure:",squash"` vmwcommon.VMXConfig `mapstructure:",squash"` - vmwcommon.WinRMConfig `mapstructure:",squash"` + wincommon.WinRMConfig `mapstructure:",squash"` BootCommand []string `mapstructure:"boot_command"` FloppyFiles []string `mapstructure:"floppy_files"` diff --git a/common/step_connect_winrm.go b/common/step_connect_winrm.go index 9251f45..e6310a4 100644 --- a/common/step_connect_winrm.go +++ b/common/step_connect_winrm.go @@ -29,11 +29,7 @@ type StepConnectWinRM struct { // if necessary for this address. WinRMAddress func(multistep.StateBag) (string, error) - // The user name to connect to WinRM as - WinRMUser string - - // The user password - WinRMPassword string + WinRMConfig *WinRMConfig // WinRMWaitTimeout is the total timeout to wait for WinRM to become available. WinRMWaitTimeout time.Duration @@ -119,7 +115,7 @@ func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan log.Printf("Attempting WinRM connection (timeout: %s)", s.WinRMWaitTimeout) - comm, err = plugin.New(endpoint, s.WinRMUser, s.WinRMPassword, s.WinRMWaitTimeout) + comm, err = plugin.New(endpoint, s.WinRMConfig.WinRMUser, s.WinRMConfig.WinRMPassword, s.WinRMWaitTimeout) if err != nil { log.Printf("WinRM connection err: %s", err) continue From 49d24ada042cf5cc973401c18184ee664453507c Mon Sep 17 00:00:00 2001 From: James Nugent Date: Wed, 25 Mar 2015 09:27:48 -0400 Subject: [PATCH 02/17] Rename connect_step.go file to match convention --- .../amazon-windows/common/{connect_step.go => step_connect.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename builder/amazon-windows/common/{connect_step.go => step_connect.go} (100%) diff --git a/builder/amazon-windows/common/connect_step.go b/builder/amazon-windows/common/step_connect.go similarity index 100% rename from builder/amazon-windows/common/connect_step.go rename to builder/amazon-windows/common/step_connect.go From 61118dcb4f93e0eca1376c7637fdc9c0c63465f5 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Wed, 25 Mar 2015 11:06:54 -0400 Subject: [PATCH 03/17] Add GetPassword step to the EBS builder This commit introduces a new build step which gets the automatically generated password using the EC2 API and decrypts it using the generated private key. The following changes are necessary to support this: - A key pair must be specified for the instance - similar to builders for other operating systems this can either be auto-generated if none is specified, or a specific key pair can be nominated. - We rely on a function not yet merged into `mitchellh/goamz` in order to make the API call on EC2. There is currently an outstanding pull request for this (https://github.com/mitchellh/goamz/pull/244), but for now it depends on the `jen20` fork of `goamz`. This still needs adding to the instance builder, and also needs some form of control to allow a password to be set explicitly if one has already been set for the source AMI. --- builder/amazon-windows/common/run_config.go | 34 ++-- .../common/step_get_password.go | 157 ++++++++++++++++++ builder/amazon-windows/ebs/builder.go | 13 +- 3 files changed, 189 insertions(+), 15 deletions(-) create mode 100644 builder/amazon-windows/common/step_get_password.go diff --git a/builder/amazon-windows/common/run_config.go b/builder/amazon-windows/common/run_config.go index cccf898..ee1d76c 100644 --- a/builder/amazon-windows/common/run_config.go +++ b/builder/amazon-windows/common/run_config.go @@ -5,6 +5,7 @@ import ( "fmt" "os" + "github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/packer" ) @@ -15,17 +16,19 @@ type RunConfig struct { AvailabilityZone string `mapstructure:"availability_zone"` IamInstanceProfile string `mapstructure:"iam_instance_profile"` InstanceType string `mapstructure:"instance_type"` + KeyPairPrivateKeyFile string `mapstructure:"key_pair_private_key_file"` RunTags map[string]string `mapstructure:"run_tags"` SourceAmi string `mapstructure:"source_ami"` SpotPrice string `mapstructure:"spot_price"` SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"` - WinRMPrivateIp bool `mapstructure:"winrm_private_ip"` SecurityGroupId string `mapstructure:"security_group_id"` SecurityGroupIds []string `mapstructure:"security_group_ids"` SubnetId string `mapstructure:"subnet_id"` + TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"` UserData string `mapstructure:"user_data"` UserDataFile string `mapstructure:"user_data_file"` VpcId string `mapstructure:"vpc_id"` + WinRMPrivateIp bool `mapstructure:"winrm_private_ip"` } func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { @@ -38,17 +41,19 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { } templates := map[string]*string{ - "iam_instance_profile": &c.IamInstanceProfile, - "instance_type": &c.InstanceType, - "spot_price": &c.SpotPrice, - "spot_price_auto_product": &c.SpotPriceAutoProduct, - "source_ami": &c.SourceAmi, - "subnet_id": &c.SubnetId, - "vpc_id": &c.VpcId, - "availability_zone": &c.AvailabilityZone, - "user_data": &c.UserData, - "user_data_file": &c.UserDataFile, - "security_group_id": &c.SecurityGroupId, + "iam_instance_profile": &c.IamInstanceProfile, + "instance_type": &c.InstanceType, + "key_pair_private_key_file": &c.KeyPairPrivateKeyFile, + "spot_price": &c.SpotPrice, + "spot_price_auto_product": &c.SpotPriceAutoProduct, + "source_ami": &c.SourceAmi, + "subnet_id": &c.SubnetId, + "temporary_key_pair_name": &c.TemporaryKeyPairName, + "vpc_id": &c.VpcId, + "availability_zone": &c.AvailabilityZone, + "user_data": &c.UserData, + "user_data_file": &c.UserDataFile, + "security_group_id": &c.SecurityGroupId, } errs := make([]error, 0) @@ -70,6 +75,11 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { errs = append(errs, errors.New("An instance_type must be specified")) } + if c.TemporaryKeyPairName == "" { + c.TemporaryKeyPairName = fmt.Sprintf( + "packer %s", uuid.TimeOrderedUUID()) + } + if c.SpotPrice == "auto" { if c.SpotPriceAutoProduct == "" { errs = append(errs, errors.New( diff --git a/builder/amazon-windows/common/step_get_password.go b/builder/amazon-windows/common/step_get_password.go new file mode 100644 index 0000000..d5f61ba --- /dev/null +++ b/builder/amazon-windows/common/step_get_password.go @@ -0,0 +1,157 @@ +package common + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "log" + "time" + + "code.google.com/p/gosshold/ssh/terminal" + + "github.com/mitchellh/goamz/ec2" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + + wincommon "github.com/packer-community/packer-windows-plugins/common" +) + +type StepGetPassword struct { + WinRMConfig *wincommon.WinRMConfig + GetPasswordTimeout time.Duration +} + +func (s *StepGetPassword) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + instance := state.Get("instance").(*ec2.Instance) + + var password string + var err error + + cancel := make(chan struct{}) + waitDone := make(chan bool, 1) + go func() { + ui.Say(fmt.Sprintf("Retrieving auto-generated password for instance %s...", instance)) + + password, err = s.waitForPassword(state, cancel) + if err != nil { + waitDone <- false + return + } + waitDone <- true + }() + + log.Printf("Waiting to retrieve instance %s password, up to timeout: %s", instance, s.GetPasswordTimeout) + timeout := time.After(s.GetPasswordTimeout) + +WaitLoop: + for { + // Wait for one of: the password becoming available, a timeout occuring + // or an interrupt coming through. + select { + case <-waitDone: + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + s.WinRMConfig.WinRMPassword = password + ui.Say(fmt.Sprintf("Retrieved password for instance %s", instance)) + break WaitLoop + + case <-timeout: + err := fmt.Errorf(fmt.Sprintf("Timeout retrieving password for instance %s", instance)) + state.Put("error", err) + ui.Error(err.Error()) + close(cancel) + return multistep.ActionHalt + + case <-time.After(1 * time.Second): + if _, ok := state.GetOk(multistep.StateCancelled); ok { + // Build was cancelled. + close(cancel) + log.Println("Interrupt detected, cancelling password retrieval") + return multistep.ActionHalt + } + } + } + + return multistep.ActionContinue + +} + +func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) { + ec2conn := state.Get("ec2").(*ec2.EC2) + instance := state.Get("instance").(*ec2.Instance) + privateKey := state.Get("privateKey").(string) + + for { + select { + case <-cancel: + log.Println("Retrieve password wait cancelled. Exiting loop.") + return "", errors.New("Retrieve password wait cancelled") + + case <-time.After(20 * time.Second): + } + + resp, err := ec2conn.GetPasswordData(instance.InstanceId) + if err != nil { + err := fmt.Errorf("Error retrieving auto-generated instance password: %s", err) + return "", err + } + + if resp.PasswordData != "" { + decryptedPassword, err := decryptPasswordDataWithPrivateKey(resp.PasswordData, []byte(privateKey)) + if err != nil { + err := fmt.Errorf("Error decrypting auto-generated instance password: %s", err) + return "", err + } + return decryptedPassword, nil + } + } +} + +func (s *StepGetPassword) Cleanup(multistep.StateBag) { + // No cleanup... +} + +func decryptPasswordDataWithPrivateKey(passwordData string, pemBytes []byte) (string, error) { + encryptedPasswd, err := base64.StdEncoding.DecodeString(passwordData) + if err != nil { + return "", err + } + + block, _ := pem.Decode(pemBytes) + var asn1Bytes []byte + if _, ok := block.Headers["DEK-Info"]; ok { + fmt.Printf("Encrypted private key. Please enter passphrase: ") + password, err := terminal.ReadPassword(0) + fmt.Printf("\n") + if err != nil { + return "", err + } + + asn1Bytes, err = x509.DecryptPEMBlock(block, password) + if err != nil { + return "", err + } + } else { + asn1Bytes = block.Bytes + } + + key, err := x509.ParsePKCS1PrivateKey(asn1Bytes) + if err != nil { + return "", err + } + + out, err := rsa.DecryptPKCS1v15(nil, key, encryptedPasswd) + if err != nil { + return "", err + } + + return string(out), nil +} diff --git a/builder/amazon-windows/ebs/builder.go b/builder/amazon-windows/ebs/builder.go index 626ffef..be47691 100644 --- a/builder/amazon-windows/ebs/builder.go +++ b/builder/amazon-windows/ebs/builder.go @@ -6,7 +6,9 @@ package ebs import ( + "fmt" "log" + "time" "github.com/mitchellh/goamz/ec2" "github.com/mitchellh/multistep" @@ -83,9 +85,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state.Put("ec2", ec2conn) state.Put("hook", hook) state.Put("ui", ui) - // Required by StepRunSourceInstance. Seems a better alternative - // to duplicating ~300 lines of code just to remove it as a dependency - state.Put("keyPair", "") + state.Put("keyPair", b.config.TemporaryKeyPairName) // Build the steps steps := []multistep.Step{ @@ -93,6 +93,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SourceAmi: b.config.SourceAmi, EnhancedNetworking: b.config.AMIEnhancedNetworking, }, + &awscommon.StepKeyPair{ + Debug: b.config.PackerDebug, + DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), + KeyPairName: b.config.TemporaryKeyPairName, + PrivateKeyFile: b.config.KeyPairPrivateKeyFile, + }, &winawscommon.StepSecurityGroup{ SecurityGroupIds: b.config.SecurityGroupIds, WinRMPort: b.config.WinRMPort, @@ -114,6 +120,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe BlockDevices: b.config.BlockDevices, Tags: b.config.RunTags, }, + &winawscommon.StepGetPassword{WinRMConfig: &b.config.WinRMConfig, GetPasswordTimeout: 5 * time.Minute}, winawscommon.NewConnectStep(ec2conn, b.config.WinRMPrivateIp, &b.config.WinRMConfig), &common.StepProvision{}, &stepStopInstance{SpotPrice: b.config.SpotPrice}, From 795288cd16e6a2c07e181fbedfdd760dc125381f Mon Sep 17 00:00:00 2001 From: James Nugent Date: Fri, 27 Mar 2015 10:32:42 -0500 Subject: [PATCH 04/17] Add HTTPS options to the WinRMConfig and propagate This commit adds members to the WinRM configuration struct for HTTPS (defaults to true) and ignoring the certificate chain (useful if you have self-signed certificates). It also adds options to the configuration for providing a custom CA certificate to validate against, though this is not currently propagated through to the communicator. It also changes the default port if none is specified to 8956 from 8955, reflecting the change to HTTPs by default. --- common/step_connect_winrm.go | 13 ++++++++++--- common/winrm_config.go | 16 ++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/common/step_connect_winrm.go b/common/step_connect_winrm.go index e6310a4..b808ee9 100644 --- a/common/step_connect_winrm.go +++ b/common/step_connect_winrm.go @@ -29,6 +29,7 @@ type StepConnectWinRM struct { // if necessary for this address. WinRMAddress func(multistep.StateBag) (string, error) + // WinRMConfig contains the configuration for WinRM. WinRMConfig *WinRMConfig // WinRMWaitTimeout is the total timeout to wait for WinRM to become available. @@ -107,7 +108,7 @@ func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan continue } - endpoint, err := newEndpoint(address) + endpoint, err := newEndpoint(address, s.WinRMConfig) if err != nil { log.Printf("Incorrect format for WinRM address: %s", err) continue @@ -127,7 +128,7 @@ func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan return comm, nil } -func newEndpoint(address string) (*winrm.Endpoint, error) { +func newEndpoint(address string, config *WinRMConfig) (*winrm.Endpoint, error) { host, port, err := net.SplitHostPort(address) if err != nil { return nil, err @@ -137,5 +138,11 @@ func newEndpoint(address string) (*winrm.Endpoint, error) { if err != nil { return nil, err } - return &winrm.Endpoint{Host: host, Port: iport}, nil + + return &winrm.Endpoint{ + Host: host, + Port: iport, + HTTPS: !config.WinRMHttp, + Insecure: config.WinRMIgnoreCertChain, + }, nil } diff --git a/common/winrm_config.go b/common/winrm_config.go index 23abac2..b89a4dd 100644 --- a/common/winrm_config.go +++ b/common/winrm_config.go @@ -10,18 +10,22 @@ import ( ) type WinRMConfig struct { - WinRMUser string `mapstructure:"winrm_username"` - WinRMPassword string `mapstructure:"winrm_password"` - WinRMHost string `mapstructure:"winrm_host"` - WinRMPort uint `mapstructure:"winrm_port"` - RawWinRMWaitTimeout string `mapstructure:"winrm_wait_timeout"` + WinRMUser string `mapstructure:"winrm_username"` + WinRMPassword string `mapstructure:"winrm_password"` + WinRMHost string `mapstructure:"winrm_host"` + WinRMPort uint `mapstructure:"winrm_port"` + RawWinRMWaitTimeout string `mapstructure:"winrm_wait_timeout"` + WinRMHttp bool `mapstructure:"winrm_http"` + WinRMIgnoreCertChain bool `mapstructure:"winrm_ignore_cert_chain"` + WinRMCACertFile string `mapstructure:"winrm_ca_cert_file"` WinRMWaitTimeout time.Duration + WinRMCACertBytes []byte } func (c *WinRMConfig) Prepare(t *packer.ConfigTemplate) []error { if c.WinRMPort == 0 { - c.WinRMPort = 5985 + c.WinRMPort = 5986 } if c.RawWinRMWaitTimeout == "" { From 3efe2f82b40af7d7068b19b4ce42fc7525affa88 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 30 Mar 2015 00:46:19 -0400 Subject: [PATCH 05/17] Add auto-configuration of WinRM over HTTPS This commit introduces a new step into the build process which generates a user-data powershell script responsible for configuring WinRM access over HTTPS. The script does the following: - Disables the firewall rules for WinRM over HTTP for both public and domain/private profiles - Sets the maximum timeout for WinRM to 1800000ms - Sets the maximum memory per WinRM shell to 1024MB - Disallows unencrypted WinRM connections - Enables Basic authentication for WinRM - Enables CredSSP access to WinRM (necessary for using it internally) - Adds firewall rules for port 5986 to both public and domain/private profiles - Adds the given certificate to the LocalMachine\My certificate store - Configures a listener for WinRM on HTTPS using the specified certificate - Restarts the WinRM service Note that the certificate is sent base-64 encoded in the user-data and is therefore visible via the console for the period of time that the builder is running. Future work may want to involve creating an S3 bucket and an IAM role into which the instance is provisioned so this is not as accessible, though it's instance specific. The certificate and WinRM configuration are currently left installed. It would be a nice option to be able to remove them after configuration but that's not straightforward because scheduling a shutdown script in Windows is hard. In this case it would also be better to add options for a certificate chain to validate against, and the option to generate the certificate chain internally in Packer rather than relying on OpenSSL or mkcert/New-SelfSignedCertificate as we currently do. As part of this change it was necessary to move to a local custom version of StepRunInstance as the version in Packer receives all it's configuration during the pipeline build phase instead and doesn't allow modification. Not sure if there was an original design reason for that but this version seems somewhat neater anyway and involves a lot less copying around of values. --- builder/amazon-windows/common/run_config.go | 22 ++- .../common/step_generate_secure_vm_config.go | 130 ++++++++++++++++++ .../common/step_get_password.go | 7 +- .../common/step_run_source_instance.go | 83 ++++++----- builder/amazon-windows/ebs/builder.go | 23 ++-- 5 files changed, 200 insertions(+), 65 deletions(-) create mode 100644 builder/amazon-windows/common/step_generate_secure_vm_config.go diff --git a/builder/amazon-windows/common/run_config.go b/builder/amazon-windows/common/run_config.go index ee1d76c..2e21d60 100644 --- a/builder/amazon-windows/common/run_config.go +++ b/builder/amazon-windows/common/run_config.go @@ -29,6 +29,8 @@ type RunConfig struct { UserDataFile string `mapstructure:"user_data_file"` VpcId string `mapstructure:"vpc_id"` WinRMPrivateIp bool `mapstructure:"winrm_private_ip"` + WinRMCertificateFile string `mapstructure:"winrm_certificate_file"` + ConfigureSecureWinRM bool `mapstructure:"winrm_autoconfigure"` } func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { @@ -87,11 +89,21 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { } } - if c.UserData != "" && c.UserDataFile != "" { - errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified.")) - } else if c.UserDataFile != "" { - if _, err := os.Stat(c.UserDataFile); err != nil { - errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile)) + if c.ConfigureSecureWinRM { + if c.UserData != "" || c.UserDataFile != "" { + errs = append(errs, fmt.Errorf("winrm_autoconfigure cannot be used in conjunction with user_data or user_data_file.")) + } + + if c.WinRMCertificateFile == "" { + errs = append(errs, fmt.Errorf("winrm_certificate_file must be set to the path of a PFX container holding the certificate to be used for WinRM.")) + } + } else { + if c.UserData != "" && c.UserDataFile != "" { + errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified.")) + } else if c.UserDataFile != "" { + if _, err := os.Stat(c.UserDataFile); err != nil { + errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile)) + } } } diff --git a/builder/amazon-windows/common/step_generate_secure_vm_config.go b/builder/amazon-windows/common/step_generate_secure_vm_config.go new file mode 100644 index 0000000..4df87ec --- /dev/null +++ b/builder/amazon-windows/common/step_generate_secure_vm_config.go @@ -0,0 +1,130 @@ +package common + +import ( + "bytes" + "encoding/base64" + "fmt" + "io/ioutil" + "text/template" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + + wincommon "github.com/packer-community/packer-windows-plugins/common" +) + +type StepGenerateSecureWinRMUserData struct { + WinRMConfig *wincommon.WinRMConfig + WinRMCertificateFile string + RunConfig *RunConfig +} + +func (s *StepGenerateSecureWinRMUserData) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if !s.RunConfig.ConfigureSecureWinRM { + return multistep.ActionContinue + } + + ui.Say("Generating user data for configuring WinRM over TLS...") + + certBytes, err := ioutil.ReadFile(s.WinRMCertificateFile) + if err != nil { + ui.Error(fmt.Sprintf("Error reading WinRM certificate file: %s", err)) + return multistep.ActionHalt + } + + encodedCert := base64.StdEncoding.EncodeToString(certBytes) + + var buffer bytes.Buffer + err = configureSecureWinRMTemplate.Execute(&buffer, configureSecureWinRMOptions{ + CertificatePfxBase64Encoded: encodedCert, + InstallListenerCommand: installListenerCommand, + AllowBasicCommand: allowBasicCommand, + AllowUnencryptedCommand: allowUnencryptedCommand, + AllowCredSSPCommand: allowCredSSPCommand, + MaxMemoryPerShellCommand: maxMemoryPerShellCommand, + MaxTimeoutMsCommand: maxTimeoutMsCommand, + }) + if err != nil { + ui.Error(fmt.Sprintf("Error executing Secure WinRM User Data template: %s", err)) + return multistep.ActionHalt + } + + s.RunConfig.UserData = buffer.String() + return multistep.ActionContinue +} + +func (s *StepGenerateSecureWinRMUserData) Cleanup(multistep.StateBag) { + // No cleanup... +} + +type configureSecureWinRMOptions struct { + CertificatePfxBase64Encoded string + InstallListenerCommand string + AllowBasicCommand string + AllowUnencryptedCommand string + AllowCredSSPCommand string + MaxMemoryPerShellCommand string + MaxTimeoutMsCommand string +} + +//This is needed to because Powershell uses ` for escapes and there's no straightforward way of constructing +// the necessary escaping in the hash otherwise. +const ( + installListenerCommand = "Start-Process -FilePath winrm -ArgumentList \"create winrm/config/listener?Address=*+Transport=HTTPS @{Hostname=`\"$certSubjectName`\";CertificateThumbprint=`\"$certThumbprint`\";Port=`\"5986`\"}\" -NoNewWindow -Wait" + + allowBasicCommand = "Start-Process -FilePath winrm -ArgumentList \"set winrm/config/service/auth @{Basic=`\"true`\"}\" -NoNewWindow -Wait" + allowUnencryptedCommand = "Start-Process -FilePath winrm -ArgumentList \"set winrm/config/service @{AllowUnencrypted=`\"false`\"}\" -NoNewWindow -Wait" + allowCredSSPCommand = "Start-Process -FilePath winrm -ArgumentList \"set winrm/config/service/auth @{CredSSP=`\"true`\"}\" -NoNewWindow -Wait" + maxMemoryPerShellCommand = "Start-Process -FilePath winrm -ArgumentList \"set winrm/config/winrs @{MaxMemoryPerShellMB=`\"1024`\"}\" -NoNewWindow -Wait" + maxTimeoutMsCommand = "Start-Process -FilePath winrm -ArgumentList \"set winrm/config @{MaxTimeoutms=`\"1800000`\"}\" -NoNewWindow -Wait" +) + +var configureSecureWinRMTemplate = template.Must(template.New("ConfigureSecureWinRM").Parse(` +Write-Host "Disabling WinRM over HTTP..." +Disable-NetFirewallRule -Name "WINRM-HTTP-In-TCP" +Disable-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" + +Start-Process -FilePath winrm -ArgumentList "delete winrm/config/listener?Address=*+Transport=HTTP" -NoNewWindow -Wait + +Write-Host "Configuring WinRM for HTTPS..." + +{{.MaxTimeoutMsCommand}} + +{{.MaxMemoryPerShellCommand}} + +{{.AllowUnencryptedCommand}} + +{{.AllowBasicCommand}} + +{{.AllowCredSSPCommand}} + +New-NetFirewallRule -Name "WINRM-HTTPS-In-TCP" -DisplayName "Windows Remote Management (HTTPS-In)" -Description "Inbound rule for Windows Remote Management via WS-Management. [TCP 5986]" -Group "Windows Remote Management" -Program "System" -Protocol TCP -LocalPort "5986" -Action Allow -Profile Domain,Private + +New-NetFirewallRule -Name "WINRM-HTTPS-In-TCP-PUBLIC" -DisplayName "Windows Remote Management (HTTPS-In)" -Description "Inbound rule for Windows Remote Management via WS-Management. [TCP 5986]" -Group "Windows Remote Management" -Program "System" -Protocol TCP -LocalPort "5986" -Action Allow -Profile Public + +$certContent = "{{.CertificatePfxBase64Encoded}}" + +$certBytes = [System.Convert]::FromBase64String($certContent) +$pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 +$pfx.Import($certBytes, "", "Exportable,PersistKeySet,MachineKeySet") +$certThumbprint = $pfx.Thumbprint +$certSubjectName = $pfx.SubjectName.Name.TrimStart("CN = ").Trim() + +$store = new-object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine") +try { + $store.Open("ReadWrite,MaxAllowed") + $store.Add($pfx) + +} finally { + $store.Close() +} + +{{.InstallListenerCommand}} + +Write-Host "Restarting WinRM Service..." +Stop-Service winrm +Set-Service winrm -StartupType "Automatic" +Start-Service winrm +`)) diff --git a/builder/amazon-windows/common/step_get_password.go b/builder/amazon-windows/common/step_get_password.go index d5f61ba..08898f1 100644 --- a/builder/amazon-windows/common/step_get_password.go +++ b/builder/amazon-windows/common/step_get_password.go @@ -34,7 +34,7 @@ func (s *StepGetPassword) Run(state multistep.StateBag) multistep.StepAction { cancel := make(chan struct{}) waitDone := make(chan bool, 1) go func() { - ui.Say(fmt.Sprintf("Retrieving auto-generated password for instance %s...", instance)) + ui.Say(fmt.Sprintf("Retrieving auto-generated password for instance %s...", instance.InstanceId)) password, err = s.waitForPassword(state, cancel) if err != nil { @@ -44,7 +44,7 @@ func (s *StepGetPassword) Run(state multistep.StateBag) multistep.StepAction { waitDone <- true }() - log.Printf("Waiting to retrieve instance %s password, up to timeout: %s", instance, s.GetPasswordTimeout) + log.Printf("Waiting to retrieve instance %s password, up to timeout: %s", instance.InstanceId, s.GetPasswordTimeout) timeout := time.After(s.GetPasswordTimeout) WaitLoop: @@ -60,11 +60,10 @@ WaitLoop: } s.WinRMConfig.WinRMPassword = password - ui.Say(fmt.Sprintf("Retrieved password for instance %s", instance)) break WaitLoop case <-timeout: - err := fmt.Errorf(fmt.Sprintf("Timeout retrieving password for instance %s", instance)) + err := fmt.Errorf(fmt.Sprintf("Timeout retrieving password for instance %s", instance.InstanceId)) state.Put("error", err) ui.Error(err.Error()) close(cancel) diff --git a/builder/amazon-windows/common/step_run_source_instance.go b/builder/amazon-windows/common/step_run_source_instance.go index 96638c5..d9b6b44 100644 --- a/builder/amazon-windows/common/step_run_source_instance.go +++ b/builder/amazon-windows/common/step_run_source_instance.go @@ -9,25 +9,16 @@ import ( "github.com/mitchellh/goamz/ec2" "github.com/mitchellh/multistep" - awscommon "github.com/mitchellh/packer/builder/amazon/common" "github.com/mitchellh/packer/packer" + + awscommon "github.com/mitchellh/packer/builder/amazon/common" ) type StepRunSourceInstance struct { - AssociatePublicIpAddress bool - AvailabilityZone string - BlockDevices awscommon.BlockDevices - Debug bool - ExpectedRootDevice string - InstanceType string - IamInstanceProfile string - SourceAMI string - SpotPrice string - SpotPriceProduct string - SubnetId string - Tags map[string]string - UserData string - UserDataFile string + RunConfig *RunConfig + BlockDevices *awscommon.BlockDevices + ExpectedRootDevice string + Debug bool instance *ec2.Instance spotRequest *ec2.SpotRequestResult @@ -39,9 +30,9 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi securityGroupIds := state.Get("securityGroupIds").([]string) ui := state.Get("ui").(packer.Ui) - userData := s.UserData - if s.UserDataFile != "" { - contents, err := ioutil.ReadFile(s.UserDataFile) + userData := s.RunConfig.UserData + if s.RunConfig.UserDataFile != "" { + contents, err := ioutil.ReadFile(s.RunConfig.UserDataFile) if err != nil { state.Put("error", fmt.Errorf("Problem reading user data file: %s", err)) return multistep.ActionHalt @@ -84,9 +75,9 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi // Detect the spot price startTime := time.Now().Add(-1 * time.Hour) resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistory{ - InstanceType: []string{s.InstanceType}, - ProductDescription: []string{s.SpotPriceProduct}, - AvailabilityZone: s.AvailabilityZone, + InstanceType: []string{s.RunConfig.InstanceType}, + ProductDescription: []string{s.RunConfig.SpotPriceAutoProduct}, + AvailabilityZone: s.RunConfig.AvailabilityZone, StartTime: startTime, }) if err != nil { @@ -123,17 +114,17 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi if spotPrice == "" { runOpts := &ec2.RunInstances{ KeyName: keyName, - ImageId: s.SourceAMI, - InstanceType: s.InstanceType, + ImageId: s.RunConfig.SourceAmi, + InstanceType: s.RunConfig.InstanceType, UserData: []byte(userData), MinCount: 0, MaxCount: 0, SecurityGroups: securityGroups, - IamInstanceProfile: s.IamInstanceProfile, - SubnetId: s.SubnetId, - AssociatePublicIpAddress: s.AssociatePublicIpAddress, + IamInstanceProfile: s.RunConfig.IamInstanceProfile, + SubnetId: s.RunConfig.SubnetId, + AssociatePublicIpAddress: s.RunConfig.AssociatePublicIpAddress, BlockDevices: s.BlockDevices.BuildLaunchDevices(), - AvailZone: s.AvailabilityZone, + AvailZone: s.RunConfig.AvailabilityZone, } runResp, err := ec2conn.RunInstances(runOpts) if err != nil { @@ -146,20 +137,20 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi } else { ui.Message(fmt.Sprintf( "Requesting spot instance '%s' for: %s", - s.InstanceType, spotPrice)) + s.RunConfig.InstanceType, spotPrice)) runOpts := &ec2.RequestSpotInstances{ SpotPrice: spotPrice, KeyName: keyName, - ImageId: s.SourceAMI, - InstanceType: s.InstanceType, + ImageId: s.RunConfig.SourceAmi, + InstanceType: s.RunConfig.InstanceType, UserData: []byte(userData), SecurityGroups: securityGroups, - IamInstanceProfile: s.IamInstanceProfile, - SubnetId: s.SubnetId, - AssociatePublicIpAddress: s.AssociatePublicIpAddress, + IamInstanceProfile: s.RunConfig.IamInstanceProfile, + SubnetId: s.RunConfig.SubnetId, + AssociatePublicIpAddress: s.RunConfig.AssociatePublicIpAddress, BlockDevices: s.BlockDevices.BuildLaunchDevices(), - AvailZone: s.AvailabilityZone, + AvailZone: s.RunConfig.AvailabilityZone, } runSpotResp, err := ec2conn.RequestSpotInstances(runOpts) if err != nil { @@ -196,18 +187,26 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi instanceId = spotResp.SpotRequestResults[0].InstanceId } - ui.Message(fmt.Sprintf("Instance ID: %s", instanceId)) + instanceResp, err := ec2conn.Instances([]string{instanceId}, nil) + if err != nil { + err := fmt.Errorf("Error finding source instance (%s): %s", instanceId, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + s.instance = &instanceResp.Reservations[0].Instances[0] + ui.Message(fmt.Sprintf("Instance ID: %s", s.instance.InstanceId)) - ui.Say(fmt.Sprintf("Waiting for instance (%v) to become ready...", instanceId)) - stateChange := StateChangeConf{ + ui.Say(fmt.Sprintf("Waiting for instance (%s) to become ready...", s.instance.InstanceId)) + stateChange := awscommon.StateChangeConf{ Pending: []string{"pending"}, Target: "running", - Refresh: InstanceStateRefreshFunc(ec2conn, instanceId), + Refresh: awscommon.InstanceStateRefreshFunc(ec2conn, s.instance), StepState: state, } - latestInstance, err := WaitForState(&stateChange) + latestInstance, err := awscommon.WaitForState(&stateChange) if err != nil { - err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err) + err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", s.instance.InstanceId, err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt @@ -215,9 +214,9 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi s.instance = latestInstance.(*ec2.Instance) - ec2Tags := make([]ec2.Tag, 1, len(s.Tags)+1) + ec2Tags := make([]ec2.Tag, 1, len(s.RunConfig.RunTags)+1) ec2Tags[0] = ec2.Tag{"Name", "Packer Builder"} - for k, v := range s.Tags { + for k, v := range s.RunConfig.RunTags { ec2Tags = append(ec2Tags, ec2.Tag{k, v}) } diff --git a/builder/amazon-windows/ebs/builder.go b/builder/amazon-windows/ebs/builder.go index be47691..734a5d7 100644 --- a/builder/amazon-windows/ebs/builder.go +++ b/builder/amazon-windows/ebs/builder.go @@ -89,6 +89,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Build the steps steps := []multistep.Step{ + &winawscommon.StepGenerateSecureWinRMUserData{ + RunConfig: &b.config.RunConfig, + WinRMConfig: &b.config.WinRMConfig, + WinRMCertificateFile: b.config.WinRMCertificateFile, + }, &awscommon.StepSourceAMIInfo{ SourceAmi: b.config.SourceAmi, EnhancedNetworking: b.config.AMIEnhancedNetworking, @@ -105,20 +110,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe VpcId: b.config.VpcId, }, &winawscommon.StepRunSourceInstance{ - Debug: b.config.PackerDebug, - ExpectedRootDevice: "ebs", - SpotPrice: b.config.SpotPrice, - SpotPriceProduct: b.config.SpotPriceAutoProduct, - InstanceType: b.config.InstanceType, - UserData: b.config.UserData, - UserDataFile: b.config.UserDataFile, - SourceAMI: b.config.SourceAmi, - IamInstanceProfile: b.config.IamInstanceProfile, - SubnetId: b.config.SubnetId, - AssociatePublicIpAddress: b.config.AssociatePublicIpAddress, - AvailabilityZone: b.config.AvailabilityZone, - BlockDevices: b.config.BlockDevices, - Tags: b.config.RunTags, + Debug: b.config.PackerDebug, + ExpectedRootDevice: "ebs", + BlockDevices: &b.config.BlockDevices, + RunConfig: &b.config.RunConfig, }, &winawscommon.StepGetPassword{WinRMConfig: &b.config.WinRMConfig, GetPasswordTimeout: 5 * time.Minute}, winawscommon.NewConnectStep(ec2conn, b.config.WinRMPrivateIp, &b.config.WinRMConfig), From f954b2378ea8d9e36db3f0960c965d25458d9caa Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 30 Mar 2015 12:38:36 -0400 Subject: [PATCH 06/17] Add support for changing Administrator password It may be desirable for users to specify a new Administrator password rather than retrieving the generated one from Amazon. To acheive this we reset the local Administrator password to a known value as part of the user data script which configures WinRM. Consequently this can only be used with the winrm_autoconfigure option turned on, though that isn't necessarily a long-term limitation. --- builder/amazon-windows/common/run_config.go | 3 +- .../common/step_generate_secure_vm_config.go | 52 ++++++++++++++----- .../common/step_get_password.go | 6 +++ builder/amazon-windows/ebs/builder.go | 6 ++- 4 files changed, 51 insertions(+), 16 deletions(-) diff --git a/builder/amazon-windows/common/run_config.go b/builder/amazon-windows/common/run_config.go index 2e21d60..739b427 100644 --- a/builder/amazon-windows/common/run_config.go +++ b/builder/amazon-windows/common/run_config.go @@ -14,9 +14,11 @@ import ( type RunConfig struct { AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"` AvailabilityZone string `mapstructure:"availability_zone"` + ConfigureSecureWinRM bool `mapstructure:"winrm_autoconfigure"` IamInstanceProfile string `mapstructure:"iam_instance_profile"` InstanceType string `mapstructure:"instance_type"` KeyPairPrivateKeyFile string `mapstructure:"key_pair_private_key_file"` + NewAdministratorPassword string `mapstructure:"new_administrator_password"` RunTags map[string]string `mapstructure:"run_tags"` SourceAmi string `mapstructure:"source_ami"` SpotPrice string `mapstructure:"spot_price"` @@ -30,7 +32,6 @@ type RunConfig struct { VpcId string `mapstructure:"vpc_id"` WinRMPrivateIp bool `mapstructure:"winrm_private_ip"` WinRMCertificateFile string `mapstructure:"winrm_certificate_file"` - ConfigureSecureWinRM bool `mapstructure:"winrm_autoconfigure"` } func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { diff --git a/builder/amazon-windows/common/step_generate_secure_vm_config.go b/builder/amazon-windows/common/step_generate_secure_vm_config.go index 4df87ec..313fe79 100644 --- a/builder/amazon-windows/common/step_generate_secure_vm_config.go +++ b/builder/amazon-windows/common/step_generate_secure_vm_config.go @@ -36,15 +36,28 @@ func (s *StepGenerateSecureWinRMUserData) Run(state multistep.StateBag) multiste encodedCert := base64.StdEncoding.EncodeToString(certBytes) + var adminPasswordBuffer bytes.Buffer + if s.RunConfig.NewAdministratorPassword != "" { + ui.Say("Configuring user data to change Administrator password...") + err = changeAdministratorPasswordTemplate.Execute(&adminPasswordBuffer, changeAdministratorPasswordOptions{ + NewAdministratorPassword: s.RunConfig.NewAdministratorPassword, + }) + if err != nil { + ui.Error(fmt.Sprintf("Error executing Change Administrator Password template: %s", err)) + return multistep.ActionHalt + } + } + var buffer bytes.Buffer err = configureSecureWinRMTemplate.Execute(&buffer, configureSecureWinRMOptions{ - CertificatePfxBase64Encoded: encodedCert, - InstallListenerCommand: installListenerCommand, - AllowBasicCommand: allowBasicCommand, - AllowUnencryptedCommand: allowUnencryptedCommand, - AllowCredSSPCommand: allowCredSSPCommand, - MaxMemoryPerShellCommand: maxMemoryPerShellCommand, - MaxTimeoutMsCommand: maxTimeoutMsCommand, + CertificatePfxBase64Encoded: encodedCert, + InstallListenerCommand: installListenerCommand, + AllowBasicCommand: allowBasicCommand, + AllowUnencryptedCommand: allowUnencryptedCommand, + AllowCredSSPCommand: allowCredSSPCommand, + MaxMemoryPerShellCommand: maxMemoryPerShellCommand, + MaxTimeoutMsCommand: maxTimeoutMsCommand, + ChangeAdministratorPasswordCommand: adminPasswordBuffer.String(), }) if err != nil { ui.Error(fmt.Sprintf("Error executing Secure WinRM User Data template: %s", err)) @@ -59,14 +72,23 @@ func (s *StepGenerateSecureWinRMUserData) Cleanup(multistep.StateBag) { // No cleanup... } +type changeAdministratorPasswordOptions struct { + NewAdministratorPassword string +} + +var changeAdministratorPasswordTemplate = template.Must(template.New("ChangeAdministratorPassword").Parse(`$user = [adsi]"WinNT://localhost/Administrator,user" +$user.SetPassword("{{.NewAdministratorPassword}}") +$user.SetInfo()`)) + type configureSecureWinRMOptions struct { - CertificatePfxBase64Encoded string - InstallListenerCommand string - AllowBasicCommand string - AllowUnencryptedCommand string - AllowCredSSPCommand string - MaxMemoryPerShellCommand string - MaxTimeoutMsCommand string + CertificatePfxBase64Encoded string + InstallListenerCommand string + AllowBasicCommand string + AllowUnencryptedCommand string + AllowCredSSPCommand string + MaxMemoryPerShellCommand string + MaxTimeoutMsCommand string + ChangeAdministratorPasswordCommand string } //This is needed to because Powershell uses ` for escapes and there's no straightforward way of constructing @@ -123,6 +145,8 @@ try { {{.InstallListenerCommand}} +{{.ChangeAdministratorPasswordCommand}} + Write-Host "Restarting WinRM Service..." Stop-Service winrm Set-Service winrm -StartupType "Automatic" diff --git a/builder/amazon-windows/common/step_get_password.go b/builder/amazon-windows/common/step_get_password.go index 08898f1..a220c2c 100644 --- a/builder/amazon-windows/common/step_get_password.go +++ b/builder/amazon-windows/common/step_get_password.go @@ -21,6 +21,7 @@ import ( type StepGetPassword struct { WinRMConfig *wincommon.WinRMConfig + RunConfig *RunConfig GetPasswordTimeout time.Duration } @@ -28,6 +29,11 @@ func (s *StepGetPassword) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) instance := state.Get("instance").(*ec2.Instance) + if s.RunConfig.NewAdministratorPassword != "" { + s.WinRMConfig.WinRMPassword = s.RunConfig.NewAdministratorPassword + return multistep.ActionContinue + } + var password string var err error diff --git a/builder/amazon-windows/ebs/builder.go b/builder/amazon-windows/ebs/builder.go index 734a5d7..0e2de2b 100644 --- a/builder/amazon-windows/ebs/builder.go +++ b/builder/amazon-windows/ebs/builder.go @@ -115,7 +115,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe BlockDevices: &b.config.BlockDevices, RunConfig: &b.config.RunConfig, }, - &winawscommon.StepGetPassword{WinRMConfig: &b.config.WinRMConfig, GetPasswordTimeout: 5 * time.Minute}, + &winawscommon.StepGetPassword{ + WinRMConfig: &b.config.WinRMConfig, + RunConfig: &b.config.RunConfig, + GetPasswordTimeout: 5 * time.Minute, + }, winawscommon.NewConnectStep(ec2conn, b.config.WinRMPrivateIp, &b.config.WinRMConfig), &common.StepProvision{}, &stepStopInstance{SpotPrice: b.config.SpotPrice}, From 90efd09c79cd7b4dbc19b40b060173ebd9df9b3f Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 30 Mar 2015 19:33:39 -0400 Subject: [PATCH 07/17] Change the builder ID for Windows Amazon EBS The builder ID should be unique per builder - this commit changes it from being the same as the AWS builder included in core packer. --- builder/amazon-windows/ebs/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/amazon-windows/ebs/builder.go b/builder/amazon-windows/ebs/builder.go index 0e2de2b..252bf36 100644 --- a/builder/amazon-windows/ebs/builder.go +++ b/builder/amazon-windows/ebs/builder.go @@ -20,7 +20,7 @@ import ( ) // The unique ID for this builder -const BuilderId = "mitchellh.amazonebs" +const BuilderId = "packercommunity.amazonwinebs" type config struct { common.PackerConfig `mapstructure:",squash"` From 772e34f547349aa9941acb89cc33a809537921ee Mon Sep 17 00:00:00 2001 From: James Nugent Date: Tue, 31 Mar 2015 06:24:10 -0400 Subject: [PATCH 08/17] Ignore IntelliJ project files --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 0206537..41fb989 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,7 @@ _testmain.go *.cov bin pkg + +#IntelliJ Go Project Files +.idea +*.iml From be6f3e467c2f49c7ba3d0d056293e8bb2e0f4202 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Tue, 31 Mar 2015 06:24:48 -0400 Subject: [PATCH 09/17] Set ExecutionPolicy to RemoteSigned in user data This commit adds a call to `Set-ExecutionPolicy RemoteSigned` if the WinRM configuration is being autogenerated. This allows provisioners to work without a custom execution command bypassing the execution policy. Ideally at some point in the future this should be a configuration option on the builders rather than hard coded. --- .../amazon-windows/common/step_generate_secure_vm_config.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builder/amazon-windows/common/step_generate_secure_vm_config.go b/builder/amazon-windows/common/step_generate_secure_vm_config.go index 313fe79..ee7f966 100644 --- a/builder/amazon-windows/common/step_generate_secure_vm_config.go +++ b/builder/amazon-windows/common/step_generate_secure_vm_config.go @@ -104,6 +104,9 @@ const ( ) var configureSecureWinRMTemplate = template.Must(template.New("ConfigureSecureWinRM").Parse(` +Write-Host "Setting execution policy to RemoteSigned..." +Set-ExecutionPolicy RemoteSigned + Write-Host "Disabling WinRM over HTTP..." Disable-NetFirewallRule -Name "WINRM-HTTP-In-TCP" Disable-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" From abe0398259795177fbb0b41fbdfcba9d2dee5e76 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 30 Mar 2015 22:10:44 -0400 Subject: [PATCH 10/17] Propagate HTTPS, Insecure and Cert to communicator This allows the communicator to work properly over HTTPS and honour the settings for ignoring the certificate chain validator or for the CA certificate to validate against. It relies on using the changes accepted into winrmcp in 2c6c680. --- common/step_connect_winrm.go | 1 + communicator/winrm/communicator.go | 3 +++ plugin/communicator-winrm/main.go | 18 +++++++++++++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/common/step_connect_winrm.go b/common/step_connect_winrm.go index b808ee9..0b58516 100644 --- a/common/step_connect_winrm.go +++ b/common/step_connect_winrm.go @@ -144,5 +144,6 @@ func newEndpoint(address string, config *WinRMConfig) (*winrm.Endpoint, error) { Port: iport, HTTPS: !config.WinRMHttp, Insecure: config.WinRMIgnoreCertChain, + CACert: &config.WinRMCACertBytes, }, nil } diff --git a/communicator/winrm/communicator.go b/communicator/winrm/communicator.go index f64003c..b872472 100644 --- a/communicator/winrm/communicator.go +++ b/communicator/winrm/communicator.go @@ -111,6 +111,9 @@ func (c *Communicator) newCopyClient() (*winrmcp.Winrmcp, error) { User: c.user, Password: c.password, }, + Https: c.endpoint.HTTPS, + Insecure: c.endpoint.Insecure, + CACertBytes: *c.endpoint.CACert, OperationTimeout: time.Minute * 5, MaxOperationsPerShell: 15, // lowest common denominator }) diff --git a/plugin/communicator-winrm/main.go b/plugin/communicator-winrm/main.go index ea8fd9e..b7ae7ed 100644 --- a/plugin/communicator-winrm/main.go +++ b/plugin/communicator-winrm/main.go @@ -2,18 +2,21 @@ package main import ( "flag" - plugin "github.com/packer-community/packer-windows-plugins/communicator/winrm" + "log" + "os" + "time" + "github.com/masterzen/winrm/winrm" "github.com/mitchellh/packer/packer" rpc "github.com/mitchellh/packer/packer/plugin" + plugin "github.com/packer-community/packer-windows-plugins/communicator/winrm" "github.com/rakyll/command" - "log" - "os" - "time" ) var host = flag.String("host", "localhost", "host machine") var port = flag.Int("port", 5985, "host port") +var http = flag.Bool("http", false, "use http") +var ignoreCertChain = flag.Bool("ignorecertchain", false, "ignore certificate chain") var user = flag.String("user", "vagrant", "user to run as") var pass = flag.String("pass", "vagrant", "user's password") var timeout = flag.Duration("timeout", 60*time.Second, "connection timeout") @@ -49,7 +52,12 @@ func (r *RunCommand) Flags(fs *flag.FlagSet) *flag.FlagSet { func (r *RunCommand) Run(args []string) { command := args[0] - endpoint := &winrm.Endpoint{Host: *host, Port: *port} + endpoint := &winrm.Endpoint{ + Host: *host, + Port: *port, + HTTPS: !*http, + Insecure: *ignoreCertChain, + } communicator, err := plugin.New(endpoint, *user, *pass, *timeout) rc := &packer.RemoteCmd{ Command: command, From abd74b08306beeb6f391e17d6c3a0c71f59fe472 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Tue, 31 Mar 2015 08:51:14 -0400 Subject: [PATCH 11/17] Implement secure WinRM config on instance builder This commmit rolls the changes made in the EBS builder into the Instance builder for AWS. Since most of the changes were in common types this is confined to adding the steps into the build pipeline and changing some of the parameters to be pointer types. Changed steps are: - [Add] StepGenerateSecureWinRMUserData - [Add] StepKeyPair - [Modify] StepRunSourceInstance - we now use the version in winawscommon rather than in awscommon - [Add] StepGetPassword - [Modify] StepConnect - now requires the WinRM Configuration as a pointer rather than a copy. --- builder/amazon-windows/instance/builder.go | 37 +++++++++++++--------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/builder/amazon-windows/instance/builder.go b/builder/amazon-windows/instance/builder.go index 06cffde..8a55bb4 100644 --- a/builder/amazon-windows/instance/builder.go +++ b/builder/amazon-windows/instance/builder.go @@ -30,7 +30,7 @@ type Config struct { awscommon.AMIConfig `mapstructure:",squash"` awscommon.BlockDevices `mapstructure:",squash"` winawscommon.RunConfig `mapstructure:",squash"` - *wincommon.WinRMConfig `mapstructure:",squash"` + wincommon.WinRMConfig `mapstructure:",squash"` AccountId string `mapstructure:"account_id"` BundleDestination string `mapstructure:"bundle_destination"` @@ -190,34 +190,41 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state.Put("ec2", ec2conn) state.Put("hook", hook) state.Put("ui", ui) + state.Put("keyPair", b.config.TemporaryKeyPairName) // Build the steps steps := []multistep.Step{ + &winawscommon.StepGenerateSecureWinRMUserData{ + RunConfig: &b.config.RunConfig, + WinRMConfig: &b.config.WinRMConfig, + WinRMCertificateFile: b.config.WinRMCertificateFile, + }, &awscommon.StepSourceAMIInfo{ SourceAmi: b.config.SourceAmi, EnhancedNetworking: b.config.AMIEnhancedNetworking, }, + &awscommon.StepKeyPair{ + Debug: b.config.PackerDebug, + DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), + KeyPairName: b.config.TemporaryKeyPairName, + PrivateKeyFile: b.config.KeyPairPrivateKeyFile, + }, &winawscommon.StepSecurityGroup{ SecurityGroupIds: b.config.SecurityGroupIds, WinRMPort: b.config.WinRMPort, VpcId: b.config.VpcId, }, &winawscommon.StepRunSourceInstance{ - Debug: b.config.PackerDebug, - SpotPrice: b.config.SpotPrice, - SpotPriceProduct: b.config.SpotPriceAutoProduct, - InstanceType: b.config.InstanceType, - IamInstanceProfile: b.config.IamInstanceProfile, - UserData: b.config.UserData, - UserDataFile: b.config.UserDataFile, - SourceAMI: b.config.SourceAmi, - SubnetId: b.config.SubnetId, - AssociatePublicIpAddress: b.config.AssociatePublicIpAddress, - AvailabilityZone: b.config.AvailabilityZone, - BlockDevices: b.config.BlockDevices, - Tags: b.config.RunTags, + Debug: b.config.PackerDebug, + BlockDevices: &b.config.BlockDevices, + RunConfig: &b.config.RunConfig, + }, + &winawscommon.StepGetPassword{ + WinRMConfig: &b.config.WinRMConfig, + RunConfig: &b.config.RunConfig, + GetPasswordTimeout: 5 * time.Minute, }, - winawscommon.NewConnectStep(ec2conn, b.config.WinRMPrivateIp, b.config.WinRMConfig), + winawscommon.NewConnectStep(ec2conn, b.config.WinRMPrivateIp, &b.config.WinRMConfig), &common.StepProvision{}, &awsinstcommon.StepUploadX509Cert{}, &awsinstcommon.StepBundleVolume{ From e0e96b5b0d17d1ae35727bd845f5cf5bd2337f0a Mon Sep 17 00:00:00 2001 From: James Nugent Date: Tue, 31 Mar 2015 09:02:50 -0400 Subject: [PATCH 12/17] Fix broken tests in communicator and builders Various tests were broken along the way in the VirtualBox, Parallels and Instance builders, and the WinRM Communicator as the previous work was done primarily on the EBS builder. This commit fixes all tests. --- builder/amazon-windows/instance/builder.go | 1 + .../common/connect_step_test.go | 4 +-- .../common/connect_step_test.go | 2 +- common/winrm_config_test.go | 2 +- communicator/winrm/communicator_test.go | 28 ++++++++++++++++--- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/builder/amazon-windows/instance/builder.go b/builder/amazon-windows/instance/builder.go index 8a55bb4..56dc227 100644 --- a/builder/amazon-windows/instance/builder.go +++ b/builder/amazon-windows/instance/builder.go @@ -8,6 +8,7 @@ import ( "log" "os" "strings" + "time" "github.com/mitchellh/goamz/ec2" "github.com/mitchellh/multistep" diff --git a/builder/parallels-windows/common/connect_step_test.go b/builder/parallels-windows/common/connect_step_test.go index 9f3ef21..8674a05 100644 --- a/builder/parallels-windows/common/connect_step_test.go +++ b/builder/parallels-windows/common/connect_step_test.go @@ -20,7 +20,7 @@ func TestWinRMAddressFunc_UsesPortForwardingFail(t *testing.T) { state.Put("driver", ¶llelscommon.DriverMock{IpAddressError: errors.New("Invalid machine state"), MacReturn: "01cd123"}) state.Put("vmName", "myvmname") - f := WinRMAddressFunc(config) + f := WinRMAddressFunc(&config) _, err := f(state) if err == nil { @@ -39,7 +39,7 @@ func TestWinRMAddressFunc_UsesPortForwarding(t *testing.T) { state.Put("driver", ¶llelscommon.DriverMock{IpAddressReturn: "172.17.4.13", MacReturn: "01cd123"}) state.Put("vmName", "myvmname") - f := WinRMAddressFunc(config) + f := WinRMAddressFunc(&config) address, err := f(state) if err != nil { diff --git a/builder/virtualbox-windows/common/connect_step_test.go b/builder/virtualbox-windows/common/connect_step_test.go index 3bba71f..e429d34 100644 --- a/builder/virtualbox-windows/common/connect_step_test.go +++ b/builder/virtualbox-windows/common/connect_step_test.go @@ -16,7 +16,7 @@ func TestWinRMAddressFunc_UsesPortForwarding(t *testing.T) { state := new(multistep.BasicStateBag) state.Put("winrmHostPort", uint(123)) - f := WinRMAddressFunc(config) + f := WinRMAddressFunc(&config) address, err := f(state) if err != nil { diff --git a/common/winrm_config_test.go b/common/winrm_config_test.go index 27088b0..45e5eef 100644 --- a/common/winrm_config_test.go +++ b/common/winrm_config_test.go @@ -27,7 +27,7 @@ func TestWinRMConfigPrepare(t *testing.T) { t.Fatalf("err: %#v", errs) } - if c.WinRMPort != 5985 { + if c.WinRMPort != 5986 { t.Errorf("bad winrm port: %d", c.WinRMPort) } } diff --git a/communicator/winrm/communicator_test.go b/communicator/winrm/communicator_test.go index ed1b904..143897e 100644 --- a/communicator/winrm/communicator_test.go +++ b/communicator/winrm/communicator_test.go @@ -24,7 +24,14 @@ func TestStart(t *testing.T) { // You can comment this line out temporarily during development t.Skip() - comm, err := New(&winrm.Endpoint{"localhost", 5985}, "vagrant", "vagrant", time.Duration(1)*time.Minute) + endpoint := &winrm.Endpoint{ + Host: "localhost", + Port: 5985, + HTTPS: false, + Insecure: false, + CACert: nil, + } + comm, err := New(endpoint, "vagrant", "vagrant", time.Duration(1)*time.Minute) if err != nil { t.Fatalf("error connecting to WinRM: %s", err) } @@ -59,7 +66,14 @@ func TestUpload(t *testing.T) { // You can comment this line out temporarily during development t.Skip() - comm, err := New(&winrm.Endpoint{"localhost", 5985}, "vagrant", "vagrant", time.Duration(1)*time.Minute) + endpoint := &winrm.Endpoint{ + Host: "localhost", + Port: 5985, + HTTPS: false, + Insecure: false, + CACert: nil, + } + comm, err := New(endpoint, "vagrant", "vagrant", time.Duration(1)*time.Minute) if err != nil { t.Fatalf("error connecting to WinRM: %s", err) } @@ -81,7 +95,14 @@ func TestUploadDir(t *testing.T) { // You can comment this line out temporarily during development t.Skip() - comm, err := New(&winrm.Endpoint{"localhost", 5985}, "vagrant", "vagrant", time.Duration(1)*time.Minute) + endpoint := &winrm.Endpoint{ + Host: "localhost", + Port: 5985, + HTTPS: false, + Insecure: false, + CACert: nil, + } + comm, err := New(endpoint, "vagrant", "vagrant", time.Duration(1)*time.Minute) if err != nil { t.Fatalf("error connecting to WinRM: %s", err) } @@ -91,4 +112,3 @@ func TestUploadDir(t *testing.T) { t.Fatalf("error uploading dir: %s", err) } } - From 844d7eef90660c03698bee3d0b3c69bbd4abdfec Mon Sep 17 00:00:00 2001 From: James Nugent Date: Tue, 31 Mar 2015 09:16:41 -0400 Subject: [PATCH 13/17] Set POSH ExecutionPolicy to Bypass in provisioners This commit changes from setting the Powershell ExecutionPolicy to RemoteSigned as part of the user data script to setting it to Bypass by default as part of the ExecuteCommand for provisioners. This seems neater as we don't change the machine more than necessary underneath the user and remove the need to override the ExecuteCommand every time a provisioner is used. --- .../amazon-windows/common/step_generate_secure_vm_config.go | 3 --- provisioner/powershell/elevated.go | 2 +- provisioner/powershell/provisioner.go | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/builder/amazon-windows/common/step_generate_secure_vm_config.go b/builder/amazon-windows/common/step_generate_secure_vm_config.go index ee7f966..313fe79 100644 --- a/builder/amazon-windows/common/step_generate_secure_vm_config.go +++ b/builder/amazon-windows/common/step_generate_secure_vm_config.go @@ -104,9 +104,6 @@ const ( ) var configureSecureWinRMTemplate = template.Must(template.New("ConfigureSecureWinRM").Parse(` -Write-Host "Setting execution policy to RemoteSigned..." -Set-ExecutionPolicy RemoteSigned - Write-Host "Disabling WinRM over HTTP..." Disable-NetFirewallRule -Name "WINRM-HTTP-In-TCP" Disable-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go index 04ebaf8..83fe938 100644 --- a/provisioner/powershell/elevated.go +++ b/provisioner/powershell/elevated.go @@ -53,7 +53,7 @@ $t.XmlText = @' cmd - /c powershell.exe -EncodedCommand {{.EncodedCommand}} > %TEMP%\{{.TaskName}}.out 2>&1 + /c powershell.exe -ExecutionPolicy Bypass -EncodedCommand {{.EncodedCommand}} > %TEMP%\{{.TaskName}}.out 2>&1 diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index c6fa28d..2fb4eea 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -120,7 +120,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell "& { {{.Vars}}{{.Path}}; exit $LastExitCode}"` + p.config.ExecuteCommand = `powershell -ExecutionPolicy Bypass "& { {{.Vars}}{{.Path}} }"` } if p.config.ElevatedExecuteCommand == "" { From 84078c6618ea9c29315a12ca0347c73985beac28 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Wed, 1 Apr 2015 10:24:30 -0400 Subject: [PATCH 14/17] Rename builder IDs to be consistent This commit renames some of the builder IDs which conflicted with the original packer versions to use the format: packercommunity.windows.. For example: packercommunity.windows.amazon.ebs Some already had third party IDs from their original authors, these have been left intact as they are unlikely to conflict (though not verified). --- builder/amazon-windows/ebs/builder.go | 2 +- builder/amazon-windows/instance/builder.go | 2 +- builder/virtualbox-windows/iso/builder.go | 2 +- builder/vmware-windows/iso/builder.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/amazon-windows/ebs/builder.go b/builder/amazon-windows/ebs/builder.go index 252bf36..21ce95d 100644 --- a/builder/amazon-windows/ebs/builder.go +++ b/builder/amazon-windows/ebs/builder.go @@ -20,7 +20,7 @@ import ( ) // The unique ID for this builder -const BuilderId = "packercommunity.amazonwinebs" +const BuilderId = "packercommunity.windows.amazon.ebs" type config struct { common.PackerConfig `mapstructure:",squash"` diff --git a/builder/amazon-windows/instance/builder.go b/builder/amazon-windows/instance/builder.go index 56dc227..8a1aab3 100644 --- a/builder/amazon-windows/instance/builder.go +++ b/builder/amazon-windows/instance/builder.go @@ -21,7 +21,7 @@ import ( ) // The unique ID for this builder -const BuilderId = "mitchellh.amazon.instance" +const BuilderId = "packercommunity.windows.amazon.instance" // Config is the configuration that is chained through the steps and // settable from the template. diff --git a/builder/virtualbox-windows/iso/builder.go b/builder/virtualbox-windows/iso/builder.go index d3e15b4..7950ae8 100644 --- a/builder/virtualbox-windows/iso/builder.go +++ b/builder/virtualbox-windows/iso/builder.go @@ -16,7 +16,7 @@ import ( wincommon "github.com/packer-community/packer-windows-plugins/common" ) -const BuilderId = "mitchellh.virtualbox" +const BuilderId = "packercommunity.windows.virtualbox.iso" type Builder struct { config config diff --git a/builder/vmware-windows/iso/builder.go b/builder/vmware-windows/iso/builder.go index 3b98f1c..68eeeef 100644 --- a/builder/vmware-windows/iso/builder.go +++ b/builder/vmware-windows/iso/builder.go @@ -17,7 +17,7 @@ import ( wincommon "github.com/packer-community/packer-windows-plugins/common" ) -const BuilderIdESX = "mitchellh.vmware-esx" +const BuilderIdESX = "packercommunity.windows.vmware.iso" type Builder struct { config config From 6e5cbea277a307aab0cf5dfd7bb64dc44a684eea Mon Sep 17 00:00:00 2001 From: James Nugent Date: Wed, 1 Apr 2015 10:25:46 -0400 Subject: [PATCH 15/17] Add plugin for Amazon instance builder This commit adds the plugin host for the Amazon Windows Instance builder which was previously missing. --- plugin/builder-amazon-windows-instance/main.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 plugin/builder-amazon-windows-instance/main.go diff --git a/plugin/builder-amazon-windows-instance/main.go b/plugin/builder-amazon-windows-instance/main.go new file mode 100644 index 0000000..8917822 --- /dev/null +++ b/plugin/builder-amazon-windows-instance/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/packer/plugin" + "github.com/packer-community/packer-windows-plugins/builder/amazon-windows/instance" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterBuilder(new(instance.Builder)) + server.Serve() +} From 6945b883dc02bb07b34a7fc166e79655b1c5e4e5 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Wed, 1 Apr 2015 10:29:41 -0400 Subject: [PATCH 16/17] Fix build error from rebasing onto upstream master --- builder/amazon-windows/common/step_run_source_instance.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/amazon-windows/common/step_run_source_instance.go b/builder/amazon-windows/common/step_run_source_instance.go index d9b6b44..80f03f2 100644 --- a/builder/amazon-windows/common/step_run_source_instance.go +++ b/builder/amazon-windows/common/step_run_source_instance.go @@ -47,14 +47,14 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi } ui.Say("Launching a source AWS instance...") - imageResp, err := ec2conn.Images([]string{s.SourceAMI}, ec2.NewFilter()) + imageResp, err := ec2conn.Images([]string{s.RunConfig.SourceAmi}, ec2.NewFilter()) if err != nil { state.Put("error", fmt.Errorf("There was a problem with the source AMI: %s", err)) return multistep.ActionHalt } if len(imageResp.Images) != 1 { - state.Put("error", fmt.Errorf("The source AMI '%s' could not be found.", s.SourceAMI)) + state.Put("error", fmt.Errorf("The source AMI '%s' could not be found.", s.RunConfig.SourceAmi)) return multistep.ActionHalt } @@ -66,11 +66,11 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi return multistep.ActionHalt } - spotPrice := s.SpotPrice + spotPrice := s.RunConfig.SpotPrice if spotPrice == "auto" { ui.Message(fmt.Sprintf( "Finding spot price for %s %s...", - s.SpotPriceProduct, s.InstanceType)) + s.RunConfig.SpotPriceAutoProduct, s.RunConfig.InstanceType)) // Detect the spot price startTime := time.Now().Add(-1 * time.Hour) From 1073ff00517a0b1147ffbb9c5f985978fb570109 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 20 Apr 2015 18:10:28 -0400 Subject: [PATCH 17/17] Move from mitchellh/goamz to awslabs/aws-sdk-go This commit moves the builders for Amazon Web Services EC2 from using the unofficial library to using the official AWS Go SDK. This relies on https://github.com/mitchellh/packer/pull/2034 being accepted. --- builder/amazon-windows/common/run_config.go | 2 +- builder/amazon-windows/common/state.go | 42 +++-- builder/amazon-windows/common/step_connect.go | 29 +-- .../common/step_generate_secure_vm_config.go | 17 +- .../common/step_get_password.go | 35 ++-- .../common/step_run_source_instance.go | 175 ++++++++++-------- .../common/step_security_group.go | 39 ++-- builder/amazon-windows/ebs/builder.go | 11 +- builder/amazon-windows/ebs/step_create_ami.go | 28 +-- .../ebs/step_modify_instance.go | 14 +- .../amazon-windows/ebs/step_stop_instance.go | 5 +- builder/amazon-windows/instance/builder.go | 11 +- .../amazon-windows/instance/builder_test.go | 3 +- 13 files changed, 222 insertions(+), 189 deletions(-) diff --git a/builder/amazon-windows/common/run_config.go b/builder/amazon-windows/common/run_config.go index 739b427..6106468 100644 --- a/builder/amazon-windows/common/run_config.go +++ b/builder/amazon-windows/common/run_config.go @@ -92,7 +92,7 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { if c.ConfigureSecureWinRM { if c.UserData != "" || c.UserDataFile != "" { - errs = append(errs, fmt.Errorf("winrm_autoconfigure cannot be used in conjunction with user_data or user_data_file.")) + errs = append(errs, fmt.Errorf("winrm_autoconfigure cannot be used in conjunction with user_data or user_data_file")) } if c.WinRMCertificateFile == "" { diff --git a/builder/amazon-windows/common/state.go b/builder/amazon-windows/common/state.go index a3abd6e..439c6ea 100644 --- a/builder/amazon-windows/common/state.go +++ b/builder/amazon-windows/common/state.go @@ -3,13 +3,15 @@ package common import ( "errors" "fmt" - "github.com/mitchellh/goamz/ec2" - "github.com/mitchellh/multistep" "log" "net" "os" "strconv" "time" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/ec2" + "github.com/mitchellh/multistep" ) // StateRefreshFunc is a function type used for StateChangeConf that is @@ -36,9 +38,13 @@ type StateChangeConf struct { // an AMI for state changes. func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc { return func() (interface{}, string, error) { - resp, err := conn.Images([]string{imageId}, ec2.NewFilter()) + input := &ec2.DescribeImagesInput{ + ImageIDs: []*string{&imageId}, + } + resp, err := conn.DescribeImages(input) + if err != nil { - if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" { + if ec2err, ok := err.(*aws.APIError); ok && ec2err.Code == "InvalidAMIID.NotFound" { // Set this to nil as if we didn't find anything. resp = nil } else if isTransientNetworkError(err) { @@ -57,17 +63,20 @@ func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc { } i := resp.Images[0] - return i, i.State, nil + return i, *i.State, nil } } // InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch // an EC2 instance. -func InstanceStateRefreshFunc(conn *ec2.EC2, instanceId string) StateRefreshFunc { +func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc { return func() (interface{}, string, error) { - resp, err := conn.Instances([]string{instanceId}, ec2.NewFilter()) + input := &ec2.DescribeInstancesInput{ + InstanceIDs: []*string{i.InstanceID}, + } + resp, err := conn.DescribeInstances(input) if err != nil { - if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" { + if ec2err, ok := err.(*aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" { // Set this to nil as if we didn't find anything. resp = nil } else if isTransientNetworkError(err) { @@ -85,8 +94,8 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, instanceId string) StateRefreshFunc return nil, "", nil } - i := &resp.Reservations[0].Instances[0] - return i, i.State.Name, nil + i := resp.Reservations[0].Instances[0] + return i, *i.State.Name, nil } } @@ -94,9 +103,12 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, instanceId string) StateRefreshFunc // a spot request for state changes. func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefreshFunc { return func() (interface{}, string, error) { - resp, err := conn.DescribeSpotRequests([]string{spotRequestId}, ec2.NewFilter()) + input := &ec2.DescribeSpotInstanceRequestsInput{ + SpotInstanceRequestIDs: []*string{&spotRequestId}, + } + resp, err := conn.DescribeSpotInstanceRequests(input) if err != nil { - if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidSpotInstanceRequestID.NotFound" { + if ec2err, ok := err.(*aws.APIError); ok && ec2err.Code == "InvalidSpotInstanceRequestID.NotFound" { // Set this to nil as if we didn't find anything. resp = nil } else if isTransientNetworkError(err) { @@ -108,14 +120,14 @@ func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefre } } - if resp == nil || len(resp.SpotRequestResults) == 0 { + if resp == nil || len(resp.SpotInstanceRequests) == 0 { // Sometimes AWS has consistency issues and doesn't see the // SpotRequest. Return an empty state. return nil, "", nil } - i := resp.SpotRequestResults[0] - return i, i.State, nil + i := resp.SpotInstanceRequests[0] + return i, *i.State, nil } } diff --git a/builder/amazon-windows/common/step_connect.go b/builder/amazon-windows/common/step_connect.go index af2566f..49db9f0 100644 --- a/builder/amazon-windows/common/step_connect.go +++ b/builder/amazon-windows/common/step_connect.go @@ -1,14 +1,14 @@ package common import ( - "fmt" - "github.com/mitchellh/goamz/ec2" - "github.com/mitchellh/multistep" - //"github.com/mitchellh/packer/common" "errors" - wincommon "github.com/packer-community/packer-windows-plugins/common" + "fmt" "log" "time" + + "github.com/awslabs/aws-sdk-go/service/ec2" + "github.com/mitchellh/multistep" + wincommon "github.com/packer-community/packer-windows-plugins/common" ) // Returns an Endpoint suitable for the WinRM communicator @@ -17,13 +17,13 @@ func WinRMAddress(e *ec2.EC2, port uint, private bool) func(multistep.StateBag) for j := 0; j < 2; j++ { var host string i := state.Get("instance").(*ec2.Instance) - if i.DNSName != "" { - host = i.DNSName - } else if i.VpcId != "" { - if i.PublicIpAddress != "" && !private { - host = i.PublicIpAddress + if *i.PublicDNSName != "" { + host = *i.PublicDNSName + } else if *i.VPCID != "" { + if *i.PublicIPAddress != "" && !private { + host = *i.PublicIPAddress } else { - host = i.PrivateIpAddress + host = *i.PrivateIPAddress } } @@ -32,13 +32,16 @@ func WinRMAddress(e *ec2.EC2, port uint, private bool) func(multistep.StateBag) return fmt.Sprintf("%s:%d", host, port), nil } - r, err := e.Instances([]string{i.InstanceId}, ec2.NewFilter()) + input := &ec2.DescribeInstancesInput{ + InstanceIDs: []*string{i.InstanceID}, + } + r, err := e.DescribeInstances(input) if err != nil { return "", err } if len(r.Reservations) == 0 || len(r.Reservations[0].Instances) == 0 { - return "", fmt.Errorf("instance not found: %s", i.InstanceId) + return "", fmt.Errorf("instance not found: %s", i.InstanceID) } state.Put("instance", &r.Reservations[0].Instances[0]) diff --git a/builder/amazon-windows/common/step_generate_secure_vm_config.go b/builder/amazon-windows/common/step_generate_secure_vm_config.go index 313fe79..c5d43e1 100644 --- a/builder/amazon-windows/common/step_generate_secure_vm_config.go +++ b/builder/amazon-windows/common/step_generate_secure_vm_config.go @@ -13,6 +13,8 @@ import ( wincommon "github.com/packer-community/packer-windows-plugins/common" ) +//A StepGenerateSecureWinRMUserData contains the state necessary to run the step to +//generate EC2 user-data for the given RunConfig. type StepGenerateSecureWinRMUserData struct { WinRMConfig *wincommon.WinRMConfig WinRMCertificateFile string @@ -50,13 +52,14 @@ func (s *StepGenerateSecureWinRMUserData) Run(state multistep.StateBag) multiste var buffer bytes.Buffer err = configureSecureWinRMTemplate.Execute(&buffer, configureSecureWinRMOptions{ - CertificatePfxBase64Encoded: encodedCert, - InstallListenerCommand: installListenerCommand, - AllowBasicCommand: allowBasicCommand, - AllowUnencryptedCommand: allowUnencryptedCommand, - AllowCredSSPCommand: allowCredSSPCommand, - MaxMemoryPerShellCommand: maxMemoryPerShellCommand, - MaxTimeoutMsCommand: maxTimeoutMsCommand, + CertificatePfxBase64Encoded: encodedCert, + InstallListenerCommand: installListenerCommand, + AllowBasicCommand: allowBasicCommand, + AllowUnencryptedCommand: allowUnencryptedCommand, + AllowCredSSPCommand: allowCredSSPCommand, + MaxMemoryPerShellCommand: maxMemoryPerShellCommand, + MaxTimeoutMsCommand: maxTimeoutMsCommand, + ChangeAdministratorPasswordCommand: adminPasswordBuffer.String(), }) if err != nil { diff --git a/builder/amazon-windows/common/step_get_password.go b/builder/amazon-windows/common/step_get_password.go index a220c2c..a861705 100644 --- a/builder/amazon-windows/common/step_get_password.go +++ b/builder/amazon-windows/common/step_get_password.go @@ -10,9 +10,7 @@ import ( "log" "time" - "code.google.com/p/gosshold/ssh/terminal" - - "github.com/mitchellh/goamz/ec2" + "github.com/awslabs/aws-sdk-go/service/ec2" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" @@ -40,7 +38,7 @@ func (s *StepGetPassword) Run(state multistep.StateBag) multistep.StepAction { cancel := make(chan struct{}) waitDone := make(chan bool, 1) go func() { - ui.Say(fmt.Sprintf("Retrieving auto-generated password for instance %s...", instance.InstanceId)) + ui.Say(fmt.Sprintf("Retrieving auto-generated password for instance %s...", *instance.InstanceID)) password, err = s.waitForPassword(state, cancel) if err != nil { @@ -50,7 +48,7 @@ func (s *StepGetPassword) Run(state multistep.StateBag) multistep.StepAction { waitDone <- true }() - log.Printf("Waiting to retrieve instance %s password, up to timeout: %s", instance.InstanceId, s.GetPasswordTimeout) + log.Printf("Waiting to retrieve instance %s password, up to timeout: %s", *instance.InstanceID, s.GetPasswordTimeout) timeout := time.After(s.GetPasswordTimeout) WaitLoop: @@ -69,7 +67,7 @@ WaitLoop: break WaitLoop case <-timeout: - err := fmt.Errorf(fmt.Sprintf("Timeout retrieving password for instance %s", instance.InstanceId)) + err := fmt.Errorf(fmt.Sprintf("Timeout retrieving password for instance %s", *instance.InstanceID)) state.Put("error", err) ui.Error(err.Error()) close(cancel) @@ -103,14 +101,17 @@ func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-cha case <-time.After(20 * time.Second): } - resp, err := ec2conn.GetPasswordData(instance.InstanceId) + input := &ec2.GetPasswordDataInput{ + InstanceID: instance.InstanceID, + } + resp, err := ec2conn.GetPasswordData(input) if err != nil { err := fmt.Errorf("Error retrieving auto-generated instance password: %s", err) return "", err } - if resp.PasswordData != "" { - decryptedPassword, err := decryptPasswordDataWithPrivateKey(resp.PasswordData, []byte(privateKey)) + if *resp.PasswordData != "" { + decryptedPassword, err := decryptPasswordDataWithPrivateKey(*resp.PasswordData, []byte(privateKey)) if err != nil { err := fmt.Errorf("Error decrypting auto-generated instance password: %s", err) return "", err @@ -133,21 +134,11 @@ func decryptPasswordDataWithPrivateKey(passwordData string, pemBytes []byte) (st block, _ := pem.Decode(pemBytes) var asn1Bytes []byte if _, ok := block.Headers["DEK-Info"]; ok { - fmt.Printf("Encrypted private key. Please enter passphrase: ") - password, err := terminal.ReadPassword(0) - fmt.Printf("\n") - if err != nil { - return "", err - } - - asn1Bytes, err = x509.DecryptPEMBlock(block, password) - if err != nil { - return "", err - } - } else { - asn1Bytes = block.Bytes + return "", fmt.Errorf("Cannot decrypt instance password as the keypair is protected with a passphrase") } + asn1Bytes = block.Bytes + key, err := x509.ParsePKCS1PrivateKey(asn1Bytes) if err != nil { return "", err diff --git a/builder/amazon-windows/common/step_run_source_instance.go b/builder/amazon-windows/common/step_run_source_instance.go index 80f03f2..ced3e70 100644 --- a/builder/amazon-windows/common/step_run_source_instance.go +++ b/builder/amazon-windows/common/step_run_source_instance.go @@ -7,7 +7,8 @@ import ( "strconv" "time" - "github.com/mitchellh/goamz/ec2" + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/ec2" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" @@ -21,15 +22,20 @@ type StepRunSourceInstance struct { Debug bool instance *ec2.Instance - spotRequest *ec2.SpotRequestResult + spotRequest *ec2.SpotInstanceRequest } func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepAction { ec2conn := state.Get("ec2").(*ec2.EC2) keyName := state.Get("keyPair").(string) - securityGroupIds := state.Get("securityGroupIds").([]string) + tempSecurityGroupIds := state.Get("securityGroupIds").([]string) ui := state.Get("ui").(packer.Ui) + securityGroupIds := make([]*string, len(tempSecurityGroupIds)) + for i, sg := range tempSecurityGroupIds { + securityGroupIds[i] = &sg + } + userData := s.RunConfig.UserData if s.RunConfig.UserDataFile != "" { contents, err := ioutil.ReadFile(s.RunConfig.UserDataFile) @@ -41,13 +47,10 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi userData = string(contents) } - securityGroups := make([]ec2.SecurityGroup, len(securityGroupIds)) - for n, securityGroupId := range securityGroupIds { - securityGroups[n] = ec2.SecurityGroup{Id: securityGroupId} - } - ui.Say("Launching a source AWS instance...") - imageResp, err := ec2conn.Images([]string{s.RunConfig.SourceAmi}, ec2.NewFilter()) + imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ + ImageIDs: []*string{&s.RunConfig.SourceAmi}, + }) if err != nil { state.Put("error", fmt.Errorf("There was a problem with the source AMI: %s", err)) return multistep.ActionHalt @@ -58,11 +61,11 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi return multistep.ActionHalt } - if s.ExpectedRootDevice != "" && imageResp.Images[0].RootDeviceType != s.ExpectedRootDevice { + if s.ExpectedRootDevice != "" && *imageResp.Images[0].RootDeviceType != s.ExpectedRootDevice { state.Put("error", fmt.Errorf( "The provided source AMI has an invalid root device type.\n"+ "Expected '%s', got '%s'.", - s.ExpectedRootDevice, imageResp.Images[0].RootDeviceType)) + s.ExpectedRootDevice, *imageResp.Images[0].RootDeviceType)) return multistep.ActionHalt } @@ -74,11 +77,11 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi // Detect the spot price startTime := time.Now().Add(-1 * time.Hour) - resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistory{ - InstanceType: []string{s.RunConfig.InstanceType}, - ProductDescription: []string{s.RunConfig.SpotPriceAutoProduct}, - AvailabilityZone: s.RunConfig.AvailabilityZone, - StartTime: startTime, + resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistoryInput{ + InstanceTypes: []*string{&s.RunConfig.InstanceType}, + ProductDescriptions: []*string{&s.RunConfig.SpotPriceAutoProduct}, + AvailabilityZone: &s.RunConfig.AvailabilityZone, + StartTime: &startTime, }) if err != nil { err := fmt.Errorf("Error finding spot price: %s", err) @@ -88,9 +91,9 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi } var price float64 - for _, history := range resp.History { + for _, history := range resp.SpotPriceHistory { log.Printf("[INFO] Candidate spot price: %s", history.SpotPrice) - current, err := strconv.ParseFloat(history.SpotPrice, 64) + current, err := strconv.ParseFloat(*history.SpotPrice, 64) if err != nil { log.Printf("[ERR] Error parsing spot price: %s", err) continue @@ -112,20 +115,33 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi var instanceId string if spotPrice == "" { - runOpts := &ec2.RunInstances{ - KeyName: keyName, - ImageId: s.RunConfig.SourceAmi, - InstanceType: s.RunConfig.InstanceType, - UserData: []byte(userData), - MinCount: 0, - MaxCount: 0, - SecurityGroups: securityGroups, - IamInstanceProfile: s.RunConfig.IamInstanceProfile, - SubnetId: s.RunConfig.SubnetId, - AssociatePublicIpAddress: s.RunConfig.AssociatePublicIpAddress, - BlockDevices: s.BlockDevices.BuildLaunchDevices(), - AvailZone: s.RunConfig.AvailabilityZone, + runOpts := &ec2.RunInstancesInput{ + KeyName: &keyName, + ImageID: &s.RunConfig.SourceAmi, + InstanceType: &s.RunConfig.InstanceType, + UserData: &userData, + MaxCount: aws.Long(1), + MinCount: aws.Long(1), + IAMInstanceProfile: &ec2.IAMInstanceProfileSpecification{Name: &s.RunConfig.IamInstanceProfile}, + BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(), + Placement: &ec2.Placement{AvailabilityZone: &s.RunConfig.AvailabilityZone}, + } + + if s.RunConfig.SubnetId != "" && s.RunConfig.AssociatePublicIpAddress { + runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ + &ec2.InstanceNetworkInterfaceSpecification{ + DeviceIndex: aws.Long(0), + AssociatePublicIPAddress: &s.RunConfig.AssociatePublicIpAddress, + SubnetID: &s.RunConfig.SubnetId, + Groups: securityGroupIds, + DeleteOnTermination: aws.Boolean(true), + }, + } + } else { + runOpts.SubnetID = &s.RunConfig.SubnetId + runOpts.SecurityGroupIDs = securityGroupIds } + runResp, err := ec2conn.RunInstances(runOpts) if err != nil { err := fmt.Errorf("Error launching source instance: %s", err) @@ -133,26 +149,29 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi ui.Error(err.Error()) return multistep.ActionHalt } - instanceId = runResp.Instances[0].InstanceId + instanceId = *runResp.Instances[0].InstanceID } else { ui.Message(fmt.Sprintf( "Requesting spot instance '%s' for: %s", s.RunConfig.InstanceType, spotPrice)) - runOpts := &ec2.RequestSpotInstances{ - SpotPrice: spotPrice, - KeyName: keyName, - ImageId: s.RunConfig.SourceAmi, - InstanceType: s.RunConfig.InstanceType, - UserData: []byte(userData), - SecurityGroups: securityGroups, - IamInstanceProfile: s.RunConfig.IamInstanceProfile, - SubnetId: s.RunConfig.SubnetId, - AssociatePublicIpAddress: s.RunConfig.AssociatePublicIpAddress, - BlockDevices: s.BlockDevices.BuildLaunchDevices(), - AvailZone: s.RunConfig.AvailabilityZone, - } - runSpotResp, err := ec2conn.RequestSpotInstances(runOpts) + runSpotResp, err := ec2conn.RequestSpotInstances(&ec2.RequestSpotInstancesInput{ + SpotPrice: &spotPrice, + LaunchSpecification: &ec2.RequestSpotLaunchSpecification{ + KeyName: &keyName, + ImageID: &s.RunConfig.SourceAmi, + InstanceType: &s.RunConfig.InstanceType, + UserData: &userData, + SecurityGroupIDs: securityGroupIds, + IAMInstanceProfile: &ec2.IAMInstanceProfileSpecification{Name: &s.RunConfig.IamInstanceProfile}, + SubnetID: &s.RunConfig.SubnetId, + NetworkInterfaces: []*ec2.InstanceNetworkInterfaceSpecification{ + &ec2.InstanceNetworkInterfaceSpecification{AssociatePublicIPAddress: &s.RunConfig.AssociatePublicIpAddress}, + }, + Placement: &ec2.SpotPlacement{AvailabilityZone: &s.RunConfig.AvailabilityZone}, + BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(), + }, + }) if err != nil { err := fmt.Errorf("Error launching source spot instance: %s", err) state.Put("error", err) @@ -160,14 +179,14 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi return multistep.ActionHalt } - s.spotRequest = &runSpotResp.SpotRequestResults[0] + s.spotRequest = runSpotResp.SpotInstanceRequests[0] - spotRequestId := s.spotRequest.SpotRequestId + spotRequestId := s.spotRequest.SpotInstanceRequestID ui.Message(fmt.Sprintf("Waiting for spot request (%s) to become active...", spotRequestId)) stateChange := awscommon.StateChangeConf{ Pending: []string{"open"}, Target: "active", - Refresh: awscommon.SpotRequestStateRefreshFunc(ec2conn, spotRequestId), + Refresh: awscommon.SpotRequestStateRefreshFunc(ec2conn, *spotRequestId), StepState: state, } _, err = awscommon.WaitForState(&stateChange) @@ -177,27 +196,29 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi ui.Error(err.Error()) return multistep.ActionHalt } - spotResp, err := ec2conn.DescribeSpotRequests([]string{spotRequestId}, nil) + spotResp, err := ec2conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{ + SpotInstanceRequestIDs: []*string{spotRequestId}, + }) if err != nil { err := fmt.Errorf("Error finding spot request (%s): %s", spotRequestId, err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - instanceId = spotResp.SpotRequestResults[0].InstanceId + instanceId = *spotResp.SpotInstanceRequests[0].InstanceID } - instanceResp, err := ec2conn.Instances([]string{instanceId}, nil) + instanceResp, err := ec2conn.DescribeInstances(&ec2.DescribeInstancesInput{InstanceIDs: []*string{&instanceId}}) if err != nil { err := fmt.Errorf("Error finding source instance (%s): %s", instanceId, err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - s.instance = &instanceResp.Reservations[0].Instances[0] - ui.Message(fmt.Sprintf("Instance ID: %s", s.instance.InstanceId)) + s.instance = instanceResp.Reservations[0].Instances[0] + ui.Message(fmt.Sprintf("Instance ID: %s", *s.instance.InstanceID)) - ui.Say(fmt.Sprintf("Waiting for instance (%s) to become ready...", s.instance.InstanceId)) + ui.Say(fmt.Sprintf("Waiting for instance (%s) to become ready...", *s.instance.InstanceID)) stateChange := awscommon.StateChangeConf{ Pending: []string{"pending"}, Target: "running", @@ -206,7 +227,7 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi } latestInstance, err := awscommon.WaitForState(&stateChange) if err != nil { - err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", s.instance.InstanceId, err) + err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", *s.instance.InstanceID, err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt @@ -214,29 +235,32 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi s.instance = latestInstance.(*ec2.Instance) - ec2Tags := make([]ec2.Tag, 1, len(s.RunConfig.RunTags)+1) - ec2Tags[0] = ec2.Tag{"Name", "Packer Builder"} + ec2Tags := make([]*ec2.Tag, 1, len(s.RunConfig.RunTags)+1) + ec2Tags[0] = &ec2.Tag{Key: aws.String("Name"), Value: aws.String("Packer Builder")} for k, v := range s.RunConfig.RunTags { - ec2Tags = append(ec2Tags, ec2.Tag{k, v}) + ec2Tags = append(ec2Tags, &ec2.Tag{Key: &k, Value: &v}) } - _, err = ec2conn.CreateTags([]string{s.instance.InstanceId}, ec2Tags) + _, err = ec2conn.CreateTags(&ec2.CreateTagsInput{ + Tags: ec2Tags, + Resources: []*string{s.instance.InstanceID}, + }) if err != nil { ui.Message( fmt.Sprintf("Failed to tag a Name on the builder instance: %s", err)) } if s.Debug { - if s.instance.DNSName != "" { - ui.Message(fmt.Sprintf("Public DNS: %s", s.instance.DNSName)) + if s.instance.PublicDNSName != nil && *s.instance.PublicDNSName != "" { + ui.Message(fmt.Sprintf("Public DNS: %s", *s.instance.PublicDNSName)) } - if s.instance.PublicIpAddress != "" { - ui.Message(fmt.Sprintf("Public IP: %s", s.instance.PublicIpAddress)) + if s.instance.PublicIPAddress != nil && *s.instance.PublicIPAddress != "" { + ui.Message(fmt.Sprintf("Public IP: %s", *s.instance.PublicIPAddress)) } - if s.instance.PrivateIpAddress != "" { - ui.Message(fmt.Sprintf("Private IP: %s", s.instance.PrivateIpAddress)) + if s.instance.PrivateIPAddress != nil && *s.instance.PrivateIPAddress != "" { + ui.Message(fmt.Sprintf("Private IP: %s", *s.instance.PrivateIPAddress)) } } @@ -253,17 +277,20 @@ func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) { // Cancel the spot request if it exists if s.spotRequest != nil { ui.Say("Cancelling the spot request...") - if _, err := ec2conn.CancelSpotRequests([]string{s.spotRequest.SpotRequestId}); err != nil { + input := &ec2.CancelSpotInstanceRequestsInput{ + SpotInstanceRequestIDs: []*string{s.spotRequest.InstanceID}, + } + if _, err := ec2conn.CancelSpotInstanceRequests(input); err != nil { ui.Error(fmt.Sprintf("Error cancelling the spot request, may still be around: %s", err)) return } - stateChange := awscommon.StateChangeConf{ + stateChange := StateChangeConf{ Pending: []string{"active", "open"}, - Refresh: awscommon.SpotRequestStateRefreshFunc(ec2conn, s.spotRequest.SpotRequestId), + Refresh: SpotRequestStateRefreshFunc(ec2conn, *s.spotRequest.SpotInstanceRequestID), Target: "cancelled", } - awscommon.WaitForState(&stateChange) + WaitForState(&stateChange) } @@ -271,16 +298,16 @@ func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) { if s.instance != nil { ui.Say("Terminating the source AWS instance...") - if _, err := ec2conn.TerminateInstances([]string{s.instance.InstanceId}); err != nil { + if _, err := ec2conn.TerminateInstances(&ec2.TerminateInstancesInput{InstanceIDs: []*string{s.instance.InstanceID}}); err != nil { ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err)) return } - stateChange := awscommon.StateChangeConf{ + stateChange := StateChangeConf{ Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, - Refresh: awscommon.InstanceStateRefreshFunc(ec2conn, s.instance), + Refresh: InstanceStateRefreshFunc(ec2conn, s.instance), Target: "terminated", } - awscommon.WaitForState(&stateChange) + WaitForState(&stateChange) } } diff --git a/builder/amazon-windows/common/step_security_group.go b/builder/amazon-windows/common/step_security_group.go index 6e19ce6..f4168ae 100644 --- a/builder/amazon-windows/common/step_security_group.go +++ b/builder/amazon-windows/common/step_security_group.go @@ -2,12 +2,14 @@ package common import ( "fmt" - "github.com/mitchellh/goamz/ec2" + "log" + "time" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/ec2" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/packer" - "log" - "time" ) type StepSecurityGroup struct { @@ -32,14 +34,15 @@ func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction { panic("WinRMPort must be set to a non-zero value.") } + // Create the group // Create the group ui.Say("Creating temporary security group for this instance...") groupName := fmt.Sprintf("packer %s", uuid.TimeOrderedUUID()) log.Printf("Temporary group name: %s", groupName) - group := ec2.SecurityGroup{ - Name: groupName, - Description: "Temporary group for Packer", - VpcId: s.VpcId, + group := &ec2.CreateSecurityGroupInput{ + GroupName: &groupName, + Description: aws.String("Temporary group for Packer"), + VPCID: &s.VpcId, } groupResp, err := ec2conn.CreateSecurityGroup(group) if err != nil { @@ -49,24 +52,24 @@ func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction { } // Set the group ID so we can delete it later - s.createdGroupId = groupResp.Id + s.createdGroupId = *groupResp.GroupID // Authorize the WinRM access - perms := []ec2.IPPerm{ - ec2.IPPerm{ - Protocol: "tcp", - FromPort: int(s.WinRMPort), - ToPort: int(s.WinRMPort), - SourceIPs: []string{"0.0.0.0/0"}, - }, + // Authorize the SSH access for the security group + req := &ec2.AuthorizeSecurityGroupIngressInput{ + GroupID: groupResp.GroupID, + IPProtocol: aws.String("tcp"), + FromPort: aws.Long(int64(s.WinRMPort)), + ToPort: aws.Long(int64(s.WinRMPort)), + CIDRIP: aws.String("0.0.0.0/0"), } // We loop and retry this a few times because sometimes the security // group isn't available immediately because AWS resources are eventaully // consistent. - ui.Say("Authorizing WinRM access on the temporary security group...") + ui.Say("Authorizing SSH access on the temporary security group...") for i := 0; i < 5; i++ { - _, err = ec2conn.AuthorizeSecurityGroup(groupResp.SecurityGroup, perms) + _, err = ec2conn.AuthorizeSecurityGroupIngress(req) if err == nil { break } @@ -100,7 +103,7 @@ func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) { var err error for i := 0; i < 5; i++ { - _, err = ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: s.createdGroupId}) + _, err = ec2conn.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{GroupID: &s.createdGroupId}) if err == nil { break } diff --git a/builder/amazon-windows/ebs/builder.go b/builder/amazon-windows/ebs/builder.go index 21ce95d..429a4ce 100644 --- a/builder/amazon-windows/ebs/builder.go +++ b/builder/amazon-windows/ebs/builder.go @@ -10,7 +10,7 @@ import ( "log" "time" - "github.com/mitchellh/goamz/ec2" + "github.com/awslabs/aws-sdk-go/service/ec2" "github.com/mitchellh/multistep" awscommon "github.com/mitchellh/packer/builder/amazon/common" "github.com/mitchellh/packer/common" @@ -67,17 +67,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { - region, err := b.config.Region() + config, err := b.config.Config() if err != nil { return nil, err } - auth, err := b.config.AccessConfig.Auth() - if err != nil { - return nil, err - } - - ec2conn := ec2.New(auth, region) + ec2conn := ec2.New(config) // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) diff --git a/builder/amazon-windows/ebs/step_create_ami.go b/builder/amazon-windows/ebs/step_create_ami.go index 967ad2d..1c38374 100644 --- a/builder/amazon-windows/ebs/step_create_ami.go +++ b/builder/amazon-windows/ebs/step_create_ami.go @@ -2,10 +2,12 @@ package ebs import ( "fmt" - "github.com/mitchellh/goamz/ec2" + "github.com/mitchellh/multistep" awscommon "github.com/mitchellh/packer/builder/amazon/common" "github.com/mitchellh/packer/packer" + + "github.com/awslabs/aws-sdk-go/service/ec2" ) type stepCreateAMI struct { @@ -20,10 +22,10 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction { // Create the image ui.Say(fmt.Sprintf("Creating the AMI: %s", config.AMIName)) - createOpts := &ec2.CreateImage{ - InstanceId: instance.InstanceId, - Name: config.AMIName, - BlockDevices: config.BlockDevices.BuildAMIDevices(), + createOpts := &ec2.CreateImageInput{ + InstanceID: instance.InstanceID, + Name: &config.AMIName, + BlockDeviceMappings: config.BlockDevices.BuildAMIDevices(), } createResp, err := ec2conn.CreateImage(createOpts) @@ -35,16 +37,16 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction { } // Set the AMI ID in the state - ui.Message(fmt.Sprintf("AMI: %s", createResp.ImageId)) + ui.Message(fmt.Sprintf("AMI: %s", *createResp.ImageID)) amis := make(map[string]string) - amis[ec2conn.Region.Name] = createResp.ImageId + amis[ec2conn.Config.Region] = *createResp.ImageID state.Put("amis", amis) // Wait for the image to become ready stateChange := awscommon.StateChangeConf{ Pending: []string{"pending"}, Target: "available", - Refresh: awscommon.AMIStateRefreshFunc(ec2conn, createResp.ImageId), + Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *createResp.ImageID), StepState: state, } @@ -56,14 +58,14 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - imagesResp, err := ec2conn.Images([]string{createResp.ImageId}, nil) + imagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIDs: []*string{createResp.ImageID}}) if err != nil { err := fmt.Errorf("Error searching for AMI: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - s.image = &imagesResp.Images[0] + s.image = imagesResp.Images[0] return multistep.ActionContinue } @@ -83,11 +85,9 @@ func (s *stepCreateAMI) Cleanup(state multistep.StateBag) { ui := state.Get("ui").(packer.Ui) ui.Say("Deregistering the AMI because cancelation or error...") - if resp, err := ec2conn.DeregisterImage(s.image.Id); err != nil { + deregisterOpts := &ec2.DeregisterImageInput{ImageID: s.image.ImageID} + if _, err := ec2conn.DeregisterImage(deregisterOpts); err != nil { ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", err)) return - } else if resp.Return == false { - ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", resp.Return)) - return } } diff --git a/builder/amazon-windows/ebs/step_modify_instance.go b/builder/amazon-windows/ebs/step_modify_instance.go index 21c5e7d..f7d9c8d 100644 --- a/builder/amazon-windows/ebs/step_modify_instance.go +++ b/builder/amazon-windows/ebs/step_modify_instance.go @@ -3,7 +3,9 @@ package ebs import ( "fmt" - "github.com/mitchellh/goamz/ec2" + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/ec2" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" ) @@ -19,12 +21,12 @@ func (s *stepModifyInstance) Run(state multistep.StateBag) multistep.StepAction // Set SriovNetSupport to "simple". See http://goo.gl/icuXh5 if config.AMIEnhancedNetworking { ui.Say("Enabling Enhanced Networking...") - _, err := ec2conn.ModifyInstance( - instance.InstanceId, - &ec2.ModifyInstance{SriovNetSupport: true}, - ) + _, err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{ + InstanceID: instance.InstanceID, + SRIOVNetSupport: &ec2.AttributeValue{Value: aws.String("simple")}, + }) if err != nil { - err := fmt.Errorf("Error enabling Enhanced Networking on %s: %s", instance.InstanceId, err) + err := fmt.Errorf("Error enabling Enhanced Networking on %s: %s", *instance.InstanceID, err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt diff --git a/builder/amazon-windows/ebs/step_stop_instance.go b/builder/amazon-windows/ebs/step_stop_instance.go index 09c19bd..1271b8b 100644 --- a/builder/amazon-windows/ebs/step_stop_instance.go +++ b/builder/amazon-windows/ebs/step_stop_instance.go @@ -2,7 +2,8 @@ package ebs import ( "fmt" - "github.com/mitchellh/goamz/ec2" + + "github.com/awslabs/aws-sdk-go/service/ec2" "github.com/mitchellh/multistep" awscommon "github.com/mitchellh/packer/builder/amazon/common" "github.com/mitchellh/packer/packer" @@ -24,7 +25,7 @@ func (s *stepStopInstance) Run(state multistep.StateBag) multistep.StepAction { // Stop the instance so we can create an AMI from it ui.Say("Stopping the source instance...") - _, err := ec2conn.StopInstances(instance.InstanceId) + _, err := ec2conn.StopInstances(&ec2.StopInstancesInput{InstanceIDs: []*string{instance.InstanceID}}) if err != nil { err := fmt.Errorf("Error stopping instance: %s", err) state.Put("error", err) diff --git a/builder/amazon-windows/instance/builder.go b/builder/amazon-windows/instance/builder.go index 8a1aab3..2c52f29 100644 --- a/builder/amazon-windows/instance/builder.go +++ b/builder/amazon-windows/instance/builder.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/mitchellh/goamz/ec2" + "github.com/awslabs/aws-sdk-go/service/ec2" "github.com/mitchellh/multistep" awscommon "github.com/mitchellh/packer/builder/amazon/common" awsinstcommon "github.com/mitchellh/packer/builder/amazon/instance" @@ -173,17 +173,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { - region, err := b.config.Region() + config, err := b.config.Config() if err != nil { return nil, err } - auth, err := b.config.AccessConfig.Auth() - if err != nil { - return nil, err - } - - ec2conn := ec2.New(auth, region) + ec2conn := ec2.New(config) // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) diff --git a/builder/amazon-windows/instance/builder_test.go b/builder/amazon-windows/instance/builder_test.go index d5ce5f2..eb23586 100644 --- a/builder/amazon-windows/instance/builder_test.go +++ b/builder/amazon-windows/instance/builder_test.go @@ -1,10 +1,11 @@ package instance import ( - "github.com/mitchellh/packer/packer" "io/ioutil" "os" "testing" + + "github.com/mitchellh/packer/packer" ) func testConfig() map[string]interface{} {