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