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