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 diff --git a/builder/amazon-windows/common/run_config.go b/builder/amazon-windows/common/run_config.go index cccf898..6106468 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" ) @@ -13,19 +14,24 @@ 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"` 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"` + WinRMCertificateFile string `mapstructure:"winrm_certificate_file"` } func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { @@ -38,17 +44,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 +78,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( @@ -77,11 +90,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/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/connect_step.go b/builder/amazon-windows/common/step_connect.go similarity index 66% rename from builder/amazon-windows/common/connect_step.go rename to builder/amazon-windows/common/step_connect.go index 028d690..49db9f0 100644 --- a/builder/amazon-windows/common/connect_step.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]) @@ -50,11 +53,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/common/step_generate_secure_vm_config.go b/builder/amazon-windows/common/step_generate_secure_vm_config.go new file mode 100644 index 0000000..c5d43e1 --- /dev/null +++ b/builder/amazon-windows/common/step_generate_secure_vm_config.go @@ -0,0 +1,157 @@ +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" +) + +//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 + 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 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, + + ChangeAdministratorPasswordCommand: adminPasswordBuffer.String(), + }) + 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 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 + ChangeAdministratorPasswordCommand 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}} + +{{.ChangeAdministratorPasswordCommand}} + +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 new file mode 100644 index 0000000..a861705 --- /dev/null +++ b/builder/amazon-windows/common/step_get_password.go @@ -0,0 +1,153 @@ +package common + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "log" + "time" + + "github.com/awslabs/aws-sdk-go/service/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 + RunConfig *RunConfig + GetPasswordTimeout time.Duration +} + +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 + + cancel := make(chan struct{}) + waitDone := make(chan bool, 1) + go func() { + ui.Say(fmt.Sprintf("Retrieving auto-generated password for instance %s...", *instance.InstanceID)) + + 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.InstanceID, 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 + break WaitLoop + + case <-timeout: + err := fmt.Errorf(fmt.Sprintf("Timeout retrieving password for instance %s", *instance.InstanceID)) + 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): + } + + 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 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 { + 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 + } + + out, err := rsa.DecryptPKCS1v15(nil, key, encryptedPasswd) + if err != nil { + return "", err + } + + return string(out), nil +} diff --git a/builder/amazon-windows/common/step_run_source_instance.go b/builder/amazon-windows/common/step_run_source_instance.go index 96638c5..ced3e70 100644 --- a/builder/amazon-windows/common/step_run_source_instance.go +++ b/builder/amazon-windows/common/step_run_source_instance.go @@ -7,41 +7,38 @@ 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" - 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 + 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) - userData := s.UserData - if s.UserDataFile != "" { - contents, err := ioutil.ReadFile(s.UserDataFile) + 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) if err != nil { state.Put("error", fmt.Errorf("Problem reading user data file: %s", err)) return multistep.ActionHalt @@ -50,44 +47,41 @@ 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.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 } 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 } - 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 } - 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) - resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistory{ - InstanceType: []string{s.InstanceType}, - ProductDescription: []string{s.SpotPriceProduct}, - AvailabilityZone: s.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) @@ -97,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 @@ -121,20 +115,33 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi var instanceId string if spotPrice == "" { - runOpts := &ec2.RunInstances{ - KeyName: keyName, - ImageId: s.SourceAMI, - InstanceType: s.InstanceType, - UserData: []byte(userData), - MinCount: 0, - MaxCount: 0, - SecurityGroups: securityGroups, - IamInstanceProfile: s.IamInstanceProfile, - SubnetId: s.SubnetId, - AssociatePublicIpAddress: s.AssociatePublicIpAddress, - BlockDevices: s.BlockDevices.BuildLaunchDevices(), - AvailZone: s.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) @@ -142,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.InstanceType, spotPrice)) - - runOpts := &ec2.RequestSpotInstances{ - SpotPrice: spotPrice, - KeyName: keyName, - ImageId: s.SourceAMI, - InstanceType: s.InstanceType, - UserData: []byte(userData), - SecurityGroups: securityGroups, - IamInstanceProfile: s.IamInstanceProfile, - SubnetId: s.SubnetId, - AssociatePublicIpAddress: s.AssociatePublicIpAddress, - BlockDevices: s.BlockDevices.BuildLaunchDevices(), - AvailZone: s.AvailabilityZone, - } - runSpotResp, err := ec2conn.RequestSpotInstances(runOpts) + s.RunConfig.InstanceType, spotPrice)) + + 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) @@ -169,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) @@ -186,28 +196,38 @@ 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 } - ui.Message(fmt.Sprintf("Instance ID: %s", instanceId)) + 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)) - 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,29 +235,32 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi s.instance = latestInstance.(*ec2.Instance) - ec2Tags := make([]ec2.Tag, 1, len(s.Tags)+1) - ec2Tags[0] = ec2.Tag{"Name", "Packer Builder"} - for k, v := range s.Tags { - ec2Tags = append(ec2Tags, ec2.Tag{k, v}) + 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{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)) } } @@ -254,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) } @@ -272,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 ff499d4..429a4ce 100644 --- a/builder/amazon-windows/ebs/builder.go +++ b/builder/amazon-windows/ebs/builder.go @@ -6,9 +6,11 @@ package ebs import ( + "fmt" "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" @@ -18,7 +20,7 @@ import ( ) // The unique ID for this builder -const BuilderId = "mitchellh.amazonebs" +const BuilderId = "packercommunity.windows.amazon.ebs" type config struct { common.PackerConfig `mapstructure:",squash"` @@ -65,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) @@ -83,38 +80,42 @@ 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{ + &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, - 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, + 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{}, &stepStopInstance{SpotPrice: b.config.SpotPrice}, // TODO(mitchellh): verify works with spots 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 f1da772..2c52f29 100644 --- a/builder/amazon-windows/instance/builder.go +++ b/builder/amazon-windows/instance/builder.go @@ -8,8 +8,9 @@ import ( "log" "os" "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" @@ -20,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. @@ -172,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) @@ -190,34 +186,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{ 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{} { 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/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/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/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/builder/virtualbox-windows/iso/builder.go b/builder/virtualbox-windows/iso/builder.go index 171b636..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 @@ -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..68eeeef 100644 --- a/builder/vmware-windows/iso/builder.go +++ b/builder/vmware-windows/iso/builder.go @@ -14,9 +14,10 @@ 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" +const BuilderIdESX = "packercommunity.windows.vmware.iso" type Builder struct { config config @@ -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..0b58516 100644 --- a/common/step_connect_winrm.go +++ b/common/step_connect_winrm.go @@ -29,11 +29,8 @@ 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 contains the configuration for WinRM. + WinRMConfig *WinRMConfig // WinRMWaitTimeout is the total timeout to wait for WinRM to become available. WinRMWaitTimeout time.Duration @@ -111,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 @@ -119,7 +116,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 @@ -131,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 @@ -141,5 +138,12 @@ 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, + CACert: &config.WinRMCACertBytes, + }, 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 == "" { 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.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/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) } } - 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() +} 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, 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 == "" {