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