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  }