github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/builder/amazon/common/step_encrypted_ami.go (about)

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