github.phpd.cn/hashicorp/packer@v1.3.2/builder/amazon/common/step_encrypted_ami.go (about)

     1  package common
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  
     8  	"github.com/aws/aws-sdk-go/aws"
     9  	"github.com/aws/aws-sdk-go/service/ec2"
    10  	"github.com/hashicorp/packer/helper/multistep"
    11  	"github.com/hashicorp/packer/packer"
    12  )
    13  
    14  type StepCreateEncryptedAMICopy struct {
    15  	image             *ec2.Image
    16  	KeyID             string
    17  	EncryptBootVolume bool
    18  	Name              string
    19  	AMIMappings       []BlockDevice
    20  }
    21  
    22  func (s *StepCreateEncryptedAMICopy) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
    23  	ec2conn := state.Get("ec2").(*ec2.EC2)
    24  	ui := state.Get("ui").(packer.Ui)
    25  	kmsKeyId := s.KeyID
    26  
    27  	// Encrypt boot not set, so skip step
    28  	if !s.EncryptBootVolume {
    29  		if kmsKeyId != "" {
    30  			log.Printf("Ignoring KMS Key ID: %s, encrypted=false", kmsKeyId)
    31  		}
    32  		return multistep.ActionContinue
    33  	}
    34  
    35  	ui.Say("Creating Encrypted AMI Copy")
    36  
    37  	amis := state.Get("amis").(map[string]string)
    38  	var region, id string
    39  	if amis != nil {
    40  		for region, id = range amis {
    41  			break // There is only ever one region:ami pair in this map
    42  		}
    43  	}
    44  
    45  	ui.Say(fmt.Sprintf("Copying AMI: %s(%s)", region, id))
    46  
    47  	if kmsKeyId != "" {
    48  		ui.Say(fmt.Sprintf("Encrypting with KMS Key ID: %s", kmsKeyId))
    49  	}
    50  
    51  	copyOpts := &ec2.CopyImageInput{
    52  		Name:          &s.Name, // Try to overwrite existing AMI
    53  		SourceImageId: aws.String(id),
    54  		SourceRegion:  aws.String(region),
    55  		Encrypted:     aws.Bool(true),
    56  		KmsKeyId:      aws.String(kmsKeyId),
    57  	}
    58  
    59  	copyResp, err := ec2conn.CopyImage(copyOpts)
    60  	if err != nil {
    61  		err := fmt.Errorf("Error copying AMI: %s", err)
    62  		state.Put("error", err)
    63  		ui.Error(err.Error())
    64  		return multistep.ActionHalt
    65  	}
    66  
    67  	// Wait for the copy to become ready
    68  	ui.Say("Waiting for AMI copy to become ready...")
    69  	if err := WaitUntilAMIAvailable(ctx, ec2conn, *copyResp.ImageId); err != nil {
    70  		err := fmt.Errorf("Error waiting for AMI Copy: %s", err)
    71  		state.Put("error", err)
    72  		ui.Error(err.Error())
    73  		return multistep.ActionHalt
    74  	}
    75  
    76  	// Get the encrypted AMI image, we need the new snapshot id's
    77  	encImagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{aws.String(*copyResp.ImageId)}})
    78  	if err != nil {
    79  		err := fmt.Errorf("Error searching for AMI: %s", err)
    80  		state.Put("error", err)
    81  		ui.Error(err.Error())
    82  		return multistep.ActionHalt
    83  	}
    84  	encImage := encImagesResp.Images[0]
    85  	var encSnapshots []string
    86  	for _, blockDevice := range encImage.BlockDeviceMappings {
    87  		if blockDevice.Ebs != nil && blockDevice.Ebs.SnapshotId != nil {
    88  			encSnapshots = append(encSnapshots, *blockDevice.Ebs.SnapshotId)
    89  		}
    90  	}
    91  
    92  	// Get the unencrypted AMI image
    93  	unencImagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{aws.String(id)}})
    94  	if err != nil {
    95  		err := fmt.Errorf("Error searching for AMI: %s", err)
    96  		state.Put("error", err)
    97  		ui.Error(err.Error())
    98  		return multistep.ActionHalt
    99  	}
   100  	unencImage := unencImagesResp.Images[0]
   101  
   102  	// Remove unencrypted AMI
   103  	ui.Say("Deregistering unencrypted AMI")
   104  	deregisterOpts := &ec2.DeregisterImageInput{ImageId: aws.String(id)}
   105  	if _, err := ec2conn.DeregisterImage(deregisterOpts); err != nil {
   106  		ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", err))
   107  		return multistep.ActionHalt
   108  	}
   109  
   110  	// Remove associated unencrypted snapshot(s)
   111  	ui.Say("Deleting unencrypted snapshots")
   112  	snapshots := state.Get("snapshots").(map[string][]string)
   113  
   114  OuterLoop:
   115  	for _, blockDevice := range unencImage.BlockDeviceMappings {
   116  		if blockDevice.Ebs != nil && blockDevice.Ebs.SnapshotId != nil {
   117  			// If this packer run didn't create it, then don't delete it
   118  			for _, origDevice := range s.AMIMappings {
   119  				if origDevice.SnapshotId == *blockDevice.Ebs.SnapshotId {
   120  					ui.Message(fmt.Sprintf("Keeping Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
   121  					continue OuterLoop
   122  				}
   123  			}
   124  
   125  			ui.Message(fmt.Sprintf("Deleting Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
   126  			deleteSnapOpts := &ec2.DeleteSnapshotInput{
   127  				SnapshotId: aws.String(*blockDevice.Ebs.SnapshotId),
   128  			}
   129  			if _, err := ec2conn.DeleteSnapshot(deleteSnapOpts); err != nil {
   130  				ui.Error(fmt.Sprintf("Error deleting snapshot, may still be around: %s", err))
   131  				return multistep.ActionHalt
   132  			}
   133  		}
   134  	}
   135  
   136  	// Replace original AMI ID with Encrypted ID in state
   137  	amis[region] = *copyResp.ImageId
   138  	snapshots[region] = encSnapshots
   139  	state.Put("amis", amis)
   140  	state.Put("snapshots", snapshots)
   141  
   142  	imagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{copyResp.ImageId}})
   143  	if err != nil {
   144  		err := fmt.Errorf("Error searching for AMI: %s", err)
   145  		state.Put("error", err)
   146  		ui.Error(err.Error())
   147  		return multistep.ActionHalt
   148  	}
   149  	s.image = imagesResp.Images[0]
   150  
   151  	return multistep.ActionContinue
   152  }
   153  
   154  func (s *StepCreateEncryptedAMICopy) Cleanup(state multistep.StateBag) {
   155  	if s.image == nil {
   156  		return
   157  	}
   158  
   159  	_, cancelled := state.GetOk(multistep.StateCancelled)
   160  	_, halted := state.GetOk(multistep.StateHalted)
   161  	if !cancelled && !halted {
   162  		return
   163  	}
   164  
   165  	ec2conn := state.Get("ec2").(*ec2.EC2)
   166  	ui := state.Get("ui").(packer.Ui)
   167  
   168  	ui.Say("Deregistering the AMI because cancellation or error...")
   169  	deregisterOpts := &ec2.DeregisterImageInput{ImageId: s.image.ImageId}
   170  	if _, err := ec2conn.DeregisterImage(deregisterOpts); err != nil {
   171  		ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", err))
   172  		return
   173  	}
   174  }