2013-05-09 17:16:39 -04:00
|
|
|
// The amazonebs package contains a packer.Builder implementation that
|
|
|
|
// builds AMIs for Amazon EC2.
|
|
|
|
//
|
|
|
|
// In general, there are two types of AMIs that can be created: ebs-backed or
|
|
|
|
// instance-store. This builder _only_ builds ebs-backed images.
|
2013-05-09 01:34:20 -04:00
|
|
|
package amazonebs
|
|
|
|
|
|
|
|
import (
|
2013-05-21 01:23:23 -04:00
|
|
|
"cgl.tideland.biz/identifier"
|
|
|
|
"encoding/hex"
|
2013-05-10 18:21:11 -04:00
|
|
|
"fmt"
|
|
|
|
"github.com/mitchellh/goamz/aws"
|
|
|
|
"github.com/mitchellh/goamz/ec2"
|
2013-05-20 19:39:43 -04:00
|
|
|
"github.com/mitchellh/mapstructure"
|
2013-05-09 01:34:20 -04:00
|
|
|
"github.com/mitchellh/packer/packer"
|
2013-05-09 16:26:40 -04:00
|
|
|
"log"
|
2013-05-10 18:21:11 -04:00
|
|
|
"time"
|
2013-05-09 01:34:20 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
type config struct {
|
2013-05-20 19:50:35 -04:00
|
|
|
// Access information
|
2013-05-20 19:39:43 -04:00
|
|
|
AccessKey string `mapstructure:"access_key"`
|
|
|
|
SecretKey string `mapstructure:"secret_key"`
|
2013-05-20 19:50:35 -04:00
|
|
|
|
2013-05-21 01:23:23 -04:00
|
|
|
// Information for the source instance
|
|
|
|
Region string
|
|
|
|
SourceAmi string `mapstructure:"source_ami"`
|
|
|
|
InstanceType string `mapstructure:"instance_type"`
|
2013-05-20 19:50:35 -04:00
|
|
|
|
|
|
|
// Configuration of the resulting AMI
|
|
|
|
AMIName string `mapstructure:"ami_name"`
|
2013-05-09 01:34:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
type Builder struct {
|
|
|
|
config config
|
|
|
|
}
|
|
|
|
|
2013-05-09 16:19:38 -04:00
|
|
|
func (b *Builder) Prepare(raw interface{}) (err error) {
|
2013-05-20 19:39:43 -04:00
|
|
|
err = mapstructure.Decode(raw, &b.config)
|
2013-05-09 16:19:38 -04:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2013-05-09 16:26:40 -04:00
|
|
|
log.Printf("Config: %+v\n", b.config)
|
2013-05-10 16:01:54 -04:00
|
|
|
|
|
|
|
// TODO: Validate the configuration
|
2013-05-09 16:19:38 -04:00
|
|
|
return
|
2013-05-09 13:54:42 -04:00
|
|
|
}
|
2013-05-09 01:34:20 -04:00
|
|
|
|
2013-05-11 13:31:30 -04:00
|
|
|
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) {
|
2013-05-10 18:21:11 -04:00
|
|
|
auth := aws.Auth{b.config.AccessKey, b.config.SecretKey}
|
|
|
|
region := aws.Regions[b.config.Region]
|
|
|
|
ec2conn := ec2.New(auth, region)
|
|
|
|
|
2013-05-21 01:23:23 -04:00
|
|
|
// Create a new keypair that we'll use to access the instance.
|
|
|
|
keyName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw()))
|
|
|
|
ui.Say("Creating temporary keypair for this instance...\n")
|
|
|
|
log.Printf("temporary keypair name: %s\n", keyName)
|
|
|
|
_, err := ec2conn.CreateKeyPair(keyName)
|
|
|
|
if err != nil {
|
|
|
|
ui.Error("%s\n", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure the keypair is properly deleted when we exit
|
|
|
|
defer func() {
|
|
|
|
ui.Say("Deleting temporary keypair...\n")
|
|
|
|
_, err := ec2conn.DeleteKeyPair(keyName)
|
|
|
|
if err != nil {
|
|
|
|
ui.Error(
|
|
|
|
"Error cleaning up keypair. Please delete the key manually: %s", keyName)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2013-05-10 18:21:11 -04:00
|
|
|
runOpts := &ec2.RunInstances{
|
2013-05-21 01:23:23 -04:00
|
|
|
KeyName: keyName,
|
2013-05-10 20:01:24 -04:00
|
|
|
ImageId: b.config.SourceAmi,
|
2013-05-21 01:23:23 -04:00
|
|
|
InstanceType: b.config.InstanceType,
|
2013-05-10 20:01:24 -04:00
|
|
|
MinCount: 0,
|
|
|
|
MaxCount: 0,
|
2013-05-10 18:21:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
ui.Say("Launching a source AWS instance...\n")
|
|
|
|
runResp, err := ec2conn.RunInstances(runOpts)
|
|
|
|
if err != nil {
|
|
|
|
ui.Error("%s\n", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
instance := &runResp.Instances[0]
|
|
|
|
log.Printf("instance id: %s\n", instance.InstanceId)
|
|
|
|
ui.Say("Waiting for instance to become ready...\n")
|
|
|
|
err = waitForState(ec2conn, instance, "running")
|
|
|
|
if err != nil {
|
|
|
|
ui.Error("%s\n", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop the instance so we can create an AMI from it
|
|
|
|
_, err = ec2conn.StopInstances(instance.InstanceId)
|
|
|
|
if err != nil {
|
|
|
|
ui.Error("%s\n", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the instance to actual stop
|
|
|
|
// TODO: Handle diff source states, i.e. this force state sucks
|
|
|
|
ui.Say("Waiting for the instance to stop...\n")
|
|
|
|
instance.State.Name = "stopping"
|
|
|
|
err = waitForState(ec2conn, instance, "stopped")
|
|
|
|
if err != nil {
|
|
|
|
ui.Error("%s\n", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the image
|
|
|
|
ui.Say("Creating the AMI...\n")
|
|
|
|
createOpts := &ec2.CreateImage{
|
|
|
|
InstanceId: instance.InstanceId,
|
2013-05-10 20:01:24 -04:00
|
|
|
Name: b.config.AMIName,
|
2013-05-10 18:21:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
createResp, err := ec2conn.CreateImage(createOpts)
|
|
|
|
if err != nil {
|
|
|
|
ui.Error("%s\n", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ui.Say("AMI: %s\n", createResp.ImageId)
|
|
|
|
|
2013-05-10 18:47:46 -04:00
|
|
|
// Wait for the image to become ready
|
|
|
|
ui.Say("Waiting for AMI to become ready...\n")
|
|
|
|
for {
|
|
|
|
imageResp, err := ec2conn.Images([]string{createResp.ImageId}, ec2.NewFilter())
|
|
|
|
if err != nil {
|
|
|
|
ui.Error("%s\n", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if imageResp.Images[0].State == "available" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-10 18:21:11 -04:00
|
|
|
// Make sure we clean up the instance by terminating it, no matter what
|
|
|
|
defer func() {
|
|
|
|
// TODO: error handling
|
|
|
|
ui.Say("Terminating the source AWS instance...\n")
|
|
|
|
ec2conn.TerminateInstances([]string{instance.InstanceId})
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func waitForState(ec2conn *ec2.EC2, i *ec2.Instance, target string) (err error) {
|
|
|
|
log.Printf("Waiting for instance state to become: %s\n", target)
|
|
|
|
|
|
|
|
original := i.State.Name
|
|
|
|
for i.State.Name == original {
|
|
|
|
var resp *ec2.InstancesResp
|
|
|
|
resp, err = ec2conn.Instances([]string{i.InstanceId}, ec2.NewFilter())
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
i = &resp.Reservations[0].Instances[0]
|
|
|
|
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
}
|
|
|
|
|
|
|
|
if i.State.Name != target {
|
|
|
|
return fmt.Errorf("unexpected target state '%s', wanted '%s'", i.State.Name, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
2013-05-10 16:01:54 -04:00
|
|
|
}
|