mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
provider/aws: Support aws_instance and volume tagging on creation (#13945)
Fixes: #13173 We now tag at instance creation and introduced `volume_tags` that can be set so that all devices created on instance creation will receive those tags ``` % make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSInstance_volumeTags' 2 ↵ ✚ ✭ ==> Checking that code complies with gofmt requirements... go generate $(go list ./... | grep -v /terraform/vendor/) 2017/04/26 06:30:48 Generated command/internal_plugin_list.go TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSInstance_volumeTags -timeout 120m === RUN TestAccAWSInstance_volumeTags --- PASS: TestAccAWSInstance_volumeTags (214.31s) PASS ok github.com/hashicorp/terraform/builtin/providers/aws 214.332s ```
This commit is contained in:
parent
0e0a5150ff
commit
f4015b43c5
@ -200,6 +200,8 @@ func resourceAwsInstance() *schema.Resource {
|
||||
|
||||
"tags": tagsSchema(),
|
||||
|
||||
"volume_tags": tagsSchema(),
|
||||
|
||||
"block_device": {
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
@ -396,6 +398,34 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
runOpts.Ipv6Addresses = ipv6Addresses
|
||||
}
|
||||
|
||||
tagsSpec := make([]*ec2.TagSpecification, 0)
|
||||
|
||||
if v, ok := d.GetOk("tags"); ok {
|
||||
tags := tagsFromMap(v.(map[string]interface{}))
|
||||
|
||||
spec := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("instance"),
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
tagsSpec = append(tagsSpec, spec)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("volume_tags"); ok {
|
||||
tags := tagsFromMap(v.(map[string]interface{}))
|
||||
|
||||
spec := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("volume"),
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
tagsSpec = append(tagsSpec, spec)
|
||||
}
|
||||
|
||||
if len(tagsSpec) > 0 {
|
||||
runOpts.TagSpecifications = tagsSpec
|
||||
}
|
||||
|
||||
// Create the instance
|
||||
log.Printf("[DEBUG] Run configuration: %s", runOpts)
|
||||
|
||||
@ -563,6 +593,10 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
||||
|
||||
d.Set("tags", tagsToMap(instance.Tags))
|
||||
|
||||
if err := readVolumeTags(conn, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := readSecurityGroups(d, instance); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -605,16 +639,27 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
conn := meta.(*AWSClient).ec2conn
|
||||
|
||||
d.Partial(true)
|
||||
if err := setTags(conn, d); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.SetPartial("tags")
|
||||
|
||||
if d.HasChange("tags") && !d.IsNewResource() {
|
||||
if err := setTags(conn, d); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.SetPartial("tags")
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange("volume_tags") && !d.IsNewResource() {
|
||||
if err := setVolumeTags(conn, d); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.SetPartial("volume_tags")
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange("iam_instance_profile") && !d.IsNewResource() {
|
||||
request := &ec2.DescribeIamInstanceProfileAssociationsInput{
|
||||
Filters: []*ec2.Filter{
|
||||
&ec2.Filter{
|
||||
{
|
||||
Name: aws.String("instance-id"),
|
||||
Values: []*string{aws.String(d.Id())},
|
||||
},
|
||||
@ -1125,6 +1170,39 @@ func readBlockDeviceMappingsFromConfig(
|
||||
return blockDevices, nil
|
||||
}
|
||||
|
||||
func readVolumeTags(conn *ec2.EC2, d *schema.ResourceData) error {
|
||||
volumeIds, err := getAwsInstanceVolumeIds(conn, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tagsResp, err := conn.DescribeTags(&ec2.DescribeTagsInput{
|
||||
Filters: []*ec2.Filter{
|
||||
{
|
||||
Name: aws.String("resource-id"),
|
||||
Values: volumeIds,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var tags []*ec2.Tag
|
||||
|
||||
for _, t := range tagsResp.Tags {
|
||||
tag := &ec2.Tag{
|
||||
Key: t.Key,
|
||||
Value: t.Value,
|
||||
}
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
d.Set("volume_tags", tagsToMap(tags))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Determine whether we're referring to security groups with
|
||||
// IDs or names. We use a heuristic to figure this out. By default,
|
||||
// we use IDs if we're in a VPC. However, if we previously had an
|
||||
@ -1372,3 +1450,27 @@ func userDataHashSum(user_data string) string {
|
||||
hash := sha1.Sum(v)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func getAwsInstanceVolumeIds(conn *ec2.EC2, d *schema.ResourceData) ([]*string, error) {
|
||||
volumeIds := make([]*string, 0)
|
||||
|
||||
opts := &ec2.DescribeVolumesInput{
|
||||
Filters: []*ec2.Filter{
|
||||
{
|
||||
Name: aws.String("attachment.instance-id"),
|
||||
Values: []*string{aws.String(d.Id())},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := conn.DescribeVolumes(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range resp.Volumes {
|
||||
volumeIds = append(volumeIds, v.VolumeId)
|
||||
}
|
||||
|
||||
return volumeIds, nil
|
||||
}
|
||||
|
@ -15,13 +15,13 @@ func resourceAwsInstanceMigrateState(
|
||||
switch v {
|
||||
case 0:
|
||||
log.Println("[INFO] Found AWS Instance State v0; migrating to v1")
|
||||
return migrateStateV0toV1(is)
|
||||
return migrateAwsInstanceStateV0toV1(is)
|
||||
default:
|
||||
return is, fmt.Errorf("Unexpected schema version: %d", v)
|
||||
}
|
||||
}
|
||||
|
||||
func migrateStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) {
|
||||
func migrateAwsInstanceStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) {
|
||||
if is.Empty() || is.Attributes == nil {
|
||||
log.Println("[DEBUG] Empty InstanceState; nothing to migrate.")
|
||||
return is, nil
|
||||
|
@ -616,7 +616,6 @@ func TestAccAWSInstance_tags(t *testing.T) {
|
||||
testAccCheckTags(&v.Tags, "#", ""),
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
Config: testAccCheckInstanceConfigTagsUpdate,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
@ -629,6 +628,56 @@ func TestAccAWSInstance_tags(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSInstance_volumeTags(t *testing.T) {
|
||||
var v ec2.Instance
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckInstanceDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckInstanceConfigNoVolumeTags,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckInstanceExists("aws_instance.foo", &v),
|
||||
resource.TestCheckNoResourceAttr(
|
||||
"aws_instance.foo", "volume_tags"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccCheckInstanceConfigWithVolumeTags,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckInstanceExists("aws_instance.foo", &v),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_instance.foo", "volume_tags.%", "1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_instance.foo", "volume_tags.Name", "acceptance-test-volume-tag"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccCheckInstanceConfigWithVolumeTagsUpdate,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckInstanceExists("aws_instance.foo", &v),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_instance.foo", "volume_tags.%", "2"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_instance.foo", "volume_tags.Name", "acceptance-test-volume-tag"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_instance.foo", "volume_tags.Environment", "dev"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccCheckInstanceConfigNoVolumeTags,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckInstanceExists("aws_instance.foo", &v),
|
||||
resource.TestCheckNoResourceAttr(
|
||||
"aws_instance.foo", "volume_tags"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSInstance_instanceProfileChange(t *testing.T) {
|
||||
var v ec2.Instance
|
||||
rName := acctest.RandString(5)
|
||||
@ -1281,6 +1330,117 @@ resource "aws_instance" "foo" {
|
||||
}
|
||||
`
|
||||
|
||||
const testAccCheckInstanceConfigNoVolumeTags = `
|
||||
resource "aws_instance" "foo" {
|
||||
ami = "ami-55a7ea65"
|
||||
|
||||
instance_type = "m3.medium"
|
||||
|
||||
root_block_device {
|
||||
volume_type = "gp2"
|
||||
volume_size = 11
|
||||
}
|
||||
ebs_block_device {
|
||||
device_name = "/dev/sdb"
|
||||
volume_size = 9
|
||||
}
|
||||
ebs_block_device {
|
||||
device_name = "/dev/sdc"
|
||||
volume_size = 10
|
||||
volume_type = "io1"
|
||||
iops = 100
|
||||
}
|
||||
|
||||
ebs_block_device {
|
||||
device_name = "/dev/sdd"
|
||||
volume_size = 12
|
||||
encrypted = true
|
||||
}
|
||||
|
||||
ephemeral_block_device {
|
||||
device_name = "/dev/sde"
|
||||
virtual_name = "ephemeral0"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const testAccCheckInstanceConfigWithVolumeTags = `
|
||||
resource "aws_instance" "foo" {
|
||||
ami = "ami-55a7ea65"
|
||||
|
||||
instance_type = "m3.medium"
|
||||
|
||||
root_block_device {
|
||||
volume_type = "gp2"
|
||||
volume_size = 11
|
||||
}
|
||||
ebs_block_device {
|
||||
device_name = "/dev/sdb"
|
||||
volume_size = 9
|
||||
}
|
||||
ebs_block_device {
|
||||
device_name = "/dev/sdc"
|
||||
volume_size = 10
|
||||
volume_type = "io1"
|
||||
iops = 100
|
||||
}
|
||||
|
||||
ebs_block_device {
|
||||
device_name = "/dev/sdd"
|
||||
volume_size = 12
|
||||
encrypted = true
|
||||
}
|
||||
|
||||
ephemeral_block_device {
|
||||
device_name = "/dev/sde"
|
||||
virtual_name = "ephemeral0"
|
||||
}
|
||||
|
||||
volume_tags {
|
||||
Name = "acceptance-test-volume-tag"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const testAccCheckInstanceConfigWithVolumeTagsUpdate = `
|
||||
resource "aws_instance" "foo" {
|
||||
ami = "ami-55a7ea65"
|
||||
|
||||
instance_type = "m3.medium"
|
||||
|
||||
root_block_device {
|
||||
volume_type = "gp2"
|
||||
volume_size = 11
|
||||
}
|
||||
ebs_block_device {
|
||||
device_name = "/dev/sdb"
|
||||
volume_size = 9
|
||||
}
|
||||
ebs_block_device {
|
||||
device_name = "/dev/sdc"
|
||||
volume_size = 10
|
||||
volume_type = "io1"
|
||||
iops = 100
|
||||
}
|
||||
|
||||
ebs_block_device {
|
||||
device_name = "/dev/sdd"
|
||||
volume_size = 12
|
||||
encrypted = true
|
||||
}
|
||||
|
||||
ephemeral_block_device {
|
||||
device_name = "/dev/sde"
|
||||
virtual_name = "ephemeral0"
|
||||
}
|
||||
|
||||
volume_tags {
|
||||
Name = "acceptance-test-volume-tag"
|
||||
Environment = "dev"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const testAccCheckInstanceConfigTagsUpdate = `
|
||||
resource "aws_instance" "foo" {
|
||||
ami = "ami-4fccb37f"
|
||||
|
@ -69,6 +69,63 @@ func setElbV2Tags(conn *elbv2.ELBV2, d *schema.ResourceData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setVolumeTags(conn *ec2.EC2, d *schema.ResourceData) error {
|
||||
if d.HasChange("volume_tags") {
|
||||
oraw, nraw := d.GetChange("volume_tags")
|
||||
o := oraw.(map[string]interface{})
|
||||
n := nraw.(map[string]interface{})
|
||||
create, remove := diffTags(tagsFromMap(o), tagsFromMap(n))
|
||||
|
||||
volumeIds, err := getAwsInstanceVolumeIds(conn, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(remove) > 0 {
|
||||
err := resource.Retry(2*time.Minute, func() *resource.RetryError {
|
||||
log.Printf("[DEBUG] Removing volume tags: %#v from %s", remove, d.Id())
|
||||
_, err := conn.DeleteTags(&ec2.DeleteTagsInput{
|
||||
Resources: volumeIds,
|
||||
Tags: remove,
|
||||
})
|
||||
if err != nil {
|
||||
ec2err, ok := err.(awserr.Error)
|
||||
if ok && strings.Contains(ec2err.Code(), ".NotFound") {
|
||||
return resource.RetryableError(err) // retry
|
||||
}
|
||||
return resource.NonRetryableError(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(create) > 0 {
|
||||
err := resource.Retry(2*time.Minute, func() *resource.RetryError {
|
||||
log.Printf("[DEBUG] Creating vol tags: %s for %s", create, d.Id())
|
||||
_, err := conn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: volumeIds,
|
||||
Tags: create,
|
||||
})
|
||||
if err != nil {
|
||||
ec2err, ok := err.(awserr.Error)
|
||||
if ok && strings.Contains(ec2err.Code(), ".NotFound") {
|
||||
return resource.RetryableError(err) // retry
|
||||
}
|
||||
return resource.NonRetryableError(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setTags is a helper to set the tags for a resource. It expects the
|
||||
// tags field to be named "tags"
|
||||
func setTags(conn *ec2.EC2, d *schema.ResourceData) error {
|
||||
|
@ -80,6 +80,7 @@ instances. See [Shutdown Behavior](https://docs.aws.amazon.com/AWSEC2/latest/Use
|
||||
* `ipv6_address_count`- (Optional) A number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet.
|
||||
* `ipv6_addresses` - (Optional) Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface
|
||||
* `tags` - (Optional) A mapping of tags to assign to the resource.
|
||||
* `volume_tags` - (Optional) A mapping of tags to assign to the devices created by the instance at launch time.
|
||||
* `root_block_device` - (Optional) Customize details about the root block
|
||||
device of the instance. See [Block Devices](#block-devices) below for details.
|
||||
* `ebs_block_device` - (Optional) Additional EBS block devices to attach to the
|
||||
|
Loading…
Reference in New Issue
Block a user