builder/amazon/chroot: boilerplate

This commit is contained in:
Mitchell Hashimoto 2013-07-29 16:42:35 -07:00
parent 8fb4e1ab88
commit b645586d58
11 changed files with 200 additions and 37 deletions

View File

@ -0,0 +1,111 @@
// The chroot package is able to create an Amazon AMI without requiring
// the launch of a new instance for every build. It does this by attaching
// and mounting the root volume of another AMI and chrooting into that
// directory. It then creates an AMI from that attached drive.
package chroot
import (
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/builder/common"
"github.com/mitchellh/packer/packer"
"log"
)
// The unique ID for this builder
const BuilderId = "mitchellh.amazon.chroot"
// Config is the configuration that is chained through the steps and
// settable from the template.
type Config struct {
common.PackerConfig `mapstructure:",squash"`
awscommon.AccessConfig `mapstructure:",squash"`
}
type Builder struct {
config Config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) error {
md, err := common.DecodeConfig(&b.config, raws...)
if err != nil {
return err
}
// Defaults
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare()...)
if errs != nil && len(errs.Errors) > 0 {
return 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, err := b.config.Region()
if err != nil {
return nil, err
}
auth, err := b.config.AccessConfig.Auth()
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{}
// 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 := &awscommon.Artifact{
Amis: state["amis"].(map[string]string),
BuilderIdValue: BuilderId,
Conn: ec2conn,
}
return artifact, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}

View File

@ -0,0 +1,18 @@
package chroot
import (
"github.com/mitchellh/packer/packer"
"testing"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}

View File

@ -1,13 +1,17 @@
package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"strings"
"unicode"
)
// AccessConfig is for common configuration related to AWS access
type AccessConfig struct {
AccessKey string `mapstructure:"access_key"`
SecretKey string `mapstructure:"secret_key"`
RawRegion string `mapstructure:"region"`
}
// Auth returns a valid aws.Auth object for access to AWS services, or
@ -16,6 +20,28 @@ func (c *AccessConfig) Auth() (aws.Auth, error) {
return aws.GetAuth(c.AccessKey, c.SecretKey)
}
// Region returns the aws.Region object for access to AWS services, requesting
// the region from the instance metadata if possible.
func (c *AccessConfig) Region() (aws.Region, error) {
if c.RawRegion != "" {
return aws.Regions[c.RawRegion], nil
}
md, err := aws.GetMetaData("placement/availability-zone")
if err != nil {
return aws.Region{}, err
}
region := strings.TrimRightFunc(string(md), unicode.IsLetter)
return aws.Regions[region], nil
}
func (c *AccessConfig) Prepare() []error {
if c.RawRegion != "" {
if _, ok := aws.Regions[c.RawRegion]; !ok {
return []error{fmt.Errorf("Unknown region: %s", c.RawRegion)}
}
}
return nil
}

View File

@ -0,0 +1,27 @@
package common
import (
"testing"
)
func testAccessConfig() *AccessConfig {
return &AccessConfig{}
}
func TestAccessConfigPrepare_Region(t *testing.T) {
c := testAccessConfig()
c.RawRegion = ""
if err := c.Prepare(); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.RawRegion = "us-east-12"
if err := c.Prepare(); err == nil {
t.Fatal("should have error")
}
c.RawRegion = "us-east-1"
if err := c.Prepare(); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
}

View File

@ -3,14 +3,12 @@ package common
import (
"errors"
"fmt"
"github.com/mitchellh/goamz/aws"
"time"
)
// RunConfig contains configuration for running an instance from a source
// AMI and details on how to access that launched image.
type RunConfig struct {
Region string
SourceAmi string `mapstructure:"source_ami"`
InstanceType string `mapstructure:"instance_type"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
@ -45,12 +43,6 @@ func (c *RunConfig) Prepare() []error {
errs = append(errs, errors.New("An instance_type must be specified"))
}
if c.Region == "" {
errs = append(errs, errors.New("A region must be specified"))
} else if _, ok := aws.Regions[c.Region]; !ok {
errs = append(errs, fmt.Errorf("Unknown region: %s", c.Region))
}
if c.SSHUsername == "" {
errs = append(errs, errors.New("An ssh_username must be specified"))
}

View File

@ -16,7 +16,6 @@ func init() {
func testConfig() *RunConfig {
return &RunConfig{
Region: "us-east-1",
SourceAmi: "abcd",
InstanceType: "m1.small",
SSHUsername: "root",
@ -39,24 +38,6 @@ func TestRunConfigPrepare_InstanceType(t *testing.T) {
}
}
func TestRunConfigPrepare_Region(t *testing.T) {
c := testConfig()
c.Region = ""
if err := c.Prepare(); len(err) != 1 {
t.Fatalf("err: %s", err)
}
c.Region = "us-east-12"
if err := c.Prepare(); len(err) != 1 {
t.Fatalf("err: %s", err)
}
c.Region = "us-east-1"
if err := c.Prepare(); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SourceAmi(t *testing.T) {
c := testConfig()
c.SourceAmi = ""

View File

@ -8,7 +8,6 @@ package ebs
import (
"errors"
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
@ -67,9 +66,9 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
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")
region, err := b.config.Region()
if err != nil {
return nil, err
}
auth, err := b.config.AccessConfig.Auth()

View File

@ -52,7 +52,7 @@ func (s *stepCreateAMI) Run(state map[string]interface{}) multistep.StepAction {
// Set the AMI ID in the state
ui.Say(fmt.Sprintf("AMI: %s", createResp.ImageId))
amis := make(map[string]string)
amis[config.Region] = createResp.ImageId
amis[ec2conn.Region.Name] = createResp.ImageId
state["amis"] = amis
// Wait for the image to become ready

View File

@ -5,7 +5,6 @@ package instance
import (
"errors"
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
@ -134,9 +133,9 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
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")
region, err := b.config.Region()
if err != nil {
return nil, err
}
auth, err := b.config.AccessConfig.Auth()

View File

@ -50,7 +50,7 @@ func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction
// Set the AMI ID in the state
ui.Say(fmt.Sprintf("AMI: %s", registerResp.ImageId))
amis := make(map[string]string)
amis[config.Region] = registerResp.ImageId
amis[ec2conn.Region.Name] = registerResp.ImageId
state["amis"] = amis
// Wait for the image to become ready

View File

@ -0,0 +1,10 @@
package main
import (
"github.com/mitchellh/packer/builder/amazon/chroot"
"github.com/mitchellh/packer/packer/plugin"
)
func main() {
plugin.ServeBuilder(new(chroot.Builder))
}