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