builder.go 5.49 KB
Newer Older
1 2 3 4 5
// 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.
6
package ebs
7 8

import (
9
	"fmt"
10 11
	"log"

Seth Vargo's avatar
Seth Vargo committed
12
	"github.com/aws/aws-sdk-go/service/ec2"
13
	"github.com/mitchellh/multistep"
14
	awscommon "github.com/mitchellh/packer/builder/amazon/common"
15
	"github.com/mitchellh/packer/common"
16
	"github.com/mitchellh/packer/helper/communicator"
17
	"github.com/mitchellh/packer/helper/config"
18
	"github.com/mitchellh/packer/packer"
19
	"github.com/mitchellh/packer/template/interpolate"
20 21
)

22 23 24
// The unique ID for this builder
const BuilderId = "mitchellh.amazonebs"

25
type Config struct {
26
	common.PackerConfig    `mapstructure:",squash"`
27
	awscommon.AccessConfig `mapstructure:",squash"`
28
	awscommon.AMIConfig    `mapstructure:",squash"`
29
	awscommon.BlockDevices `mapstructure:",squash"`
30
	awscommon.RunConfig    `mapstructure:",squash"`
Mitchell Hashimoto's avatar
Mitchell Hashimoto committed
31

32
	ctx *interpolate.Context
33 34 35
}

type Builder struct {
36
	config Config
37
	runner multistep.Runner
38 39
}

40
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
41 42 43 44 45
	b.config.ctx = &interpolate.Context{Funcs: awscommon.TemplateFuncs}
	err := config.Decode(&b.config, &config.DecodeOpts{
		Interpolate:        true,
		InterpolateContext: b.config.ctx,
	}, raws...)
46
	if err != nil {
47
		return nil, err
48
	}
49

50
	// Accumulate any errors
51 52 53 54 55
	var errs *packer.MultiError
	errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.ctx)...)
	errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(b.config.ctx)...)
	errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.ctx)...)
	errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.ctx)...)
56

57
	if errs != nil && len(errs.Errors) > 0 {
58
		return nil, errs
59
	}
60

61
	log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
62
	return nil, nil
63
}
64

65
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
66
	config, err := b.config.Config()
67 68
	if err != nil {
		return nil, err
69 70
	}

71
	ec2conn := ec2.New(config)
72

73
	// Setup the state bag and initial state for the steps
74 75 76 77 78
	state := new(multistep.BasicStateBag)
	state.Put("config", b.config)
	state.Put("ec2", ec2conn)
	state.Put("hook", hook)
	state.Put("ui", ui)
79

80
	// Build the steps
81
	steps := []multistep.Step{
82
		&awscommon.StepPreValidate{
83 84
			DestAmiName:     b.config.AMIName,
			ForceDeregister: b.config.AMIForceDeregister,
85
		},
86 87 88 89
		&awscommon.StepSourceAMIInfo{
			SourceAmi:          b.config.SourceAmi,
			EnhancedNetworking: b.config.AMIEnhancedNetworking,
		},
90
		&awscommon.StepKeyPair{
91 92 93
			Debug:          b.config.PackerDebug,
			DebugKeyPath:   fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
			KeyPairName:    b.config.TemporaryKeyPairName,
94
			PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
95
		},
96
		&awscommon.StepSecurityGroup{
97
			SecurityGroupIds: b.config.SecurityGroupIds,
98
			CommConfig:       &b.config.RunConfig.Comm,
99
			VpcId:            b.config.VpcId,
100 101
		},
		&awscommon.StepRunSourceInstance{
102 103
			Debug:                    b.config.PackerDebug,
			ExpectedRootDevice:       "ebs",
104
			SpotPrice:                b.config.SpotPrice,
105
			SpotPriceProduct:         b.config.SpotPriceAutoProduct,
106 107 108 109 110 111 112 113 114
			InstanceType:             b.config.InstanceType,
			UserData:                 b.config.UserData,
			UserDataFile:             b.config.UserDataFile,
			SourceAMI:                b.config.SourceAmi,
			IamInstanceProfile:       b.config.IamInstanceProfile,
			SubnetId:                 b.config.SubnetId,
			AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
			AvailabilityZone:         b.config.AvailabilityZone,
			BlockDevices:             b.config.BlockDevices,
115
			Tags:                     b.config.RunTags,
116
		},
117 118 119 120
		&awscommon.StepGetPassword{
			Comm:    &b.config.RunConfig.Comm,
			Timeout: b.config.WindowsPasswordTimeout,
		},
121 122
		&communicator.StepConnect{
			Config: &b.config.RunConfig.Comm,
123
			Host: awscommon.SSHHost(
124 125 126 127
				ec2conn,
				b.config.SSHPrivateIp),
			SSHConfig: awscommon.SSHConfig(
				b.config.RunConfig.Comm.SSHUsername),
128
		},
129
		&common.StepProvision{},
130
		&stepStopInstance{SpotPrice: b.config.SpotPrice},
131
		// TODO(mitchellh): verify works with spots
132
		&stepModifyInstance{},
133 134 135 136
		&awscommon.StepDeregisterAMI{
			ForceDeregister: b.config.AMIForceDeregister,
			AMIName:         b.config.AMIName,
		},
137
		&stepCreateAMI{},
138
		&awscommon.StepAMIRegionCopy{
139 140
			AccessConfig: &b.config.AccessConfig,
			Regions:      b.config.AMIRegions,
141
			Name:         b.config.AMIName,
142
		},
143 144 145 146 147
		&awscommon.StepModifyAMIAttributes{
			Description: b.config.AMIDescription,
			Users:       b.config.AMIUsers,
			Groups:      b.config.AMIGroups,
		},
148 149
		&awscommon.StepCreateTags{
			Tags: b.config.AMITags,
150
		},
151 152
	}

153
	// Run!
154 155 156 157 158 159 160 161 162
	if b.config.PackerDebug {
		b.runner = &multistep.DebugRunner{
			Steps:   steps,
			PauseFn: common.MultistepDebugFn(ui),
		}
	} else {
		b.runner = &multistep.BasicRunner{Steps: steps}
	}

163
	b.runner.Run(state)
164

165
	// If there was an error, return that
166
	if rawErr, ok := state.GetOk("error"); ok {
167 168 169
		return nil, rawErr.(error)
	}

Mitchell Hashimoto's avatar
fmt  
Mitchell Hashimoto committed
170
	// If there are no AMIs, then just return
171
	if _, ok := state.GetOk("amis"); !ok {
172
		return nil, nil
173 174
	}

175
	// Build the artifact and return it
176
	artifact := &awscommon.Artifact{
177
		Amis:           state.Get("amis").(map[string]string),
178
		BuilderIdValue: BuilderId,
Mitchell Hashimoto's avatar
fmt  
Mitchell Hashimoto committed
179
		Conn:           ec2conn,
180 181 182
	}

	return artifact, nil
183
}
184 185

func (b *Builder) Cancel() {
186 187 188 189
	if b.runner != nil {
		log.Println("Cancelling the step runner...")
		b.runner.Cancel()
	}
190
}