github.com/jerryclinesmith/packer@v0.3.7/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 CopyFiles []string `mapstructure:"copy_files"` 31 DevicePath string `mapstructure:"device_path"` 32 MountCommand string `mapstructure:"mount_command"` 33 MountPath string `mapstructure:"mount_path"` 34 SourceAmi string `mapstructure:"source_ami"` 35 UnmountCommand string `mapstructure:"unmount_command"` 36 37 tpl *packer.ConfigTemplate 38 } 39 40 type Builder struct { 41 config Config 42 runner multistep.Runner 43 } 44 45 func (b *Builder) Prepare(raws ...interface{}) error { 46 md, err := common.DecodeConfig(&b.config, raws...) 47 if err != nil { 48 return err 49 } 50 51 b.config.tpl, err = packer.NewConfigTemplate() 52 if err != nil { 53 return err 54 } 55 b.config.tpl.UserVars = b.config.PackerUserVars 56 b.config.tpl.Funcs(awscommon.TemplateFuncs) 57 58 // Defaults 59 if b.config.ChrootMounts == nil { 60 b.config.ChrootMounts = make([][]string, 0) 61 } 62 63 if b.config.CopyFiles == nil { 64 b.config.CopyFiles = make([]string, 0) 65 } 66 67 if len(b.config.ChrootMounts) == 0 { 68 b.config.ChrootMounts = [][]string{ 69 []string{"proc", "proc", "/proc"}, 70 []string{"sysfs", "sysfs", "/sys"}, 71 []string{"bind", "/dev", "/dev"}, 72 []string{"devpts", "devpts", "/dev/pts"}, 73 []string{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"}, 74 } 75 } 76 77 if len(b.config.CopyFiles) == 0 { 78 b.config.CopyFiles = []string{"/etc/resolv.conf"} 79 } 80 81 if b.config.MountCommand == "" { 82 b.config.MountCommand = "mount" 83 } 84 85 if b.config.MountPath == "" { 86 b.config.MountPath = "packer-amazon-chroot-volumes/{{.Device}}" 87 } 88 89 if b.config.UnmountCommand == "" { 90 b.config.UnmountCommand = "umount" 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 "mount_command": &b.config.MountCommand, 132 "source_ami": &b.config.SourceAmi, 133 "unmount_command": &b.config.UnmountCommand, 134 } 135 136 for n, ptr := range templates { 137 var err error 138 *ptr, err = b.config.tpl.Process(*ptr, nil) 139 if err != nil { 140 errs = packer.MultiErrorAppend( 141 errs, fmt.Errorf("Error processing %s: %s", n, err)) 142 } 143 } 144 145 if errs != nil && len(errs.Errors) > 0 { 146 return errs 147 } 148 149 log.Printf("Config: %+v", b.config) 150 return nil 151 } 152 153 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 154 if runtime.GOOS != "linux" { 155 return nil, errors.New("The amazon-chroot builder only works on Linux environments.") 156 } 157 158 region, err := b.config.Region() 159 if err != nil { 160 return nil, err 161 } 162 163 auth, err := b.config.AccessConfig.Auth() 164 if err != nil { 165 return nil, err 166 } 167 168 ec2conn := ec2.New(auth, region) 169 170 // Setup the state bag and initial state for the steps 171 state := new(multistep.BasicStateBag) 172 state.Put("config", &b.config) 173 state.Put("ec2", ec2conn) 174 state.Put("hook", hook) 175 state.Put("ui", ui) 176 177 // Build the steps 178 steps := []multistep.Step{ 179 &StepInstanceInfo{}, 180 &StepSourceAMIInfo{}, 181 &StepFlock{}, 182 &StepPrepareDevice{}, 183 &StepCreateVolume{}, 184 &StepAttachVolume{}, 185 &StepEarlyUnflock{}, 186 &StepMountDevice{}, 187 &StepMountExtra{}, 188 &StepCopyFiles{}, 189 &StepChrootProvision{}, 190 &StepEarlyCleanup{}, 191 &StepSnapshot{}, 192 &StepRegisterAMI{}, 193 &awscommon.StepAMIRegionCopy{ 194 Regions: b.config.AMIRegions, 195 }, 196 &awscommon.StepModifyAMIAttributes{ 197 Description: b.config.AMIDescription, 198 Users: b.config.AMIUsers, 199 Groups: b.config.AMIGroups, 200 }, 201 &awscommon.StepCreateTags{ 202 Tags: b.config.AMITags, 203 }, 204 } 205 206 // Run! 207 if b.config.PackerDebug { 208 b.runner = &multistep.DebugRunner{ 209 Steps: steps, 210 PauseFn: common.MultistepDebugFn(ui), 211 } 212 } else { 213 b.runner = &multistep.BasicRunner{Steps: steps} 214 } 215 216 b.runner.Run(state) 217 218 // If there was an error, return that 219 if rawErr, ok := state.GetOk("error"); ok { 220 return nil, rawErr.(error) 221 } 222 223 // If there are no AMIs, then just return 224 if _, ok := state.GetOk("amis"); !ok { 225 return nil, nil 226 } 227 228 // Build the artifact and return it 229 artifact := &awscommon.Artifact{ 230 Amis: state.Get("amis").(map[string]string), 231 BuilderIdValue: BuilderId, 232 Conn: ec2conn, 233 } 234 235 return artifact, nil 236 } 237 238 func (b *Builder) Cancel() { 239 if b.runner != nil { 240 log.Println("Cancelling the step runner...") 241 b.runner.Cancel() 242 } 243 }