github.phpd.cn/hashicorp/packer@v1.3.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 "log" 10 "runtime" 11 12 "github.com/aws/aws-sdk-go/service/ec2" 13 awscommon "github.com/hashicorp/packer/builder/amazon/common" 14 "github.com/hashicorp/packer/common" 15 "github.com/hashicorp/packer/helper/config" 16 "github.com/hashicorp/packer/helper/multistep" 17 "github.com/hashicorp/packer/packer" 18 "github.com/hashicorp/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.AMIBlockDevices `mapstructure:",squash"` 29 awscommon.AMIConfig `mapstructure:",squash"` 30 awscommon.AccessConfig `mapstructure:",squash"` 31 32 ChrootMounts [][]string `mapstructure:"chroot_mounts"` 33 CommandWrapper string `mapstructure:"command_wrapper"` 34 CopyFiles []string `mapstructure:"copy_files"` 35 DevicePath string `mapstructure:"device_path"` 36 NVMEDevicePath string `mapstructure:"nvme_device_path"` 37 FromScratch bool `mapstructure:"from_scratch"` 38 MountOptions []string `mapstructure:"mount_options"` 39 MountPartition string `mapstructure:"mount_partition"` 40 MountPath string `mapstructure:"mount_path"` 41 PostMountCommands []string `mapstructure:"post_mount_commands"` 42 PreMountCommands []string `mapstructure:"pre_mount_commands"` 43 RootDeviceName string `mapstructure:"root_device_name"` 44 RootVolumeSize int64 `mapstructure:"root_volume_size"` 45 RootVolumeType string `mapstructure:"root_volume_type"` 46 SourceAmi string `mapstructure:"source_ami"` 47 SourceAmiFilter awscommon.AmiFilterOptions `mapstructure:"source_ami_filter"` 48 RootVolumeTags awscommon.TagMap `mapstructure:"root_volume_tags"` 49 50 ctx interpolate.Context 51 } 52 53 type wrappedCommandTemplate struct { 54 Command string 55 } 56 57 type Builder struct { 58 config Config 59 runner multistep.Runner 60 } 61 62 func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 63 b.config.ctx.Funcs = awscommon.TemplateFuncs 64 err := config.Decode(&b.config, &config.DecodeOpts{ 65 Interpolate: true, 66 InterpolateContext: &b.config.ctx, 67 InterpolateFilter: &interpolate.RenderFilter{ 68 Exclude: []string{ 69 "ami_description", 70 "snapshot_tags", 71 "tags", 72 "root_volume_tags", 73 "command_wrapper", 74 "post_mount_commands", 75 "pre_mount_commands", 76 "mount_path", 77 }, 78 }, 79 }, raws...) 80 if err != nil { 81 return nil, err 82 } 83 84 if b.config.PackerConfig.PackerForce { 85 b.config.AMIForceDeregister = true 86 } 87 88 // Defaults 89 if b.config.ChrootMounts == nil { 90 b.config.ChrootMounts = make([][]string, 0) 91 } 92 93 if len(b.config.ChrootMounts) == 0 { 94 b.config.ChrootMounts = [][]string{ 95 {"proc", "proc", "/proc"}, 96 {"sysfs", "sysfs", "/sys"}, 97 {"bind", "/dev", "/dev"}, 98 {"devpts", "devpts", "/dev/pts"}, 99 {"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"}, 100 } 101 } 102 103 // set default copy file if we're not giving our own 104 if b.config.CopyFiles == nil { 105 b.config.CopyFiles = make([]string, 0) 106 if !b.config.FromScratch { 107 b.config.CopyFiles = []string{"/etc/resolv.conf"} 108 } 109 } 110 111 if b.config.CommandWrapper == "" { 112 b.config.CommandWrapper = "{{.Command}}" 113 } 114 115 if b.config.MountPath == "" { 116 b.config.MountPath = "/mnt/packer-amazon-chroot-volumes/{{.Device}}" 117 } 118 119 if b.config.MountPartition == "" { 120 b.config.MountPartition = "1" 121 } 122 123 // Accumulate any errors or warnings 124 var errs *packer.MultiError 125 var warns []string 126 127 errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...) 128 errs = packer.MultiErrorAppend(errs, 129 b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...) 130 131 for _, mounts := range b.config.ChrootMounts { 132 if len(mounts) != 3 { 133 errs = packer.MultiErrorAppend( 134 errs, errors.New("Each chroot_mounts entry should be three elements.")) 135 break 136 } 137 } 138 139 if b.config.FromScratch { 140 if b.config.SourceAmi != "" || !b.config.SourceAmiFilter.Empty() { 141 warns = append(warns, "source_ami and source_ami_filter are unused when from_scratch is true") 142 } 143 if b.config.RootVolumeSize == 0 { 144 errs = packer.MultiErrorAppend( 145 errs, errors.New("root_volume_size is required with from_scratch.")) 146 } 147 if len(b.config.PreMountCommands) == 0 { 148 errs = packer.MultiErrorAppend( 149 errs, errors.New("pre_mount_commands is required with from_scratch.")) 150 } 151 if b.config.AMIVirtType == "" { 152 errs = packer.MultiErrorAppend( 153 errs, errors.New("ami_virtualization_type is required with from_scratch.")) 154 } 155 if b.config.RootDeviceName == "" { 156 errs = packer.MultiErrorAppend( 157 errs, errors.New("root_device_name is required with from_scratch.")) 158 } 159 if len(b.config.AMIMappings) == 0 { 160 errs = packer.MultiErrorAppend( 161 errs, errors.New("ami_block_device_mappings is required with from_scratch.")) 162 } 163 } else { 164 if b.config.SourceAmi == "" && b.config.SourceAmiFilter.Empty() { 165 errs = packer.MultiErrorAppend( 166 errs, errors.New("source_ami or source_ami_filter is required.")) 167 } 168 if len(b.config.AMIMappings) != 0 { 169 warns = append(warns, "ami_block_device_mappings are unused when from_scratch is false") 170 } 171 if b.config.RootDeviceName != "" { 172 warns = append(warns, "root_device_name is unused when from_scratch is false") 173 } 174 } 175 176 if errs != nil && len(errs.Errors) > 0 { 177 return warns, errs 178 } 179 180 packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token) 181 return warns, nil 182 } 183 184 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 185 if runtime.GOOS != "linux" { 186 return nil, errors.New("The amazon-chroot builder only works on Linux environments.") 187 } 188 189 session, err := b.config.Session() 190 if err != nil { 191 return nil, err 192 } 193 ec2conn := ec2.New(session) 194 195 wrappedCommand := func(command string) (string, error) { 196 ctx := b.config.ctx 197 ctx.Data = &wrappedCommandTemplate{Command: command} 198 return interpolate.Render(b.config.CommandWrapper, &ctx) 199 } 200 201 // Setup the state bag and initial state for the steps 202 state := new(multistep.BasicStateBag) 203 state.Put("config", &b.config) 204 state.Put("ec2", ec2conn) 205 state.Put("awsSession", session) 206 state.Put("hook", hook) 207 state.Put("ui", ui) 208 state.Put("wrappedCommand", CommandWrapper(wrappedCommand)) 209 210 // Build the steps 211 steps := []multistep.Step{ 212 &awscommon.StepPreValidate{ 213 DestAmiName: b.config.AMIName, 214 ForceDeregister: b.config.AMIForceDeregister, 215 }, 216 &StepInstanceInfo{}, 217 } 218 219 if !b.config.FromScratch { 220 steps = append(steps, 221 &awscommon.StepSourceAMIInfo{ 222 SourceAmi: b.config.SourceAmi, 223 EnableAMISriovNetSupport: b.config.AMISriovNetSupport, 224 EnableAMIENASupport: b.config.AMIENASupport, 225 AmiFilters: b.config.SourceAmiFilter, 226 AMIVirtType: b.config.AMIVirtType, 227 }, 228 &StepCheckRootDevice{}, 229 ) 230 } 231 232 steps = append(steps, 233 &StepFlock{}, 234 &StepPrepareDevice{}, 235 &StepCreateVolume{ 236 RootVolumeType: b.config.RootVolumeType, 237 RootVolumeSize: b.config.RootVolumeSize, 238 RootVolumeTags: b.config.RootVolumeTags, 239 Ctx: b.config.ctx, 240 }, 241 &StepAttachVolume{}, 242 &StepEarlyUnflock{}, 243 &StepPreMountCommands{ 244 Commands: b.config.PreMountCommands, 245 }, 246 &StepMountDevice{ 247 MountOptions: b.config.MountOptions, 248 MountPartition: b.config.MountPartition, 249 }, 250 &StepPostMountCommands{ 251 Commands: b.config.PostMountCommands, 252 }, 253 &StepMountExtra{}, 254 &StepCopyFiles{}, 255 &StepChrootProvision{}, 256 &StepEarlyCleanup{}, 257 &StepSnapshot{}, 258 &awscommon.StepDeregisterAMI{ 259 AccessConfig: &b.config.AccessConfig, 260 ForceDeregister: b.config.AMIForceDeregister, 261 ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot, 262 AMIName: b.config.AMIName, 263 Regions: b.config.AMIRegions, 264 }, 265 &StepRegisterAMI{ 266 RootVolumeSize: b.config.RootVolumeSize, 267 EnableAMISriovNetSupport: b.config.AMISriovNetSupport, 268 EnableAMIENASupport: b.config.AMIENASupport, 269 }, 270 &awscommon.StepCreateEncryptedAMICopy{ 271 KeyID: b.config.AMIKmsKeyId, 272 EncryptBootVolume: b.config.AMIEncryptBootVolume, 273 Name: b.config.AMIName, 274 AMIMappings: b.config.AMIBlockDevices.AMIMappings, 275 }, 276 &awscommon.StepAMIRegionCopy{ 277 AccessConfig: &b.config.AccessConfig, 278 Regions: b.config.AMIRegions, 279 RegionKeyIds: b.config.AMIRegionKMSKeyIDs, 280 EncryptBootVolume: b.config.AMIEncryptBootVolume, 281 Name: b.config.AMIName, 282 }, 283 &awscommon.StepModifyAMIAttributes{ 284 Description: b.config.AMIDescription, 285 Users: b.config.AMIUsers, 286 Groups: b.config.AMIGroups, 287 ProductCodes: b.config.AMIProductCodes, 288 SnapshotUsers: b.config.SnapshotUsers, 289 SnapshotGroups: b.config.SnapshotGroups, 290 Ctx: b.config.ctx, 291 }, 292 &awscommon.StepCreateTags{ 293 Tags: b.config.AMITags, 294 SnapshotTags: b.config.SnapshotTags, 295 Ctx: b.config.ctx, 296 }, 297 ) 298 299 // Run! 300 b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) 301 b.runner.Run(state) 302 303 // If there was an error, return that 304 if rawErr, ok := state.GetOk("error"); ok { 305 return nil, rawErr.(error) 306 } 307 308 // If there are no AMIs, then just return 309 if _, ok := state.GetOk("amis"); !ok { 310 return nil, nil 311 } 312 313 // Build the artifact and return it 314 artifact := &awscommon.Artifact{ 315 Amis: state.Get("amis").(map[string]string), 316 BuilderIdValue: BuilderId, 317 Session: session, 318 } 319 320 return artifact, nil 321 } 322 323 func (b *Builder) Cancel() { 324 if b.runner != nil { 325 log.Println("Cancelling the step runner...") 326 b.runner.Cancel() 327 } 328 }