github.phpd.cn/hashicorp/packer@v1.3.2/builder/amazon/common/step_ami_region_copy.go (about) 1 package common 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/service/ec2" 10 11 "github.com/hashicorp/packer/helper/multistep" 12 "github.com/hashicorp/packer/packer" 13 ) 14 15 type StepAMIRegionCopy struct { 16 AccessConfig *AccessConfig 17 Regions []string 18 RegionKeyIds map[string]string 19 EncryptBootVolume bool 20 Name string 21 } 22 23 func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 24 ec2conn := state.Get("ec2").(*ec2.EC2) 25 ui := state.Get("ui").(packer.Ui) 26 amis := state.Get("amis").(map[string]string) 27 snapshots := state.Get("snapshots").(map[string][]string) 28 ami := amis[*ec2conn.Config.Region] 29 30 if len(s.Regions) == 0 { 31 return multistep.ActionContinue 32 } 33 34 ui.Say(fmt.Sprintf("Copying AMI (%s) to other regions...", ami)) 35 36 var lock sync.Mutex 37 var wg sync.WaitGroup 38 var regKeyID string 39 errs := new(packer.MultiError) 40 for _, region := range s.Regions { 41 if region == *ec2conn.Config.Region { 42 ui.Message(fmt.Sprintf( 43 "Avoiding copying AMI to duplicate region %s", region)) 44 continue 45 } 46 47 wg.Add(1) 48 ui.Message(fmt.Sprintf("Copying to: %s", region)) 49 50 if s.EncryptBootVolume { 51 regKeyID = s.RegionKeyIds[region] 52 } 53 54 go func(region string) { 55 defer wg.Done() 56 id, snapshotIds, err := amiRegionCopy(ctx, state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region, regKeyID) 57 lock.Lock() 58 defer lock.Unlock() 59 amis[region] = id 60 snapshots[region] = snapshotIds 61 if err != nil { 62 errs = packer.MultiErrorAppend(errs, err) 63 } 64 }(region) 65 } 66 67 // TODO(mitchellh): Wait but also allow for cancels to go through... 68 ui.Message("Waiting for all copies to complete...") 69 wg.Wait() 70 71 // If there were errors, show them 72 if len(errs.Errors) > 0 { 73 state.Put("error", errs) 74 ui.Error(errs.Error()) 75 return multistep.ActionHalt 76 } 77 78 state.Put("amis", amis) 79 return multistep.ActionContinue 80 } 81 82 func (s *StepAMIRegionCopy) Cleanup(state multistep.StateBag) { 83 // No cleanup... 84 } 85 86 // amiRegionCopy does a copy for the given AMI to the target region and 87 // returns the resulting ID and snapshot IDs, or error. 88 func amiRegionCopy(ctx context.Context, state multistep.StateBag, config *AccessConfig, name string, imageId string, 89 target string, source string, keyID string) (string, []string, error) { 90 snapshotIds := []string{} 91 isEncrypted := false 92 93 // Connect to the region where the AMI will be copied to 94 session, err := config.Session() 95 if err != nil { 96 return "", snapshotIds, err 97 } 98 // if we've provided a map of key ids to regions, use those keys. 99 if len(keyID) > 0 { 100 isEncrypted = true 101 } 102 regionconn := ec2.New(session.Copy(&aws.Config{ 103 Region: aws.String(target)}, 104 )) 105 106 resp, err := regionconn.CopyImage(&ec2.CopyImageInput{ 107 SourceRegion: &source, 108 SourceImageId: &imageId, 109 Name: &name, 110 Encrypted: aws.Bool(isEncrypted), 111 KmsKeyId: aws.String(keyID), 112 }) 113 114 if err != nil { 115 return "", snapshotIds, fmt.Errorf("Error Copying AMI (%s) to region (%s): %s", 116 imageId, target, err) 117 } 118 119 // Wait for the image to become ready 120 if err := WaitUntilAMIAvailable(ctx, regionconn, *resp.ImageId); err != nil { 121 return "", snapshotIds, fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s", 122 *resp.ImageId, target, err) 123 } 124 125 // Getting snapshot IDs out of the copied AMI 126 describeImageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{resp.ImageId}}) 127 if err != nil { 128 return "", snapshotIds, fmt.Errorf("Error describing copied AMI (%s) in region (%s): %s", 129 imageId, target, err) 130 } 131 132 for _, blockDeviceMapping := range describeImageResp.Images[0].BlockDeviceMappings { 133 if blockDeviceMapping.Ebs != nil && blockDeviceMapping.Ebs.SnapshotId != nil { 134 snapshotIds = append(snapshotIds, *blockDeviceMapping.Ebs.SnapshotId) 135 } 136 } 137 138 return *resp.ImageId, snapshotIds, nil 139 }