diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index 9e7452182..18b07b81c 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -147,6 +147,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Build the steps steps := []multistep.Step{ + &awscommon.StepPreValidate{ + DestAmiName: b.config.AMIName, + ForceDeregister: b.config.AMIForceDeregister, + }, &StepInstanceInfo{}, &awscommon.StepSourceAMIInfo{ SourceAmi: b.config.SourceAmi, @@ -164,6 +168,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &StepChrootProvision{}, &StepEarlyCleanup{}, &StepSnapshot{}, + &awscommon.StepDeregisterAMI{ + ForceDeregister: b.config.AMIForceDeregister, + AMIName: b.config.AMIName, + }, &StepRegisterAMI{}, &awscommon.StepAMIRegionCopy{ AccessConfig: &b.config.AccessConfig, diff --git a/builder/amazon/common/ami_config.go b/builder/amazon/common/ami_config.go index 14b880f4c..377201902 100644 --- a/builder/amazon/common/ami_config.go +++ b/builder/amazon/common/ami_config.go @@ -17,6 +17,7 @@ type AMIConfig struct { AMIRegions []string `mapstructure:"ami_regions"` AMITags map[string]string `mapstructure:"tags"` AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"` + AMIForceDeregister bool `mapstructure:"force_deregister"` } func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error { diff --git a/builder/amazon/common/step_deregister_ami.go b/builder/amazon/common/step_deregister_ami.go new file mode 100644 index 000000000..ce20a5d90 --- /dev/null +++ b/builder/amazon/common/step_deregister_ami.go @@ -0,0 +1,56 @@ +package common + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type StepDeregisterAMI struct { + ForceDeregister bool + AMIName string +} + +func (s *StepDeregisterAMI) Run(state multistep.StateBag) multistep.StepAction { + ec2conn := state.Get("ec2").(*ec2.EC2) + ui := state.Get("ui").(packer.Ui) + + // check for force deregister + if s.ForceDeregister { + resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ + Filters: []*ec2.Filter{&ec2.Filter{ + Name: aws.String("name"), + Values: []*string{aws.String(s.AMIName)}, + }}}) + + if err != nil { + err := fmt.Errorf("Error creating AMI: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // deregister image(s) by that name + for _, i := range resp.Images { + _, err := ec2conn.DeregisterImage(&ec2.DeregisterImageInput{ + ImageID: i.ImageID, + }) + + if err != nil { + err := fmt.Errorf("Error deregistering existing AMI: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + ui.Say(fmt.Sprintf("Deregistered AMI %s, id: %s", s.AMIName, *i.ImageID)) + } + } + + return multistep.ActionContinue +} + +func (s *StepDeregisterAMI) Cleanup(state multistep.StateBag) { +} diff --git a/builder/amazon/common/step_pre_validate.go b/builder/amazon/common/step_pre_validate.go index 5eb263eca..bbeacea43 100644 --- a/builder/amazon/common/step_pre_validate.go +++ b/builder/amazon/common/step_pre_validate.go @@ -13,12 +13,18 @@ import ( // the build before actually doing any time consuming work // type StepPreValidate struct { - DestAmiName string + DestAmiName string + ForceDeregister bool } func (s *StepPreValidate) Run(state multistep.StateBag) multistep.StepAction { - ec2conn := state.Get("ec2").(*ec2.EC2) ui := state.Get("ui").(packer.Ui) + if s.ForceDeregister { + ui.Say("Force Deregister flag found, skipping prevalidating AMI Name") + return multistep.ActionContinue + } + + ec2conn := state.Get("ec2").(*ec2.EC2) ui.Say("Prevalidating AMI Name...") resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 162c06e28..e6fbc8c27 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -80,7 +80,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Build the steps steps := []multistep.Step{ &awscommon.StepPreValidate{ - DestAmiName: b.config.AMIName, + DestAmiName: b.config.AMIName, + ForceDeregister: b.config.AMIForceDeregister, }, &awscommon.StepSourceAMIInfo{ SourceAmi: b.config.SourceAmi, @@ -129,6 +130,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &stepStopInstance{SpotPrice: b.config.SpotPrice}, // TODO(mitchellh): verify works with spots &stepModifyInstance{}, + &awscommon.StepDeregisterAMI{ + ForceDeregister: b.config.AMIForceDeregister, + AMIName: b.config.AMIName, + }, &stepCreateAMI{}, &awscommon.StepAMIRegionCopy{ AccessConfig: &b.config.AccessConfig, diff --git a/builder/amazon/ebs/builder_acc_test.go b/builder/amazon/ebs/builder_acc_test.go index b70f7f7b9..1b4de70ce 100644 --- a/builder/amazon/ebs/builder_acc_test.go +++ b/builder/amazon/ebs/builder_acc_test.go @@ -28,6 +28,22 @@ func TestBuilderAcc_regionCopy(t *testing.T) { }) } +func TestBuilderAcc_forceDeregister(t *testing.T) { + // Build the same AMI name twice, with force_deregister on the second run + builderT.Test(t, builderT.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Builder: &Builder{}, + Template: buildForceDeregisterConfig("false", "dereg"), + SkipArtifactTeardown: true, + }) + + builderT.Test(t, builderT.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Builder: &Builder{}, + Template: buildForceDeregisterConfig("true", "dereg"), + }) +} + func checkRegionCopy(regions []string) builderT.TestCheckFunc { return func(artifacts []packer.Artifact) error { if len(artifacts) > 1 { @@ -107,3 +123,21 @@ const testBuilderAccRegionCopy = ` }] } ` + +const testBuilderAccForceDeregister = ` +{ + "builders": [{ + "type": "test", + "region": "us-east-1", + "instance_type": "m3.medium", + "source_ami": "ami-76b2a71e", + "ssh_username": "ubuntu", + "force_deregister": "%s", + "ami_name": "packer-test-%s" + }] +} +` + +func buildForceDeregisterConfig(name, flag string) string { + return fmt.Sprintf(testBuilderAccForceDeregister, name, flag) +} diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index ec62394ee..0243d4a77 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -168,6 +168,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Build the steps steps := []multistep.Step{ + &awscommon.StepPreValidate{ + DestAmiName: b.config.AMIName, + ForceDeregister: b.config.AMIForceDeregister, + }, &awscommon.StepSourceAMIInfo{ SourceAmi: b.config.SourceAmi, EnhancedNetworking: b.config.AMIEnhancedNetworking, @@ -218,6 +222,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &StepUploadBundle{ Debug: b.config.PackerDebug, }, + &awscommon.StepDeregisterAMI{ + ForceDeregister: b.config.AMIForceDeregister, + AMIName: b.config.AMIName, + }, &StepRegisterAMI{}, &awscommon.StepAMIRegionCopy{ AccessConfig: &b.config.AccessConfig, diff --git a/helper/builder/testing/testing.go b/helper/builder/testing/testing.go index 29200e108..522d7a265 100644 --- a/helper/builder/testing/testing.go +++ b/helper/builder/testing/testing.go @@ -41,6 +41,10 @@ type TestCase struct { // in the case that the test can't guarantee all resources were // properly cleaned up. Teardown TestTeardownFunc + + // If SkipArtifactTeardown is true, we will not attempt to destroy the + // artifact created in this test run. + SkipArtifactTeardown bool } // TestCheckFunc is the callback used for Check in TestStep. @@ -163,12 +167,14 @@ func Test(t TestT, c TestCase) { } TEARDOWN: - // Delete all artifacts - for _, a := range artifacts { - if err := a.Destroy(); err != nil { - t.Error(fmt.Sprintf( - "!!! ERROR REMOVING ARTIFACT '%s': %s !!!", - a.String(), err)) + if !c.SkipArtifactTeardown { + // Delete all artifacts + for _, a := range artifacts { + if err := a.Destroy(); err != nil { + t.Error(fmt.Sprintf( + "!!! ERROR REMOVING ARTIFACT '%s': %s !!!", + a.String(), err)) + } } } diff --git a/website/source/docs/builders/amazon-chroot.html.markdown b/website/source/docs/builders/amazon-chroot.html.markdown index e4e8cefa4..637153dab 100644 --- a/website/source/docs/builders/amazon-chroot.html.markdown +++ b/website/source/docs/builders/amazon-chroot.html.markdown @@ -124,6 +124,9 @@ each category, the available configuration keys are alphabetized. * `enhanced_networking` (boolean) - Enable enhanced networking (SriovNetSupport) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute` to your AWS IAM policy. +* `force_deregister` (boolean) – Force Packer to first deregister an existing +AMI if one with the same name already exists. Default `false`. + * `mount_path` (string) - The path where the volume will be mounted. This is where the chroot environment will be. This defaults to `packer-amazon-chroot-volumes/{{.Device}}`. This is a configuration diff --git a/website/source/docs/builders/amazon-ebs.html.markdown b/website/source/docs/builders/amazon-ebs.html.markdown index 088e6e974..a79a27f94 100644 --- a/website/source/docs/builders/amazon-ebs.html.markdown +++ b/website/source/docs/builders/amazon-ebs.html.markdown @@ -96,6 +96,9 @@ each category, the available configuration keys are alphabetized. * `enhanced_networking` (boolean) - Enable enhanced networking (SriovNetSupport) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute` to your AWS IAM policy. +* `force_deregister` (boolean) – Force Packer to first deregister an existing +AMI if one with the same name already exists. Default `false`. + * `iam_instance_profile` (string) - The name of an [IAM instance profile](http://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html) to launch the EC2 instance with. diff --git a/website/source/docs/builders/amazon-instance.html.markdown b/website/source/docs/builders/amazon-instance.html.markdown index 249313160..3ca82731b 100644 --- a/website/source/docs/builders/amazon-instance.html.markdown +++ b/website/source/docs/builders/amazon-instance.html.markdown @@ -136,6 +136,9 @@ each category, the available configuration keys are alphabetized. * `enhanced_networking` (boolean) - Enable enhanced networking (SriovNetSupport) on HVM-compatible AMIs. If true, add `ec2:ModifyInstanceAttribute` to your AWS IAM policy. +* `force_deregister` (boolean) – Force Packer to first deregister an existing +AMI if one with the same name already exists. Default `false`. + * `iam_instance_profile` (string) - The name of an [IAM instance profile](http://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html) to launch the EC2 instance with.