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  }