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 }