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  }