packer-cn/builder/virtualbox/vm/config.go

206 lines
8.7 KiB
Go
Raw Normal View History

//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type Config
2019-02-16 08:32:22 -05:00
package vm
import (
"fmt"
"log"
"time"
2019-02-16 08:32:22 -05:00
vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer-plugin-sdk/template/interpolate"
2019-02-16 08:32:22 -05:00
)
// Config is the configuration structure for the builder.
type Config struct {
common.PackerConfig `mapstructure:",squash"`
common.HTTPConfig `mapstructure:",squash"`
common.FloppyConfig `mapstructure:",squash"`
common.CDConfig `mapstructure:",squash"`
bootcommand.BootConfig `mapstructure:",squash"`
vboxcommon.ExportConfig `mapstructure:",squash"`
vboxcommon.OutputConfig `mapstructure:",squash"`
vboxcommon.RunConfig `mapstructure:",squash"`
vboxcommon.CommConfig `mapstructure:",squash"`
vboxcommon.ShutdownConfig `mapstructure:",squash"`
vboxcommon.VBoxManageConfig `mapstructure:",squash"`
vboxcommon.VBoxVersionConfig `mapstructure:",squash"`
vboxcommon.GuestAdditionsConfig `mapstructure:",squash"`
// This is the name of the virtual machine to which the
// builder shall attach.
VMName string `mapstructure:"vm_name" required:"true"`
// Default to `null/empty`. The name of an
// **existing** snapshot to which the builder shall attach the VM before
// starting it. If no snapshot is specified the builder will simply start the
// VM from it's current state i.e. snapshot.
AttachSnapshot string `mapstructure:"attach_snapshot" required:"false"`
// Default to `null/empty`. The name of the
// snapshot which shall be created after all provisioners has been run by the
// builder. If no target snapshot is specified and `keep_registered` is set to
// `false` the builder will revert to the snapshot to which the VM was attached
// before the builder has been executed, which will revert all changes applied
// by the provisioners. This is handy if only an export shall be created and no
// further snapshot is required.
TargetSnapshot string `mapstructure:"target_snapshot" required:"false"`
// Defaults to `false`. If set to `true`,
// overwrite an existing `target_snapshot`. Otherwise the builder will yield an
// error if the specified target snapshot already exists.
DeleteTargetSnapshot bool `mapstructure:"force_delete_snapshot" required:"false"`
// Set this to `true` if you would like to keep
// the VM attached to the snapshot specified by `attach_snapshot`. Otherwise
// the builder will reset the VM to the snapshot to which the VM was attached
// before the builder started. Defaults to `false`.
KeepRegistered bool `mapstructure:"keep_registered" required:"false"`
// Defaults to `false`. When enabled, Packer will
// not export the VM. Useful if the builder should be applied again on the created
// target snapshot.
SkipExport bool `mapstructure:"skip_export" required:"false"`
2019-02-16 08:32:22 -05:00
ctx interpolate.Context
}
build using HCL2 (#8423) This follows #8232 which added the code to generate the code required to parse HCL files for each packer component. All old config files of packer will keep on working the same. Packer takes one argument. When a directory is passed, all files in the folder with a name ending with “.pkr.hcl” or “.pkr.json” will be parsed using the HCL2 format. When a file ending with “.pkr.hcl” or “.pkr.json” is passed it will be parsed using the HCL2 format. For every other case; the old packer style will be used. ## 1. the hcl2template pkg can create a packer.Build from a set of HCL (v2) files I had to make the packer.coreBuild (which is our one and only packer.Build ) a public struct with public fields ## 2. Components interfaces get a new ConfigSpec Method to read a file from an HCL file. This is a breaking change for packer plugins. a packer component can be a: builder/provisioner/post-processor each component interface now gets a `ConfigSpec() hcldec.ObjectSpec` which allows packer to tell what is the layout of the hcl2 config meant to configure that specific component. This ObjectSpec is sent through the wire (RPC) and a cty.Value is now sent through the already existing configuration entrypoints: Provisioner.Prepare(raws ...interface{}) error Builder.Prepare(raws ...interface{}) ([]string, error) PostProcessor.Configure(raws ...interface{}) error close #1768 Example hcl files: ```hcl // file amazon-ebs-kms-key/run.pkr.hcl build { sources = [ "source.amazon-ebs.first", ] provisioner "shell" { inline = [ "sleep 5" ] } post-processor "shell-local" { inline = [ "sleep 5" ] } } // amazon-ebs-kms-key/source.pkr.hcl source "amazon-ebs" "first" { ami_name = "hcl2-test" region = "us-east-1" instance_type = "t2.micro" kms_key_id = "c729958f-c6ba-44cd-ab39-35ab68ce0a6c" encrypt_boot = true source_ami_filter { filters { virtualization-type = "hvm" name = "amzn-ami-hvm-????.??.?.????????-x86_64-gp2" root-device-type = "ebs" } most_recent = true owners = ["amazon"] } launch_block_device_mappings { device_name = "/dev/xvda" volume_size = 20 volume_type = "gp2" delete_on_termination = "true" } launch_block_device_mappings { device_name = "/dev/xvdf" volume_size = 500 volume_type = "gp2" delete_on_termination = true encrypted = true } ami_regions = ["eu-central-1"] run_tags { Name = "packer-solr-something" stack-name = "DevOps Tools" } communicator = "ssh" ssh_pty = true ssh_username = "ec2-user" associate_public_ip_address = true } ```
2019-12-17 05:25:56 -05:00
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
2019-02-16 08:32:22 -05:00
err := config.Decode(c, &config.DecodeOpts{
PluginType: vboxcommon.BuilderId, // "mitchellh.virtualbox"
2019-02-16 08:32:22 -05:00
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"boot_command",
"guest_additions_path",
"guest_additions_url",
"vboxmanage",
"vboxmanage_post",
},
},
}, raws...)
if err != nil {
build using HCL2 (#8423) This follows #8232 which added the code to generate the code required to parse HCL files for each packer component. All old config files of packer will keep on working the same. Packer takes one argument. When a directory is passed, all files in the folder with a name ending with “.pkr.hcl” or “.pkr.json” will be parsed using the HCL2 format. When a file ending with “.pkr.hcl” or “.pkr.json” is passed it will be parsed using the HCL2 format. For every other case; the old packer style will be used. ## 1. the hcl2template pkg can create a packer.Build from a set of HCL (v2) files I had to make the packer.coreBuild (which is our one and only packer.Build ) a public struct with public fields ## 2. Components interfaces get a new ConfigSpec Method to read a file from an HCL file. This is a breaking change for packer plugins. a packer component can be a: builder/provisioner/post-processor each component interface now gets a `ConfigSpec() hcldec.ObjectSpec` which allows packer to tell what is the layout of the hcl2 config meant to configure that specific component. This ObjectSpec is sent through the wire (RPC) and a cty.Value is now sent through the already existing configuration entrypoints: Provisioner.Prepare(raws ...interface{}) error Builder.Prepare(raws ...interface{}) ([]string, error) PostProcessor.Configure(raws ...interface{}) error close #1768 Example hcl files: ```hcl // file amazon-ebs-kms-key/run.pkr.hcl build { sources = [ "source.amazon-ebs.first", ] provisioner "shell" { inline = [ "sleep 5" ] } post-processor "shell-local" { inline = [ "sleep 5" ] } } // amazon-ebs-kms-key/source.pkr.hcl source "amazon-ebs" "first" { ami_name = "hcl2-test" region = "us-east-1" instance_type = "t2.micro" kms_key_id = "c729958f-c6ba-44cd-ab39-35ab68ce0a6c" encrypt_boot = true source_ami_filter { filters { virtualization-type = "hvm" name = "amzn-ami-hvm-????.??.?.????????-x86_64-gp2" root-device-type = "ebs" } most_recent = true owners = ["amazon"] } launch_block_device_mappings { device_name = "/dev/xvda" volume_size = 20 volume_type = "gp2" delete_on_termination = "true" } launch_block_device_mappings { device_name = "/dev/xvdf" volume_size = 500 volume_type = "gp2" delete_on_termination = true encrypted = true } ami_regions = ["eu-central-1"] run_tags { Name = "packer-solr-something" stack-name = "DevOps Tools" } communicator = "ssh" ssh_pty = true ssh_username = "ec2-user" associate_public_ip_address = true } ```
2019-12-17 05:25:56 -05:00
return nil, err
2019-02-16 08:32:22 -05:00
}
// Defaults
if c.PostShutdownDelay == 0 {
c.PostShutdownDelay = 2 * time.Second
}
2019-02-16 08:32:22 -05:00
// Prepare the errors
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.CDConfig.Prepare(&c.ctx)...)
2019-02-16 08:32:22 -05:00
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(&c.ctx, &c.PackerConfig)...)
errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.CommConfig.Prepare(&c.ctx)...)
2019-02-16 08:32:22 -05:00
errs = packer.MultiErrorAppend(errs, c.VBoxManageConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VBoxVersionConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.GuestAdditionsConfig.Prepare(&c.ctx)...)
if c.GuestAdditionsInterface == "" {
c.GuestAdditionsInterface = "ide"
}
2019-02-16 08:32:22 -05:00
log.Printf("PostShutdownDelay: %s", c.PostShutdownDelay)
2019-02-16 08:32:22 -05:00
if c.VMName == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("vm_name is required"))
}
// Warnings
var warnings []string
if c.TargetSnapshot == "" && c.SkipExport {
warnings = append(warnings,
"No target snapshot is specified (target_snapshot empty) and no export will be created (skip_export=true).\n"+
"You might lose all changes applied by this run, the next time you execute packer.")
}
2019-02-16 08:32:22 -05:00
if c.ShutdownCommand == "" {
warnings = append(warnings,
"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
"will forcibly halt the virtual machine, which may result in data loss.")
}
2019-02-16 08:32:22 -05:00
driver, err := vboxcommon.NewDriver()
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed creating VirtualBox driver: %s", err))
} else {
if c.AttachSnapshot != "" && c.TargetSnapshot != "" && c.AttachSnapshot == c.TargetSnapshot {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Attach snapshot %s and target snapshot %s cannot be the same", c.AttachSnapshot, c.TargetSnapshot))
}
snapshotTree, err := driver.LoadSnapshots(c.VMName)
log.Printf("")
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed to load snapshots for VM %s: %s", c.VMName, err))
} else {
log.Printf("Snapshots loaded from VM %s", c.VMName)
var attachSnapshot *vboxcommon.VBoxSnapshot
if nil != snapshotTree {
attachSnapshot = snapshotTree.GetCurrentSnapshot()
log.Printf("VM %s is currently attached to snapshot: %s/%s", c.VMName, attachSnapshot.Name, attachSnapshot.UUID)
}
if c.AttachSnapshot != "" {
log.Printf("Checking configuration attach_snapshot [%s]", c.AttachSnapshot)
if nil == snapshotTree {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("No snapshots defined on VM %s. Unable to attach to %s", c.VMName, c.AttachSnapshot))
} else {
snapshots := snapshotTree.GetSnapshotsByName(c.AttachSnapshot)
if 0 >= len(snapshots) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Snapshot %s does not exist on VM %s", c.AttachSnapshot, c.VMName))
} else if 1 < len(snapshots) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Multiple Snapshots with name %s exist on VM %s", c.AttachSnapshot, c.VMName))
} else {
attachSnapshot = snapshots[0]
}
2019-02-16 08:32:22 -05:00
}
}
if c.TargetSnapshot != "" {
log.Printf("Checking configuration target_snapshot [%s]", c.TargetSnapshot)
if nil == snapshotTree {
log.Printf("Currently no snapshots defined in VM %s", c.VMName)
} else {
if c.TargetSnapshot == attachSnapshot.Name {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Target snapshot %s cannot be the same as the snapshot to which the builder shall attach: %s", c.TargetSnapshot, attachSnapshot.Name))
} else {
snapshots := snapshotTree.GetSnapshotsByName(c.TargetSnapshot)
if 0 < len(snapshots) {
if nil == attachSnapshot {
panic("Internal error. Expecting a handle to a VBoxSnapshot")
}
isChild := false
for _, snapshot := range snapshots {
log.Printf("Checking if target snaphot %s/%s is child of %s/%s", snapshot.Name, snapshot.UUID, attachSnapshot.Name, attachSnapshot.UUID)
isChild = nil != snapshot.Parent && snapshot.Parent.UUID == attachSnapshot.UUID
}
if !isChild {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Target snapshot %s already exists and is not a direct child of %s", c.TargetSnapshot, attachSnapshot.Name))
} else if !c.DeleteTargetSnapshot {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Target snapshot %s already exists as direct child of %s for VM %s. Use force_delete_snapshot = true to overwrite snapshot",
c.TargetSnapshot,
attachSnapshot.Name,
c.VMName))
}
} else {
log.Printf("No snapshot with name %s defined in VM %s", c.TargetSnapshot, c.VMName)
}
}
2019-02-16 08:32:22 -05:00
}
}
}
}
// Check for any errors.
if errs != nil && len(errs.Errors) > 0 {
build using HCL2 (#8423) This follows #8232 which added the code to generate the code required to parse HCL files for each packer component. All old config files of packer will keep on working the same. Packer takes one argument. When a directory is passed, all files in the folder with a name ending with “.pkr.hcl” or “.pkr.json” will be parsed using the HCL2 format. When a file ending with “.pkr.hcl” or “.pkr.json” is passed it will be parsed using the HCL2 format. For every other case; the old packer style will be used. ## 1. the hcl2template pkg can create a packer.Build from a set of HCL (v2) files I had to make the packer.coreBuild (which is our one and only packer.Build ) a public struct with public fields ## 2. Components interfaces get a new ConfigSpec Method to read a file from an HCL file. This is a breaking change for packer plugins. a packer component can be a: builder/provisioner/post-processor each component interface now gets a `ConfigSpec() hcldec.ObjectSpec` which allows packer to tell what is the layout of the hcl2 config meant to configure that specific component. This ObjectSpec is sent through the wire (RPC) and a cty.Value is now sent through the already existing configuration entrypoints: Provisioner.Prepare(raws ...interface{}) error Builder.Prepare(raws ...interface{}) ([]string, error) PostProcessor.Configure(raws ...interface{}) error close #1768 Example hcl files: ```hcl // file amazon-ebs-kms-key/run.pkr.hcl build { sources = [ "source.amazon-ebs.first", ] provisioner "shell" { inline = [ "sleep 5" ] } post-processor "shell-local" { inline = [ "sleep 5" ] } } // amazon-ebs-kms-key/source.pkr.hcl source "amazon-ebs" "first" { ami_name = "hcl2-test" region = "us-east-1" instance_type = "t2.micro" kms_key_id = "c729958f-c6ba-44cd-ab39-35ab68ce0a6c" encrypt_boot = true source_ami_filter { filters { virtualization-type = "hvm" name = "amzn-ami-hvm-????.??.?.????????-x86_64-gp2" root-device-type = "ebs" } most_recent = true owners = ["amazon"] } launch_block_device_mappings { device_name = "/dev/xvda" volume_size = 20 volume_type = "gp2" delete_on_termination = "true" } launch_block_device_mappings { device_name = "/dev/xvdf" volume_size = 500 volume_type = "gp2" delete_on_termination = true encrypted = true } ami_regions = ["eu-central-1"] run_tags { Name = "packer-solr-something" stack-name = "DevOps Tools" } communicator = "ssh" ssh_pty = true ssh_username = "ec2-user" associate_public_ip_address = true } ```
2019-12-17 05:25:56 -05:00
return warnings, errs
2019-02-16 08:32:22 -05:00
}
build using HCL2 (#8423) This follows #8232 which added the code to generate the code required to parse HCL files for each packer component. All old config files of packer will keep on working the same. Packer takes one argument. When a directory is passed, all files in the folder with a name ending with “.pkr.hcl” or “.pkr.json” will be parsed using the HCL2 format. When a file ending with “.pkr.hcl” or “.pkr.json” is passed it will be parsed using the HCL2 format. For every other case; the old packer style will be used. ## 1. the hcl2template pkg can create a packer.Build from a set of HCL (v2) files I had to make the packer.coreBuild (which is our one and only packer.Build ) a public struct with public fields ## 2. Components interfaces get a new ConfigSpec Method to read a file from an HCL file. This is a breaking change for packer plugins. a packer component can be a: builder/provisioner/post-processor each component interface now gets a `ConfigSpec() hcldec.ObjectSpec` which allows packer to tell what is the layout of the hcl2 config meant to configure that specific component. This ObjectSpec is sent through the wire (RPC) and a cty.Value is now sent through the already existing configuration entrypoints: Provisioner.Prepare(raws ...interface{}) error Builder.Prepare(raws ...interface{}) ([]string, error) PostProcessor.Configure(raws ...interface{}) error close #1768 Example hcl files: ```hcl // file amazon-ebs-kms-key/run.pkr.hcl build { sources = [ "source.amazon-ebs.first", ] provisioner "shell" { inline = [ "sleep 5" ] } post-processor "shell-local" { inline = [ "sleep 5" ] } } // amazon-ebs-kms-key/source.pkr.hcl source "amazon-ebs" "first" { ami_name = "hcl2-test" region = "us-east-1" instance_type = "t2.micro" kms_key_id = "c729958f-c6ba-44cd-ab39-35ab68ce0a6c" encrypt_boot = true source_ami_filter { filters { virtualization-type = "hvm" name = "amzn-ami-hvm-????.??.?.????????-x86_64-gp2" root-device-type = "ebs" } most_recent = true owners = ["amazon"] } launch_block_device_mappings { device_name = "/dev/xvda" volume_size = 20 volume_type = "gp2" delete_on_termination = "true" } launch_block_device_mappings { device_name = "/dev/xvdf" volume_size = 500 volume_type = "gp2" delete_on_termination = true encrypted = true } ami_regions = ["eu-central-1"] run_tags { Name = "packer-solr-something" stack-name = "DevOps Tools" } communicator = "ssh" ssh_pty = true ssh_username = "ec2-user" associate_public_ip_address = true } ```
2019-12-17 05:25:56 -05:00
return warnings, nil
2019-02-16 08:32:22 -05:00
}