2019-05-31 08:27:41 -04:00
|
|
|
//go:generate struct-markdown
|
2019-10-14 10:43:59 -04:00
|
|
|
//go:generate mapstructure-to-hcl2 -type BlockDevice
|
2019-05-31 08:27:41 -04:00
|
|
|
|
2013-08-15 17:05:08 -04:00
|
|
|
package common
|
|
|
|
|
|
|
|
import (
|
2018-01-12 18:10:51 -05:00
|
|
|
"fmt"
|
2015-07-15 20:07:36 -04:00
|
|
|
"strings"
|
|
|
|
|
2015-06-03 17:13:52 -04:00
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
2020-12-17 16:29:25 -05:00
|
|
|
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
|
|
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
2013-08-15 17:05:08 -04:00
|
|
|
)
|
|
|
|
|
2020-11-04 14:29:09 -05:00
|
|
|
const (
|
2020-12-09 07:06:57 -05:00
|
|
|
minIops = 100
|
|
|
|
maxIops = 64000
|
|
|
|
minIopsGp3 = 3000
|
|
|
|
maxIopsGp3 = 16000
|
|
|
|
minThroughput = 125
|
|
|
|
maxThroughput = 1000
|
2020-11-04 14:29:09 -05:00
|
|
|
)
|
|
|
|
|
2020-08-14 05:35:35 -04:00
|
|
|
// These will be attached when launching your instance. Your
|
2019-09-25 19:48:04 -04:00
|
|
|
// options here may vary depending on the type of VM you use.
|
|
|
|
//
|
|
|
|
// Example use case:
|
|
|
|
//
|
|
|
|
// The following mapping will tell Packer to encrypt the root volume of the
|
|
|
|
// build instance at launch using a specific non-default kms key:
|
2019-06-18 11:05:10 -04:00
|
|
|
//
|
2020-07-20 10:34:13 -04:00
|
|
|
// JSON example:
|
2020-07-17 18:52:11 -04:00
|
|
|
//
|
2020-03-12 10:05:08 -04:00
|
|
|
// ```json
|
2020-08-14 05:35:35 -04:00
|
|
|
// launch_block_device_mappings: [
|
2020-07-17 18:52:11 -04:00
|
|
|
// {
|
|
|
|
// "device_name": "/dev/sda1",
|
|
|
|
// "encrypted": true,
|
|
|
|
// "kms_key_id": "1a2b3c4d-5e6f-1a2b-3c4d-5e6f1a2b3c4d"
|
|
|
|
// }
|
|
|
|
// ]
|
|
|
|
// ```
|
|
|
|
//
|
2020-07-20 10:34:13 -04:00
|
|
|
// HCL2 example:
|
2020-07-17 18:52:11 -04:00
|
|
|
//
|
|
|
|
// ```hcl
|
2020-08-14 05:35:35 -04:00
|
|
|
// launch_block_device_mappings {
|
2020-07-17 18:52:11 -04:00
|
|
|
// device_name = "/dev/sda1"
|
|
|
|
// encrypted = true
|
|
|
|
// kms_key_id = "1a2b3c4d-5e6f-1a2b-3c4d-5e6f1a2b3c4d"
|
|
|
|
// }
|
|
|
|
// ```
|
|
|
|
//
|
2020-08-14 05:35:35 -04:00
|
|
|
// Please note that the kms_key_id option in this example exists for
|
|
|
|
// launch_block_device_mappings but not ami_block_device_mappings.
|
|
|
|
//
|
2019-06-18 11:37:33 -04:00
|
|
|
// Documentation for Block Devices Mappings can be found here:
|
|
|
|
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html
|
2019-09-25 19:48:04 -04:00
|
|
|
//
|
2013-08-15 17:05:08 -04:00
|
|
|
type BlockDevice struct {
|
2019-06-18 05:55:12 -04:00
|
|
|
// 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.
|
2019-06-03 11:55:09 -04:00
|
|
|
DeleteOnTermination bool `mapstructure:"delete_on_termination" required:"false"`
|
2019-06-18 05:55:12 -04:00
|
|
|
// The device name exposed to the instance (for example, /dev/sdh or xvdh).
|
|
|
|
// Required for every device in the block device mapping.
|
2019-06-03 11:55:09 -04:00
|
|
|
DeviceName string `mapstructure:"device_name" required:"false"`
|
2019-06-18 05:55:12 -04:00
|
|
|
// Indicates whether or not to encrypt the volume. By default, Packer will
|
|
|
|
// keep the encryption setting to what it was in the source image. Setting
|
|
|
|
// false will result in an unencrypted device, and true will result in an
|
|
|
|
// encrypted one.
|
2019-09-20 04:54:38 -04:00
|
|
|
Encrypted config.Trilean `mapstructure:"encrypted" required:"false"`
|
2019-06-18 05:55:12 -04:00
|
|
|
// The number of I/O operations per second (IOPS) that the volume supports.
|
2019-08-26 10:56:57 -04:00
|
|
|
// See the documentation on
|
|
|
|
// [IOPs](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html)
|
|
|
|
// for more information
|
2019-06-03 11:55:09 -04:00
|
|
|
IOPS int64 `mapstructure:"iops" required:"false"`
|
2019-06-18 05:55:12 -04:00
|
|
|
// Suppresses the specified device included in the block device mapping of
|
|
|
|
// the AMI.
|
2019-06-03 11:55:09 -04:00
|
|
|
NoDevice bool `mapstructure:"no_device" required:"false"`
|
2019-05-28 11:50:58 -04:00
|
|
|
// The ID of the snapshot.
|
2019-06-03 11:55:09 -04:00
|
|
|
SnapshotId string `mapstructure:"snapshot_id" required:"false"`
|
2020-12-09 07:06:57 -05:00
|
|
|
// The throughput for gp3 volumes, only valid for gp3 types
|
|
|
|
// See the documentation on
|
|
|
|
// [Throughput](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html)
|
|
|
|
// for more information
|
|
|
|
Throughput int64 `mapstructure:"throughput" required:"false"`
|
2019-06-18 05:55:12 -04:00
|
|
|
// The virtual device name. See the documentation on Block Device Mapping
|
2019-06-03 11:55:09 -04:00
|
|
|
// for more information.
|
|
|
|
VirtualName string `mapstructure:"virtual_name" required:"false"`
|
2020-12-09 07:06:57 -05:00
|
|
|
// The volume type. gp2 & gp3 for General Purpose (SSD) volumes, io1 & io2
|
|
|
|
// for Provisioned IOPS (SSD) volumes, st1 for Throughput Optimized HDD,
|
|
|
|
// sc1 for Cold HDD, and standard for Magnetic volumes.
|
2019-06-03 11:55:09 -04:00
|
|
|
VolumeType string `mapstructure:"volume_type" required:"false"`
|
2019-06-18 05:55:12 -04:00
|
|
|
// The size of the volume, in GiB. Required if not specifying a
|
|
|
|
// snapshot_id.
|
2019-06-03 11:55:09 -04:00
|
|
|
VolumeSize int64 `mapstructure:"volume_size" required:"false"`
|
2020-08-14 05:35:35 -04:00
|
|
|
// ID, alias or ARN of the KMS key to use for boot volume encryption.
|
|
|
|
// This option exists for launch_block_device_mappings but not
|
|
|
|
// ami_block_device_mappings. The kms key id defined here only applies to
|
|
|
|
// the original build region; if the AMI gets copied to other regions, the
|
|
|
|
// volume in those regions will be encrypted by the default EBS KMS key.
|
|
|
|
// For valid formats see KmsKeyId in the [AWS API docs -
|
2019-08-21 06:28:34 -04:00
|
|
|
// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html)
|
2020-08-14 05:35:35 -04:00
|
|
|
// This field is validated by Packer. When using an alias, you will have to
|
2019-08-21 06:28:34 -04:00
|
|
|
// prefix kms_key_id with alias/.
|
|
|
|
KmsKeyId string `mapstructure:"kms_key_id" required:"false"`
|
2013-08-15 17:05:08 -04:00
|
|
|
}
|
|
|
|
|
2019-06-18 06:37:47 -04:00
|
|
|
type BlockDevices []BlockDevice
|
2016-08-12 19:29:55 -04:00
|
|
|
|
2019-06-18 06:44:24 -04:00
|
|
|
func (bds BlockDevices) BuildEC2BlockDeviceMappings() []*ec2.BlockDeviceMapping {
|
2015-04-05 17:58:48 -04:00
|
|
|
var blockDevices []*ec2.BlockDeviceMapping
|
2013-08-15 17:05:08 -04:00
|
|
|
|
2019-06-18 06:37:47 -04:00
|
|
|
for _, blockDevice := range bds {
|
2019-06-18 06:49:54 -04:00
|
|
|
blockDevices = append(blockDevices, blockDevice.BuildEC2BlockDeviceMapping())
|
|
|
|
}
|
|
|
|
return blockDevices
|
|
|
|
}
|
|
|
|
|
|
|
|
func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapping {
|
2015-06-09 12:38:53 -04:00
|
|
|
|
2019-06-18 06:49:54 -04:00
|
|
|
mapping := &ec2.BlockDeviceMapping{
|
|
|
|
DeviceName: aws.String(blockDevice.DeviceName),
|
|
|
|
}
|
|
|
|
|
|
|
|
if blockDevice.NoDevice {
|
|
|
|
mapping.NoDevice = aws.String("")
|
|
|
|
return mapping
|
|
|
|
} else if blockDevice.VirtualName != "" {
|
|
|
|
if strings.HasPrefix(blockDevice.VirtualName, "ephemeral") {
|
|
|
|
mapping.VirtualName = aws.String(blockDevice.VirtualName)
|
2015-04-05 17:58:48 -04:00
|
|
|
}
|
2019-06-18 06:49:54 -04:00
|
|
|
return mapping
|
|
|
|
}
|
2015-04-05 17:58:48 -04:00
|
|
|
|
2019-06-18 06:49:54 -04:00
|
|
|
ebsBlockDevice := &ec2.EbsBlockDevice{
|
|
|
|
DeleteOnTermination: aws.Bool(blockDevice.DeleteOnTermination),
|
2013-08-15 17:05:08 -04:00
|
|
|
}
|
2019-06-18 06:49:54 -04:00
|
|
|
|
|
|
|
if blockDevice.VolumeType != "" {
|
|
|
|
ebsBlockDevice.VolumeType = aws.String(blockDevice.VolumeType)
|
|
|
|
}
|
|
|
|
|
|
|
|
if blockDevice.VolumeSize > 0 {
|
|
|
|
ebsBlockDevice.VolumeSize = aws.Int64(blockDevice.VolumeSize)
|
|
|
|
}
|
|
|
|
|
2020-12-09 07:06:57 -05:00
|
|
|
switch blockDevice.VolumeType {
|
|
|
|
case "io1", "io2", "gp3":
|
2019-06-18 06:49:54 -04:00
|
|
|
ebsBlockDevice.Iops = aws.Int64(blockDevice.IOPS)
|
|
|
|
}
|
|
|
|
|
2020-12-09 07:06:57 -05:00
|
|
|
// Throughput is only valid for gp3 types
|
|
|
|
if blockDevice.VolumeType == "gp3" {
|
|
|
|
ebsBlockDevice.Throughput = aws.Int64(blockDevice.Throughput)
|
|
|
|
}
|
|
|
|
|
2019-06-18 06:49:54 -04:00
|
|
|
// You cannot specify Encrypted if you specify a Snapshot ID
|
|
|
|
if blockDevice.SnapshotId != "" {
|
|
|
|
ebsBlockDevice.SnapshotId = aws.String(blockDevice.SnapshotId)
|
|
|
|
}
|
2019-09-20 04:54:38 -04:00
|
|
|
ebsBlockDevice.Encrypted = blockDevice.Encrypted.ToBoolPointer()
|
|
|
|
|
|
|
|
if blockDevice.KmsKeyId != "" {
|
|
|
|
ebsBlockDevice.KmsKeyId = aws.String(blockDevice.KmsKeyId)
|
|
|
|
}
|
2019-06-18 06:49:54 -04:00
|
|
|
|
|
|
|
mapping.Ebs = ebsBlockDevice
|
|
|
|
|
|
|
|
return mapping
|
2013-08-15 17:05:08 -04:00
|
|
|
}
|
|
|
|
|
2020-10-31 08:10:51 -04:00
|
|
|
var iopsRatios = map[string]int64{
|
|
|
|
"io1": 50,
|
|
|
|
"io2": 500,
|
|
|
|
}
|
|
|
|
|
2018-01-12 18:10:51 -05:00
|
|
|
func (b *BlockDevice) Prepare(ctx *interpolate.Context) error {
|
2018-10-12 16:01:13 -04:00
|
|
|
if b.DeviceName == "" {
|
|
|
|
return fmt.Errorf("The `device_name` must be specified " +
|
|
|
|
"for every device in the block device mapping.")
|
|
|
|
}
|
2019-08-22 17:24:22 -04:00
|
|
|
|
2019-07-12 17:59:11 -04:00
|
|
|
// Warn that encrypted must be true or nil when setting kms_key_id
|
2019-08-22 17:24:22 -04:00
|
|
|
if b.KmsKeyId != "" && b.Encrypted.False() {
|
2018-01-12 18:10:51 -05:00
|
|
|
return fmt.Errorf("The device %v, must also have `encrypted: "+
|
|
|
|
"true` when setting a kms_key_id.", b.DeviceName)
|
|
|
|
}
|
2019-05-03 12:39:52 -04:00
|
|
|
|
2020-10-31 08:10:51 -04:00
|
|
|
if ratio, ok := iopsRatios[b.VolumeType]; b.VolumeSize != 0 && ok {
|
|
|
|
if b.IOPS/b.VolumeSize > ratio {
|
2020-11-04 14:29:09 -05:00
|
|
|
return fmt.Errorf("%s: the maximum ratio of provisioned IOPS to requested volume size "+
|
|
|
|
"(in GiB) is %v:1 for %s volumes", b.DeviceName, ratio, b.VolumeType)
|
2020-10-31 08:10:51 -04:00
|
|
|
}
|
|
|
|
|
2020-11-04 14:49:04 -05:00
|
|
|
if b.IOPS < minIops || b.IOPS > maxIops {
|
|
|
|
return fmt.Errorf("IOPS must be between %d and %d for device %s",
|
|
|
|
minIops, maxIops, b.DeviceName)
|
|
|
|
}
|
2020-11-04 14:29:09 -05:00
|
|
|
}
|
|
|
|
|
2020-12-09 07:06:57 -05:00
|
|
|
if b.VolumeType == "gp3" {
|
|
|
|
if b.Throughput < minThroughput || b.Throughput > maxThroughput {
|
|
|
|
return fmt.Errorf("Throughput must be between %d and %d for device %s",
|
|
|
|
minThroughput, maxThroughput, b.DeviceName)
|
|
|
|
}
|
|
|
|
|
|
|
|
if b.IOPS < minIopsGp3 || b.IOPS > maxIopsGp3 {
|
|
|
|
return fmt.Errorf("IOPS must be between %d and %d for device %s",
|
|
|
|
minIopsGp3, maxIopsGp3, b.DeviceName)
|
|
|
|
}
|
|
|
|
} else if b.Throughput > 0 {
|
|
|
|
return fmt.Errorf("Throughput is not available for device %s",
|
|
|
|
b.DeviceName)
|
|
|
|
}
|
|
|
|
|
2019-08-27 09:17:57 -04:00
|
|
|
_, err := interpolate.RenderInterface(&b, ctx)
|
|
|
|
return err
|
2014-09-05 15:38:19 -04:00
|
|
|
}
|
|
|
|
|
2019-06-18 06:37:47 -04:00
|
|
|
func (bds BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
|
|
|
|
for _, block := range bds {
|
|
|
|
if err := block.Prepare(ctx); err != nil {
|
|
|
|
errs = append(errs, err)
|
2018-01-12 18:10:51 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return errs
|
|
|
|
}
|