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 }