github.com/kimor79/packer@v0.8.7-0.20151221212622-d507b18eb4cf/builder/amazon/common/step_run_source_instance.go (about) 1 package common 2 3 import ( 4 "encoding/base64" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "strconv" 9 "time" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/service/ec2" 13 14 "github.com/mitchellh/multistep" 15 "github.com/mitchellh/packer/packer" 16 ) 17 18 type StepRunSourceInstance struct { 19 AssociatePublicIpAddress bool 20 AvailabilityZone string 21 BlockDevices BlockDevices 22 Debug bool 23 EbsOptimized bool 24 ExpectedRootDevice string 25 InstanceType string 26 IamInstanceProfile string 27 SourceAMI string 28 SpotPrice string 29 SpotPriceProduct string 30 SubnetId string 31 Tags map[string]string 32 UserData string 33 UserDataFile string 34 35 instanceId string 36 spotRequest *ec2.SpotInstanceRequest 37 } 38 39 func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepAction { 40 ec2conn := state.Get("ec2").(*ec2.EC2) 41 keyName := state.Get("keyPair").(string) 42 tempSecurityGroupIds := state.Get("securityGroupIds").([]string) 43 ui := state.Get("ui").(packer.Ui) 44 45 securityGroupIds := make([]*string, len(tempSecurityGroupIds)) 46 for i, sg := range tempSecurityGroupIds { 47 securityGroupIds[i] = aws.String(sg) 48 } 49 50 userData := s.UserData 51 if s.UserDataFile != "" { 52 contents, err := ioutil.ReadFile(s.UserDataFile) 53 if err != nil { 54 state.Put("error", fmt.Errorf("Problem reading user data file: %s", err)) 55 return multistep.ActionHalt 56 } 57 58 // Test if it is encoded already, and if not, encode it 59 if _, err := base64.StdEncoding.DecodeString(string(contents)); err != nil { 60 log.Printf("[DEBUG] base64 encoding user data...") 61 contents = []byte(base64.StdEncoding.EncodeToString(contents)) 62 } 63 64 userData = string(contents) 65 66 } 67 68 ui.Say("Launching a source AWS instance...") 69 imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ 70 ImageIds: []*string{&s.SourceAMI}, 71 }) 72 if err != nil { 73 state.Put("error", fmt.Errorf("There was a problem with the source AMI: %s", err)) 74 return multistep.ActionHalt 75 } 76 77 if len(imageResp.Images) != 1 { 78 state.Put("error", fmt.Errorf("The source AMI '%s' could not be found.", s.SourceAMI)) 79 return multistep.ActionHalt 80 } 81 82 if s.ExpectedRootDevice != "" && *imageResp.Images[0].RootDeviceType != s.ExpectedRootDevice { 83 state.Put("error", fmt.Errorf( 84 "The provided source AMI has an invalid root device type.\n"+ 85 "Expected '%s', got '%s'.", 86 s.ExpectedRootDevice, *imageResp.Images[0].RootDeviceType)) 87 return multistep.ActionHalt 88 } 89 90 spotPrice := s.SpotPrice 91 availabilityZone := s.AvailabilityZone 92 if spotPrice == "auto" { 93 ui.Message(fmt.Sprintf( 94 "Finding spot price for %s %s...", 95 s.SpotPriceProduct, s.InstanceType)) 96 97 // Detect the spot price 98 startTime := time.Now().Add(-1 * time.Hour) 99 resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistoryInput{ 100 InstanceTypes: []*string{&s.InstanceType}, 101 ProductDescriptions: []*string{&s.SpotPriceProduct}, 102 AvailabilityZone: &s.AvailabilityZone, 103 StartTime: &startTime, 104 }) 105 if err != nil { 106 err := fmt.Errorf("Error finding spot price: %s", err) 107 state.Put("error", err) 108 ui.Error(err.Error()) 109 return multistep.ActionHalt 110 } 111 112 var price float64 113 for _, history := range resp.SpotPriceHistory { 114 log.Printf("[INFO] Candidate spot price: %s", *history.SpotPrice) 115 current, err := strconv.ParseFloat(*history.SpotPrice, 64) 116 if err != nil { 117 log.Printf("[ERR] Error parsing spot price: %s", err) 118 continue 119 } 120 if price == 0 || current < price { 121 price = current 122 if s.AvailabilityZone == "" { 123 availabilityZone = *history.AvailabilityZone 124 } 125 } 126 } 127 if price == 0 { 128 err := fmt.Errorf("No candidate spot prices found!") 129 state.Put("error", err) 130 ui.Error(err.Error()) 131 return multistep.ActionHalt 132 } 133 134 spotPrice = strconv.FormatFloat(price, 'f', -1, 64) 135 } 136 137 var instanceId string 138 139 if spotPrice == "" || spotPrice == "0" { 140 runOpts := &ec2.RunInstancesInput{ 141 KeyName: &keyName, 142 ImageId: &s.SourceAMI, 143 InstanceType: &s.InstanceType, 144 UserData: &userData, 145 MaxCount: aws.Int64(1), 146 MinCount: aws.Int64(1), 147 IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile}, 148 BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(), 149 Placement: &ec2.Placement{AvailabilityZone: &s.AvailabilityZone}, 150 EbsOptimized: &s.EbsOptimized, 151 } 152 153 if s.SubnetId != "" && s.AssociatePublicIpAddress { 154 runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ 155 &ec2.InstanceNetworkInterfaceSpecification{ 156 DeviceIndex: aws.Int64(0), 157 AssociatePublicIpAddress: &s.AssociatePublicIpAddress, 158 SubnetId: &s.SubnetId, 159 Groups: securityGroupIds, 160 DeleteOnTermination: aws.Bool(true), 161 }, 162 } 163 } else { 164 runOpts.SubnetId = &s.SubnetId 165 runOpts.SecurityGroupIds = securityGroupIds 166 } 167 168 runResp, err := ec2conn.RunInstances(runOpts) 169 if err != nil { 170 err := fmt.Errorf("Error launching source instance: %s", err) 171 state.Put("error", err) 172 ui.Error(err.Error()) 173 return multistep.ActionHalt 174 } 175 instanceId = *runResp.Instances[0].InstanceId 176 } else { 177 ui.Message(fmt.Sprintf( 178 "Requesting spot instance '%s' for: %s", 179 s.InstanceType, spotPrice)) 180 runSpotResp, err := ec2conn.RequestSpotInstances(&ec2.RequestSpotInstancesInput{ 181 SpotPrice: &spotPrice, 182 LaunchSpecification: &ec2.RequestSpotLaunchSpecification{ 183 KeyName: &keyName, 184 ImageId: &s.SourceAMI, 185 InstanceType: &s.InstanceType, 186 UserData: &userData, 187 IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile}, 188 NetworkInterfaces: []*ec2.InstanceNetworkInterfaceSpecification{ 189 &ec2.InstanceNetworkInterfaceSpecification{ 190 DeviceIndex: aws.Int64(0), 191 AssociatePublicIpAddress: &s.AssociatePublicIpAddress, 192 SubnetId: &s.SubnetId, 193 Groups: securityGroupIds, 194 DeleteOnTermination: aws.Bool(true), 195 }, 196 }, 197 Placement: &ec2.SpotPlacement{ 198 AvailabilityZone: &availabilityZone, 199 }, 200 BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(), 201 EbsOptimized: &s.EbsOptimized, 202 }, 203 }) 204 if err != nil { 205 err := fmt.Errorf("Error launching source spot instance: %s", err) 206 state.Put("error", err) 207 ui.Error(err.Error()) 208 return multistep.ActionHalt 209 } 210 211 s.spotRequest = runSpotResp.SpotInstanceRequests[0] 212 213 spotRequestId := s.spotRequest.SpotInstanceRequestId 214 ui.Message(fmt.Sprintf("Waiting for spot request (%s) to become active...", *spotRequestId)) 215 stateChange := StateChangeConf{ 216 Pending: []string{"open"}, 217 Target: "active", 218 Refresh: SpotRequestStateRefreshFunc(ec2conn, *spotRequestId), 219 StepState: state, 220 } 221 _, err = WaitForState(&stateChange) 222 if err != nil { 223 err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", *spotRequestId, err) 224 state.Put("error", err) 225 ui.Error(err.Error()) 226 return multistep.ActionHalt 227 } 228 229 spotResp, err := ec2conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{ 230 SpotInstanceRequestIds: []*string{spotRequestId}, 231 }) 232 if err != nil { 233 err := fmt.Errorf("Error finding spot request (%s): %s", *spotRequestId, err) 234 state.Put("error", err) 235 ui.Error(err.Error()) 236 return multistep.ActionHalt 237 } 238 instanceId = *spotResp.SpotInstanceRequests[0].InstanceId 239 } 240 241 // Set the instance ID so that the cleanup works properly 242 s.instanceId = instanceId 243 244 ui.Message(fmt.Sprintf("Instance ID: %s", instanceId)) 245 ui.Say(fmt.Sprintf("Waiting for instance (%v) to become ready...", instanceId)) 246 stateChange := StateChangeConf{ 247 Pending: []string{"pending"}, 248 Target: "running", 249 Refresh: InstanceStateRefreshFunc(ec2conn, instanceId), 250 StepState: state, 251 } 252 latestInstance, err := WaitForState(&stateChange) 253 if err != nil { 254 err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err) 255 state.Put("error", err) 256 ui.Error(err.Error()) 257 return multistep.ActionHalt 258 } 259 260 instance := latestInstance.(*ec2.Instance) 261 262 ec2Tags := make([]*ec2.Tag, 1, len(s.Tags)+1) 263 ec2Tags[0] = &ec2.Tag{Key: aws.String("Name"), Value: aws.String("Packer Builder")} 264 for k, v := range s.Tags { 265 ec2Tags = append(ec2Tags, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)}) 266 } 267 268 _, err = ec2conn.CreateTags(&ec2.CreateTagsInput{ 269 Tags: ec2Tags, 270 Resources: []*string{instance.InstanceId}, 271 }) 272 if err != nil { 273 ui.Message( 274 fmt.Sprintf("Failed to tag a Name on the builder instance: %s", err)) 275 } 276 277 if s.Debug { 278 if instance.PublicDnsName != nil && *instance.PublicDnsName != "" { 279 ui.Message(fmt.Sprintf("Public DNS: %s", *instance.PublicDnsName)) 280 } 281 282 if instance.PublicIpAddress != nil && *instance.PublicIpAddress != "" { 283 ui.Message(fmt.Sprintf("Public IP: %s", *instance.PublicIpAddress)) 284 } 285 286 if instance.PrivateIpAddress != nil && *instance.PrivateIpAddress != "" { 287 ui.Message(fmt.Sprintf("Private IP: %s", *instance.PrivateIpAddress)) 288 } 289 } 290 291 state.Put("instance", instance) 292 293 return multistep.ActionContinue 294 } 295 296 func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) { 297 298 ec2conn := state.Get("ec2").(*ec2.EC2) 299 ui := state.Get("ui").(packer.Ui) 300 301 // Cancel the spot request if it exists 302 if s.spotRequest != nil { 303 ui.Say("Cancelling the spot request...") 304 input := &ec2.CancelSpotInstanceRequestsInput{ 305 SpotInstanceRequestIds: []*string{s.spotRequest.SpotInstanceRequestId}, 306 } 307 if _, err := ec2conn.CancelSpotInstanceRequests(input); err != nil { 308 ui.Error(fmt.Sprintf("Error cancelling the spot request, may still be around: %s", err)) 309 return 310 } 311 stateChange := StateChangeConf{ 312 Pending: []string{"active", "open"}, 313 Refresh: SpotRequestStateRefreshFunc(ec2conn, *s.spotRequest.SpotInstanceRequestId), 314 Target: "cancelled", 315 } 316 317 WaitForState(&stateChange) 318 319 } 320 321 // Terminate the source instance if it exists 322 if s.instanceId != "" { 323 ui.Say("Terminating the source AWS instance...") 324 if _, err := ec2conn.TerminateInstances(&ec2.TerminateInstancesInput{InstanceIds: []*string{&s.instanceId}}); err != nil { 325 ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err)) 326 return 327 } 328 stateChange := StateChangeConf{ 329 Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, 330 Refresh: InstanceStateRefreshFunc(ec2conn, s.instanceId), 331 Target: "terminated", 332 } 333 334 WaitForState(&stateChange) 335 } 336 }