diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index 507ff46ff..973d1c1b6 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -64,6 +64,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { InterpolateContext: &b.config.ctx, InterpolateFilter: &interpolate.RenderFilter{ Exclude: []string{ + "ami_description", + "snapshot_tags", + "tags", "command_wrapper", "post_mount_commands", "pre_mount_commands", @@ -263,10 +266,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ProductCodes: b.config.AMIProductCodes, SnapshotUsers: b.config.SnapshotUsers, SnapshotGroups: b.config.SnapshotGroups, + Ctx: b.config.ctx, }, &awscommon.StepCreateTags{ Tags: b.config.AMITags, SnapshotTags: b.config.SnapshotTags, + Ctx: b.config.ctx, }, ) diff --git a/builder/amazon/common/interpolate_build_info.go b/builder/amazon/common/interpolate_build_info.go new file mode 100644 index 000000000..a279a2f72 --- /dev/null +++ b/builder/amazon/common/interpolate_build_info.go @@ -0,0 +1,6 @@ +package common + +type BuildInfoTemplate struct { + SourceAMI string + BuildRegion string +} diff --git a/builder/amazon/common/step_create_tags.go b/builder/amazon/common/step_create_tags.go index b3a811bad..c4203e9f2 100644 --- a/builder/amazon/common/step_create_tags.go +++ b/builder/amazon/common/step_create_tags.go @@ -2,7 +2,6 @@ package common import ( "fmt" - "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" @@ -11,11 +10,13 @@ import ( "github.com/mitchellh/multistep" retry "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template/interpolate" ) type StepCreateTags struct { Tags map[string]string SnapshotTags map[string]string + Ctx interpolate.Context } func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction { @@ -23,6 +24,13 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) amis := state.Get("amis").(map[string]string) + var sourceAMI string + if rawSourceAMI, hasSourceAMI := state.GetOk("source_image"); hasSourceAMI { + sourceAMI = *rawSourceAMI.(*ec2.Image).ImageId + } else { + sourceAMI = "" + } + if len(s.Tags) == 0 && len(s.SnapshotTags) == 0 { return multistep.ActionContinue } @@ -31,6 +39,22 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction { for region, ami := range amis { ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami)) + // Convert tags to ec2.Tag format + amiTags, err := ConvertToEC2Tags(s.Tags, *ec2conn.Config.Region, sourceAMI, s.Ctx, ui) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Snapshot tags:")) + snapshotTags, err := ConvertToEC2Tags(s.SnapshotTags, *ec2conn.Config.Region, sourceAMI, s.Ctx, ui) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + // Declare list of resources to tag awsConfig := aws.Config{ Credentials: ec2conn.Config.Credentials, @@ -79,9 +103,20 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction { // Convert tags to ec2.Tag format ui.Say("Creating AMI tags") - amiTags := ConvertToEC2Tags(s.Tags) + amiTags, err = ConvertToEC2Tags(s.Tags, *ec2conn.Config.Region, sourceAMI, s.Ctx, ui) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + ui.Say("Creating snapshot tags") - snapshotTags := ConvertToEC2Tags(s.SnapshotTags) + snapshotTags, err = ConvertToEC2Tags(s.SnapshotTags, *ec2conn.Config.Region, sourceAMI, s.Ctx, ui) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } // Retry creating tags for about 2.5 minutes err = retry.Retry(0.2, 30, 11, func() (bool, error) { @@ -130,14 +165,25 @@ func (s *StepCreateTags) Cleanup(state multistep.StateBag) { // No cleanup... } -func ConvertToEC2Tags(tags map[string]string) []*ec2.Tag { - var ec2tags []*ec2.Tag +func ConvertToEC2Tags(tags map[string]string, region, sourceAmiId string, ctx interpolate.Context, ui packer.Ui) ([]*ec2.Tag, error) { + var amiTags []*ec2.Tag for key, value := range tags { - log.Printf("[DEBUG] Creating tag %s=%s", key, value) - ec2tags = append(ec2tags, &ec2.Tag{ + + ctx.Data = &BuildInfoTemplate{ + SourceAMI: sourceAmiId, + BuildRegion: region, + } + interpolatedValue, err := interpolate.Render(value, &ctx) + if err != nil { + return amiTags, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err) + } + + ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, interpolatedValue)) + amiTags = append(amiTags, &ec2.Tag{ Key: aws.String(key), - Value: aws.String(value), + Value: aws.String(interpolatedValue), }) } - return ec2tags + + return amiTags, nil } diff --git a/builder/amazon/common/step_modify_ami_attributes.go b/builder/amazon/common/step_modify_ami_attributes.go index 514b84c86..647a37a25 100644 --- a/builder/amazon/common/step_modify_ami_attributes.go +++ b/builder/amazon/common/step_modify_ami_attributes.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template/interpolate" ) type StepModifyAMIAttributes struct { @@ -17,12 +18,20 @@ type StepModifyAMIAttributes struct { SnapshotGroups []string ProductCodes []string Description string + Ctx interpolate.Context } func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAction { ec2conn := state.Get("ec2").(*ec2.EC2) ui := state.Get("ui").(packer.Ui) amis := state.Get("amis").(map[string]string) + + var sourceAMI string + if rawSourceAMI, hasSourceAMI := state.GetOk("source_image"); hasSourceAMI { + sourceAMI = *rawSourceAMI.(*ec2.Image).ImageId + } else { + sourceAMI = "" + } snapshots := state.Get("snapshots").(map[string][]string) // Determine if there is any work to do. @@ -38,6 +47,18 @@ func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAc return multistep.ActionContinue } + var err error + s.Ctx.Data = &BuildInfoTemplate{ + SourceAMI: sourceAMI, + BuildRegion: *ec2conn.Config.Region, + } + s.Description, err = interpolate.Render(s.Description, &s.Ctx) + if err != nil { + err = fmt.Errorf("Error interpolating AMI description: %s", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + // Construct the modify image and snapshot attribute requests we're going // to make. We need to make each separately since the EC2 API only allows // changing one type at a kind currently. diff --git a/builder/amazon/common/step_run_source_instance.go b/builder/amazon/common/step_run_source_instance.go index 3b61a2485..68b0c2951 100644 --- a/builder/amazon/common/step_run_source_instance.go +++ b/builder/amazon/common/step_run_source_instance.go @@ -13,6 +13,7 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template/interpolate" ) type StepRunSourceInstance struct { @@ -32,6 +33,7 @@ type StepRunSourceInstance struct { Tags map[string]string UserData string UserDataFile string + Ctx interpolate.Context instanceId string spotRequest *ec2.SpotInstanceRequest @@ -275,7 +277,29 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi if _, exists := s.Tags["Name"]; !exists { s.Tags["Name"] = "Packer Builder" } - ec2Tags := ConvertToEC2Tags(s.Tags) + + ec2Tags := make([]*ec2.Tag, 1, len(s.Tags)) + for k, v := range s.Tags { + s.Ctx.Data = &BuildInfoTemplate{ + SourceAMI: s.SourceAMI, + BuildRegion: *ec2conn.Config.Region, + } + interpolatedValue, err := interpolate.Render(v, &s.Ctx) + if err != nil { + err = fmt.Errorf("Error processing tag: %s:%s - %s", k, v, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + ec2Tags = append(ec2Tags, &ec2.Tag{Key: aws.String(k), Value: aws.String(interpolatedValue)}) + } + ec2Tags, err = ConvertToEC2Tags(s.Tags, *ec2conn.Config.Region, s.SourceAMI, s.Ctx, ui) + if err != nil { + err := fmt.Errorf("Error tagging source instance: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } _, err = ec2conn.CreateTags(&ec2.CreateTagsInput{ Tags: ec2Tags, diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 725a43adc..e8a1a15f6 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -44,6 +44,15 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{ + "ami_description", + "run_tags", + "run_volume_tags", + "snapshot_tags", + "tags", + }, + }, }, raws...) if err != nil { return nil, err @@ -137,10 +146,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe AvailabilityZone: b.config.AvailabilityZone, BlockDevices: b.config.BlockDevices, Tags: b.config.RunTags, + Ctx: b.config.ctx, InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, }, &stepTagEBSVolumes{ VolumeRunTags: b.config.VolumeRunTags, + Ctx: b.config.ctx, }, &awscommon.StepGetPassword{ Debug: b.config.PackerDebug, @@ -184,10 +195,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ProductCodes: b.config.AMIProductCodes, SnapshotUsers: b.config.SnapshotUsers, SnapshotGroups: b.config.SnapshotGroups, + Ctx: b.config.ctx, }, &awscommon.StepCreateTags{ Tags: b.config.AMITags, SnapshotTags: b.config.SnapshotTags, + Ctx: b.config.ctx, }, } diff --git a/builder/amazon/ebs/step_tag_ebs_volumes.go b/builder/amazon/ebs/step_tag_ebs_volumes.go index bf7355c8d..0ecee7264 100644 --- a/builder/amazon/ebs/step_tag_ebs_volumes.go +++ b/builder/amazon/ebs/step_tag_ebs_volumes.go @@ -7,15 +7,18 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/builder/amazon/common" "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template/interpolate" ) type stepTagEBSVolumes struct { VolumeRunTags map[string]string + Ctx interpolate.Context } func (s *stepTagEBSVolumes) Run(state multistep.StateBag) multistep.StepAction { ec2conn := state.Get("ec2").(*ec2.EC2) instance := state.Get("instance").(*ec2.Instance) + sourceAMI := state.Get("source_image").(*ec2.Image) ui := state.Get("ui").(packer.Ui) if len(s.VolumeRunTags) == 0 { @@ -33,10 +36,32 @@ func (s *stepTagEBSVolumes) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } - ui.Say("Adding tags to source EBS Volumes") - tags := common.ConvertToEC2Tags(s.VolumeRunTags) + tags := make([]*ec2.Tag, len(s.VolumeRunTags)) + for key, value := range s.VolumeRunTags { + s.Ctx.Data = &common.BuildInfoTemplate{ + SourceAMI: *sourceAMI.ImageId, + BuildRegion: *ec2conn.Config.Region, + } + interpolatedValue, err := interpolate.Render(value, &s.Ctx) + if err != nil { + err = fmt.Errorf("Error processing volume tag: %s:%s - %s", key, value, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + tags = append(tags, &ec2.Tag{Key: &key, Value: &interpolatedValue}) + } - _, err := ec2conn.CreateTags(&ec2.CreateTagsInput{ + ui.Say("Adding tags to source EBS Volumes") + tags, err := common.ConvertToEC2Tags(s.VolumeRunTags, *ec2conn.Config.Region, *sourceAMI.ImageId, s.Ctx, ui) + if err != nil { + err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + _, err = ec2conn.CreateTags(&ec2.CreateTagsInput{ Resources: volumeIds, Tags: tags, }) diff --git a/builder/amazon/ebsvolume/builder.go b/builder/amazon/ebsvolume/builder.go index cb70aec47..c61bfdc7d 100644 --- a/builder/amazon/ebsvolume/builder.go +++ b/builder/amazon/ebsvolume/builder.go @@ -41,6 +41,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{ + "run_tags", + "tags", + }, + }, }, raws...) if err != nil { return nil, err @@ -128,10 +134,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe AvailabilityZone: b.config.AvailabilityZone, BlockDevices: launchBlockDevices, Tags: b.config.RunTags, + Ctx: b.config.ctx, InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, }, &stepTagEBSVolumes{ VolumeMapping: b.config.VolumeMappings, + Ctx: b.config.ctx, }, &awscommon.StepGetPassword{ Debug: b.config.PackerDebug, diff --git a/builder/amazon/ebsvolume/step_tag_ebs_volumes.go b/builder/amazon/ebsvolume/step_tag_ebs_volumes.go index 297f8683f..3528c92d1 100644 --- a/builder/amazon/ebsvolume/step_tag_ebs_volumes.go +++ b/builder/amazon/ebsvolume/step_tag_ebs_volumes.go @@ -6,16 +6,20 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/mitchellh/multistep" + awscommon "github.com/mitchellh/packer/builder/amazon/common" "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template/interpolate" ) type stepTagEBSVolumes struct { VolumeMapping []BlockDevice + Ctx interpolate.Context } func (s *stepTagEBSVolumes) Run(state multistep.StateBag) multistep.StepAction { ec2conn := state.Get("ec2").(*ec2.EC2) instance := state.Get("instance").(*ec2.Instance) + sourceAMI := state.Get("source_image").(*ec2.Image) ui := state.Get("ui").(packer.Ui) volumes := make(EbsVolumes) @@ -42,9 +46,21 @@ func (s *stepTagEBSVolumes) Run(state multistep.StateBag) multistep.StepAction { tags := make([]*ec2.Tag, 0, len(mapping.Tags)) for key, value := range mapping.Tags { + s.Ctx.Data = &awscommon.BuildInfoTemplate{ + SourceAMI: *sourceAMI.ImageId, + BuildRegion: *ec2conn.Config.Region, + } + interpolatedValue, err := interpolate.Render(value, &s.Ctx) + if err != nil { + err = fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + tags = append(tags, &ec2.Tag{ Key: aws.String(fmt.Sprintf("%s", key)), - Value: aws.String(fmt.Sprintf("%s", value)), + Value: aws.String(fmt.Sprintf("%s", interpolatedValue)), }) } diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index 62644a2cd..ce5664d26 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -63,8 +63,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { InterpolateContext: &b.config.ctx, InterpolateFilter: &interpolate.RenderFilter{ Exclude: []string{ + "ami_description", "bundle_upload_command", "bundle_vol_command", + "run_tags", + "run_volume_tags", + "snapshot_tags", + "tags", }, }, }, configs...) @@ -223,6 +228,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe AvailabilityZone: b.config.AvailabilityZone, BlockDevices: b.config.BlockDevices, Tags: b.config.RunTags, + Ctx: b.config.ctx, }, &awscommon.StepGetPassword{ Debug: b.config.PackerDebug, @@ -265,10 +271,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ProductCodes: b.config.AMIProductCodes, SnapshotUsers: b.config.SnapshotUsers, SnapshotGroups: b.config.SnapshotGroups, + Ctx: b.config.ctx, }, &awscommon.StepCreateTags{ Tags: b.config.AMITags, SnapshotTags: b.config.SnapshotTags, + Ctx: b.config.ctx, }, }