github.com/ttysteale/packer@v0.8.2-0.20150708160520-e5f8ea386ed8/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  	"log"
    10  	"runtime"
    11  
    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/config"
    17  	"github.com/mitchellh/packer/packer"
    18  	"github.com/mitchellh/packer/template/interpolate"
    19  )
    20  
    21  // The unique ID for this builder
    22  const BuilderId = "mitchellh.amazon.chroot"
    23  
    24  // Config is the configuration that is chained through the steps and
    25  // settable from the template.
    26  type Config struct {
    27  	common.PackerConfig    `mapstructure:",squash"`
    28  	awscommon.AccessConfig `mapstructure:",squash"`
    29  	awscommon.AMIConfig    `mapstructure:",squash"`
    30  
    31  	ChrootMounts   [][]string `mapstructure:"chroot_mounts"`
    32  	CommandWrapper string     `mapstructure:"command_wrapper"`
    33  	CopyFiles      []string   `mapstructure:"copy_files"`
    34  	DevicePath     string     `mapstructure:"device_path"`
    35  	MountPath      string     `mapstructure:"mount_path"`
    36  	SourceAmi      string     `mapstructure:"source_ami"`
    37  	RootVolumeSize int64      `mapstructure:"root_volume_size"`
    38  	MountOptions   []string   `mapstructure:"mount_options"`
    39  
    40  	ctx interpolate.Context
    41  }
    42  
    43  type wrappedCommandTemplate struct {
    44  	Command string
    45  }
    46  
    47  type Builder struct {
    48  	config Config
    49  	runner multistep.Runner
    50  }
    51  
    52  func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
    53  	b.config.ctx.Funcs = awscommon.TemplateFuncs
    54  	err := config.Decode(&b.config, &config.DecodeOpts{
    55  		Interpolate:        true,
    56  		InterpolateContext: &b.config.ctx,
    57  		InterpolateFilter: &interpolate.RenderFilter{
    58  			Exclude: []string{
    59  				"command_wrapper",
    60  				"mount_path",
    61  			},
    62  		},
    63  	}, raws...)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	// Defaults
    69  	if b.config.ChrootMounts == nil {
    70  		b.config.ChrootMounts = make([][]string, 0)
    71  	}
    72  
    73  	if b.config.CopyFiles == nil {
    74  		b.config.CopyFiles = make([]string, 0)
    75  	}
    76  
    77  	if len(b.config.ChrootMounts) == 0 {
    78  		b.config.ChrootMounts = [][]string{
    79  			[]string{"proc", "proc", "/proc"},
    80  			[]string{"sysfs", "sysfs", "/sys"},
    81  			[]string{"bind", "/dev", "/dev"},
    82  			[]string{"devpts", "devpts", "/dev/pts"},
    83  			[]string{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
    84  		}
    85  	}
    86  
    87  	if len(b.config.CopyFiles) == 0 {
    88  		b.config.CopyFiles = []string{"/etc/resolv.conf"}
    89  	}
    90  
    91  	if b.config.CommandWrapper == "" {
    92  		b.config.CommandWrapper = "{{.Command}}"
    93  	}
    94  
    95  	if b.config.MountPath == "" {
    96  		b.config.MountPath = "/mnt/packer-amazon-chroot-volumes/{{.Device}}"
    97  	}
    98  
    99  	// Accumulate any errors
   100  	var errs *packer.MultiError
   101  	errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
   102  	errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...)
   103  
   104  	for _, mounts := range b.config.ChrootMounts {
   105  		if len(mounts) != 3 {
   106  			errs = packer.MultiErrorAppend(
   107  				errs, errors.New("Each chroot_mounts entry should be three elements."))
   108  			break
   109  		}
   110  	}
   111  
   112  	if b.config.SourceAmi == "" {
   113  		errs = packer.MultiErrorAppend(errs, errors.New("source_ami is required."))
   114  	}
   115  
   116  	if errs != nil && len(errs.Errors) > 0 {
   117  		return nil, errs
   118  	}
   119  
   120  	log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
   121  	return nil, nil
   122  }
   123  
   124  func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
   125  	if runtime.GOOS != "linux" {
   126  		return nil, errors.New("The amazon-chroot builder only works on Linux environments.")
   127  	}
   128  
   129  	config, err := b.config.Config()
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	ec2conn := ec2.New(config)
   135  
   136  	wrappedCommand := func(command string) (string, error) {
   137  		ctx := b.config.ctx
   138  		ctx.Data = &wrappedCommandTemplate{Command: command}
   139  		return interpolate.Render(b.config.CommandWrapper, &ctx)
   140  	}
   141  
   142  	// Setup the state bag and initial state for the steps
   143  	state := new(multistep.BasicStateBag)
   144  	state.Put("config", &b.config)
   145  	state.Put("ec2", ec2conn)
   146  	state.Put("hook", hook)
   147  	state.Put("ui", ui)
   148  	state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
   149  
   150  	// Build the steps
   151  	steps := []multistep.Step{
   152  		&awscommon.StepPreValidate{
   153  			DestAmiName:     b.config.AMIName,
   154  			ForceDeregister: b.config.AMIForceDeregister,
   155  		},
   156  		&StepInstanceInfo{},
   157  		&awscommon.StepSourceAMIInfo{
   158  			SourceAmi:          b.config.SourceAmi,
   159  			EnhancedNetworking: b.config.AMIEnhancedNetworking,
   160  		},
   161  		&StepCheckRootDevice{},
   162  		&StepFlock{},
   163  		&StepPrepareDevice{},
   164  		&StepCreateVolume{
   165  			RootVolumeSize: b.config.RootVolumeSize,
   166  		},
   167  		&StepAttachVolume{},
   168  		&StepEarlyUnflock{},
   169  		&StepMountDevice{
   170  			MountOptions: b.config.MountOptions,
   171  		},
   172  		&StepMountExtra{},
   173  		&StepCopyFiles{},
   174  		&StepChrootProvision{},
   175  		&StepEarlyCleanup{},
   176  		&StepSnapshot{},
   177  		&awscommon.StepDeregisterAMI{
   178  			ForceDeregister: b.config.AMIForceDeregister,
   179  			AMIName:         b.config.AMIName,
   180  		},
   181  		&StepRegisterAMI{
   182  			RootVolumeSize: b.config.RootVolumeSize,
   183  		},
   184  		&awscommon.StepAMIRegionCopy{
   185  			AccessConfig: &b.config.AccessConfig,
   186  			Regions:      b.config.AMIRegions,
   187  			Name:         b.config.AMIName,
   188  		},
   189  		&awscommon.StepModifyAMIAttributes{
   190  			Description: b.config.AMIDescription,
   191  			Users:       b.config.AMIUsers,
   192  			Groups:      b.config.AMIGroups,
   193  		},
   194  		&awscommon.StepCreateTags{
   195  			Tags: b.config.AMITags,
   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.GetOk("error"); ok {
   213  		return nil, rawErr.(error)
   214  	}
   215  
   216  	// If there are no AMIs, then just return
   217  	if _, ok := state.GetOk("amis"); !ok {
   218  		return nil, nil
   219  	}
   220  
   221  	// Build the artifact and return it
   222  	artifact := &awscommon.Artifact{
   223  		Amis:           state.Get("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  }