github.phpd.cn/hashicorp/packer@v1.3.2/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  	awscommon "github.com/hashicorp/packer/builder/amazon/common"
    14  	"github.com/hashicorp/packer/common"
    15  	"github.com/hashicorp/packer/helper/config"
    16  	"github.com/hashicorp/packer/helper/multistep"
    17  	"github.com/hashicorp/packer/packer"
    18  	"github.com/hashicorp/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.AMIBlockDevices `mapstructure:",squash"`
    29  	awscommon.AMIConfig       `mapstructure:",squash"`
    30  	awscommon.AccessConfig    `mapstructure:",squash"`
    31  
    32  	ChrootMounts      [][]string                 `mapstructure:"chroot_mounts"`
    33  	CommandWrapper    string                     `mapstructure:"command_wrapper"`
    34  	CopyFiles         []string                   `mapstructure:"copy_files"`
    35  	DevicePath        string                     `mapstructure:"device_path"`
    36  	NVMEDevicePath    string                     `mapstructure:"nvme_device_path"`
    37  	FromScratch       bool                       `mapstructure:"from_scratch"`
    38  	MountOptions      []string                   `mapstructure:"mount_options"`
    39  	MountPartition    string                     `mapstructure:"mount_partition"`
    40  	MountPath         string                     `mapstructure:"mount_path"`
    41  	PostMountCommands []string                   `mapstructure:"post_mount_commands"`
    42  	PreMountCommands  []string                   `mapstructure:"pre_mount_commands"`
    43  	RootDeviceName    string                     `mapstructure:"root_device_name"`
    44  	RootVolumeSize    int64                      `mapstructure:"root_volume_size"`
    45  	RootVolumeType    string                     `mapstructure:"root_volume_type"`
    46  	SourceAmi         string                     `mapstructure:"source_ami"`
    47  	SourceAmiFilter   awscommon.AmiFilterOptions `mapstructure:"source_ami_filter"`
    48  	RootVolumeTags    awscommon.TagMap           `mapstructure:"root_volume_tags"`
    49  
    50  	ctx interpolate.Context
    51  }
    52  
    53  type wrappedCommandTemplate struct {
    54  	Command string
    55  }
    56  
    57  type Builder struct {
    58  	config Config
    59  	runner multistep.Runner
    60  }
    61  
    62  func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
    63  	b.config.ctx.Funcs = awscommon.TemplateFuncs
    64  	err := config.Decode(&b.config, &config.DecodeOpts{
    65  		Interpolate:        true,
    66  		InterpolateContext: &b.config.ctx,
    67  		InterpolateFilter: &interpolate.RenderFilter{
    68  			Exclude: []string{
    69  				"ami_description",
    70  				"snapshot_tags",
    71  				"tags",
    72  				"root_volume_tags",
    73  				"command_wrapper",
    74  				"post_mount_commands",
    75  				"pre_mount_commands",
    76  				"mount_path",
    77  			},
    78  		},
    79  	}, raws...)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	if b.config.PackerConfig.PackerForce {
    85  		b.config.AMIForceDeregister = true
    86  	}
    87  
    88  	// Defaults
    89  	if b.config.ChrootMounts == nil {
    90  		b.config.ChrootMounts = make([][]string, 0)
    91  	}
    92  
    93  	if len(b.config.ChrootMounts) == 0 {
    94  		b.config.ChrootMounts = [][]string{
    95  			{"proc", "proc", "/proc"},
    96  			{"sysfs", "sysfs", "/sys"},
    97  			{"bind", "/dev", "/dev"},
    98  			{"devpts", "devpts", "/dev/pts"},
    99  			{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
   100  		}
   101  	}
   102  
   103  	// set default copy file if we're not giving our own
   104  	if b.config.CopyFiles == nil {
   105  		b.config.CopyFiles = make([]string, 0)
   106  		if !b.config.FromScratch {
   107  			b.config.CopyFiles = []string{"/etc/resolv.conf"}
   108  		}
   109  	}
   110  
   111  	if b.config.CommandWrapper == "" {
   112  		b.config.CommandWrapper = "{{.Command}}"
   113  	}
   114  
   115  	if b.config.MountPath == "" {
   116  		b.config.MountPath = "/mnt/packer-amazon-chroot-volumes/{{.Device}}"
   117  	}
   118  
   119  	if b.config.MountPartition == "" {
   120  		b.config.MountPartition = "1"
   121  	}
   122  
   123  	// Accumulate any errors or warnings
   124  	var errs *packer.MultiError
   125  	var warns []string
   126  
   127  	errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
   128  	errs = packer.MultiErrorAppend(errs,
   129  		b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
   130  
   131  	for _, mounts := range b.config.ChrootMounts {
   132  		if len(mounts) != 3 {
   133  			errs = packer.MultiErrorAppend(
   134  				errs, errors.New("Each chroot_mounts entry should be three elements."))
   135  			break
   136  		}
   137  	}
   138  
   139  	if b.config.FromScratch {
   140  		if b.config.SourceAmi != "" || !b.config.SourceAmiFilter.Empty() {
   141  			warns = append(warns, "source_ami and source_ami_filter are unused when from_scratch is true")
   142  		}
   143  		if b.config.RootVolumeSize == 0 {
   144  			errs = packer.MultiErrorAppend(
   145  				errs, errors.New("root_volume_size is required with from_scratch."))
   146  		}
   147  		if len(b.config.PreMountCommands) == 0 {
   148  			errs = packer.MultiErrorAppend(
   149  				errs, errors.New("pre_mount_commands is required with from_scratch."))
   150  		}
   151  		if b.config.AMIVirtType == "" {
   152  			errs = packer.MultiErrorAppend(
   153  				errs, errors.New("ami_virtualization_type is required with from_scratch."))
   154  		}
   155  		if b.config.RootDeviceName == "" {
   156  			errs = packer.MultiErrorAppend(
   157  				errs, errors.New("root_device_name is required with from_scratch."))
   158  		}
   159  		if len(b.config.AMIMappings) == 0 {
   160  			errs = packer.MultiErrorAppend(
   161  				errs, errors.New("ami_block_device_mappings is required with from_scratch."))
   162  		}
   163  	} else {
   164  		if b.config.SourceAmi == "" && b.config.SourceAmiFilter.Empty() {
   165  			errs = packer.MultiErrorAppend(
   166  				errs, errors.New("source_ami or source_ami_filter is required."))
   167  		}
   168  		if len(b.config.AMIMappings) != 0 {
   169  			warns = append(warns, "ami_block_device_mappings are unused when from_scratch is false")
   170  		}
   171  		if b.config.RootDeviceName != "" {
   172  			warns = append(warns, "root_device_name is unused when from_scratch is false")
   173  		}
   174  	}
   175  
   176  	if errs != nil && len(errs.Errors) > 0 {
   177  		return warns, errs
   178  	}
   179  
   180  	packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
   181  	return warns, nil
   182  }
   183  
   184  func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
   185  	if runtime.GOOS != "linux" {
   186  		return nil, errors.New("The amazon-chroot builder only works on Linux environments.")
   187  	}
   188  
   189  	session, err := b.config.Session()
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	ec2conn := ec2.New(session)
   194  
   195  	wrappedCommand := func(command string) (string, error) {
   196  		ctx := b.config.ctx
   197  		ctx.Data = &wrappedCommandTemplate{Command: command}
   198  		return interpolate.Render(b.config.CommandWrapper, &ctx)
   199  	}
   200  
   201  	// Setup the state bag and initial state for the steps
   202  	state := new(multistep.BasicStateBag)
   203  	state.Put("config", &b.config)
   204  	state.Put("ec2", ec2conn)
   205  	state.Put("awsSession", session)
   206  	state.Put("hook", hook)
   207  	state.Put("ui", ui)
   208  	state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
   209  
   210  	// Build the steps
   211  	steps := []multistep.Step{
   212  		&awscommon.StepPreValidate{
   213  			DestAmiName:     b.config.AMIName,
   214  			ForceDeregister: b.config.AMIForceDeregister,
   215  		},
   216  		&StepInstanceInfo{},
   217  	}
   218  
   219  	if !b.config.FromScratch {
   220  		steps = append(steps,
   221  			&awscommon.StepSourceAMIInfo{
   222  				SourceAmi:                b.config.SourceAmi,
   223  				EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
   224  				EnableAMIENASupport:      b.config.AMIENASupport,
   225  				AmiFilters:               b.config.SourceAmiFilter,
   226  				AMIVirtType:              b.config.AMIVirtType,
   227  			},
   228  			&StepCheckRootDevice{},
   229  		)
   230  	}
   231  
   232  	steps = append(steps,
   233  		&StepFlock{},
   234  		&StepPrepareDevice{},
   235  		&StepCreateVolume{
   236  			RootVolumeType: b.config.RootVolumeType,
   237  			RootVolumeSize: b.config.RootVolumeSize,
   238  			RootVolumeTags: b.config.RootVolumeTags,
   239  			Ctx:            b.config.ctx,
   240  		},
   241  		&StepAttachVolume{},
   242  		&StepEarlyUnflock{},
   243  		&StepPreMountCommands{
   244  			Commands: b.config.PreMountCommands,
   245  		},
   246  		&StepMountDevice{
   247  			MountOptions:   b.config.MountOptions,
   248  			MountPartition: b.config.MountPartition,
   249  		},
   250  		&StepPostMountCommands{
   251  			Commands: b.config.PostMountCommands,
   252  		},
   253  		&StepMountExtra{},
   254  		&StepCopyFiles{},
   255  		&StepChrootProvision{},
   256  		&StepEarlyCleanup{},
   257  		&StepSnapshot{},
   258  		&awscommon.StepDeregisterAMI{
   259  			AccessConfig:        &b.config.AccessConfig,
   260  			ForceDeregister:     b.config.AMIForceDeregister,
   261  			ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot,
   262  			AMIName:             b.config.AMIName,
   263  			Regions:             b.config.AMIRegions,
   264  		},
   265  		&StepRegisterAMI{
   266  			RootVolumeSize:           b.config.RootVolumeSize,
   267  			EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
   268  			EnableAMIENASupport:      b.config.AMIENASupport,
   269  		},
   270  		&awscommon.StepCreateEncryptedAMICopy{
   271  			KeyID:             b.config.AMIKmsKeyId,
   272  			EncryptBootVolume: b.config.AMIEncryptBootVolume,
   273  			Name:              b.config.AMIName,
   274  			AMIMappings:       b.config.AMIBlockDevices.AMIMappings,
   275  		},
   276  		&awscommon.StepAMIRegionCopy{
   277  			AccessConfig:      &b.config.AccessConfig,
   278  			Regions:           b.config.AMIRegions,
   279  			RegionKeyIds:      b.config.AMIRegionKMSKeyIDs,
   280  			EncryptBootVolume: b.config.AMIEncryptBootVolume,
   281  			Name:              b.config.AMIName,
   282  		},
   283  		&awscommon.StepModifyAMIAttributes{
   284  			Description:    b.config.AMIDescription,
   285  			Users:          b.config.AMIUsers,
   286  			Groups:         b.config.AMIGroups,
   287  			ProductCodes:   b.config.AMIProductCodes,
   288  			SnapshotUsers:  b.config.SnapshotUsers,
   289  			SnapshotGroups: b.config.SnapshotGroups,
   290  			Ctx:            b.config.ctx,
   291  		},
   292  		&awscommon.StepCreateTags{
   293  			Tags:         b.config.AMITags,
   294  			SnapshotTags: b.config.SnapshotTags,
   295  			Ctx:          b.config.ctx,
   296  		},
   297  	)
   298  
   299  	// Run!
   300  	b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
   301  	b.runner.Run(state)
   302  
   303  	// If there was an error, return that
   304  	if rawErr, ok := state.GetOk("error"); ok {
   305  		return nil, rawErr.(error)
   306  	}
   307  
   308  	// If there are no AMIs, then just return
   309  	if _, ok := state.GetOk("amis"); !ok {
   310  		return nil, nil
   311  	}
   312  
   313  	// Build the artifact and return it
   314  	artifact := &awscommon.Artifact{
   315  		Amis:           state.Get("amis").(map[string]string),
   316  		BuilderIdValue: BuilderId,
   317  		Session:        session,
   318  	}
   319  
   320  	return artifact, nil
   321  }
   322  
   323  func (b *Builder) Cancel() {
   324  	if b.runner != nil {
   325  		log.Println("Cancelling the step runner...")
   326  		b.runner.Cancel()
   327  	}
   328  }