github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/builder/amazon/common/step_create_tags.go (about) 1 package common 2 3 import ( 4 "fmt" 5 6 "github.com/aws/aws-sdk-go/aws" 7 "github.com/aws/aws-sdk-go/aws/awserr" 8 "github.com/aws/aws-sdk-go/aws/session" 9 "github.com/aws/aws-sdk-go/service/ec2" 10 retry "github.com/hashicorp/packer/common" 11 "github.com/hashicorp/packer/packer" 12 "github.com/hashicorp/packer/template/interpolate" 13 "github.com/mitchellh/multistep" 14 ) 15 16 type StepCreateTags struct { 17 Tags map[string]string 18 SnapshotTags map[string]string 19 Ctx interpolate.Context 20 } 21 22 func (s *StepCreateTags) 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 27 var sourceAMI string 28 if rawSourceAMI, hasSourceAMI := state.GetOk("source_image"); hasSourceAMI { 29 sourceAMI = *rawSourceAMI.(*ec2.Image).ImageId 30 } else { 31 sourceAMI = "" 32 } 33 34 if len(s.Tags) == 0 && len(s.SnapshotTags) == 0 { 35 return multistep.ActionContinue 36 } 37 38 // Adds tags to AMIs and snapshots 39 for region, ami := range amis { 40 ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami)) 41 42 // Declare list of resources to tag 43 awsConfig := aws.Config{ 44 Credentials: ec2conn.Config.Credentials, 45 Region: aws.String(region), 46 } 47 session, err := session.NewSession(&awsConfig) 48 if err != nil { 49 err := fmt.Errorf("Error creating AWS session: %s", err) 50 state.Put("error", err) 51 ui.Error(err.Error()) 52 return multistep.ActionHalt 53 } 54 regionconn := ec2.New(session) 55 56 // Retrieve image list for given AMI 57 resourceIds := []*string{&ami} 58 imageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{ 59 ImageIds: resourceIds, 60 }) 61 62 if err != nil { 63 err := fmt.Errorf("Error retrieving details for AMI (%s): %s", ami, err) 64 state.Put("error", err) 65 ui.Error(err.Error()) 66 return multistep.ActionHalt 67 } 68 69 if len(imageResp.Images) == 0 { 70 err := fmt.Errorf("Error retrieving details for AMI (%s), no images found", ami) 71 state.Put("error", err) 72 ui.Error(err.Error()) 73 return multistep.ActionHalt 74 } 75 76 image := imageResp.Images[0] 77 snapshotIds := []*string{} 78 79 // Add only those with a Snapshot ID, i.e. not Ephemeral 80 for _, device := range image.BlockDeviceMappings { 81 if device.Ebs != nil && device.Ebs.SnapshotId != nil { 82 ui.Say(fmt.Sprintf("Tagging snapshot: %s", *device.Ebs.SnapshotId)) 83 resourceIds = append(resourceIds, device.Ebs.SnapshotId) 84 snapshotIds = append(snapshotIds, device.Ebs.SnapshotId) 85 } 86 } 87 88 // Convert tags to ec2.Tag format 89 ui.Say("Creating AMI tags") 90 amiTags, err := ConvertToEC2Tags(s.Tags, *ec2conn.Config.Region, sourceAMI, s.Ctx) 91 if err != nil { 92 state.Put("error", err) 93 ui.Error(err.Error()) 94 return multistep.ActionHalt 95 } 96 ReportTags(ui, amiTags) 97 98 ui.Say("Creating snapshot tags") 99 snapshotTags, err := ConvertToEC2Tags(s.SnapshotTags, *ec2conn.Config.Region, sourceAMI, s.Ctx) 100 if err != nil { 101 state.Put("error", err) 102 ui.Error(err.Error()) 103 return multistep.ActionHalt 104 } 105 ReportTags(ui, snapshotTags) 106 107 // Retry creating tags for about 2.5 minutes 108 err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) { 109 // Tag images and snapshots 110 _, err := regionconn.CreateTags(&ec2.CreateTagsInput{ 111 Resources: resourceIds, 112 Tags: amiTags, 113 }) 114 if awsErr, ok := err.(awserr.Error); ok { 115 if awsErr.Code() == "InvalidAMIID.NotFound" || 116 awsErr.Code() == "InvalidSnapshot.NotFound" { 117 return false, nil 118 } 119 } 120 121 // Override tags on snapshots 122 if len(snapshotTags) > 0 { 123 _, err = regionconn.CreateTags(&ec2.CreateTagsInput{ 124 Resources: snapshotIds, 125 Tags: snapshotTags, 126 }) 127 } 128 if err == nil { 129 return true, nil 130 } 131 if awsErr, ok := err.(awserr.Error); ok { 132 if awsErr.Code() == "InvalidSnapshot.NotFound" { 133 return false, nil 134 } 135 } 136 return true, err 137 }) 138 139 if err != nil { 140 err := fmt.Errorf("Error adding tags to Resources (%#v): %s", resourceIds, err) 141 state.Put("error", err) 142 ui.Error(err.Error()) 143 return multistep.ActionHalt 144 } 145 } 146 147 return multistep.ActionContinue 148 } 149 150 func (s *StepCreateTags) Cleanup(state multistep.StateBag) { 151 // No cleanup... 152 } 153 154 func ReportTags(ui packer.Ui, tags []*ec2.Tag) { 155 for _, tag := range tags { 156 ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", 157 aws.StringValue(tag.Key), aws.StringValue(tag.Value))) 158 } 159 } 160 161 func ConvertToEC2Tags(tags map[string]string, region, sourceAmiId string, ctx interpolate.Context) ([]*ec2.Tag, error) { 162 var ec2Tags []*ec2.Tag 163 for key, value := range tags { 164 165 ctx.Data = &BuildInfoTemplate{ 166 SourceAMI: sourceAmiId, 167 BuildRegion: region, 168 } 169 interpolatedKey, err := interpolate.Render(key, &ctx) 170 if err != nil { 171 return ec2Tags, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err) 172 } 173 interpolatedValue, err := interpolate.Render(value, &ctx) 174 if err != nil { 175 return ec2Tags, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err) 176 } 177 178 ec2Tags = append(ec2Tags, &ec2.Tag{ 179 Key: aws.String(interpolatedKey), 180 Value: aws.String(interpolatedValue), 181 }) 182 } 183 184 return ec2Tags, nil 185 }