Currently the passed in AWS auth or AWS environment variables are interpreted by packer. This change moves that logic into goamz in order to support both the existing and instance based IAM role authentication. This requires a corresponding change to goamz.
203 lines
4.6 KiB
Go
203 lines
4.6 KiB
Go
// 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.
|
|
package amazonebs
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/mitchellh/goamz/aws"
|
|
"github.com/mitchellh/goamz/ec2"
|
|
"github.com/mitchellh/mapstructure"
|
|
"github.com/mitchellh/multistep"
|
|
"github.com/mitchellh/packer/builder/common"
|
|
"github.com/mitchellh/packer/packer"
|
|
"log"
|
|
"sort"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
)
|
|
|
|
// The unique ID for this builder
|
|
const BuilderId = "mitchellh.amazonebs"
|
|
|
|
type config struct {
|
|
// Access information
|
|
AccessKey string `mapstructure:"access_key"`
|
|
SecretKey string `mapstructure:"secret_key"`
|
|
|
|
// Information for the source instance
|
|
Region string
|
|
SourceAmi string `mapstructure:"source_ami"`
|
|
InstanceType string `mapstructure:"instance_type"`
|
|
SSHUsername string `mapstructure:"ssh_username"`
|
|
SSHPort int `mapstructure:"ssh_port"`
|
|
SSHTimeout time.Duration
|
|
SecurityGroupId string `mapstructure:"security_group_id"`
|
|
|
|
// Configuration of the resulting AMI
|
|
AMIName string `mapstructure:"ami_name"`
|
|
|
|
PackerDebug bool `mapstructure:"packer_debug"`
|
|
RawSSHTimeout string `mapstructure:"ssh_timeout"`
|
|
}
|
|
|
|
type Builder struct {
|
|
config config
|
|
runner multistep.Runner
|
|
}
|
|
|
|
func (b *Builder) Prepare(raws ...interface{}) error {
|
|
var md mapstructure.Metadata
|
|
decoderConfig := &mapstructure.DecoderConfig{
|
|
Metadata: &md,
|
|
Result: &b.config,
|
|
}
|
|
|
|
decoder, err := mapstructure.NewDecoder(decoderConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, raw := range raws {
|
|
err := decoder.Decode(raw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Accumulate any errors
|
|
errs := make([]error, 0)
|
|
|
|
// Unused keys are errors
|
|
if len(md.Unused) > 0 {
|
|
sort.Strings(md.Unused)
|
|
for _, unused := range md.Unused {
|
|
if unused != "type" && !strings.HasPrefix(unused, "packer_") {
|
|
errs = append(
|
|
errs, fmt.Errorf("Unknown configuration key: %s", unused))
|
|
}
|
|
}
|
|
}
|
|
|
|
if b.config.SSHPort == 0 {
|
|
b.config.SSHPort = 22
|
|
}
|
|
|
|
if b.config.RawSSHTimeout == "" {
|
|
b.config.RawSSHTimeout = "1m"
|
|
}
|
|
|
|
// Accumulate any errors
|
|
if b.config.SourceAmi == "" {
|
|
errs = append(errs, errors.New("A source_ami must be specified"))
|
|
}
|
|
|
|
if b.config.InstanceType == "" {
|
|
errs = append(errs, errors.New("An instance_type must be specified"))
|
|
}
|
|
|
|
if b.config.Region == "" {
|
|
errs = append(errs, errors.New("A region must be specified"))
|
|
} else if _, ok := aws.Regions[b.config.Region]; !ok {
|
|
errs = append(errs, fmt.Errorf("Unknown region: %s", b.config.Region))
|
|
}
|
|
|
|
if b.config.SSHUsername == "" {
|
|
errs = append(errs, errors.New("An ssh_username must be specified"))
|
|
}
|
|
|
|
b.config.SSHTimeout, err = time.ParseDuration(b.config.RawSSHTimeout)
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
|
|
}
|
|
|
|
if b.config.AMIName == "" {
|
|
errs = append(errs, errors.New("ami_name must be specified"))
|
|
} else {
|
|
_, err = template.New("ami").Parse(b.config.AMIName)
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("Failed parsing ami_name: %s", err))
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return &packer.MultiError{errs}
|
|
}
|
|
|
|
log.Printf("Config: %+v", b.config)
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
|
region, ok := aws.Regions[b.config.Region]
|
|
if !ok {
|
|
panic("region not found")
|
|
}
|
|
|
|
auth, err := aws.GetAuth(b.config.AccessKey, b.config.SecretKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ec2conn := ec2.New(auth, region)
|
|
|
|
// Setup the state bag and initial state for the steps
|
|
state := make(map[string]interface{})
|
|
state["config"] = b.config
|
|
state["ec2"] = ec2conn
|
|
state["hook"] = hook
|
|
state["ui"] = ui
|
|
|
|
// Build the steps
|
|
steps := []multistep.Step{
|
|
&stepKeyPair{},
|
|
&stepSecurityGroup{},
|
|
&stepRunSourceInstance{},
|
|
&stepConnectSSH{},
|
|
&stepProvision{},
|
|
&stepStopInstance{},
|
|
&stepCreateAMI{},
|
|
}
|
|
|
|
// Run!
|
|
if b.config.PackerDebug {
|
|
b.runner = &multistep.DebugRunner{
|
|
Steps: steps,
|
|
PauseFn: common.MultistepDebugFn(ui),
|
|
}
|
|
} else {
|
|
b.runner = &multistep.BasicRunner{Steps: steps}
|
|
}
|
|
|
|
b.runner.Run(state)
|
|
|
|
// If there was an error, return that
|
|
if rawErr, ok := state["error"]; ok {
|
|
return nil, rawErr.(error)
|
|
}
|
|
|
|
// If there are no AMIs, then just return
|
|
if _, ok := state["amis"]; !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
// Build the artifact and return it
|
|
artifact := &artifact{
|
|
amis: state["amis"].(map[string]string),
|
|
conn: ec2conn,
|
|
}
|
|
|
|
return artifact, nil
|
|
}
|
|
|
|
func (b *Builder) Cancel() {
|
|
if b.runner != nil {
|
|
log.Println("Cancelling the step runner...")
|
|
b.runner.Cancel()
|
|
}
|
|
}
|