github.com/ttysteale/packer@v0.8.2-0.20150708160520-e5f8ea386ed8/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 ExpectedRootDevice string 24 InstanceType string 25 IamInstanceProfile string 26 SourceAMI string 27 SpotPrice string 28 SpotPriceProduct string 29 SubnetId string 30 Tags map[string]string 31 UserData string 32 UserDataFile string 33 34 instance *ec2.Instance 35 spotRequest *ec2.SpotInstanceRequest 36 } 37 38 func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepAction { 39 ec2conn := state.Get("ec2").(*ec2.EC2) 40 keyName := state.Get("keyPair").(string) 41 tempSecurityGroupIds := state.Get("securityGroupIds").([]string) 42 ui := state.Get("ui").(packer.Ui) 43 44 securityGroupIds := make([]*string, len(tempSecurityGroupIds)) 45 for i, sg := range tempSecurityGroupIds { 46 securityGroupIds[i] = aws.String(sg) 47 } 48 49 userData := s.UserData 50 if s.UserDataFile != "" { 51 contents, err := ioutil.ReadFile(s.UserDataFile) 52 if err != nil { 53 state.Put("error", fmt.Errorf("Problem reading user data file: %s", err)) 54 return multistep.ActionHalt 55 } 56 57 // Test if it is encoded already, and if not, encode it 58 if _, err := base64.StdEncoding.DecodeString(string(contents)); err != nil { 59 log.Printf("[DEBUG] base64 encoding user data...") 60 contents = []byte(base64.StdEncoding.EncodeToString(contents)) 61 } 62 63 userData = string(contents) 64 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 == "" { 139 runOpts := &ec2.RunInstancesInput{ 140 KeyName: &keyName, 141 ImageID: &s.SourceAMI, 142 InstanceType: &s.InstanceType, 143 UserData: &userData, 144 MaxCount: aws.Long(1), 145 MinCount: aws.Long(1), 146 IAMInstanceProfile: &ec2.IAMInstanceProfileSpecification{Name: &s.IamInstanceProfile}, 147 BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(), 148 Placement: &ec2.Placement{AvailabilityZone: &s.AvailabilityZone}, 149 } 150 151 if s.SubnetId != "" && s.AssociatePublicIpAddress { 152 runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ 153 &ec2.InstanceNetworkInterfaceSpecification{ 154 DeviceIndex: aws.Long(0), 155 AssociatePublicIPAddress: &s.AssociatePublicIpAddress, 156 SubnetID: &s.SubnetId, 157 Groups: securityGroupIds, 158 DeleteOnTermination: aws.Boolean(true), 159 }, 160 } 161 } else { 162 runOpts.SubnetID = &s.SubnetId 163 runOpts.SecurityGroupIDs = securityGroupIds 164 } 165 166 runResp, err := ec2conn.RunInstances(runOpts) 167 if err != nil { 168 err := fmt.Errorf("Error launching source instance: %s", err) 169 state.Put("error", err) 170 ui.Error(err.Error()) 171 return multistep.ActionHalt 172 } 173 instanceId = *runResp.Instances[0].InstanceID 174 } else { 175 ui.Message(fmt.Sprintf( 176 "Requesting spot instance '%s' for: %s", 177 s.InstanceType, spotPrice)) 178 runSpotResp, err := ec2conn.RequestSpotInstances(&ec2.RequestSpotInstancesInput{ 179 SpotPrice: &spotPrice, 180 LaunchSpecification: &ec2.RequestSpotLaunchSpecification{ 181 KeyName: &keyName, 182 ImageID: &s.SourceAMI, 183 InstanceType: &s.InstanceType, 184 UserData: &userData, 185 IAMInstanceProfile: &ec2.IAMInstanceProfileSpecification{Name: &s.IamInstanceProfile}, 186 NetworkInterfaces: []*ec2.InstanceNetworkInterfaceSpecification{ 187 &ec2.InstanceNetworkInterfaceSpecification{ 188 DeviceIndex: aws.Long(0), 189 AssociatePublicIPAddress: &s.AssociatePublicIpAddress, 190 SubnetID: &s.SubnetId, 191 Groups: securityGroupIds, 192 DeleteOnTermination: aws.Boolean(true), 193 }, 194 }, 195 Placement: &ec2.SpotPlacement{ 196 AvailabilityZone: &availabilityZone, 197 }, 198 BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(), 199 }, 200 }) 201 if err != nil { 202 err := fmt.Errorf("Error launching source spot instance: %s", err) 203 state.Put("error", err) 204 ui.Error(err.Error()) 205 return multistep.ActionHalt 206 } 207 208 s.spotRequest = runSpotResp.SpotInstanceRequests[0] 209 210 spotRequestId := s.spotRequest.SpotInstanceRequestID 211 ui.Message(fmt.Sprintf("Waiting for spot request (%s) to become active...", *spotRequestId)) 212 stateChange := StateChangeConf{ 213 Pending: []string{"open"}, 214 Target: "active", 215 Refresh: SpotRequestStateRefreshFunc(ec2conn, *spotRequestId), 216 StepState: state, 217 } 218 _, err = WaitForState(&stateChange) 219 if err != nil { 220 err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", *spotRequestId, err) 221 state.Put("error", err) 222 ui.Error(err.Error()) 223 return multistep.ActionHalt 224 } 225 226 spotResp, err := ec2conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{ 227 SpotInstanceRequestIDs: []*string{spotRequestId}, 228 }) 229 if err != nil { 230 err := fmt.Errorf("Error finding spot request (%s): %s", *spotRequestId, err) 231 state.Put("error", err) 232 ui.Error(err.Error()) 233 return multistep.ActionHalt 234 } 235 instanceId = *spotResp.SpotInstanceRequests[0].InstanceID 236 } 237 238 ui.Message(fmt.Sprintf("Instance ID: %s", instanceId)) 239 ui.Say(fmt.Sprintf("Waiting for instance (%v) to become ready...", instanceId)) 240 stateChange := StateChangeConf{ 241 Pending: []string{"pending"}, 242 Target: "running", 243 Refresh: InstanceStateRefreshFunc(ec2conn, instanceId), 244 StepState: state, 245 } 246 latestInstance, err := WaitForState(&stateChange) 247 if err != nil { 248 err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err) 249 state.Put("error", err) 250 ui.Error(err.Error()) 251 return multistep.ActionHalt 252 } 253 254 s.instance = latestInstance.(*ec2.Instance) 255 256 ec2Tags := make([]*ec2.Tag, 1, len(s.Tags)+1) 257 ec2Tags[0] = &ec2.Tag{Key: aws.String("Name"), Value: aws.String("Packer Builder")} 258 for k, v := range s.Tags { 259 ec2Tags = append(ec2Tags, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)}) 260 } 261 262 _, err = ec2conn.CreateTags(&ec2.CreateTagsInput{ 263 Tags: ec2Tags, 264 Resources: []*string{s.instance.InstanceID}, 265 }) 266 if err != nil { 267 ui.Message( 268 fmt.Sprintf("Failed to tag a Name on the builder instance: %s", err)) 269 } 270 271 if s.Debug { 272 if s.instance.PublicDNSName != nil && *s.instance.PublicDNSName != "" { 273 ui.Message(fmt.Sprintf("Public DNS: %s", *s.instance.PublicDNSName)) 274 } 275 276 if s.instance.PublicIPAddress != nil && *s.instance.PublicIPAddress != "" { 277 ui.Message(fmt.Sprintf("Public IP: %s", *s.instance.PublicIPAddress)) 278 } 279 280 if s.instance.PrivateIPAddress != nil && *s.instance.PrivateIPAddress != "" { 281 ui.Message(fmt.Sprintf("Private IP: %s", *s.instance.PrivateIPAddress)) 282 } 283 } 284 285 state.Put("instance", s.instance) 286 287 return multistep.ActionContinue 288 } 289 290 func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) { 291 292 ec2conn := state.Get("ec2").(*ec2.EC2) 293 ui := state.Get("ui").(packer.Ui) 294 295 // Cancel the spot request if it exists 296 if s.spotRequest != nil { 297 ui.Say("Cancelling the spot request...") 298 input := &ec2.CancelSpotInstanceRequestsInput{ 299 SpotInstanceRequestIDs: []*string{s.spotRequest.SpotInstanceRequestID}, 300 } 301 if _, err := ec2conn.CancelSpotInstanceRequests(input); err != nil { 302 ui.Error(fmt.Sprintf("Error cancelling the spot request, may still be around: %s", err)) 303 return 304 } 305 stateChange := StateChangeConf{ 306 Pending: []string{"active", "open"}, 307 Refresh: SpotRequestStateRefreshFunc(ec2conn, *s.spotRequest.SpotInstanceRequestID), 308 Target: "cancelled", 309 } 310 311 WaitForState(&stateChange) 312 313 } 314 315 // Terminate the source instance if it exists 316 if s.instance != nil { 317 318 ui.Say("Terminating the source AWS instance...") 319 if _, err := ec2conn.TerminateInstances(&ec2.TerminateInstancesInput{InstanceIDs: []*string{s.instance.InstanceID}}); err != nil { 320 ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err)) 321 return 322 } 323 stateChange := StateChangeConf{ 324 Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, 325 Refresh: InstanceStateRefreshFunc(ec2conn, *s.instance.InstanceID), 326 Target: "terminated", 327 } 328 329 WaitForState(&stateChange) 330 } 331 }