Merge pull request #4015 from artburkart:closes_3908
Closes #3908: Adds snapshot tag overrides
This commit is contained in:
commit
a09f20f996
|
@ -263,6 +263,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
},
|
||||
&awscommon.StepCreateTags{
|
||||
Tags: b.config.AMITags,
|
||||
SnapshotTags: b.config.SnapshotTags,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ type AMIConfig struct {
|
|||
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
|
||||
AMIForceDeregister bool `mapstructure:"force_deregister"`
|
||||
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`
|
||||
SnapshotTags map[string]string `mapstructure:"snapshot_tags"`
|
||||
}
|
||||
|
||||
func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
|
|
|
@ -79,6 +79,19 @@ func (a *Artifact) Destroy() error {
|
|||
}
|
||||
regionConn := ec2.New(session)
|
||||
|
||||
// Get image metadata
|
||||
imageResp, err := regionConn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{&imageId},
|
||||
})
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
if len(imageResp.Images) == 0 {
|
||||
err := fmt.Errorf("Error retrieving details for AMI (%s), no images found", imageId)
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
// Deregister ami
|
||||
input := &ec2.DeregisterImageInput{
|
||||
ImageId: &imageId,
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
type StepCreateTags struct {
|
||||
Tags map[string]string
|
||||
SnapshotTags map[string]string
|
||||
}
|
||||
|
||||
func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
|
||||
|
@ -21,21 +22,20 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
|
|||
ui := state.Get("ui").(packer.Ui)
|
||||
amis := state.Get("amis").(map[string]string)
|
||||
|
||||
if len(s.Tags) > 0 {
|
||||
if len(s.Tags) == 0 && len(s.SnapshotTags) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Adds tags to AMIs and snapshots
|
||||
for region, ami := range amis {
|
||||
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami))
|
||||
|
||||
var ec2Tags []*ec2.Tag
|
||||
for key, value := range s.Tags {
|
||||
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
|
||||
ec2Tags = append(ec2Tags, &ec2.Tag{
|
||||
Key: aws.String(key),
|
||||
Value: aws.String(value),
|
||||
})
|
||||
}
|
||||
// Convert tags to ec2.Tag format
|
||||
amiTags := ConvertToEC2Tags(s.Tags, ui)
|
||||
ui.Say(fmt.Sprintf("Snapshot tags:"))
|
||||
snapshotTags := ConvertToEC2Tags(s.SnapshotTags, ui)
|
||||
|
||||
// Declare list of resources to tag
|
||||
resourceIds := []*string{&ami}
|
||||
awsConfig := aws.Config{
|
||||
Credentials: ec2conn.Config.Credentials,
|
||||
Region: aws.String(region),
|
||||
|
@ -47,10 +47,10 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
|
|||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
regionconn := ec2.New(session)
|
||||
|
||||
// Retrieve image list for given AMI
|
||||
resourceIds := []*string{&ami}
|
||||
imageResp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: resourceIds,
|
||||
})
|
||||
|
@ -70,27 +70,41 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
|
|||
}
|
||||
|
||||
image := imageResp.Images[0]
|
||||
snapshotIds := []*string{}
|
||||
|
||||
// Add only those with a Snapshot ID, i.e. not Ephemeral
|
||||
for _, device := range image.BlockDeviceMappings {
|
||||
if device.Ebs != nil && device.Ebs.SnapshotId != nil {
|
||||
ui.Say(fmt.Sprintf("Tagging snapshot: %s", *device.Ebs.SnapshotId))
|
||||
resourceIds = append(resourceIds, device.Ebs.SnapshotId)
|
||||
snapshotIds = append(snapshotIds, device.Ebs.SnapshotId)
|
||||
}
|
||||
}
|
||||
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
err = retry.Retry(0.2, 30, 11, func() (bool, error) {
|
||||
// Tag images and snapshots
|
||||
_, err := regionconn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: resourceIds,
|
||||
Tags: ec2Tags,
|
||||
Tags: amiTags,
|
||||
})
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
if awsErr.Code() == "InvalidAMIID.NotFound" ||
|
||||
awsErr.Code() == "InvalidSnapshot.NotFound" {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Override tags on snapshots
|
||||
_, err = regionconn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: snapshotIds,
|
||||
Tags: snapshotTags,
|
||||
})
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
if awsErr.Code() == "InvalidAMIID.NotFound" ||
|
||||
awsErr.Code() == "InvalidSnapshot.NotFound" {
|
||||
if awsErr.Code() == "InvalidSnapshot.NotFound" {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +118,6 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
@ -112,3 +125,15 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
|
|||
func (s *StepCreateTags) Cleanup(state multistep.StateBag) {
|
||||
// No cleanup...
|
||||
}
|
||||
|
||||
func ConvertToEC2Tags(tags map[string]string, ui packer.Ui) []*ec2.Tag {
|
||||
var amiTags []*ec2.Tag
|
||||
for key, value := range tags {
|
||||
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
|
||||
amiTags = append(amiTags, &ec2.Tag{
|
||||
Key: aws.String(key),
|
||||
Value: aws.String(value),
|
||||
})
|
||||
}
|
||||
return amiTags
|
||||
}
|
||||
|
|
|
@ -183,6 +183,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
},
|
||||
&awscommon.StepCreateTags{
|
||||
Tags: b.config.AMITags,
|
||||
SnapshotTags: b.config.SnapshotTags,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ebs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
|
@ -11,6 +12,21 @@ import (
|
|||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type TFBuilder struct {
|
||||
Type string `json:"type"`
|
||||
Region string `json:"region"`
|
||||
SourceAmi string `json:"source_ami"`
|
||||
InstanceType string `json:"instance_type"`
|
||||
SshUsername string `json:"ssh_username"`
|
||||
AmiName string `json:"ami_name"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
SnapshotTags map[string]string `json:"snapshot_tags"`
|
||||
}
|
||||
|
||||
type TFConfig struct {
|
||||
Builders []TFBuilder `json:"builders"`
|
||||
}
|
||||
|
||||
func TestBuilderTagsAcc_basic(t *testing.T) {
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
|
@ -26,9 +42,10 @@ func checkTags() builderT.TestCheckFunc {
|
|||
return fmt.Errorf("more than 1 artifact")
|
||||
}
|
||||
|
||||
tags := make(map[string]string)
|
||||
tags["OS_Version"] = "Ubuntu"
|
||||
tags["Release"] = "Latest"
|
||||
config := TFConfig{}
|
||||
json.Unmarshal([]byte(testBuilderTagsAccBasic), &config)
|
||||
tags := config.Builders[0].Tags
|
||||
snapshotTags := config.Builders[0].SnapshotTags
|
||||
|
||||
// Get the actual *Artifact pointer so we can access the AMIs directly
|
||||
artifactRaw := artifacts[0]
|
||||
|
@ -37,18 +54,18 @@ func checkTags() builderT.TestCheckFunc {
|
|||
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
|
||||
}
|
||||
|
||||
// describe the image, get block devices with a snapshot
|
||||
// Describe the image, get block devices with a snapshot
|
||||
ec2conn, _ := testEC2Conn()
|
||||
imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{aws.String(artifact.Amis["us-east-1"])},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error retrieving details for AMI Artifcat (%#v) in Tags Test: %s", artifact, err)
|
||||
return fmt.Errorf("Error retrieving details for AMI Artifact (%#v) in Tags Test: %s", artifact, err)
|
||||
}
|
||||
|
||||
if len(imageResp.Images) == 0 {
|
||||
return fmt.Errorf("No images found for AMI Artifcat (%#v) in Tags Test: %s", artifact, err)
|
||||
return fmt.Errorf("No images found for AMI Artifact (%#v) in Tags Test: %s", artifact, err)
|
||||
}
|
||||
|
||||
image := imageResp.Images[0]
|
||||
|
@ -61,7 +78,7 @@ func checkTags() builderT.TestCheckFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// grab matching snapshot info
|
||||
// Grab matching snapshot info
|
||||
resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{
|
||||
SnapshotIds: snapshots,
|
||||
})
|
||||
|
@ -74,12 +91,14 @@ func checkTags() builderT.TestCheckFunc {
|
|||
return fmt.Errorf("No Snapshots found for AMI Artifcat (%#v) in Tags Test", artifact)
|
||||
}
|
||||
|
||||
// grab the snapshots, check the tags
|
||||
// Grab the snapshots, check the tags
|
||||
for _, s := range resp.Snapshots {
|
||||
expected := len(tags)
|
||||
for _, t := range s.Tags {
|
||||
for key, value := range tags {
|
||||
if key == *t.Key && value == *t.Value {
|
||||
if val, ok := snapshotTags[key]; ok && val == *t.Value {
|
||||
expected--
|
||||
} else if key == *t.Key && value == *t.Value {
|
||||
expected--
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +125,11 @@ const testBuilderTagsAccBasic = `
|
|||
"ami_name": "packer-tags-testing-{{timestamp}}",
|
||||
"tags": {
|
||||
"OS_Version": "Ubuntu",
|
||||
"Release": "Latest"
|
||||
"Release": "Latest",
|
||||
"Name": "Bleep"
|
||||
},
|
||||
"snapshot_tags": {
|
||||
"Name": "Foobar"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -264,6 +264,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
},
|
||||
&awscommon.StepCreateTags{
|
||||
Tags: b.config.AMITags,
|
||||
SnapshotTags: b.config.SnapshotTags,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -200,6 +200,9 @@ each category, the available configuration keys are alphabetized.
|
|||
- `most_recent` (bool) - Selects the newest created image when true.
|
||||
This is most useful for selecting a daily distro build.
|
||||
|
||||
- `snapshot_tags` (object of key/value strings) - Tags to apply to snapshot.
|
||||
They will override AMI tags if already applied to snapshot.
|
||||
|
||||
- `tags` (object of key/value strings) - Tags applied to the AMI.
|
||||
|
||||
## Basic Example
|
||||
|
|
|
@ -204,6 +204,9 @@ builder.
|
|||
- `most_recent` (bool) - Selects the newest created image when true.
|
||||
This is most useful for selecting a daily distro build.
|
||||
|
||||
- `snapshot_tags` (object of key/value strings) - Tags to apply to snapshot.
|
||||
They will override AMI tags if already applied to snapshot.
|
||||
|
||||
- `spot_price` (string) - The maximum hourly price to pay for a spot instance
|
||||
to create the AMI. Spot instances are a type of instance that EC2 starts
|
||||
when the current spot price is less than the maximum price you specify. Spot
|
||||
|
|
|
@ -219,6 +219,9 @@ builder.
|
|||
- `most_recent` (bool) - Selects the newest created image when true.
|
||||
This is most useful for selecting a daily distro build.
|
||||
|
||||
- `snapshot_tags` (object of key/value strings) - Tags to apply to snapshot.
|
||||
They will override AMI tags if already applied to snapshot.
|
||||
|
||||
- `spot_price` (string) - The maximum hourly price to launch a spot instance
|
||||
to create the AMI. It is a type of instances that EC2 starts when the
|
||||
maximum price that you specify exceeds the current spot price. Spot price
|
||||
|
|
Loading…
Reference in New Issue