github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/builder/amazon/common/step_run_source_instance.go (about) 1 package common 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "log" 7 "strconv" 8 "time" 9 10 "github.com/mitchellh/goamz/ec2" 11 "github.com/mitchellh/multistep" 12 "github.com/mitchellh/packer/packer" 13 ) 14 15 type StepRunSourceInstance struct { 16 AssociatePublicIpAddress bool 17 AvailabilityZone string 18 BlockDevices BlockDevices 19 Debug bool 20 ExpectedRootDevice string 21 InstanceType string 22 IamInstanceProfile string 23 SourceAMI string 24 SpotPrice string 25 SpotPriceProduct string 26 SubnetId string 27 Tags map[string]string 28 UserData string 29 UserDataFile string 30 31 instance *ec2.Instance 32 spotRequest *ec2.SpotRequestResult 33 } 34 35 func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepAction { 36 ec2conn := state.Get("ec2").(*ec2.EC2) 37 keyName := state.Get("keyPair").(string) 38 securityGroupIds := state.Get("securityGroupIds").([]string) 39 ui := state.Get("ui").(packer.Ui) 40 41 userData := s.UserData 42 if s.UserDataFile != "" { 43 contents, err := ioutil.ReadFile(s.UserDataFile) 44 if err != nil { 45 state.Put("error", fmt.Errorf("Problem reading user data file: %s", err)) 46 return multistep.ActionHalt 47 } 48 49 userData = string(contents) 50 } 51 52 securityGroups := make([]ec2.SecurityGroup, len(securityGroupIds)) 53 for n, securityGroupId := range securityGroupIds { 54 securityGroups[n] = ec2.SecurityGroup{Id: securityGroupId} 55 } 56 57 ui.Say("Launching a source AWS instance...") 58 imageResp, err := ec2conn.Images([]string{s.SourceAMI}, ec2.NewFilter()) 59 if err != nil { 60 state.Put("error", fmt.Errorf("There was a problem with the source AMI: %s", err)) 61 return multistep.ActionHalt 62 } 63 64 if len(imageResp.Images) != 1 { 65 state.Put("error", fmt.Errorf("The source AMI '%s' could not be found.", s.SourceAMI)) 66 return multistep.ActionHalt 67 } 68 69 if s.ExpectedRootDevice != "" && imageResp.Images[0].RootDeviceType != s.ExpectedRootDevice { 70 state.Put("error", fmt.Errorf( 71 "The provided source AMI has an invalid root device type.\n"+ 72 "Expected '%s', got '%s'.", 73 s.ExpectedRootDevice, imageResp.Images[0].RootDeviceType)) 74 return multistep.ActionHalt 75 } 76 77 spotPrice := s.SpotPrice 78 if spotPrice == "auto" { 79 ui.Message(fmt.Sprintf( 80 "Finding spot price for %s %s...", 81 s.SpotPriceProduct, s.InstanceType)) 82 83 // Detect the spot price 84 startTime := time.Now().Add(-1 * time.Hour) 85 resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistory{ 86 InstanceType: []string{s.InstanceType}, 87 ProductDescription: []string{s.SpotPriceProduct}, 88 AvailabilityZone: s.AvailabilityZone, 89 StartTime: startTime, 90 }) 91 if err != nil { 92 err := fmt.Errorf("Error finding spot price: %s", err) 93 state.Put("error", err) 94 ui.Error(err.Error()) 95 return multistep.ActionHalt 96 } 97 98 var price float64 99 for _, history := range resp.History { 100 log.Printf("[INFO] Candidate spot price: %s", history.SpotPrice) 101 current, err := strconv.ParseFloat(history.SpotPrice, 64) 102 if err != nil { 103 log.Printf("[ERR] Error parsing spot price: %s", err) 104 continue 105 } 106 if price == 0 || current < price { 107 price = current 108 } 109 } 110 if price == 0 { 111 err := fmt.Errorf("No candidate spot prices found!") 112 state.Put("error", err) 113 ui.Error(err.Error()) 114 return multistep.ActionHalt 115 } 116 117 spotPrice = strconv.FormatFloat(price, 'f', -1, 64) 118 } 119 120 var instanceId string 121 122 if spotPrice == "" { 123 runOpts := &ec2.RunInstances{ 124 KeyName: keyName, 125 ImageId: s.SourceAMI, 126 InstanceType: s.InstanceType, 127 UserData: []byte(userData), 128 MinCount: 0, 129 MaxCount: 0, 130 SecurityGroups: securityGroups, 131 IamInstanceProfile: s.IamInstanceProfile, 132 SubnetId: s.SubnetId, 133 AssociatePublicIpAddress: s.AssociatePublicIpAddress, 134 BlockDevices: s.BlockDevices.BuildLaunchDevices(), 135 AvailZone: s.AvailabilityZone, 136 } 137 runResp, err := ec2conn.RunInstances(runOpts) 138 if err != nil { 139 err := fmt.Errorf("Error launching source instance: %s", err) 140 state.Put("error", err) 141 ui.Error(err.Error()) 142 return multistep.ActionHalt 143 } 144 instanceId = runResp.Instances[0].InstanceId 145 } else { 146 ui.Message(fmt.Sprintf( 147 "Requesting spot instance '%s' for: %s", 148 s.InstanceType, spotPrice)) 149 150 runOpts := &ec2.RequestSpotInstances{ 151 SpotPrice: spotPrice, 152 KeyName: keyName, 153 ImageId: s.SourceAMI, 154 InstanceType: s.InstanceType, 155 UserData: []byte(userData), 156 SecurityGroups: securityGroups, 157 IamInstanceProfile: s.IamInstanceProfile, 158 SubnetId: s.SubnetId, 159 AssociatePublicIpAddress: s.AssociatePublicIpAddress, 160 BlockDevices: s.BlockDevices.BuildLaunchDevices(), 161 AvailZone: s.AvailabilityZone, 162 } 163 runSpotResp, err := ec2conn.RequestSpotInstances(runOpts) 164 if err != nil { 165 err := fmt.Errorf("Error launching source spot instance: %s", err) 166 state.Put("error", err) 167 ui.Error(err.Error()) 168 return multistep.ActionHalt 169 } 170 171 s.spotRequest = &runSpotResp.SpotRequestResults[0] 172 173 spotRequestId := s.spotRequest.SpotRequestId 174 ui.Message(fmt.Sprintf("Waiting for spot request (%s) to become active...", spotRequestId)) 175 stateChange := StateChangeConf{ 176 Pending: []string{"open"}, 177 Target: "active", 178 Refresh: SpotRequestStateRefreshFunc(ec2conn, spotRequestId), 179 StepState: state, 180 } 181 _, err = WaitForState(&stateChange) 182 if err != nil { 183 err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", spotRequestId, err) 184 state.Put("error", err) 185 ui.Error(err.Error()) 186 return multistep.ActionHalt 187 } 188 spotResp, err := ec2conn.DescribeSpotRequests([]string{spotRequestId}, nil) 189 if err != nil { 190 err := fmt.Errorf("Error finding spot request (%s): %s", spotRequestId, err) 191 state.Put("error", err) 192 ui.Error(err.Error()) 193 return multistep.ActionHalt 194 } 195 instanceId = spotResp.SpotRequestResults[0].InstanceId 196 } 197 198 instanceResp, err := ec2conn.Instances([]string{instanceId}, nil) 199 if err != nil { 200 err := fmt.Errorf("Error finding source instance (%s): %s", instanceId, err) 201 state.Put("error", err) 202 ui.Error(err.Error()) 203 return multistep.ActionHalt 204 } 205 s.instance = &instanceResp.Reservations[0].Instances[0] 206 ui.Message(fmt.Sprintf("Instance ID: %s", s.instance.InstanceId)) 207 208 ui.Say(fmt.Sprintf("Waiting for instance (%s) to become ready...", s.instance.InstanceId)) 209 stateChange := StateChangeConf{ 210 Pending: []string{"pending"}, 211 Target: "running", 212 Refresh: InstanceStateRefreshFunc(ec2conn, s.instance), 213 StepState: state, 214 } 215 latestInstance, err := WaitForState(&stateChange) 216 if err != nil { 217 err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", s.instance.InstanceId, err) 218 state.Put("error", err) 219 ui.Error(err.Error()) 220 return multistep.ActionHalt 221 } 222 223 s.instance = latestInstance.(*ec2.Instance) 224 225 ec2Tags := make([]ec2.Tag, 1, len(s.Tags)+1) 226 ec2Tags[0] = ec2.Tag{"Name", "Packer Builder"} 227 for k, v := range s.Tags { 228 ec2Tags = append(ec2Tags, ec2.Tag{k, v}) 229 } 230 231 _, err = ec2conn.CreateTags([]string{s.instance.InstanceId}, ec2Tags) 232 if err != nil { 233 ui.Message( 234 fmt.Sprintf("Failed to tag a Name on the builder instance: %s", err)) 235 } 236 237 if s.Debug { 238 if s.instance.DNSName != "" { 239 ui.Message(fmt.Sprintf("Public DNS: %s", s.instance.DNSName)) 240 } 241 242 if s.instance.PublicIpAddress != "" { 243 ui.Message(fmt.Sprintf("Public IP: %s", s.instance.PublicIpAddress)) 244 } 245 246 if s.instance.PrivateIpAddress != "" { 247 ui.Message(fmt.Sprintf("Private IP: %s", s.instance.PrivateIpAddress)) 248 } 249 } 250 251 state.Put("instance", s.instance) 252 253 return multistep.ActionContinue 254 } 255 256 func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) { 257 258 ec2conn := state.Get("ec2").(*ec2.EC2) 259 ui := state.Get("ui").(packer.Ui) 260 261 // Cancel the spot request if it exists 262 if s.spotRequest != nil { 263 ui.Say("Cancelling the spot request...") 264 if _, err := ec2conn.CancelSpotRequests([]string{s.spotRequest.SpotRequestId}); err != nil { 265 ui.Error(fmt.Sprintf("Error cancelling the spot request, may still be around: %s", err)) 266 return 267 } 268 stateChange := StateChangeConf{ 269 Pending: []string{"active", "open"}, 270 Refresh: SpotRequestStateRefreshFunc(ec2conn, s.spotRequest.SpotRequestId), 271 Target: "cancelled", 272 } 273 274 WaitForState(&stateChange) 275 276 } 277 278 // Terminate the source instance if it exists 279 if s.instance != nil { 280 281 ui.Say("Terminating the source AWS instance...") 282 if _, err := ec2conn.TerminateInstances([]string{s.instance.InstanceId}); err != nil { 283 ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err)) 284 return 285 } 286 stateChange := StateChangeConf{ 287 Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, 288 Refresh: InstanceStateRefreshFunc(ec2conn, s.instance), 289 Target: "terminated", 290 } 291 292 WaitForState(&stateChange) 293 } 294 }