Repository: ansible-collections/amazon.aws

Issue:
Implement instance metadata tags support
### Summary

I would to enable instance metadata tags using `ec2_instance`. Additionally I'd like to be able to query those tags using `ec2_metadata_facts`

### Issue Type

Feature Idea

### Component Name

ec2_instance, ec2_metadata_facts

### Additional Information

This feature will help me to **_enable_** the ability to query instance metadata tags, as well as actually **_query_** instance metadata tags. https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html#work-with-tags-in-IMDS

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/ec2_metadata_facts.py`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/blob/main/plugins/modules/ec2_metadata_facts.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @jillr @roadmapper @s-hertel @silviud @tremble
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@charltonstanley Thank you for this feature idea. Would you be willing to implement it and open a pull request?
@alinabuzachis I would if I knew a lick of python, but I don't. I'm not afraid to learn, but my commits ~might~ would lack sufficient sophistication to be worthy of inclusion in your code base. :smile: 

Resolving patch:
diff --git a/plugins/modules/ec2_metadata_facts.py b/plugins/modules/ec2_metadata_facts.py
--- a/plugins/modules/ec2_metadata_facts.py
+++ b/plugins/modules/ec2_metadata_facts.py
@@ -252,6 +252,14 @@
             description: The purchasing option of the instance.
             type: str
             sample: "on-demand"
+        ansible_ec2_instance_tags_keys:
+            description:
+                - The list of tags keys of the instance.
+                - Returns empty list if access to tags (InstanceMetadataTags) in instance metadata is not enabled.
+            type: list
+            elements: str
+            sample: ["tagKey1", "tag_key2"]
+            version_added: 5.1.0
         ansible_ec2_instance_type:
             description: The type of the instance.
             type: str
@@ -445,14 +453,25 @@
 class Ec2Metadata(object):
     ec2_metadata_token_uri = 'http://169.254.169.254/latest/api/token'
     ec2_metadata_uri = 'http://169.254.169.254/latest/meta-data/'
+    ec2_metadata_instance_tags_uri = 'http://169.254.169.254/latest/meta-data/tags/instance'
     ec2_sshdata_uri = 'http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key'
     ec2_userdata_uri = 'http://169.254.169.254/latest/user-data/'
     ec2_dynamicdata_uri = 'http://169.254.169.254/latest/dynamic/'
 
-    def __init__(self, module, ec2_metadata_token_uri=None, ec2_metadata_uri=None, ec2_sshdata_uri=None, ec2_userdata_uri=None, ec2_dynamicdata_uri=None):
+    def __init__(
+        self,
+        module,
+        ec2_metadata_token_uri=None,
+        ec2_metadata_uri=None,
+        ec2_metadata_instance_tags_uri=None,
+        ec2_sshdata_uri=None,
+        ec2_userdata_uri=None,
+        ec2_dynamicdata_uri=None,
+    ):
         self.module = module
         self.uri_token = ec2_metadata_token_uri or self.ec2_metadata_token_uri
         self.uri_meta = ec2_metadata_uri or self.ec2_metadata_uri
+        self.uri_instance_tags = ec2_metadata_instance_tags_uri or self.ec2_metadata_instance_tags_uri
         self.uri_user = ec2_userdata_uri or self.ec2_userdata_uri
         self.uri_ssh = ec2_sshdata_uri or self.ec2_sshdata_uri
         self.uri_dynamic = ec2_dynamicdata_uri or self.ec2_dynamicdata_uri
@@ -576,6 +595,10 @@ def run(self):
         data.update(dyndata)
         data = self.fix_invalid_varnames(data)
 
+        instance_tags_keys = self._fetch(self.uri_instance_tags)
+        instance_tags_keys = instance_tags_keys.split('\n') if instance_tags_keys != "None" else []
+        data[self._prefix % 'instance_tags_keys'] = instance_tags_keys
+
         # Maintain old key for backwards compatibility
         if 'ansible_ec2_instance_identity_document_region' in data:
             data['ansible_ec2_placement_region'] = data['ansible_ec2_instance_identity_document_region']


Test patch:
diff --git a/tests/integration/targets/ec2_metadata_facts/meta/main.yml b/tests/integration/targets/ec2_metadata_facts/meta/main.yml
--- a/tests/integration/targets/ec2_metadata_facts/meta/main.yml
+++ b/tests/integration/targets/ec2_metadata_facts/meta/main.yml
@@ -1,3 +1,7 @@
 dependencies:
-- setup_ec2_facts
-- setup_sshkey
+  - setup_ec2_facts
+  - setup_sshkey
+  #required for run_instances with MetadataOptions.InstanceMetadataTags
+  - role: setup_botocore_pip
+    vars:
+      botocore_version: '1.23.30'
diff --git a/tests/integration/targets/ec2_metadata_facts/playbooks/setup.yml b/tests/integration/targets/ec2_metadata_facts/playbooks/setup.yml
--- a/tests/integration/targets/ec2_metadata_facts/playbooks/setup.yml
+++ b/tests/integration/targets/ec2_metadata_facts/playbooks/setup.yml
@@ -25,6 +25,11 @@
   - include_role:
       name: '../setup_ec2_facts'
 
+  - include_role:
+      name: '../setup_botocore_pip'
+    vars:
+      botocore_version: '1.23.30'
+
   - set_fact:
       availability_zone: '{{ ec2_availability_zone_names[0] }}'
 
@@ -115,9 +120,15 @@
       network:
         assign_public_ip: true
         delete_on_termination: true
+      metadata_options:
+          instance_metadata_tags: enabled
+      tags:
+        snake_case_key: a_snake_case_value
+        camelCaseKey: aCamelCaseValue
       wait: True
-
     register: ec2_instance
+    vars:
+      ansible_python_interpreter: "{{ botocore_virtualenv_interpreter }}"
 
   - set_fact:
       ec2_instance_id: "{{ ec2_instance.instances[0].instance_id }}"
diff --git a/tests/integration/targets/ec2_metadata_facts/playbooks/test_metadata.yml b/tests/integration/targets/ec2_metadata_facts/playbooks/test_metadata.yml
--- a/tests/integration/targets/ec2_metadata_facts/playbooks/test_metadata.yml
+++ b/tests/integration/targets/ec2_metadata_facts/playbooks/test_metadata.yml
@@ -14,3 +14,5 @@
         - ansible_ec2_placement_availability_zone == availability_zone
         - ansible_ec2_security_groups == "{{ resource_prefix }}-sg"
         - ansible_ec2_user_data == "None"
+        - ansible_ec2_instance_tags_keys is defined
+        - ansible_ec2_instance_tags_keys | length == 3