diff --git a/changelog.d/22069_aws_ec2_metadata_categories.enhancement.md b/changelog.d/22069_aws_ec2_metadata_categories.enhancement.md new file mode 100644 index 0000000000000..20add25c16336 --- /dev/null +++ b/changelog.d/22069_aws_ec2_metadata_categories.enhancement.md @@ -0,0 +1,3 @@ +The `aws_ec2_metadata` transform now supports additional IMDS instance metadata categories. The following fields can now be requested via the `fields` option: `availability-zone-id`, `partition`, `domain`, `placement-group-name`, `placement-partition-number`, `host-id`, and `autoscaling-target-lifecycle-state`. + +These are useful for locating impacted instances (for example, by availability zone ID, which is consistent across accounts, or by Dedicated Host ID) and for correlating EC2 Auto Scaling lifecycle events with on-instance events. As with the existing optional fields, these are only fetched when explicitly listed in `fields`. diff --git a/src/transforms/aws_ec2_metadata.rs b/src/transforms/aws_ec2_metadata.rs index ed55d5aa2dfe8..c3a0c272abaee 100644 --- a/src/transforms/aws_ec2_metadata.rs +++ b/src/transforms/aws_ec2_metadata.rs @@ -51,9 +51,31 @@ const SUBNET_ID_KEY: &str = "subnet-id"; const VPC_ID_KEY: &str = "vpc-id"; const ROLE_NAME_KEY: &str = "role-name"; const TAGS_KEY: &str = "tags"; +const PARTITION_KEY: &str = "partition"; +const DOMAIN_KEY: &str = "domain"; +const AVAILABILITY_ZONE_ID_KEY: &str = "availability-zone-id"; +const PLACEMENT_GROUP_NAME_KEY: &str = "placement-group-name"; +const PLACEMENT_PARTITION_NUMBER_KEY: &str = "placement-partition-number"; +const HOST_ID_KEY: &str = "host-id"; +const AUTOSCALING_TARGET_LIFECYCLE_STATE_KEY: &str = "autoscaling-target-lifecycle-state"; static AVAILABILITY_ZONE: LazyLock = LazyLock::new(|| PathAndQuery::from_static("/latest/meta-data/placement/availability-zone")); +static AVAILABILITY_ZONE_ID: LazyLock = + LazyLock::new(|| PathAndQuery::from_static("/latest/meta-data/placement/availability-zone-id")); +static PARTITION: LazyLock = + LazyLock::new(|| PathAndQuery::from_static("/latest/meta-data/services/partition")); +static DOMAIN: LazyLock = + LazyLock::new(|| PathAndQuery::from_static("/latest/meta-data/services/domain")); +static PLACEMENT_GROUP_NAME: LazyLock = + LazyLock::new(|| PathAndQuery::from_static("/latest/meta-data/placement/group-name")); +static PLACEMENT_PARTITION_NUMBER: LazyLock = + LazyLock::new(|| PathAndQuery::from_static("/latest/meta-data/placement/partition-number")); +static HOST_ID: LazyLock = + LazyLock::new(|| PathAndQuery::from_static("/latest/meta-data/placement/host-id")); +static AUTOSCALING_TARGET_LIFECYCLE_STATE: LazyLock = LazyLock::new(|| { + PathAndQuery::from_static("/latest/meta-data/autoscaling/target-lifecycle-state") +}); static LOCAL_HOSTNAME: LazyLock = LazyLock::new(|| PathAndQuery::from_static("/latest/meta-data/local-hostname")); static LOCAL_IPV4: LazyLock = @@ -185,6 +207,7 @@ struct Keys { account_id_key: MetadataKey, ami_id_key: MetadataKey, availability_zone_key: MetadataKey, + availability_zone_id_key: MetadataKey, instance_id_key: MetadataKey, instance_type_key: MetadataKey, local_hostname_key: MetadataKey, @@ -192,6 +215,12 @@ struct Keys { public_hostname_key: MetadataKey, public_ipv4_key: MetadataKey, region_key: MetadataKey, + partition_key: MetadataKey, + domain_key: MetadataKey, + placement_group_name_key: MetadataKey, + placement_partition_number_key: MetadataKey, + host_id_key: MetadataKey, + autoscaling_target_lifecycle_state_key: MetadataKey, subnet_id_key: MetadataKey, vpc_id_key: MetadataKey, role_name_key: MetadataKey, @@ -263,6 +292,7 @@ impl TransformConfig for Ec2Metadata { &added_keys.account_id_key.log_path, &added_keys.ami_id_key.log_path, &added_keys.availability_zone_key.log_path, + &added_keys.availability_zone_id_key.log_path, &added_keys.instance_id_key.log_path, &added_keys.instance_type_key.log_path, &added_keys.local_hostname_key.log_path, @@ -270,6 +300,12 @@ impl TransformConfig for Ec2Metadata { &added_keys.public_hostname_key.log_path, &added_keys.public_ipv4_key.log_path, &added_keys.region_key.log_path, + &added_keys.partition_key.log_path, + &added_keys.domain_key.log_path, + &added_keys.placement_group_name_key.log_path, + &added_keys.placement_partition_number_key.log_path, + &added_keys.host_id_key.log_path, + &added_keys.autoscaling_target_lifecycle_state_key.log_path, &added_keys.subnet_id_key.log_path, &added_keys.vpc_id_key.log_path, &added_keys.role_name_key.log_path, @@ -487,6 +523,63 @@ impl MetadataClient { new_state.push((self.keys.availability_zone_key.clone(), availability_zone)); } + if self.fields.contains(AVAILABILITY_ZONE_ID_KEY) + && let Some(availability_zone_id) = self.get_metadata(&AVAILABILITY_ZONE_ID).await? + { + new_state.push(( + self.keys.availability_zone_id_key.clone(), + availability_zone_id, + )); + } + + if self.fields.contains(PARTITION_KEY) + && let Some(partition) = self.get_metadata(&PARTITION).await? + { + new_state.push((self.keys.partition_key.clone(), partition)); + } + + if self.fields.contains(DOMAIN_KEY) + && let Some(domain) = self.get_metadata(&DOMAIN).await? + { + new_state.push((self.keys.domain_key.clone(), domain)); + } + + if self.fields.contains(PLACEMENT_GROUP_NAME_KEY) + && let Some(placement_group_name) = self.get_metadata(&PLACEMENT_GROUP_NAME).await? + { + new_state.push(( + self.keys.placement_group_name_key.clone(), + placement_group_name, + )); + } + + if self.fields.contains(PLACEMENT_PARTITION_NUMBER_KEY) + && let Some(placement_partition_number) = + self.get_metadata(&PLACEMENT_PARTITION_NUMBER).await? + { + new_state.push(( + self.keys.placement_partition_number_key.clone(), + placement_partition_number, + )); + } + + if self.fields.contains(HOST_ID_KEY) + && let Some(host_id) = self.get_metadata(&HOST_ID).await? + { + new_state.push((self.keys.host_id_key.clone(), host_id)); + } + + if self.fields.contains(AUTOSCALING_TARGET_LIFECYCLE_STATE_KEY) + && let Some(autoscaling_target_lifecycle_state) = self + .get_metadata(&AUTOSCALING_TARGET_LIFECYCLE_STATE) + .await? + { + new_state.push(( + self.keys.autoscaling_target_lifecycle_state_key.clone(), + autoscaling_target_lifecycle_state, + )); + } + if self.fields.contains(LOCAL_HOSTNAME_KEY) && let Some(local_hostname) = self.get_metadata(&LOCAL_HOSTNAME).await? { @@ -669,6 +762,7 @@ impl Keys { account_id_key: create_key(&namespace, ACCOUNT_ID_KEY), ami_id_key: create_key(&namespace, AMI_ID_KEY), availability_zone_key: create_key(&namespace, AVAILABILITY_ZONE_KEY), + availability_zone_id_key: create_key(&namespace, AVAILABILITY_ZONE_ID_KEY), instance_id_key: create_key(&namespace, INSTANCE_ID_KEY), instance_type_key: create_key(&namespace, INSTANCE_TYPE_KEY), local_hostname_key: create_key(&namespace, LOCAL_HOSTNAME_KEY), @@ -676,6 +770,15 @@ impl Keys { public_hostname_key: create_key(&namespace, PUBLIC_HOSTNAME_KEY), public_ipv4_key: create_key(&namespace, PUBLIC_IPV4_KEY), region_key: create_key(&namespace, REGION_KEY), + partition_key: create_key(&namespace, PARTITION_KEY), + domain_key: create_key(&namespace, DOMAIN_KEY), + placement_group_name_key: create_key(&namespace, PLACEMENT_GROUP_NAME_KEY), + placement_partition_number_key: create_key(&namespace, PLACEMENT_PARTITION_NUMBER_KEY), + host_id_key: create_key(&namespace, HOST_ID_KEY), + autoscaling_target_lifecycle_state_key: create_key( + &namespace, + AUTOSCALING_TARGET_LIFECYCLE_STATE_KEY, + ), subnet_id_key: create_key(&namespace, SUBNET_ID_KEY), vpc_id_key: create_key(&namespace, VPC_ID_KEY), role_name_key: create_key(&namespace, ROLE_NAME_KEY), diff --git a/website/cue/reference/components/transforms/aws_ec2_metadata.cue b/website/cue/reference/components/transforms/aws_ec2_metadata.cue index ed5bff826033a..d4ccc409655b1 100644 --- a/website/cue/reference/components/transforms/aws_ec2_metadata.cue +++ b/website/cue/reference/components/transforms/aws_ec2_metadata.cue @@ -148,6 +148,62 @@ components: transforms: aws_ec2_metadata: { examples: ["us-east-1"] } } + "availability-zone-id": { + description: "The `availability-zone-id` that the current EC2 instance is running in. Unlike `availability-zone`, this ID is consistent across AWS accounts." + required: false + type: string: { + default: null + examples: ["use1-az1"] + } + } + "partition": { + description: "The AWS partition (from `services/partition`) that the current EC2 instance is running in." + required: false + type: string: { + default: null + examples: ["aws"] + } + } + "domain": { + description: "The AWS region domain (from `services/domain`) for the current EC2 instance." + required: false + type: string: { + default: null + examples: ["amazonaws.com"] + } + } + "placement-group-name": { + description: "The name of the placement group (from `placement/group-name`) that the current EC2 instance is in, if any." + required: false + type: string: { + default: null + examples: ["my-placement-group"] + } + } + "placement-partition-number": { + description: "The number of the partition (from `placement/partition-number`) within the placement group that the current EC2 instance is in, if any." + required: false + type: string: { + default: null + examples: ["1"] + } + } + "host-id": { + description: "The ID of the Dedicated Host (from `placement/host-id`) that the current EC2 instance is running on, if any." + required: false + type: string: { + default: null + examples: ["h-0123456789abcdef0"] + } + } + "autoscaling-target-lifecycle-state": { + description: "The EC2 Auto Scaling target lifecycle state (from `autoscaling/target-lifecycle-state`) of the current EC2 instance, if it is part of an Auto Scaling group." + required: false + type: string: { + default: null + examples: ["InService"] + } + } "role-name": { description: "The `role-name` that the current EC2 instance is using." required: true