diff --git a/builder/amazon/ebssurrogate/builder.go b/builder/amazon/ebssurrogate/builder.go new file mode 100644 index 000000000..7a6c93239 --- /dev/null +++ b/builder/amazon/ebssurrogate/builder.go @@ -0,0 +1,218 @@ +// The ebssurrogate package contains a packer.Builder implementation that +// builds a new EBS-backed AMI using an ephemeral instance. +package ebssurrogate + +import ( + "errors" + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/errwrap" + "github.com/mitchellh/multistep" + awscommon "github.com/mitchellh/packer/builder/amazon/common" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/helper/communicator" + "github.com/mitchellh/packer/helper/config" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template/interpolate" +) + +const BuilderId = "mitchellh.amazon.ebssurrogate" + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + awscommon.AccessConfig `mapstructure:",squash"` + awscommon.RunConfig `mapstructure:",squash"` + awscommon.BlockDevices `mapstructure:",squash"` + awscommon.AMIConfig `mapstructure:",squash"` + + RootDevice RootBlockDevice `mapstructure:"ami_root_device"` + + ctx interpolate.Context +} + +type Builder struct { + config Config + runner multistep.Runner +} + +func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { + b.config.ctx.Funcs = awscommon.TemplateFuncs + err := config.Decode(&b.config, &config.DecodeOpts{ + Interpolate: true, + InterpolateContext: &b.config.ctx, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{ + "ami_description", + "run_tags", + "tags", + }, + }, + }, raws...) + if err != nil { + return nil, err + } + + // Accumulate any errors + var errs *packer.MultiError + errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.RootDevice.Prepare(&b.config.ctx)...) + + if b.config.AMIVirtType == "" { + errs = packer.MultiErrorAppend(errs, errors.New("ami_virtualization_type is required.")) + } + + foundRootVolume := false + for _, launchDevice := range b.config.BlockDevices.LaunchMappings { + if launchDevice.DeviceName == b.config.RootDevice.SourceDeviceName { + foundRootVolume = true + } + } + + if !foundRootVolume { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("no volume with name '%s' is found", b.config.RootDevice.SourceDeviceName)) + } + + if errs != nil && len(errs.Errors) > 0 { + return nil, errs + } + + log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey)) + return nil, nil +} + +func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { + awsConfig, err := b.config.Config() + if err != nil { + return nil, err + } + + awsSession, err := session.NewSession(awsConfig) + if err != nil { + return nil, errwrap.Wrapf("Error creating AWS Session: {{err}}", err) + } + + ec2conn := ec2.New(awsSession) + + // If the subnet is specified but not the AZ, try to determine the AZ automatically + if b.config.SubnetId != "" && b.config.AvailabilityZone == "" { + log.Printf("[INFO] Finding AZ for the given subnet '%s'", b.config.SubnetId) + resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}}) + if err != nil { + return nil, err + } + b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone + log.Printf("[INFO] AZ found: '%s'", b.config.AvailabilityZone) + } + + // Setup the state bag and initial state for the steps + state := new(multistep.BasicStateBag) + state.Put("config", &b.config) + state.Put("ec2", ec2conn) + state.Put("hook", hook) + state.Put("ui", ui) + + // Build the steps + steps := []multistep.Step{ + &awscommon.StepSourceAMIInfo{ + SourceAmi: b.config.SourceAmi, + EnhancedNetworking: b.config.AMIEnhancedNetworking, + AmiFilters: b.config.SourceAmiFilter, + }, + &awscommon.StepKeyPair{ + Debug: b.config.PackerDebug, + SSHAgentAuth: b.config.Comm.SSHAgentAuth, + DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), + KeyPairName: b.config.SSHKeyPairName, + TemporaryKeyPairName: b.config.TemporaryKeyPairName, + PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey, + }, + &awscommon.StepSecurityGroup{ + SecurityGroupIds: b.config.SecurityGroupIds, + CommConfig: &b.config.RunConfig.Comm, + VpcId: b.config.VpcId, + }, + &awscommon.StepRunSourceInstance{ + Debug: b.config.PackerDebug, + ExpectedRootDevice: "ebs", + SpotPrice: b.config.SpotPrice, + SpotPriceProduct: b.config.SpotPriceAutoProduct, + InstanceType: b.config.InstanceType, + UserData: b.config.UserData, + UserDataFile: b.config.UserDataFile, + SourceAMI: b.config.SourceAmi, + IamInstanceProfile: b.config.IamInstanceProfile, + SubnetId: b.config.SubnetId, + AssociatePublicIpAddress: b.config.AssociatePublicIpAddress, + EbsOptimized: b.config.EbsOptimized, + AvailabilityZone: b.config.AvailabilityZone, + BlockDevices: b.config.BlockDevices, + Tags: b.config.RunTags, + InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, + }, + &awscommon.StepGetPassword{ + Debug: b.config.PackerDebug, + Comm: &b.config.RunConfig.Comm, + Timeout: b.config.WindowsPasswordTimeout, + }, + &communicator.StepConnect{ + Config: &b.config.RunConfig.Comm, + Host: awscommon.SSHHost( + ec2conn, + b.config.SSHPrivateIp), + SSHConfig: awscommon.SSHConfig( + b.config.RunConfig.Comm.SSHAgentAuth, + b.config.RunConfig.Comm.SSHUsername, + b.config.RunConfig.Comm.SSHPassword), + }, + &common.StepProvision{}, + &awscommon.StepStopEBSBackedInstance{ + SpotPrice: b.config.SpotPrice, + DisableStopInstance: b.config.DisableStopInstance, + }, + &awscommon.StepModifyEBSBackedInstance{ + EnableEnhancedNetworking: b.config.AMIEnhancedNetworking, + }, + &StepSnapshotNewRootVolume{ + NewRootMountPoint: b.config.RootDevice.SourceDeviceName, + }, + &StepRegisterAMI{ + RootDevice: b.config.RootDevice, + BlockDevices: b.config.BlockDevices.BuildLaunchDevices(), + }, + } + + // Run! + b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) + b.runner.Run(state) + + // If there was an error, return that + if rawErr, ok := state.GetOk("error"); ok { + return nil, rawErr.(error) + } + + if amis, ok := state.GetOk("amis"); ok { + // Build the artifact and return it + artifact := &awscommon.Artifact{ + Amis: amis.(map[string]string), + BuilderIdValue: BuilderId, + Conn: ec2conn, + } + + return artifact, nil + } + + return nil, nil +} + +func (b *Builder) Cancel() { + if b.runner != nil { + log.Println("Cancelling the step runner...") + b.runner.Cancel() + } +} diff --git a/builder/amazon/ebssurrogate/builder_test.go b/builder/amazon/ebssurrogate/builder_test.go new file mode 100644 index 000000000..89a134ba8 --- /dev/null +++ b/builder/amazon/ebssurrogate/builder_test.go @@ -0,0 +1,56 @@ +package ebssurrogate + +import ( + "testing" + + "github.com/mitchellh/packer/packer" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + "access_key": "foo", + "secret_key": "bar", + "source_ami": "foo", + "instance_type": "foo", + "region": "us-east-1", + "ssh_username": "root", + } +} + +func TestBuilder_ImplementsBuilder(t *testing.T) { + var raw interface{} + raw = &Builder{} + if _, ok := raw.(packer.Builder); !ok { + t.Fatal("Builder should be a builder") + } +} + +func TestBuilder_Prepare_BadType(t *testing.T) { + b := &Builder{} + c := map[string]interface{}{ + "access_key": []string{}, + } + + warnings, err := b.Prepare(c) + if len(warnings) > 0 { + t.Fatalf("bad: %#v", warnings) + } + if err == nil { + t.Fatal("prepare should fail") + } +} + +func TestBuilderPrepare_InvalidKey(t *testing.T) { + var b Builder + config := testConfig() + + // Add a random key + config["i_should_not_be_valid"] = true + warnings, err := b.Prepare(config) + if len(warnings) > 0 { + t.Fatalf("bad: %#v", warnings) + } + if err == nil { + t.Fatal("should have error") + } +} diff --git a/builder/amazon/ebssurrogate/root_block_device.go b/builder/amazon/ebssurrogate/root_block_device.go new file mode 100644 index 000000000..db8a44f72 --- /dev/null +++ b/builder/amazon/ebssurrogate/root_block_device.go @@ -0,0 +1,65 @@ +package ebssurrogate + +import ( + "errors" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/mitchellh/packer/template/interpolate" +) + +type RootBlockDevice struct { + SourceDeviceName string `mapstructure:"source_device_name"` + DeviceName string `mapstructure:"device_name"` + DeleteOnTermination bool `mapstructure:"delete_on_termination"` + IOPS int64 `mapstructure:"iops"` + VolumeType string `mapstructure:"volume_type"` + VolumeSize int64 `mapstructure:"volume_size"` +} + +func (c *RootBlockDevice) Prepare(ctx *interpolate.Context) []error { + var errs []error + + if c.SourceDeviceName == "" { + errs = append(errs, errors.New("source_device_name for the root_device must be specified")) + } + + if c.DeviceName == "" { + errs = append(errs, errors.New("device_name for the root_device must be specified")) + } + + if c.VolumeType == "gp2" && c.IOPS != 0 { + errs = append(errs, errors.New("iops may not be specified for a gp2 volume")) + } + + if c.IOPS < 0 { + errs = append(errs, errors.New("iops must be greater than 0")) + } + + if c.VolumeSize < 0 { + errs = append(errs, errors.New("volume_size must be greater than 0")) + } + + if len(errs) > 0 { + return errs + } + + return nil +} + +func (d *RootBlockDevice) createBlockDeviceMapping(snapshotId string) *ec2.BlockDeviceMapping { + rootBlockDevice := &ec2.EbsBlockDevice{ + SnapshotId: aws.String(snapshotId), + VolumeType: aws.String(d.VolumeType), + VolumeSize: aws.Int64(d.VolumeSize), + DeleteOnTermination: aws.Bool(d.DeleteOnTermination), + } + + if d.IOPS != 0 { + rootBlockDevice.Iops = aws.Int64(d.IOPS) + } + + return &ec2.BlockDeviceMapping{ + DeviceName: aws.String(d.DeviceName), + Ebs: rootBlockDevice, + } +} diff --git a/builder/amazon/ebssurrogate/step_register_ami.go b/builder/amazon/ebssurrogate/step_register_ami.go new file mode 100644 index 000000000..26dc36bd2 --- /dev/null +++ b/builder/amazon/ebssurrogate/step_register_ami.go @@ -0,0 +1,75 @@ +package ebssurrogate + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/mitchellh/multistep" + awscommon "github.com/mitchellh/packer/builder/amazon/common" + "github.com/mitchellh/packer/packer" +) + +// StepRegisterAMI creates the AMI. +type StepRegisterAMI struct { + RootDevice RootBlockDevice + BlockDevices []*ec2.BlockDeviceMapping +} + +func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + ec2conn := state.Get("ec2").(*ec2.EC2) + snapshotId := state.Get("snapshot_id").(string) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Registering the AMI...") + + blockDevices := s.BlockDevices + blockDevices = append(blockDevices, s.RootDevice.createBlockDeviceMapping(snapshotId)) + + registerOpts := &ec2.RegisterImageInput{ + Name: &config.AMIName, + Architecture: aws.String(ec2.ArchitectureValuesX8664), + RootDeviceName: aws.String(s.RootDevice.DeviceName), + VirtualizationType: aws.String(config.AMIVirtType), + BlockDeviceMappings: blockDevices, + } + + // Set SriovNetSupport to "simple". See http://goo.gl/icuXh5 + if config.AMIEnhancedNetworking { + registerOpts.SriovNetSupport = aws.String("simple") + } + + registerResp, err := ec2conn.RegisterImage(registerOpts) + if err != nil { + state.Put("error", fmt.Errorf("Error registering AMI: %s", err)) + ui.Error(state.Get("error").(error).Error()) + return multistep.ActionHalt + } + + // Set the AMI ID in the state + ui.Say(fmt.Sprintf("AMI: %s", *registerResp.ImageId)) + amis := make(map[string]string) + amis[*ec2conn.Config.Region] = *registerResp.ImageId + state.Put("amis", amis) + + // Wait for the image to become ready + stateChange := awscommon.StateChangeConf{ + Pending: []string{"pending"}, + Target: "available", + Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *registerResp.ImageId), + StepState: state, + } + + ui.Say("Waiting for AMI to become ready...") + if _, err := awscommon.WaitForState(&stateChange); err != nil { + err := fmt.Errorf("Error waiting for AMI: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {} diff --git a/builder/amazon/ebssurrogate/step_snapshot_new_root.go b/builder/amazon/ebssurrogate/step_snapshot_new_root.go new file mode 100644 index 000000000..858f04934 --- /dev/null +++ b/builder/amazon/ebssurrogate/step_snapshot_new_root.go @@ -0,0 +1,102 @@ +package ebssurrogate + +import ( + "errors" + "fmt" + "time" + + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/mitchellh/multistep" + awscommon "github.com/mitchellh/packer/builder/amazon/common" + "github.com/mitchellh/packer/packer" +) + +// StepSnapshotNewRootVolume creates a snapshot of the created volume. +// +// Produces: +// snapshot_id string - ID of the created snapshot +type StepSnapshotNewRootVolume struct { + NewRootMountPoint string + snapshotId string +} + +func (s *StepSnapshotNewRootVolume) Run(state multistep.StateBag) multistep.StepAction { + ec2conn := state.Get("ec2").(*ec2.EC2) + ui := state.Get("ui").(packer.Ui) + instance := state.Get("instance").(*ec2.Instance) + + var newRootVolume string + for _, volume := range instance.BlockDeviceMappings { + if *volume.DeviceName == s.NewRootMountPoint { + newRootVolume = *volume.Ebs.VolumeId + } + } + + ui.Say(fmt.Sprintf("Creating snapshot of EBS Volume %s...", newRootVolume)) + description := fmt.Sprintf("Packer: %s", time.Now().String()) + + createSnapResp, err := ec2conn.CreateSnapshot(&ec2.CreateSnapshotInput{ + VolumeId: &newRootVolume, + Description: &description, + }) + if err != nil { + err := fmt.Errorf("Error creating snapshot: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Set the snapshot ID so we can delete it later + s.snapshotId = *createSnapResp.SnapshotId + ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId)) + + // Wait for the snapshot to be ready + stateChange := awscommon.StateChangeConf{ + Pending: []string{"pending"}, + StepState: state, + Target: "completed", + Refresh: func() (interface{}, string, error) { + resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{SnapshotIds: []*string{&s.snapshotId}}) + if err != nil { + return nil, "", err + } + + if len(resp.Snapshots) == 0 { + return nil, "", errors.New("No snapshots found.") + } + + s := resp.Snapshots[0] + return s, *s.State, nil + }, + } + + _, err = awscommon.WaitForState(&stateChange) + if err != nil { + err := fmt.Errorf("Error waiting for snapshot: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + state.Put("snapshot_id", s.snapshotId) + return multistep.ActionContinue +} + +func (s *StepSnapshotNewRootVolume) Cleanup(state multistep.StateBag) { + if s.snapshotId == "" { + return + } + + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if cancelled || halted { + ec2conn := state.Get("ec2").(*ec2.EC2) + ui := state.Get("ui").(packer.Ui) + ui.Say("Removing snapshot since we cancelled or halted...") + _, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{SnapshotId: &s.snapshotId}) + if err != nil { + ui.Error(fmt.Sprintf("Error: %s", err)) + } + } +} diff --git a/command/plugin.go b/command/plugin.go index ffc3ad404..8f09f0a74 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -15,6 +15,7 @@ import ( amazonchrootbuilder "github.com/mitchellh/packer/builder/amazon/chroot" amazonebsbuilder "github.com/mitchellh/packer/builder/amazon/ebs" + amazonebssurrogatebuilder "github.com/mitchellh/packer/builder/amazon/ebssurrogate" amazonebsvolumebuilder "github.com/mitchellh/packer/builder/amazon/ebsvolume" amazoninstancebuilder "github.com/mitchellh/packer/builder/amazon/instance" azurearmbuilder "github.com/mitchellh/packer/builder/azure/arm" @@ -72,29 +73,30 @@ type PluginCommand struct { } var Builders = map[string]packer.Builder{ - "amazon-chroot": new(amazonchrootbuilder.Builder), - "amazon-ebs": new(amazonebsbuilder.Builder), - "amazon-ebsvolume": new(amazonebsvolumebuilder.Builder), - "amazon-instance": new(amazoninstancebuilder.Builder), - "azure-arm": new(azurearmbuilder.Builder), - "cloudstack": new(cloudstackbuilder.Builder), - "digitalocean": new(digitaloceanbuilder.Builder), - "docker": new(dockerbuilder.Builder), - "file": new(filebuilder.Builder), - "googlecompute": new(googlecomputebuilder.Builder), - "hyperv-iso": new(hypervisobuilder.Builder), - "null": new(nullbuilder.Builder), - "oneandone": new(oneandonebuilder.Builder), - "openstack": new(openstackbuilder.Builder), - "parallels-iso": new(parallelsisobuilder.Builder), - "parallels-pvm": new(parallelspvmbuilder.Builder), - "profitbricks": new(profitbricksbuilder.Builder), - "qemu": new(qemubuilder.Builder), - "triton": new(tritonbuilder.Builder), - "virtualbox-iso": new(virtualboxisobuilder.Builder), - "virtualbox-ovf": new(virtualboxovfbuilder.Builder), - "vmware-iso": new(vmwareisobuilder.Builder), - "vmware-vmx": new(vmwarevmxbuilder.Builder), + "amazon-chroot": new(amazonchrootbuilder.Builder), + "amazon-ebs": new(amazonebsbuilder.Builder), + "amazon-ebsvolume": new(amazonebsvolumebuilder.Builder), + "amazon-ebssurrogate": new(amazonebssurrogatebuilder.Builder), + "amazon-instance": new(amazoninstancebuilder.Builder), + "azure-arm": new(azurearmbuilder.Builder), + "cloudstack": new(cloudstackbuilder.Builder), + "digitalocean": new(digitaloceanbuilder.Builder), + "docker": new(dockerbuilder.Builder), + "file": new(filebuilder.Builder), + "googlecompute": new(googlecomputebuilder.Builder), + "hyperv-iso": new(hypervisobuilder.Builder), + "null": new(nullbuilder.Builder), + "oneandone": new(oneandonebuilder.Builder), + "openstack": new(openstackbuilder.Builder), + "parallels-iso": new(parallelsisobuilder.Builder), + "parallels-pvm": new(parallelspvmbuilder.Builder), + "profitbricks": new(profitbricksbuilder.Builder), + "qemu": new(qemubuilder.Builder), + "triton": new(tritonbuilder.Builder), + "virtualbox-iso": new(virtualboxisobuilder.Builder), + "virtualbox-ovf": new(virtualboxovfbuilder.Builder), + "vmware-iso": new(vmwareisobuilder.Builder), + "vmware-vmx": new(vmwarevmxbuilder.Builder), } var Provisioners = map[string]packer.Provisioner{ diff --git a/website/source/docs/builders/amazon-ebssurrogate.html.md b/website/source/docs/builders/amazon-ebssurrogate.html.md new file mode 100644 index 000000000..6dae386a8 --- /dev/null +++ b/website/source/docs/builders/amazon-ebssurrogate.html.md @@ -0,0 +1,399 @@ +--- +description: | + The `amazon-ebssurrogate` Packer builder is like the chroot builder, but does + not require running inside an EC2 instance. +layout: docs +page_title: 'Amazon EBS Surrogate Builder' +... + +# EBS Surrogate Builder + +Type: `amazon-ebssurrogate` + +The `amazon-ebssurrogate` Packer builder is able to create Amazon AMIs by running +a source instance with an attached volume, provisioning the attached volume in such +a way that it can be used as the root volume for the AMI, and then snapshotting and +creating the AMI from that volume. + +This builder can therefore be used to bootstrap scratch-build images - for example +FreeBSD or Ubuntu using ZFS as the root file system. + +This is all done in your own AWS account. The builder will create temporary +key pairs, security group rules, etc. that provide it temporary access to the +instance while the image is being created. + +## Configuration Reference + +There are many configuration options available for the builder. They are +segmented below into two categories: required and optional parameters. Within +each category, the available configuration keys are alphabetized. + +In addition to the options listed here, a +[communicator](/docs/templates/communicator.html) can be configured for this +builder. + +### Required: + +- `access_key` (string) - The access key used to communicate with AWS. [Learn + how to set this.](/docs/builders/amazon.html#specifying-amazon-credentials) + +- `instance_type` (string) - The EC2 instance type to use while building the + AMI, such as `m1.small`. + +- `region` (string) - The name of the region, such as `us-east-1`, in which to + launch the EC2 instance to create the AMI. + +- `secret_key` (string) - The secret key used to communicate with AWS. [Learn + how to set this.](/docs/builders/amazon.html#specifying-amazon-credentials) + +- `source_ami` (string) - The initial AMI used as a base for the newly + created machine. `source_ami_filter` may be used instead to populate this + automatically. + +- `ami_root_device` (block device mapping) - A block device mapping describing + the root device of the AMI. This looks like the mappings in `ami_block_device_mapping`, + except with an additional field: + + - `source_device_name` (string) - The device name of the block device on the + source instance to be used as the root device for the AMI. This must correspond + to a block device in `launch_block_device_mapping`. + +### Optional: + +- `ami_block_device_mappings` (array of block device mappings) - Add one or + more [block device mappings](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html) + to the AMI. These will be attached when booting a new instance from your + AMI. To add a block device during the packer build see + `launch_block_device_mappings` below. Your options here may vary depending + on the type of VM you use. The block device mappings allow for the following + configuration: + + - `delete_on_termination` (boolean) - Indicates whether the EBS volume is + deleted on instance termination. Default `false`. **NOTE**: If this + value is not explicitly set to `true` and volumes are not cleaned up by + an alternative method, additional volumes will accumulate after + every build. + + - `device_name` (string) - The device name exposed to the instance (for + example, `/dev/sdh` or `xvdh`). Required when specifying `volume_size`. + + - `encrypted` (boolean) - Indicates whether to encrypt the volume or not + + - `iops` (integer) - The number of I/O operations per second (IOPS) that the + volume supports. See the documentation on + [IOPs](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html) + for more information + + - `no_device` (boolean) - Suppresses the specified device included in the + block device mapping of the AMI + + - `snapshot_id` (string) - The ID of the snapshot + + - `virtual_name` (string) - The virtual device name. See the documentation on + [Block Device + Mapping](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_BlockDeviceMapping.html) + for more information + + - `volume_size` (integer) - The size of the volume, in GiB. Required if not + specifying a `snapshot_id` + + - `volume_type` (string) - The volume type. `gp2` for General Purpose (SSD) + volumes, `io1` for Provisioned IOPS (SSD) volumes, and `standard` for Magnetic + volumes + +- `ami_description` (string) - The description to set for the + resulting AMI(s). By default this description is empty. This is a + [configuration template](/docs/templates/configuration-templates.html) + where the `SourceAMI` variable is replaced with the source AMI ID and + `BuildRegion` variable is replaced with the value of `region`. + +- `ami_groups` (array of strings) - A list of groups that have access to + launch the resulting AMI(s). By default no groups have permission to launch + the AMI. `all` will make the AMI publicly accessible. AWS currently doesn't + accept any value other than `all`. + +- `ami_product_codes` (array of strings) - A list of product codes to + associate with the AMI. By default no product codes are associated with + the AMI. + +- `ami_regions` (array of strings) - A list of regions to copy the AMI to. + Tags and attributes are copied along with the AMI. AMI copying takes time + depending on the size of the AMI, but will generally take many minutes. + +- `ami_users` (array of strings) - A list of account IDs that have access to + launch the resulting AMI(s). By default no additional users other than the + user creating the AMI has permissions to launch it. + +- `ami_virtualization_type` (string) - The type of virtualization for the AMI + you are building. This option must match the supported virtualization + type of `source_ami`. Can be `paravirtual` or `hvm`. + +- `associate_public_ip_address` (boolean) - If using a non-default VPC, public + IP addresses are not provided by default. If this is toggled, your new + instance will get a Public IP. + +- `availability_zone` (string) - Destination availability zone to launch + instance in. Leave this empty to allow Amazon to auto-assign. + +- `disable_stop_instance` (boolean) - Packer normally stops the build instance + after all provisioners have run. For Windows instances, it is sometimes + desirable to [run Sysprep](http://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ami-create-standard.html) + which will stop the instance for you. If this is set to true, Packer *will not* + stop the instance and will wait for you to stop it manually. You can do this + with a [windows-shell provisioner](https://www.packer.io/docs/provisioners/windows-shell.html). + + ``` {.javascript} + { + "type": "windows-shell", + "inline": ["\"c:\\Program Files\\Amazon\\Ec2ConfigService\\ec2config.exe\" -sysprep"] + } + ``` + +- `ebs_optimized` (boolean) - Mark instance as [EBS + Optimized](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSOptimized.html). + Default `false`. + +- `enhanced_networking` (boolean) - Enable enhanced + networking (SriovNetSupport) on HVM-compatible AMIs. If true, add + `ec2:ModifyInstanceAttribute` to your AWS IAM policy. + +- `force_deregister` (boolean) - Force Packer to first deregister an existing + AMI if one with the same name already exists. Default `false`. + +- `force_delete_snapshot` (boolean) - Force Packer to delete snapshots associated with + AMIs, which have been deregistered by `force_deregister`. Default `false`. + +- `encrypt_boot` (boolean) - Instruct packer to automatically create a copy of the + AMI with an encrypted boot volume (discarding the initial unencrypted AMI in the + process). Default `false`. + +- `kms_key_id` (string) - The ID of the KMS key to use for boot volume encryption. + This only applies to the main `region`, other regions where the AMI will be copied + will be encrypted by the default EBS KMS key. + +- `iam_instance_profile` (string) - The name of an [IAM instance + profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html) + to launch the EC2 instance with. + +- `launch_block_device_mappings` (array of block device mappings) - Add one or + more block devices before the packer build starts. These are not necessarily + preserved when booting from the AMI built with packer. See + `ami_block_device_mappings`, above, for details. + +- `run_tags` (object of key/value strings) - Tags to apply to the instance + that is *launched* to create the AMI. These tags are *not* applied to the + resulting AMI unless they're duplicated in `tags`. This is a + [configuration template](/docs/templates/configuration-templates.html) + where the `SourceAMI` variable is replaced with the source AMI ID and + `BuildRegion` variable is replaced with the value of `region`. + +- `run_volume_tags` (object of key/value strings) - Tags to apply to the volumes + that are *launched* to create the AMI. These tags are *not* applied to the + resulting AMI unless they're duplicated in `tags`. This is a + [configuration template](/docs/templates/configuration-templates.html) + where the `SourceAMI` variable is replaced with the source AMI ID and + `BuildRegion` variable is replaced with the value of `region`. + +- `security_group_id` (string) - The ID (*not* the name) of the security group + to assign to the instance. By default this is not set and Packer will + automatically create a new temporary security group to allow SSH access. + Note that if this is specified, you must be sure the security group allows + access to the `ssh_port` given below. + +- `security_group_ids` (array of strings) - A list of security groups as + described above. Note that if this is specified, you must omit the + `security_group_id`. + +- `shutdown_behavior` (string) - Automatically terminate instances on shutdown + incase packer exits ungracefully. Possible values are "stop" and "terminate", + default is `stop`. + +- `skip_region_validation` (boolean) - Set to true if you want to skip + validation of the region configuration option. Default `false`. + +- `snapshot_groups` (array of strings) - A list of groups that have access to + create volumes from the snapshot(s). By default no groups have permission to create + volumes form the snapshot(s). `all` will make the snapshot publicly accessible. + +- `snapshot_users` (array of strings) - A list of account IDs that have access to + create volumes from the snapshot(s). By default no additional users other than the + user creating the AMI has permissions to create volumes from the backing snapshot(s). + +- `snapshot_tags` (object of key/value strings) - Tags to apply to snapshot. + They will override AMI tags if already applied to snapshot. This is a + [configuration template](/docs/templates/configuration-templates.html) + where the `SourceAMI` variable is replaced with the source AMI ID and + `BuildRegion` variable is replaced with the value of `region`. + +- `source_ami_filter` (object) - Filters used to populate the `source_ami` field. + Example: + + ``` {.javascript} + "source_ami_filter": { + "filters": { + "virtualization-type": "hvm", + "name": "*ubuntu-xenial-16.04-amd64-server-*", + "root-device-type": "ebs" + }, + "owners": ["099720109477"], + "most_recent": true + } + ``` + + This selects the most recent Ubuntu 16.04 HVM EBS AMI from Canonical. + NOTE: This will fail unless *exactly* one AMI is returned. In the above + example, `most_recent` will cause this to succeed by selecting the newest image. + + - `filters` (map of strings) - filters used to select a `source_ami`. + NOTE: This will fail unless *exactly* one AMI is returned. + Any filter described in the docs for [DescribeImages](http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeImages.html) + is valid. + + - `owners` (array of strings) - This scopes the AMIs to certain Amazon account IDs. + This is helpful to limit the AMIs to a trusted third party, or to your own account. + + - `most_recent` (bool) - Selects the newest created image when true. + This is most useful for selecting a daily distro build. + +- `spot_price` (string) - The maximum hourly price to pay for a spot instance + to create the AMI. Spot instances are a type of instance that EC2 starts + when the current spot price is less than the maximum price you specify. Spot + price will be updated based on available spot instance capacity and current + spot instance requests. It may save you some costs. You can set this to + `auto` for Packer to automatically discover the best spot price or to "0" + to use an on demand instance (default). + +- `spot_price_auto_product` (string) - Required if `spot_price` is set + to `auto`. This tells Packer what sort of AMI you're launching to find the + best spot price. This must be one of: `Linux/UNIX`, `SUSE Linux`, `Windows`, + `Linux/UNIX (Amazon VPC)`, `SUSE Linux (Amazon VPC)`, `Windows (Amazon VPC)` + +- `ssh_keypair_name` (string) - If specified, this is the key that will be + used for SSH with the machine. The key must match a key pair name loaded + up into Amazon EC2. By default, this is blank, and Packer will + generate a temporary keypair unless + [`ssh_password`](/docs/templates/communicator.html#ssh_password) is used. + [`ssh_private_key_file`](/docs/templates/communicator.html#ssh_private_key_file) + or `ssh_agent_auth` must be specified when `ssh_keypair_name` is utilized. + +- `ssh_agent_auth` (boolean) - If true, the local SSH agent will be used to + authenticate connections to the source instance. No temporary keypair will + be created, and the values of `ssh_password` and `ssh_private_key_file` will + be ignored. To use this option with a key pair already configured in the source + AMI, leave the `ssh_keypair_name` blank. To associate an existing key pair + in AWS with the source instance, set the `ssh_keypair_name` field to the name + of the key pair. + +- `ssh_private_ip` (boolean) - If true, then SSH will always use the private + IP if available. + +- `subnet_id` (string) - If using VPC, the ID of the subnet, such as + `subnet-12345def`, where Packer will launch the EC2 instance. This field is + required if you are using an non-default VPC. + +- `tags` (object of key/value strings) - Tags applied to the AMI and + relevant snapshots. This is a + [configuration template](/docs/templates/configuration-templates.html) + where the `SourceAMI` variable is replaced with the source AMI ID and + `BuildRegion` variable is replaced with the value of `region`. + +- `temporary_key_pair_name` (string) - The name of the temporary keypair + to generate. By default, Packer generates a name with a UUID. + +- `token` (string) - The access token to use. This is different from the + access key and secret key. If you're not sure what this is, then you + probably don't need it. This will also be read from the `AWS_SESSION_TOKEN` + environmental variable. + +- `user_data` (string) - User data to apply when launching the instance. Note + that you need to be careful about escaping characters due to the templates + being JSON. It is often more convenient to use `user_data_file`, instead. + +- `user_data_file` (string) - Path to a file that will be used for the user + data when launching the instance. + +- `vpc_id` (string) - If launching into a VPC subnet, Packer needs the VPC ID + in order to create a temporary security group within the VPC. Requires `subnet_id` + to be set. + +- `windows_password_timeout` (string) - The timeout for waiting for a Windows + password for Windows instances. Defaults to 20 minutes. Example value: `10m` + +## Basic Example + +Here is a basic example. You will need to provide access keys, and may need to +change the AMI IDs according to what images exist at the time the template is run: + +``` {.javascript} +{ + "type": "amazon-ebs", + "access_key": "YOUR KEY HERE", + "secret_key": "YOUR SECRET KEY HERE", + "region": "us-east-1", + "source_ami": "ami-fce3c696", + "instance_type": "t2.micro", + "ssh_username": "ubuntu", + "ami_name": "packer-quick-start {{timestamp}}" +} +``` + +-> **Note:** Packer can also read the access key and secret access key from +environmental variables. See the configuration reference in the section above +for more information on what environmental variables Packer will look for. + +Further information on locating AMI IDs and their relationship to instance types +and regions can be found in the AWS EC2 Documentation +[for Linux](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/finding-an-ami.html) +or [for Windows](http://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/finding-an-ami.html). + +## Basic Example + +``` {.javascript} +{ + "type" : "amazon-surrogate", + "secret_key" : "YOUR SECRET KEY HERE", + "access_key" : "YOUR KEY HERE", + "region" : "us-east-1", + "ssh_username" : "ubuntu", + "instance_type" : "t2.medium", + "source_ami" : "ami-40d28157", + "launch_block_device_mappings" : [ + { + "volume_type" : "gp2", + "device_name" : "/dev/xvdf", + "delete_on_termination" : false, + "volume_size" : 10 + }, + ], + "ami_root_device": { + "source_device_name": "/dev/xvdf", + "device_name": "/dev/xvda", + "delete_on_termination": true, + "volume_size": 16, + "volume_type": "gp2" + } + +} +``` + +-> **Note:** Packer can also read the access key and secret access key from +environmental variables. See the configuration reference in the section above +for more information on what environmental variables Packer will look for. + +Further information on locating AMI IDs and their relationship to instance +types and regions can be found in the AWS EC2 Documentation +[for Linux](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/finding-an-ami.html) +or [for Windows](http://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/finding-an-ami.html). + +## Accessing the Instance to Debug + +If you need to access the instance to debug for some reason, run the builder +with the `-debug` flag. In debug mode, the Amazon builder will save the private +key in the current directory and will output the DNS or IP information as well. +You can use this information to access the instance as it is running. + +-> **Note:** Packer uses pre-built AMIs as the source for building images. +These source AMIs may include volumes that are not flagged to be destroyed on +termination of the instance building the new image. In addition to those volumes +created by this builder, any volumes inn the source AMI which are not marked for +deletion on termination will remain in your account. diff --git a/website/source/docs/builders/amazon.html.md b/website/source/docs/builders/amazon.html.md index bcbd4a47c..9d02ba2dc 100644 --- a/website/source/docs/builders/amazon.html.md +++ b/website/source/docs/builders/amazon.html.md @@ -27,6 +27,11 @@ Packer supports the following builders at the moment: that device. This is an **advanced builder and should not be used by newcomers**. However, it is also the fastest way to build an EBS-backed AMI since no new EC2 instance needs to be launched. + +- [amazon-ebssurrogate](/docs/builders/amazone-ebssurrogate.html) - Create EBS + -backed AMIs from scratch. Works similarly to the `chroot` builder but does + not require running in AWS. This is an **advanced builder and should not be + used by newcomers**. -> **Don't know which builder to use?** If in doubt, use the [amazon-ebs builder](/docs/builders/amazon-ebs.html). It is much easier to use and Amazon