github.com/rahart/packer@v0.12.2-0.20161229105310-282bb6ad370f/builder/amazon/common/step_ami_region_copy.go (about) 1 package common 2 3 import ( 4 "fmt" 5 6 "sync" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/aws/session" 10 "github.com/aws/aws-sdk-go/service/ec2" 11 12 "github.com/mitchellh/multistep" 13 "github.com/mitchellh/packer/packer" 14 ) 15 16 type StepAMIRegionCopy struct { 17 AccessConfig *AccessConfig 18 Regions []string 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 errs := new(packer.MultiError) 38 for _, region := range s.Regions { 39 if region == *ec2conn.Config.Region { 40 ui.Message(fmt.Sprintf( 41 "Avoiding copying AMI to duplicate region %s", region)) 42 continue 43 } 44 45 wg.Add(1) 46 ui.Message(fmt.Sprintf("Copying to: %s", region)) 47 48 go func(region string) { 49 defer wg.Done() 50 id, snapshotIds, err := amiRegionCopy(state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region) 51 52 lock.Lock() 53 defer lock.Unlock() 54 amis[region] = id 55 snapshots[region] = snapshotIds 56 if err != nil { 57 errs = packer.MultiErrorAppend(errs, err) 58 } 59 }(region) 60 } 61 62 // TODO(mitchellh): Wait but also allow for cancels to go through... 63 ui.Message("Waiting for all copies to complete...") 64 wg.Wait() 65 66 // If there were errors, show them 67 if len(errs.Errors) > 0 { 68 state.Put("error", errs) 69 ui.Error(errs.Error()) 70 return multistep.ActionHalt 71 } 72 73 state.Put("amis", amis) 74 return multistep.ActionContinue 75 } 76 77 func (s *StepAMIRegionCopy) Cleanup(state multistep.StateBag) { 78 // No cleanup... 79 } 80 81 // amiRegionCopy does a copy for the given AMI to the target region and 82 // returns the resulting ID and snapshot IDs, or error. 83 func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string, imageId string, 84 target string, source string) (string, []string, error) { 85 snapshotIds := []string{} 86 87 // Connect to the region where the AMI will be copied to 88 awsConfig, err := config.Config() 89 if err != nil { 90 return "", snapshotIds, err 91 } 92 awsConfig.Region = aws.String(target) 93 94 session, err := session.NewSession(awsConfig) 95 if err != nil { 96 return "", snapshotIds, err 97 } 98 regionconn := ec2.New(session) 99 100 resp, err := regionconn.CopyImage(&ec2.CopyImageInput{ 101 SourceRegion: &source, 102 SourceImageId: &imageId, 103 Name: &name, 104 }) 105 106 if err != nil { 107 return "", snapshotIds, fmt.Errorf("Error Copying AMI (%s) to region (%s): %s", 108 imageId, target, err) 109 } 110 111 stateChange := StateChangeConf{ 112 Pending: []string{"pending"}, 113 Target: "available", 114 Refresh: AMIStateRefreshFunc(regionconn, *resp.ImageId), 115 StepState: state, 116 } 117 118 if _, err := WaitForState(&stateChange); err != nil { 119 return "", snapshotIds, fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s", 120 *resp.ImageId, target, err) 121 } 122 123 // Getting snapshot IDs out of the copied AMI 124 describeImageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{resp.ImageId}}) 125 if err != nil { 126 return "", snapshotIds, fmt.Errorf("Error describing copied AMI (%s) in region (%s): %s", 127 imageId, target, err) 128 } 129 130 for _, blockDeviceMapping := range describeImageResp.Images[0].BlockDeviceMappings { 131 if blockDeviceMapping.Ebs != nil { 132 snapshotIds = append(snapshotIds, *blockDeviceMapping.Ebs.SnapshotId) 133 } 134 } 135 136 return *resp.ImageId, snapshotIds, nil 137 }