github.com/phobos182/packer@v0.2.3-0.20130819023704-c84d2aeffc68/builder/amazon/chroot/builder.go (about)

     1  // The chroot package is able to create an Amazon AMI without requiring
     2  // the launch of a new instance for every build. It does this by attaching
     3  // and mounting the root volume of another AMI and chrooting into that
     4  // directory. It then creates an AMI from that attached drive.
     5  package chroot
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"github.com/mitchellh/goamz/ec2"
    11  	"github.com/mitchellh/multistep"
    12  	awscommon "github.com/mitchellh/packer/builder/amazon/common"
    13  	"github.com/mitchellh/packer/common"
    14  	"github.com/mitchellh/packer/packer"
    15  	"log"
    16  	"runtime"
    17  )
    18  
    19  // The unique ID for this builder
    20  const BuilderId = "mitchellh.amazon.chroot"
    21  
    22  // Config is the configuration that is chained through the steps and
    23  // settable from the template.
    24  type Config struct {
    25  	common.PackerConfig    `mapstructure:",squash"`
    26  	awscommon.AccessConfig `mapstructure:",squash"`
    27  	awscommon.AMIConfig    `mapstructure:",squash"`
    28  
    29  	ChrootMounts   [][]string `mapstructure:"chroot_mounts"`
    30  	CopyFiles      []string   `mapstructure:"copy_files"`
    31  	DevicePath     string     `mapstructure:"device_path"`
    32  	MountCommand   string     `mapstructure:"mount_command"`
    33  	MountPath      string     `mapstructure:"mount_path"`
    34  	SourceAmi      string     `mapstructure:"source_ami"`
    35  	UnmountCommand string     `mapstructure:"unmount_command"`
    36  
    37  	tpl *packer.ConfigTemplate
    38  }
    39  
    40  type Builder struct {
    41  	config Config
    42  	runner multistep.Runner
    43  }
    44  
    45  func (b *Builder) Prepare(raws ...interface{}) error {
    46  	md, err := common.DecodeConfig(&b.config, raws...)
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	b.config.tpl, err = packer.NewConfigTemplate()
    52  	if err != nil {
    53  		return err
    54  	}
    55  	b.config.tpl.UserVars = b.config.PackerUserVars
    56  
    57  	// Defaults
    58  	if b.config.ChrootMounts == nil {
    59  		b.config.ChrootMounts = make([][]string, 0)
    60  	}
    61  
    62  	if b.config.CopyFiles == nil {
    63  		b.config.CopyFiles = make([]string, 0)
    64  	}
    65  
    66  	if len(b.config.ChrootMounts) == 0 {
    67  		b.config.ChrootMounts = [][]string{
    68  			[]string{"proc", "proc", "/proc"},
    69  			[]string{"sysfs", "sysfs", "/sys"},
    70  			[]string{"bind", "/dev", "/dev"},
    71  			[]string{"devpts", "devpts", "/dev/pts"},
    72  			[]string{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
    73  		}
    74  	}
    75  
    76  	if len(b.config.CopyFiles) == 0 {
    77  		b.config.CopyFiles = []string{"/etc/resolv.conf"}
    78  	}
    79  
    80  	if b.config.MountCommand == "" {
    81  		b.config.MountCommand = "mount"
    82  	}
    83  
    84  	if b.config.MountPath == "" {
    85  		b.config.MountPath = "packer-amazon-chroot-volumes/{{.Device}}"
    86  	}
    87  
    88  	if b.config.UnmountCommand == "" {
    89  		b.config.UnmountCommand = "umount"
    90  	}
    91  
    92  	// Accumulate any errors
    93  	errs := common.CheckUnusedConfig(md)
    94  	errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...)
    95  	errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.tpl)...)
    96  
    97  	for i, mounts := range b.config.ChrootMounts {
    98  		if len(mounts) != 3 {
    99  			errs = packer.MultiErrorAppend(
   100  				errs, errors.New("Each chroot_mounts entry should be three elements."))
   101  			break
   102  		}
   103  
   104  		for j, entry := range mounts {
   105  			b.config.ChrootMounts[i][j], err = b.config.tpl.Process(entry, nil)
   106  			if err != nil {
   107  				errs = packer.MultiErrorAppend(errs,
   108  					fmt.Errorf("Error processing chroot_mounts[%d][%d]: %s",
   109  						i, j, err))
   110  			}
   111  		}
   112  	}
   113  
   114  	for i, file := range b.config.CopyFiles {
   115  		var err error
   116  		b.config.CopyFiles[i], err = b.config.tpl.Process(file, nil)
   117  		if err != nil {
   118  			errs = packer.MultiErrorAppend(errs,
   119  				fmt.Errorf("Error processing copy_files[%d]: %s",
   120  					i, err))
   121  		}
   122  	}
   123  
   124  	if b.config.SourceAmi == "" {
   125  		errs = packer.MultiErrorAppend(errs, errors.New("source_ami is required."))
   126  	}
   127  
   128  	templates := map[string]*string{
   129  		"device_path":     &b.config.DevicePath,
   130  		"mount_command":   &b.config.MountCommand,
   131  		"source_ami":      &b.config.SourceAmi,
   132  		"unmount_command": &b.config.UnmountCommand,
   133  	}
   134  
   135  	for n, ptr := range templates {
   136  		var err error
   137  		*ptr, err = b.config.tpl.Process(*ptr, nil)
   138  		if err != nil {
   139  			errs = packer.MultiErrorAppend(
   140  				errs, fmt.Errorf("Error processing %s: %s", n, err))
   141  		}
   142  	}
   143  
   144  	if errs != nil && len(errs.Errors) > 0 {
   145  		return errs
   146  	}
   147  
   148  	log.Printf("Config: %+v", b.config)
   149  	return nil
   150  }
   151  
   152  func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
   153  	if runtime.GOOS != "linux" {
   154  		return nil, errors.New("The amazon-chroot builder only works on Linux environments.")
   155  	}
   156  
   157  	region, err := b.config.Region()
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	auth, err := b.config.AccessConfig.Auth()
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	ec2conn := ec2.New(auth, region)
   168  
   169  	// Setup the state bag and initial state for the steps
   170  	state := make(map[string]interface{})
   171  	state["config"] = &b.config
   172  	state["ec2"] = ec2conn
   173  	state["hook"] = hook
   174  	state["ui"] = ui
   175  
   176  	// Build the steps
   177  	steps := []multistep.Step{
   178  		&StepInstanceInfo{},
   179  		&StepSourceAMIInfo{},
   180  		&StepFlock{},
   181  		&StepPrepareDevice{},
   182  		&StepCreateVolume{},
   183  		&StepAttachVolume{},
   184  		&StepEarlyUnflock{},
   185  		&StepMountDevice{},
   186  		&StepMountExtra{},
   187  		&StepCopyFiles{},
   188  		&StepChrootProvision{},
   189  		&StepEarlyCleanup{},
   190  		&StepSnapshot{},
   191  		&StepRegisterAMI{},
   192  		&awscommon.StepModifyAMIAttributes{
   193  			Description: b.config.AMIDescription,
   194  			Users:       b.config.AMIUsers,
   195  			Groups:      b.config.AMIGroups,
   196  		},
   197  	}
   198  
   199  	// Run!
   200  	if b.config.PackerDebug {
   201  		b.runner = &multistep.DebugRunner{
   202  			Steps:   steps,
   203  			PauseFn: common.MultistepDebugFn(ui),
   204  		}
   205  	} else {
   206  		b.runner = &multistep.BasicRunner{Steps: steps}
   207  	}
   208  
   209  	b.runner.Run(state)
   210  
   211  	// If there was an error, return that
   212  	if rawErr, ok := state["error"]; ok {
   213  		return nil, rawErr.(error)
   214  	}
   215  
   216  	// If there are no AMIs, then just return
   217  	if _, ok := state["amis"]; !ok {
   218  		return nil, nil
   219  	}
   220  
   221  	// Build the artifact and return it
   222  	artifact := &awscommon.Artifact{
   223  		Amis:           state["amis"].(map[string]string),
   224  		BuilderIdValue: BuilderId,
   225  		Conn:           ec2conn,
   226  	}
   227  
   228  	return artifact, nil
   229  }
   230  
   231  func (b *Builder) Cancel() {
   232  	if b.runner != nil {
   233  		log.Println("Cancelling the step runner...")
   234  		b.runner.Cancel()
   235  	}
   236  }