diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index 503af210a..2d2a101a5 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -14,6 +14,7 @@ import ( "log" "os" "strings" + "text/template" ) // The unique ID for this builder @@ -27,6 +28,7 @@ type Config struct { awscommon.RunConfig `mapstructure:",squash"` AccountId string `mapstructure:"account_id"` + AMIName string `mapstructure:"ami_name"` BundleDestination string `mapstructure:"bundle_destination"` BundlePrefix string `mapstructure:"bundle_prefix"` BundleUploadCommand string `mapstructure:"bundle_upload_command"` @@ -90,6 +92,17 @@ func (b *Builder) Prepare(raws ...interface{}) error { b.config.AccountId = strings.Replace(b.config.AccountId, "-", "", -1) } + if b.config.AMIName == "" { + errs = packer.MultiErrorAppend( + errs, errors.New("ami_name must be specified")) + } else { + _, err = template.New("ami").Parse(b.config.AMIName) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Failed parsing ami_name: %s", err)) + } + } + if b.config.S3Bucket == "" { errs = packer.MultiErrorAppend(errs, errors.New("s3_bucket is required")) } @@ -163,6 +176,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &StepUploadX509Cert{}, &StepBundleVolume{}, &StepUploadBundle{}, + &StepRegisterAMI{}, } // Run! @@ -187,7 +201,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, nil } - return nil, nil + // Build the artifact and return it + artifact := &awscommon.Artifact{ + Amis: state["amis"].(map[string]string), + BuilderIdValue: BuilderId, + Conn: ec2conn, + } + + return artifact, nil } func (b *Builder) Cancel() { diff --git a/builder/amazon/instance/builder_test.go b/builder/amazon/instance/builder_test.go index 2e6b3639a..9633dff84 100644 --- a/builder/amazon/instance/builder_test.go +++ b/builder/amazon/instance/builder_test.go @@ -15,6 +15,7 @@ func testConfig() map[string]interface{} { return map[string]interface{}{ "account_id": "foo", + "ami_name": "foo", "instance_type": "m1.small", "region": "us-east-1", "s3_bucket": "foo", @@ -61,6 +62,34 @@ func TestBuilderPrepare_AccountId(t *testing.T) { } } +func TestBuilderPrepare_AMIName(t *testing.T) { + var b Builder + config := testConfig() + + // Test good + config["ami_name"] = "foo" + err := b.Prepare(config) + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + // Test bad + config["ami_name"] = "foo {{" + b = Builder{} + err = b.Prepare(config) + if err == nil { + t.Fatal("should have error") + } + + // Test bad + delete(config, "ami_name") + b = Builder{} + err = b.Prepare(config) + if err == nil { + t.Fatal("should have error") + } +} + func TestBuilderPrepare_BundleDestination(t *testing.T) { b := &Builder{} config := testConfig() diff --git a/builder/amazon/instance/step_bundle_volume.go b/builder/amazon/instance/step_bundle_volume.go index 23fb7a415..f5da54dde 100644 --- a/builder/amazon/instance/step_bundle_volume.go +++ b/builder/amazon/instance/step_bundle_volume.go @@ -93,8 +93,10 @@ func (s *StepBundleVolume) Run(state map[string]interface{}) multistep.StepActio } // Store the manifest path + manifestName := bundlePrefix.String() + ".manifest.xml" + state["manifest_name"] = manifestName state["manifest_path"] = fmt.Sprintf( - "%s/%s.manifest.xml", config.BundleDestination, bundlePrefix.String()) + "%s/%s", config.BundleDestination, manifestName) return multistep.ActionContinue } diff --git a/builder/amazon/instance/step_register_ami.go b/builder/amazon/instance/step_register_ami.go new file mode 100644 index 000000000..52f5c30e6 --- /dev/null +++ b/builder/amazon/instance/step_register_ami.go @@ -0,0 +1,61 @@ +package instance + +import ( + "bytes" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "strconv" + "text/template" + "time" +) + +type amiNameData struct { + CreateTime string +} + +type StepRegisterAMI struct{} + +func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction { + comm := state["communicator"].(packer.Communicator) + config := state["config"].(*Config) + manifestPath := state["remote_manifest_path"].(string) + ui := state["ui"].(packer.Ui) + + // Parse the name of the AMI + amiNameBuf := new(bytes.Buffer) + tData := amiNameData{ + strconv.FormatInt(time.Now().UTC().Unix(), 10), + } + + t := template.Must(template.New("ami").Parse(config.AMIName)) + t.Execute(amiNameBuf, tData) + amiName := amiNameBuf.String() + + ui.Say("Registering the AMI...") + cmd := &packer.RemoteCmd{ + Command: fmt.Sprintf( + "ec2-register %s -n '%s' -O '%s' -W '%s'", + manifestPath, + amiName, + config.AccessKey, + config.SecretKey), + } + if err := cmd.StartWithUi(comm, ui); err != nil { + state["error"] = fmt.Errorf("Error registering AMI: %s", err) + ui.Error(state["error"].(error).Error()) + return multistep.ActionHalt + } + + if cmd.ExitStatus != 0 { + state["error"] = fmt.Errorf( + "AMI registration failed. Please see the output above for more\n" + + "details on what went wrong.") + ui.Error(state["error"].(error).Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepRegisterAMI) Cleanup(map[string]interface{}) {} diff --git a/builder/amazon/instance/step_upload_bundle.go b/builder/amazon/instance/step_upload_bundle.go index 827daf8b0..8c8a9fdb5 100644 --- a/builder/amazon/instance/step_upload_bundle.go +++ b/builder/amazon/instance/step_upload_bundle.go @@ -21,6 +21,7 @@ type StepUploadBundle struct{} func (s *StepUploadBundle) Run(state map[string]interface{}) multistep.StepAction { comm := state["communicator"].(packer.Communicator) config := state["config"].(*Config) + manifestName := state["manifest_name"].(string) manifestPath := state["manifest_path"].(string) ui := state["ui"].(packer.Ui) @@ -51,6 +52,9 @@ func (s *StepUploadBundle) Run(state map[string]interface{}) multistep.StepActio return multistep.ActionHalt } + state["remote_manifest_path"] = fmt.Sprintf( + "%s/%s", config.S3Bucket, manifestName) + return multistep.ActionContinue }