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