github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/imagepublishers/amipublisher/libAws.go (about) 1 package amipublisher 2 3 import ( 4 "errors" 5 "fmt" 6 "path" 7 "strings" 8 "time" 9 10 "github.com/Cloud-Foundations/Dominator/lib/awsutil" 11 "github.com/Cloud-Foundations/Dominator/lib/log" 12 libtags "github.com/Cloud-Foundations/Dominator/lib/tags" 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/service/ec2" 15 "github.com/aws/aws-sdk-go/service/s3" 16 ) 17 18 const ( 19 creationTimeFormat = "2006-01-02T15:04:05.000Z" 20 rootDevName = "/dev/sda1" 21 ) 22 23 var ( 24 amiNameReplacer = strings.NewReplacer( 25 "@", "@@", 26 "~", "@tilde@", 27 "!", "@bang@", 28 "#", "@hash@", 29 "$", "@dollar@", 30 "%", "@percent@", 31 "^", "@caret@", 32 "&", "@ampersand@", 33 "*", "@asterix@", // the gaul. 34 "=", "@equal@", 35 "+", "@plus@", 36 ":", ".", 37 ) 38 ) 39 40 func attachVolume(awsService *ec2.EC2, instance *ec2.Instance, volumeId string, 41 logger log.Logger) error { 42 usedBlockDevices := make(map[string]struct{}) 43 for _, device := range instance.BlockDeviceMappings { 44 usedBlockDevices[aws.StringValue(device.DeviceName)] = struct{}{} 45 } 46 blockDeviceName := getFreeVolumeName(usedBlockDevices) 47 if blockDeviceName == "" { 48 return errors.New("no space for new block device") 49 } 50 _, err := awsService.AttachVolume(&ec2.AttachVolumeInput{ 51 Device: aws.String(blockDeviceName), 52 InstanceId: instance.InstanceId, 53 VolumeId: aws.String(volumeId), 54 }) 55 if err != nil { 56 return fmt.Errorf("ec2.AttachVolume: %s", err) 57 } 58 blockDevMappings := make([]*ec2.InstanceBlockDeviceMappingSpecification, 1) 59 blockDevMappings[0] = &ec2.InstanceBlockDeviceMappingSpecification{ 60 DeviceName: aws.String(blockDeviceName), 61 Ebs: &ec2.EbsInstanceBlockDeviceSpecification{ 62 DeleteOnTermination: aws.Bool(true), 63 VolumeId: aws.String(volumeId), 64 }, 65 } 66 _, err = awsService.ModifyInstanceAttribute( 67 &ec2.ModifyInstanceAttributeInput{ 68 BlockDeviceMappings: blockDevMappings, 69 InstanceId: instance.InstanceId, 70 }) 71 if err != nil { 72 return fmt.Errorf("ec2.ModifyInstanceAttribute: %s", err) 73 } 74 logger.Printf("requested attach(%s): %s on %s, waiting...\n", 75 aws.StringValue(instance.InstanceId), volumeId, blockDeviceName) 76 dvi := &ec2.DescribeVolumesInput{ 77 VolumeIds: aws.StringSlice([]string{volumeId}), 78 } 79 if err := awsService.WaitUntilVolumeInUse(dvi); err != nil { 80 return fmt.Errorf("ec2.WaitUntilVolumeInUse: %s", err) 81 } 82 for ; true; time.Sleep(time.Second) { 83 desc, err := awsService.DescribeVolumes(dvi) 84 if err != nil { 85 return fmt.Errorf("ec2.DescribeVolumes: %s", err) 86 } 87 if len(desc.Volumes[0].Attachments) < 1 { 88 logger.Printf("attachments: %d\n", 89 len(desc.Volumes[0].Attachments)) 90 continue 91 } 92 state := *desc.Volumes[0].Attachments[0].State 93 logger.Printf("state: \"%s\"\n", state) 94 if state == ec2.VolumeAttachmentStateAttached { 95 break 96 } 97 } 98 logger.Printf("attached: %s\n", volumeId) 99 return nil 100 } 101 102 // computeImageConsumption returns the size in GiB. 103 func computeImageConsumption(image *ec2.Image) int64 { 104 var sizeGiB int64 105 for _, bdMapping := range image.BlockDeviceMappings { 106 // TODO(rgooch): Should use the snapshot size instead. 107 if ebs := bdMapping.Ebs; ebs != nil { 108 sizeGiB += aws.Int64Value(ebs.VolumeSize) 109 } 110 } 111 // TODO(rgooch): Should query the S3 folder instead. 112 if sizeGiB < 1 { 113 sizeGiB = 1 114 } 115 return sizeGiB 116 } 117 118 func createSnapshot(awsService *ec2.EC2, volumeId string, description string, 119 tags libtags.Tags, logger log.Logger) ( 120 string, error) { 121 snapshot, err := awsService.CreateSnapshot(&ec2.CreateSnapshotInput{ 122 VolumeId: aws.String(volumeId), 123 Description: aws.String(description), 124 }) 125 if err != nil { 126 return "", fmt.Errorf("ec2.CreateSnapshot: %s", err) 127 } 128 snapshotIds := make([]string, 1) 129 snapshotIds[0] = *snapshot.SnapshotId 130 logger.Printf("Created: %s\n", *snapshot.SnapshotId) 131 tags = tags.Copy() 132 tags["Name"] = description 133 if err := createTags(awsService, *snapshot.SnapshotId, tags); err != nil { 134 return "", err 135 } 136 logger.Printf("Tagged: %s, waiting...\n", *snapshot.SnapshotId) 137 err = awsService.WaitUntilSnapshotCompleted(&ec2.DescribeSnapshotsInput{ 138 SnapshotIds: aws.StringSlice(snapshotIds), 139 }) 140 if err != nil { 141 return "", fmt.Errorf("ec2.WaitUntilSnapshotCompleted: %s", err) 142 } 143 return *snapshot.SnapshotId, nil 144 } 145 146 func createTags(awsService *ec2.EC2, resourceId string, 147 tags map[string]string) error { 148 resourceIds := make([]string, 1) 149 resourceIds[0] = resourceId 150 awsTags := make([]*ec2.Tag, 0, len(tags)) 151 for key, value := range tags { 152 awsTags = append(awsTags, 153 &ec2.Tag{Key: aws.String(key), Value: aws.String(value)}) 154 } 155 _, err := awsService.CreateTags(&ec2.CreateTagsInput{ 156 Resources: aws.StringSlice(resourceIds), 157 Tags: awsTags, 158 }) 159 if err != nil { 160 return fmt.Errorf("ec2.CreateTags: %s", err) 161 } 162 return nil 163 } 164 165 func createTagSpecification(resourceType string, 166 tags libtags.Tags) *ec2.TagSpecification { 167 if tags == nil { 168 return nil 169 } 170 awsTags := make([]*ec2.Tag, 0, len(tags)) 171 for key, value := range tags { 172 awsTags = append(awsTags, 173 &ec2.Tag{Key: aws.String(key), Value: aws.String(value)}) 174 } 175 return &ec2.TagSpecification{ 176 ResourceType: aws.String(resourceType), 177 Tags: awsTags, 178 } 179 } 180 181 func createVolume(awsService *ec2.EC2, availabilityZone *string, size uint64, 182 tags libtags.Tags, logger log.Logger) (string, error) { 183 tags = tags.Copy() 184 delete(tags, "ExpiresAt") 185 tags["Name"] = "image unpacker" 186 sizeInGiB := int64(size) >> 30 187 if sizeInGiB<<30 < int64(size) { 188 sizeInGiB++ 189 } 190 volume, err := awsService.CreateVolume(&ec2.CreateVolumeInput{ 191 AvailabilityZone: availabilityZone, 192 Encrypted: aws.Bool(true), 193 Size: aws.Int64(sizeInGiB), 194 TagSpecifications: []*ec2.TagSpecification{ 195 createTagSpecification(ec2.ResourceTypeVolume, tags), 196 }, 197 VolumeType: aws.String("gp2"), 198 }) 199 if err != nil { 200 return "", fmt.Errorf("ec2.CreateVolume: %s", err) 201 } 202 volumeIds := make([]string, 1) 203 volumeIds[0] = *volume.VolumeId 204 logger.Printf("Created: %s, waiting...\n", *volume.VolumeId) 205 err = awsService.WaitUntilVolumeAvailable(&ec2.DescribeVolumesInput{ 206 VolumeIds: aws.StringSlice(volumeIds), 207 }) 208 if err != nil { 209 return "", fmt.Errorf("ec2.WaitUntilVolumeAvailable: %s", err) 210 } 211 return *volume.VolumeId, nil 212 } 213 214 func deleteImage(cs *awsutil.CredentialsStore, accountName, region string, 215 image *ec2.Image) error { 216 ec2Service := cs.GetEC2Service(accountName, region) 217 s3Service := s3.New(cs.GetSessionForAccount(accountName), 218 &aws.Config{Region: aws.String(region)}) 219 imageId := aws.StringValue(image.ImageId) 220 if err := deregisterAmi(ec2Service, imageId); err != nil { 221 return errors.New(imageId + ": " + err.Error()) 222 } 223 switch rootDevType := aws.StringValue(image.RootDeviceType); rootDevType { 224 case "ebs": 225 var firstError error 226 for _, bdMapping := range image.BlockDeviceMappings { 227 if bdMapping.Ebs == nil { 228 return nil 229 } 230 snapshotId := aws.StringValue(bdMapping.Ebs.SnapshotId) 231 if err := deleteSnapshot(ec2Service, snapshotId); err != nil { 232 if firstError == nil { 233 firstError = errors.New(snapshotId + ": " + err.Error()) 234 } 235 } 236 } 237 if firstError != nil { 238 return firstError 239 } 240 case "instance-store": 241 splitPath := strings.Split(aws.StringValue(image.ImageLocation), "/") 242 bucket := splitPath[0] 243 folder := strings.Join(splitPath[1:len(splitPath)-1], "/") 244 return deleteS3Directory(s3Service, bucket, folder) 245 default: 246 return errors.New("unsupported root device type: " + rootDevType + 247 " for: " + imageId) 248 } 249 return nil 250 } 251 252 func deleteS3Directory(awsService *s3.S3, bucket, dir string) error { 253 out, err := awsService.ListObjects(&s3.ListObjectsInput{ 254 Bucket: aws.String(bucket), 255 Prefix: aws.String(dir), 256 }) 257 if err != nil { 258 fmt.Printf("error listing: %s: %s\n", dir, err) 259 return fmt.Errorf("s3.ListObjects: %s", err) 260 } 261 if len(out.Contents) < 1 { 262 return nil 263 } 264 objectIds := make([]*s3.ObjectIdentifier, 0, len(out.Contents)) 265 for _, obj := range out.Contents { 266 objectIds = append(objectIds, 267 &s3.ObjectIdentifier{Key: obj.Key}) 268 } 269 _, err = awsService.DeleteObjects(&s3.DeleteObjectsInput{ 270 Bucket: aws.String(bucket), 271 Delete: &s3.Delete{Objects: objectIds}, 272 }) 273 if err != nil { 274 fmt.Printf("error deleting objects: %s\n", err) 275 } 276 return nil 277 } 278 279 func deleteSnapshot(awsService *ec2.EC2, snapshotId string) error { 280 var err error 281 for i := 0; i < 60; i++ { 282 _, err = awsService.DeleteSnapshot(&ec2.DeleteSnapshotInput{ 283 SnapshotId: aws.String(snapshotId), 284 }) 285 if err == nil { 286 return nil 287 } 288 if !strings.Contains(err.Error(), "in use by ami") && 289 !strings.Contains(err.Error(), "RequestLimitExceeded") { 290 return err 291 } 292 time.Sleep(time.Second) 293 } 294 if strings.Contains(err.Error(), "in use by ami") { 295 return errors.New("timed out waiting for delete: " + snapshotId) 296 } 297 return err 298 } 299 300 func deleteTagsFromResources(awsService *ec2.EC2, tagKeys []string, 301 resourceId ...string) error { 302 if len(tagKeys) < 1 { 303 return nil 304 } 305 resourceIds := make([]string, 0) 306 for _, id := range resourceId { 307 if id != "" { 308 resourceIds = append(resourceIds, id) 309 } 310 } 311 if len(resourceIds) < 1 { 312 return nil 313 } 314 tags := make([]*ec2.Tag, 0, len(tagKeys)) 315 for _, tagKey := range tagKeys { 316 tags = append(tags, &ec2.Tag{Key: aws.String(tagKey)}) 317 } 318 _, err := awsService.DeleteTags(&ec2.DeleteTagsInput{ 319 Resources: aws.StringSlice(resourceIds), 320 Tags: tags, 321 }) 322 if err != nil { 323 return fmt.Errorf("ec2.DeleteTags: %s", err) 324 } 325 return nil 326 } 327 328 func deleteVolume(awsService *ec2.EC2, volumeId string) error { 329 _, err := awsService.DeleteVolume(&ec2.DeleteVolumeInput{ 330 VolumeId: aws.String(volumeId), 331 }) 332 if err != nil { 333 return fmt.Errorf("ec2.DeleteVolume: %s", err) 334 } 335 return nil 336 } 337 338 func detachVolume(awsService *ec2.EC2, instanceId, volumeId string) error { 339 _, err := awsService.DetachVolume(&ec2.DetachVolumeInput{ 340 InstanceId: aws.String(instanceId), 341 VolumeId: aws.String(volumeId), 342 }) 343 if err != nil { 344 return fmt.Errorf("ec2.DetachVolume: %s", err) 345 } 346 err = awsService.WaitUntilVolumeAvailable(&ec2.DescribeVolumesInput{ 347 VolumeIds: aws.StringSlice([]string{volumeId}), 348 }) 349 if err != nil { 350 return fmt.Errorf("ec2.WaitUntilVolumeAvailable: %s", err) 351 } 352 return nil 353 } 354 355 func deregisterAmi(awsService *ec2.EC2, amiId string) error { 356 var err error 357 for i := 0; i < 6; i++ { 358 _, err = awsService.DeregisterImage(&ec2.DeregisterImageInput{ 359 ImageId: aws.String(amiId), 360 }) 361 if err == nil { 362 break 363 } 364 if !strings.Contains(err.Error(), "RequestLimitExceeded") { 365 return err 366 } 367 time.Sleep(time.Second * 11) 368 } 369 if err != nil { 370 return errors.New("timed out waiting for delete: " + amiId) 371 } 372 imageIds := make([]*string, 1) 373 imageIds[0] = aws.String(amiId) 374 for i := 0; i < 60; i++ { 375 out, err := awsService.DescribeImages(&ec2.DescribeImagesInput{ 376 ImageIds: imageIds, 377 }) 378 if err != nil { 379 return fmt.Errorf("ec2.DescribeImages: %s", err) 380 } 381 if len(out.Images) < 1 { 382 return nil 383 } 384 time.Sleep(time.Second) 385 } 386 return errors.New("timed out waiting for deregister: " + amiId) 387 } 388 389 func describeInstances(awsService *ec2.EC2, input *ec2.DescribeInstancesInput) ( 390 []*ec2.Instance, error) { 391 if input == nil { 392 input = &ec2.DescribeInstancesInput{} 393 } 394 inp := *input 395 inp.NextToken = nil 396 instances := make([]*ec2.Instance, 0) 397 for { 398 out, err := awsService.DescribeInstances(&inp) 399 if err != nil { 400 return nil, fmt.Errorf("ec2.DescribeInstances: %s", err) 401 } 402 for _, reservation := range out.Reservations { 403 for _, instance := range reservation.Instances { 404 instances = append(instances, instance) 405 } 406 } 407 if out.NextToken == nil { 408 break 409 } 410 inp.NextToken = out.NextToken 411 } 412 return instances, nil 413 } 414 415 func findImage(awsService *ec2.EC2, tags libtags.Tags) (*ec2.Image, error) { 416 images, err := getImages(awsService, "", tags) 417 if err != nil { 418 return nil, err 419 } 420 return findLatestImage(images) 421 } 422 423 func findMarketplaceImage(awsService *ec2.EC2, productCode string) ( 424 *ec2.Image, error) { 425 out, err := awsService.DescribeImages( 426 &ec2.DescribeImagesInput{ 427 Filters: []*ec2.Filter{ 428 { 429 Name: aws.String("product-code"), 430 Values: aws.StringSlice([]string{productCode}), 431 }, 432 { 433 Name: aws.String("product-code.type"), 434 Values: aws.StringSlice([]string{"marketplace"}), 435 }, 436 }, 437 }) 438 if err != nil { 439 return nil, fmt.Errorf("ec2.DescribeImages: %s", err) 440 } 441 return findLatestImage(out.Images) 442 } 443 444 func findLatestImage(images []*ec2.Image) (*ec2.Image, error) { 445 var youngestImage *ec2.Image 446 var youngestTime time.Time 447 for _, image := range images { 448 creationTime, err := time.Parse(creationTimeFormat, 449 aws.StringValue(image.CreationDate)) 450 if err != nil { 451 return nil, err 452 } 453 if creationTime.After(youngestTime) { 454 youngestImage = image 455 youngestTime = creationTime 456 } 457 } 458 return youngestImage, nil 459 } 460 461 func getAccountId(awsService *ec2.EC2) (string, error) { 462 // TODO(rgooch): This relies on at least one instance existing. This doesn't 463 // work in a fresh account. Figure out a better way. 464 out, err := awsService.DescribeInstances(&ec2.DescribeInstancesInput{ 465 MaxResults: aws.Int64(5), 466 }) 467 if err != nil { 468 return "", fmt.Errorf("ec2.DescribeInstances: %s", err) 469 } 470 if len(out.Reservations) < 1 { 471 return "", errors.New("no instances found") 472 } 473 return aws.StringValue(out.Reservations[0].OwnerId), nil 474 } 475 476 func getFreeVolumeName(usedBlockDevices map[string]struct{}) string { 477 // First try the "/dev/sdb" through "/dev/sdz" range. 478 for c := 'b'; c <= 'z'; c++ { 479 name := "/dev/sd" + string(c) 480 if _, ok := usedBlockDevices[name]; !ok { 481 return name 482 } 483 } 484 // Now try the "/dev/xvdba" through "/dev/xvdzz" range. Note that the entire 485 // range may not be supported. 486 var dev [2]byte 487 dev[0] = 'b' 488 dev[1] = 'a' 489 for { 490 name := "/dev/xvd" + string(dev[:]) 491 if _, ok := usedBlockDevices[name]; !ok { 492 return name 493 } 494 dev[1]++ 495 if dev[1] > 'z' { 496 dev[1] = 'a' 497 dev[0]++ 498 if dev[0] > 'z' { 499 return "" 500 } 501 } 502 } 503 } 504 505 func getImages(awsService *ec2.EC2, accountId string, tags libtags.Tags) ( 506 []*ec2.Image, error) { 507 filters := awsutil.MakeFiltersFromTags(tags) 508 if accountId != "" { 509 filters = append(filters, &ec2.Filter{ 510 Name: aws.String("owner-id"), 511 Values: aws.StringSlice([]string{accountId}), 512 }) 513 } 514 filters = append(filters, &ec2.Filter{ 515 Name: aws.String("is-public"), 516 Values: aws.StringSlice([]string{"false"}), 517 }) 518 out, err := awsService.DescribeImages( 519 &ec2.DescribeImagesInput{Filters: filters}) 520 if err != nil { 521 return nil, fmt.Errorf("ec2.DescribeImages: %s", err) 522 } 523 return out.Images, nil 524 } 525 526 func getInstances(awsService *ec2.EC2, nameTag string) ( 527 []*ec2.Instance, error) { 528 if nameTag == "" { 529 return nil, errors.New("no name given") 530 } 531 states := []string{ 532 ec2.InstanceStateNamePending, 533 ec2.InstanceStateNameRunning, 534 ec2.InstanceStateNameStopping, 535 ec2.InstanceStateNameStopped, 536 } 537 instances, err := describeInstances(awsService, &ec2.DescribeInstancesInput{ 538 Filters: []*ec2.Filter{ 539 { 540 Name: aws.String("tag:Name"), 541 Values: aws.StringSlice([]string{nameTag}), 542 }, 543 { 544 Name: aws.String("instance-state-name"), 545 Values: aws.StringSlice(states), 546 }, 547 }, 548 }) 549 return instances, err 550 } 551 552 func getInstanceIds(instances []*ec2.Instance) []string { 553 instanceIds := make([]string, 0, len(instances)) 554 for _, instance := range instances { 555 instanceIds = append(instanceIds, aws.StringValue(instance.InstanceId)) 556 } 557 return instanceIds 558 } 559 560 func getRunningInstance(awsService *ec2.EC2, instances []*ec2.Instance, 561 logger log.Logger) (*ec2.Instance, error) { 562 for _, instance := range instances { 563 if aws.StringValue(instance.State.Name) == 564 ec2.InstanceStateNameRunning { 565 return instance, nil 566 } 567 } 568 var stoppedInstance *ec2.Instance 569 isStopped := false 570 for _, instance := range instances { 571 if stoppedInstance != nil { 572 break 573 } 574 switch aws.StringValue(instance.State.Name) { 575 case ec2.InstanceStateNameStopped: 576 stoppedInstance = instance 577 isStopped = true 578 case ec2.InstanceStateNamePending: 579 stoppedInstance = instance 580 } 581 } 582 if stoppedInstance == nil { 583 return nil, nil 584 } 585 instanceIds := make([]*string, 1) 586 instanceIds[0] = stoppedInstance.InstanceId 587 if isStopped { 588 logger.Printf("starting instance: %s\n", 589 aws.StringValue(instanceIds[0])) 590 _, err := awsService.StartInstances(&ec2.StartInstancesInput{ 591 InstanceIds: instanceIds, 592 }) 593 if err != nil { 594 return nil, fmt.Errorf("ec2.StartInstances: %s", err) 595 } 596 stoppedInstance.LaunchTime = aws.Time(time.Now()) 597 } 598 logger.Printf("waiting for pending instance: %s\n", 599 aws.StringValue(instanceIds[0])) 600 err := awsService.WaitUntilInstanceRunning(&ec2.DescribeInstancesInput{ 601 InstanceIds: instanceIds, 602 }) 603 if err != nil { 604 return nil, fmt.Errorf("ec2.WaitUntilInstanceRunning: %s", err) 605 } 606 return stoppedInstance, nil 607 } 608 609 func getSecurityGroup(awsService *ec2.EC2, vpcId string, tags libtags.Tags) ( 610 *ec2.SecurityGroup, error) { 611 filters := awsutil.MakeFiltersFromTags(tags) 612 filters = append(filters, &ec2.Filter{ 613 Name: aws.String("vpc-id"), 614 Values: aws.StringSlice([]string{vpcId}), 615 }) 616 out, err := awsService.DescribeSecurityGroups( 617 &ec2.DescribeSecurityGroupsInput{Filters: filters}) 618 if err != nil { 619 return nil, fmt.Errorf("ec2.DescribeSecurityGroups: %s", err) 620 } 621 if len(out.SecurityGroups) < 1 { 622 return nil, errors.New("no security group found") 623 } 624 if len(out.SecurityGroups) > 1 { 625 return nil, errors.New("too many security groups found") 626 } 627 return out.SecurityGroups[0], nil 628 } 629 630 func getSubnet(awsService *ec2.EC2, vpcId string, tags libtags.Tags) ( 631 *ec2.Subnet, error) { 632 filters := awsutil.MakeFiltersFromTags(tags) 633 filters = append(filters, &ec2.Filter{ 634 Name: aws.String("vpc-id"), 635 Values: aws.StringSlice([]string{vpcId}), 636 }) 637 out, err := awsService.DescribeSubnets( 638 &ec2.DescribeSubnetsInput{Filters: filters}) 639 if err != nil { 640 return nil, fmt.Errorf("ec2.DescribeSubnets: %s", err) 641 } 642 if len(out.Subnets) < 1 { 643 return nil, errors.New("no subnets found") 644 } 645 for _, subnet := range out.Subnets { 646 if aws.Int64Value(subnet.AvailableIpAddressCount) > 0 { 647 return subnet, nil 648 } 649 } 650 return nil, errors.New("no subnets with available IPs found") 651 } 652 653 func getVpc(awsService *ec2.EC2, tags libtags.Tags) (*ec2.Vpc, error) { 654 out, err := awsService.DescribeVpcs( 655 &ec2.DescribeVpcsInput{Filters: awsutil.MakeFiltersFromTags(tags)}) 656 if err != nil { 657 return nil, fmt.Errorf("ec2.DescribeVpcs: %s", err) 658 } 659 if len(out.Vpcs) < 1 { 660 return nil, errors.New("no VPC found") 661 } 662 if len(out.Vpcs) > 1 { 663 return nil, errors.New("too many VPCs found") 664 } 665 return out.Vpcs[0], nil 666 } 667 668 func launchInstance(awsService *ec2.EC2, image *ec2.Image, rootVolumeSize uint, 669 tags libtags.Tags, vpcSearchTags, subnetSearchTags, 670 securityGroupSearchTags libtags.Tags, instanceType string, 671 sshKeyName string) (*ec2.Instance, error) { 672 vpc, err := getVpc(awsService, vpcSearchTags) 673 if err != nil { 674 return nil, err 675 } 676 subnet, err := getSubnet(awsService, aws.StringValue(vpc.VpcId), 677 subnetSearchTags) 678 if err != nil { 679 return nil, err 680 } 681 sg, err := getSecurityGroup(awsService, aws.StringValue(vpc.VpcId), 682 securityGroupSearchTags) 683 if err != nil { 684 return nil, err 685 } 686 var blockDeviceMappings []*ec2.BlockDeviceMapping 687 if rootVolumeSize != 0 { 688 blockDeviceMappings = []*ec2.BlockDeviceMapping{{ 689 DeviceName: aws.String(rootDevName), 690 Ebs: &ec2.EbsBlockDevice{ 691 DeleteOnTermination: aws.Bool(true), 692 VolumeSize: aws.Int64(int64(rootVolumeSize)), 693 }}, 694 } 695 } 696 reservation, err := awsService.RunInstances(&ec2.RunInstancesInput{ 697 BlockDeviceMappings: blockDeviceMappings, 698 ImageId: image.ImageId, 699 InstanceType: aws.String(instanceType), 700 KeyName: aws.String(sshKeyName), 701 MaxCount: aws.Int64(1), 702 MinCount: aws.Int64(1), 703 SecurityGroupIds: []*string{sg.GroupId}, 704 SubnetId: subnet.SubnetId, 705 TagSpecifications: []*ec2.TagSpecification{ 706 createTagSpecification(ec2.ResourceTypeInstance, tags), 707 createTagSpecification(ec2.ResourceTypeVolume, tags), 708 }, 709 }) 710 if err != nil { 711 return nil, fmt.Errorf("ec2.RunInstances: %s", err) 712 } 713 instance := reservation.Instances[0] 714 err = awsService.WaitUntilInstanceExists(&ec2.DescribeInstancesInput{ 715 InstanceIds: []*string{instance.InstanceId}, 716 }) 717 if err != nil { 718 return nil, fmt.Errorf("ec2.WaitUntilInstanceExists: %s", err) 719 } 720 return instance, nil 721 } 722 723 func registerAmi(awsService *ec2.EC2, snapshotId string, s3Manifest string, 724 amiName string, imageName string, tags libtags.Tags, imageGiB uint64, 725 publishOptions *PublishOptions, logger log.Logger) (string, error) { 726 blkDevMaps := make([]*ec2.BlockDeviceMapping, 1) 727 var volumeSize *int64 728 if imageGiB > 0 { 729 volumeSize = aws.Int64(int64(imageGiB)) 730 } 731 blkDevMaps[0] = &ec2.BlockDeviceMapping{ 732 DeviceName: aws.String(rootDevName), 733 Ebs: &ec2.EbsBlockDevice{ 734 DeleteOnTermination: aws.Bool(true), 735 SnapshotId: aws.String(snapshotId), 736 VolumeSize: volumeSize, 737 VolumeType: aws.String("gp2"), 738 }, 739 } 740 if amiName == "" { 741 amiName = imageName 742 } 743 if publishOptions == nil { 744 publishOptions = new(PublishOptions) 745 } 746 params := &ec2.RegisterImageInput{ 747 Architecture: aws.String("x86_64"), 748 Description: aws.String(imageName), 749 EnaSupport: aws.Bool(publishOptions.EnaSupport), 750 Name: aws.String(amiNameReplacer.Replace(amiName)), 751 RootDeviceName: aws.String(rootDevName), 752 SriovNetSupport: aws.String("simple"), 753 VirtualizationType: aws.String("hvm"), 754 } 755 if snapshotId != "" { 756 params.BlockDeviceMappings = blkDevMaps 757 } 758 if s3Manifest != "" { 759 params.ImageLocation = aws.String(s3Manifest) 760 } 761 ami, err := awsService.RegisterImage(params) 762 if err != nil { 763 return "", fmt.Errorf("ec2.RegisterImage: %s", err) 764 } 765 logger.Printf("Created: %s\n", *ami.ImageId) 766 imageIds := []string{*ami.ImageId} 767 err = awsService.WaitUntilImageExists(&ec2.DescribeImagesInput{ 768 ImageIds: aws.StringSlice(imageIds), 769 }) 770 if err != nil { 771 return "", fmt.Errorf("ec2.WaitUntilImageExists: %s", err) 772 } 773 tags = tags.Copy() 774 tags["Name"] = path.Dir(imageName) 775 if err := createTags(awsService, *ami.ImageId, tags); err != nil { 776 return "", err 777 } 778 logger.Printf("Tagged: %s, waiting...\n", *ami.ImageId) 779 err = awsService.WaitUntilImageAvailable(&ec2.DescribeImagesInput{ 780 ImageIds: aws.StringSlice(imageIds), 781 }) 782 if err != nil { 783 return "", fmt.Errorf("ec2.WaitUntilImageAvailable: %s", err) 784 } 785 logger.Printf("AMI: %s available\n", *ami.ImageId) 786 return *ami.ImageId, nil 787 } 788 789 func libStartInstances(awsService *ec2.EC2, instanceIds ...string) error { 790 _, err := awsService.StartInstances(&ec2.StartInstancesInput{ 791 InstanceIds: aws.StringSlice(instanceIds), 792 }) 793 if err != nil { 794 return fmt.Errorf("ec2.StartInstances: %s", err) 795 } 796 err = awsService.WaitUntilInstanceRunning(&ec2.DescribeInstancesInput{ 797 InstanceIds: aws.StringSlice(instanceIds), 798 }) 799 if err != nil { 800 return fmt.Errorf("ec2.WaitUntilInstanceRunning: %s", err) 801 } 802 return nil 803 } 804 805 func stopInstances(awsService *ec2.EC2, instanceIds ...string) error { 806 _, err := awsService.StopInstances(&ec2.StopInstancesInput{ 807 InstanceIds: aws.StringSlice(instanceIds), 808 }) 809 if err != nil { 810 return fmt.Errorf("ec2.StopInstances: %s", err) 811 } 812 return nil 813 } 814 815 func libTerminateInstances(awsService *ec2.EC2, instanceIds ...string) error { 816 _, err := awsService.TerminateInstances(&ec2.TerminateInstancesInput{ 817 InstanceIds: aws.StringSlice(instanceIds), 818 }) 819 if err != nil { 820 return fmt.Errorf("ec2.TerminateInstances: %s", err) 821 } 822 return nil 823 }