github.com/jerryclinesmith/packer@v0.3.7/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  	b.config.tpl.Funcs(awscommon.TemplateFuncs)
    57  
    58  	// Defaults
    59  	if b.config.ChrootMounts == nil {
    60  		b.config.ChrootMounts = make([][]string, 0)
    61  	}
    62  
    63  	if b.config.CopyFiles == nil {
    64  		b.config.CopyFiles = make([]string, 0)
    65  	}
    66  
    67  	if len(b.config.ChrootMounts) == 0 {
    68  		b.config.ChrootMounts = [][]string{
    69  			[]string{"proc", "proc", "/proc"},
    70  			[]string{"sysfs", "sysfs", "/sys"},
    71  			[]string{"bind", "/dev", "/dev"},
    72  			[]string{"devpts", "devpts", "/dev/pts"},
    73  			[]string{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
    74  		}
    75  	}
    76  
    77  	if len(b.config.CopyFiles) == 0 {
    78  		b.config.CopyFiles = []string{"/etc/resolv.conf"}
    79  	}
    80  
    81  	if b.config.MountCommand == "" {
    82  		b.config.MountCommand = "mount"
    83  	}
    84  
    85  	if b.config.MountPath == "" {
    86  		b.config.MountPath = "packer-amazon-chroot-volumes/{{.Device}}"
    87  	}
    88  
    89  	if b.config.UnmountCommand == "" {
    90  		b.config.UnmountCommand = "umount"
    91  	}
    92  
    93  	// Accumulate any errors
    94  	errs := common.CheckUnusedConfig(md)
    95  	errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...)
    96  	errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.tpl)...)
    97  
    98  	for i, mounts := range b.config.ChrootMounts {
    99  		if len(mounts) != 3 {
   100  			errs = packer.MultiErrorAppend(
   101  				errs, errors.New("Each chroot_mounts entry should be three elements."))
   102  			break
   103  		}
   104  
   105  		for j, entry := range mounts {
   106  			b.config.ChrootMounts[i][j], err = b.config.tpl.Process(entry, nil)
   107  			if err != nil {
   108  				errs = packer.MultiErrorAppend(errs,
   109  					fmt.Errorf("Error processing chroot_mounts[%d][%d]: %s",
   110  						i, j, err))
   111  			}
   112  		}
   113  	}
   114  
   115  	for i, file := range b.config.CopyFiles {
   116  		var err error
   117  		b.config.CopyFiles[i], err = b.config.tpl.Process(file, nil)
   118  		if err != nil {
   119  			errs = packer.MultiErrorAppend(errs,
   120  				fmt.Errorf("Error processing copy_files[%d]: %s",
   121  					i, err))
   122  		}
   123  	}
   124  
   125  	if b.config.SourceAmi == "" {
   126  		errs = packer.MultiErrorAppend(errs, errors.New("source_ami is required."))
   127  	}
   128  
   129  	templates := map[string]*string{
   130  		"device_path":     &b.config.DevicePath,
   131  		"mount_command":   &b.config.MountCommand,
   132  		"source_ami":      &b.config.SourceAmi,
   133  		"unmount_command": &b.config.UnmountCommand,
   134  	}
   135  
   136  	for n, ptr := range templates {
   137  		var err error
   138  		*ptr, err = b.config.tpl.Process(*ptr, nil)
   139  		if err != nil {
   140  			errs = packer.MultiErrorAppend(
   141  				errs, fmt.Errorf("Error processing %s: %s", n, err))
   142  		}
   143  	}
   144  
   145  	if errs != nil && len(errs.Errors) > 0 {
   146  		return errs
   147  	}
   148  
   149  	log.Printf("Config: %+v", b.config)
   150  	return nil
   151  }
   152  
   153  func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
   154  	if runtime.GOOS != "linux" {
   155  		return nil, errors.New("The amazon-chroot builder only works on Linux environments.")
   156  	}
   157  
   158  	region, err := b.config.Region()
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	auth, err := b.config.AccessConfig.Auth()
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	ec2conn := ec2.New(auth, region)
   169  
   170  	// Setup the state bag and initial state for the steps
   171  	state := new(multistep.BasicStateBag)
   172  	state.Put("config", &b.config)
   173  	state.Put("ec2", ec2conn)
   174  	state.Put("hook", hook)
   175  	state.Put("ui", ui)
   176  
   177  	// Build the steps
   178  	steps := []multistep.Step{
   179  		&StepInstanceInfo{},
   180  		&StepSourceAMIInfo{},
   181  		&StepFlock{},
   182  		&StepPrepareDevice{},
   183  		&StepCreateVolume{},
   184  		&StepAttachVolume{},
   185  		&StepEarlyUnflock{},
   186  		&StepMountDevice{},
   187  		&StepMountExtra{},
   188  		&StepCopyFiles{},
   189  		&StepChrootProvision{},
   190  		&StepEarlyCleanup{},
   191  		&StepSnapshot{},
   192  		&StepRegisterAMI{},
   193  		&awscommon.StepAMIRegionCopy{
   194  			Regions: b.config.AMIRegions,
   195  		},
   196  		&awscommon.StepModifyAMIAttributes{
   197  			Description: b.config.AMIDescription,
   198  			Users:       b.config.AMIUsers,
   199  			Groups:      b.config.AMIGroups,
   200  		},
   201  		&awscommon.StepCreateTags{
   202  			Tags: b.config.AMITags,
   203  		},
   204  	}
   205  
   206  	// Run!
   207  	if b.config.PackerDebug {
   208  		b.runner = &multistep.DebugRunner{
   209  			Steps:   steps,
   210  			PauseFn: common.MultistepDebugFn(ui),
   211  		}
   212  	} else {
   213  		b.runner = &multistep.BasicRunner{Steps: steps}
   214  	}
   215  
   216  	b.runner.Run(state)
   217  
   218  	// If there was an error, return that
   219  	if rawErr, ok := state.GetOk("error"); ok {
   220  		return nil, rawErr.(error)
   221  	}
   222  
   223  	// If there are no AMIs, then just return
   224  	if _, ok := state.GetOk("amis"); !ok {
   225  		return nil, nil
   226  	}
   227  
   228  	// Build the artifact and return it
   229  	artifact := &awscommon.Artifact{
   230  		Amis:           state.Get("amis").(map[string]string),
   231  		BuilderIdValue: BuilderId,
   232  		Conn:           ec2conn,
   233  	}
   234  
   235  	return artifact, nil
   236  }
   237  
   238  func (b *Builder) Cancel() {
   239  	if b.runner != nil {
   240  		log.Println("Cancelling the step runner...")
   241  		b.runner.Cancel()
   242  	}
   243  }