---
- name: Properly clone a running EC2 instance
  hosts: localhost
  gather_facts: false
  vars:
    original_instance_name_tag: "Use ME!!"
  pre_tasks:
    - name: Get information from the original instance
      tags:
        - gather_information
        - pre_task
      amazon.aws.ec2_instance_info:
        filters:
          "tag:Name": "{{ original_instance_name_tag }}"
          "instance-state-name": ["running"]
      register: original_instance_information
    - name: Check a running instance with Name tag '{{ original_instance_name_tag }}' has been found
      tags:
        - gather_information
        - pre_task
      ansible.builtin.assert:
        that: original_instance_information.instances | length > 0
        fail_msg: No running instances found with Name tag '{{ original_instance_name_tag }}'
        success_msg: At least one running instance has been found with Name tag '{{ original_instance_name_tag }}'
  tasks:
    - name: Create a Security Group with only the connections required for testing
      tags: security_group
      amazon.aws.ec2_security_group:
        name: Clone EC2 Instance SG
        description: Temporary SG for cloning EC2 Instances
        rules_egress:
          - cidr_ip: 0.0.0.0/0
            ports: 443
            rule_desc: Required by SSM, but could be stricter
      register: clone_instance_security_group_information
    - name: Create snapshots of the instance's volumes
      # Allows for more control over the snapshot, namely to avoid recreating snapshot of massive volumes and lose hours
      tags: snapshot
      amazon.aws.ec2_snapshot:
        volume_id: "{{ item }}"
        description: Temporary snapshot for cloning EC2 Instances
        last_snapshot_min_age: 1440  # 1d
        wait_timeout: 7200  # 2h might still be not enough for big boi volumes
      loop: "{{ original_instance_information.instances[0].block_device_mappings | map(attribute='ebs.volume_id') }}"
      register: original_instance_snapshots
    - name: Create an AMI from the snapshot
      tags: ami
      amazon.aws.ec2_ami:
        # no_reboot: false  # set to true if one does *not* want to have the original instance shut down
        wait: true
        name: temp-{{ original_instance_name_tag | regex_replace(' ', '-') | lower }}-ami
        description: Temporary AMI for cloning EC2 Instances
        tags:
          Name: Clone EC2 Instance AMI
        root_device_name: "{{ original_instance_information.instances[0].root_device_name }}"
        device_mapping:
          # Refer https://jinja.palletsprojects.com/en/3.0.x/templates/#assignments for the namespace object's reason
          >-
            {%- set ns = namespace(devices_list = []) -%}
            {%- for result in original_instance_snapshots.results -%}
            {%-   for device in original_instance_information.instances[0].block_device_mappings
                  | selectattr('ebs.volume_id', 'equalto', result.volume_id) -%}
            {{-
                    ns.devices_list.append({
                      'device_name': device.device_name,
                      'snapshot_id': result.snapshots | sort(attribute='start_time') | last | json_query('snapshot_id'),
                      'volume_type': 'gp3',
                      'delete_on_termination': true,
                    })
            -}}
            {%-   endfor -%}
            {%- endfor -%}
            {{  ns.devices_list }}
      register: original_instance_ami
    - name: Use the AMI to launch a clone
      tags:
        - clone
        - instance
      when: original_instance_ami.image_id is defined
      amazon.aws.ec2_instance:
        name: Clone EC2 Instance
        vpc_subnet_id: "{{ original_instance_information.instances[0].subnet_id }}"
        instance_type: "{{ original_instance_information.instances[0].instance_type }}"
        image:
          id: "{{ original_instance_ami.image_id }}"
        security_group: "{{ clone_instance_security_group_information.group_id }}"
        iam_instance_profile: "{{ original_instance_information.instances[0].iam_instance_profile.arn }}"
      register: clone_instance_information
    - name: Wait for the instance to be ready
      tags:
        - clone
        - instance
        - check
      when: clone_instance_information.instance_ids is defined
      block:
        - name: Just pause enough for the instance to initialize
          # Because of course there seems to be no effing way to distinguish between just running and ready, and of
          # course the SSM connection plugin crashes badly instead of just erroring the task out (ノಠ益ಠ)ノ彡┻━┻
          ansible.builtin.pause:
            minutes: 3
        - name: Try connecting with SSM
          delegate_to: "{{ clone_instance_information.instances[0].instance_id }}"
          vars:
            ansible_connection: community.aws.aws_ssm
            ansible_aws_ssm_bucket_name: some-bucket
            ansible_aws_ssm_region: eu-west-1
            ansible_aws_ssm_timeout: 300
          ansible.builtin.ping:
    - name: Ready!
      ansible.builtin.debug:
        msg: The clone instance is ready!
  post_tasks:
    - name: Remove the clone
      tags:
        - clone
        - instance
        - cleanup
        - post_task
      when: clone_instance_information.instance_ids is defined
      amazon.aws.ec2_instance:
        instance_ids: "{{ clone_instance_information.instance_ids }}"
        state: absent
    - name: Remove the AMI
      tags:
        - always  # stupid ami module fails if already existing
        - ami
        - cleanup
        - post_task
      when: original_instance_ami.image_id is defined
      amazon.aws.ec2_ami:
        image_id: "{{ original_instance_ami.image_id }}"
        state: absent
