github.com/phobos182/packer@v0.2.3-0.20130819023704-c84d2aeffc68/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 57 // Defaults 58 if b.config.ChrootMounts == nil { 59 b.config.ChrootMounts = make([][]string, 0) 60 } 61 62 if b.config.CopyFiles == nil { 63 b.config.CopyFiles = make([]string, 0) 64 } 65 66 if len(b.config.ChrootMounts) == 0 { 67 b.config.ChrootMounts = [][]string{ 68 []string{"proc", "proc", "/proc"}, 69 []string{"sysfs", "sysfs", "/sys"}, 70 []string{"bind", "/dev", "/dev"}, 71 []string{"devpts", "devpts", "/dev/pts"}, 72 []string{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"}, 73 } 74 } 75 76 if len(b.config.CopyFiles) == 0 { 77 b.config.CopyFiles = []string{"/etc/resolv.conf"} 78 } 79 80 if b.config.MountCommand == "" { 81 b.config.MountCommand = "mount" 82 } 83 84 if b.config.MountPath == "" { 85 b.config.MountPath = "packer-amazon-chroot-volumes/{{.Device}}" 86 } 87 88 if b.config.UnmountCommand == "" { 89 b.config.UnmountCommand = "umount" 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 "mount_command": &b.config.MountCommand, 131 "source_ami": &b.config.SourceAmi, 132 "unmount_command": &b.config.UnmountCommand, 133 } 134 135 for n, ptr := range templates { 136 var err error 137 *ptr, err = b.config.tpl.Process(*ptr, nil) 138 if err != nil { 139 errs = packer.MultiErrorAppend( 140 errs, fmt.Errorf("Error processing %s: %s", n, err)) 141 } 142 } 143 144 if errs != nil && len(errs.Errors) > 0 { 145 return errs 146 } 147 148 log.Printf("Config: %+v", b.config) 149 return nil 150 } 151 152 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 153 if runtime.GOOS != "linux" { 154 return nil, errors.New("The amazon-chroot builder only works on Linux environments.") 155 } 156 157 region, err := b.config.Region() 158 if err != nil { 159 return nil, err 160 } 161 162 auth, err := b.config.AccessConfig.Auth() 163 if err != nil { 164 return nil, err 165 } 166 167 ec2conn := ec2.New(auth, region) 168 169 // Setup the state bag and initial state for the steps 170 state := make(map[string]interface{}) 171 state["config"] = &b.config 172 state["ec2"] = ec2conn 173 state["hook"] = hook 174 state["ui"] = ui 175 176 // Build the steps 177 steps := []multistep.Step{ 178 &StepInstanceInfo{}, 179 &StepSourceAMIInfo{}, 180 &StepFlock{}, 181 &StepPrepareDevice{}, 182 &StepCreateVolume{}, 183 &StepAttachVolume{}, 184 &StepEarlyUnflock{}, 185 &StepMountDevice{}, 186 &StepMountExtra{}, 187 &StepCopyFiles{}, 188 &StepChrootProvision{}, 189 &StepEarlyCleanup{}, 190 &StepSnapshot{}, 191 &StepRegisterAMI{}, 192 &awscommon.StepModifyAMIAttributes{ 193 Description: b.config.AMIDescription, 194 Users: b.config.AMIUsers, 195 Groups: b.config.AMIGroups, 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["error"]; ok { 213 return nil, rawErr.(error) 214 } 215 216 // If there are no AMIs, then just return 217 if _, ok := state["amis"]; !ok { 218 return nil, nil 219 } 220 221 // Build the artifact and return it 222 artifact := &awscommon.Artifact{ 223 Amis: state["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 }