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