diff --git a/changelogs/fragments/1690-ec2_ami-add-support-org_arn-orgu_arn.yml b/changelogs/fragments/1690-ec2_ami-add-support-org_arn-orgu_arn.yml new file mode 100644 index 00000000000..4a96ec8e199 --- /dev/null +++ b/changelogs/fragments/1690-ec2_ami-add-support-org_arn-orgu_arn.yml @@ -0,0 +1,2 @@ +minor_changes: + - "ec2_ami - add support for ``org_arns`` and ``org_unit_arns`` in launch_permissions (https://github.com/ansible-collections/amazon.aws/pull/1690)." diff --git a/plugins/modules/ec2_ami.py b/plugins/modules/ec2_ami.py index 60940bb4312..418e267b9d0 100644 --- a/plugins/modules/ec2_ami.py +++ b/plugins/modules/ec2_ami.py @@ -115,12 +115,33 @@ type: bool launch_permissions: description: - - Users and groups that should be able to launch the AMI. - - Expects dictionary with a key of C(user_ids) and/or C(group_names). - - C(user_ids) should be a list of account IDs. - - C(group_name) should be a list of groups, C(all) is the only acceptable value currently. + - Launch permissions for the AMI. - You must pass all desired launch permissions if you wish to modify existing launch permissions (passing just groups will remove all users). + required: false type: dict + suboptions: + user_ids: + description: List of account IDs. + type: list + elements: str + required: false + group_names: + description: List of group names. + type: list + elements: str + required: false + org_arns: + description: List of The Amazon Resource Name(s) (ARN) of organization(s). + type: list + elements: str + required: false + version_added: 6.4.0 + org_unit_arns: + description: List of The Amazon Resource Name(s) (ARN) of an organizational unit(s) (OU). + type: list + elements: str + required: false + version_added: 6.4.0 image_location: description: - The S3 location of an image to use for the AMI. @@ -279,6 +300,14 @@ state: present launch_permissions: user_ids: ['123456789012'] + +- name: Update AMI Launch Permissions, share AMI across an Organization and Organizational Units + amazon.aws.ec2_ami: + image_id: "{{ instance.image_id }}" + state: present + launch_permissions: + org_arns: ['arn:aws:organizations::123456789012:organization/o-123ab4cdef'] + org_unit_arns: ['arn:aws:organizations::123456789012:ou/o-123example/ou-1234-5example'] """ RETURN = r""" @@ -633,18 +662,39 @@ def set_launch_permission(connection, image, launch_permissions, check_mode): desired_users = set(str(user_id) for user_id in launch_permissions.get("user_ids", [])) current_groups = set(permission["Group"] for permission in current_permissions if "Group" in permission) desired_groups = set(launch_permissions.get("group_names", [])) + current_org_arns = set( + permission["OrganizationArn"] for permission in current_permissions if "OrganizationArn" in permission + ) + desired_org_arns = set(str(org_arn) for org_arn in launch_permissions.get("org_arns", [])) + current_org_unit_arns = set( + permission["OrganizationalUnitArn"] + for permission in current_permissions + if "OrganizationalUnitArn" in permission + ) + desired_org_unit_arns = set(str(org_unit_arn) for org_unit_arn in launch_permissions.get("org_unit_arns", [])) to_add_users = desired_users - current_users to_remove_users = current_users - desired_users to_add_groups = desired_groups - current_groups to_remove_groups = current_groups - desired_groups + to_add_org_arns = desired_org_arns - current_org_arns + to_remove_org_arns = current_org_arns - desired_org_arns + to_add_org_unit_arns = desired_org_unit_arns - current_org_unit_arns + to_remove_org_unit_arns = current_org_unit_arns - desired_org_unit_arns + + to_add = ( + [dict(Group=group) for group in sorted(to_add_groups)] + + [dict(UserId=user_id) for user_id in sorted(to_add_users)] + + [dict(OrganizationArn=org_arn) for org_arn in sorted(to_add_org_arns)] + + [dict(OrganizationalUnitArn=org_unit_arn) for org_unit_arn in sorted(to_add_org_unit_arns)] + ) - to_add = [dict(Group=group) for group in sorted(to_add_groups)] + [ - dict(UserId=user_id) for user_id in sorted(to_add_users) - ] - to_remove = [dict(Group=group) for group in sorted(to_remove_groups)] + [ - dict(UserId=user_id) for user_id in sorted(to_remove_users) - ] + to_remove = ( + [dict(Group=group) for group in sorted(to_remove_groups)] + + [dict(UserId=user_id) for user_id in sorted(to_remove_users)] + + [dict(OrganizationArn=org_arn) for org_arn in sorted(to_remove_org_arns)] + + [dict(OrganizationalUnitArn=org_unit_arn) for org_unit_arn in sorted(to_remove_org_unit_arns)] + ) if not (to_add or to_remove): return False @@ -693,6 +743,10 @@ def set_description(connection, module, image, description): def do(cls, module, connection, image_id): """Entry point to update an image""" launch_permissions = module.params.get("launch_permissions") + # remove any keys with value=None + if launch_permissions: + launch_permissions = {k: v for k, v in launch_permissions.items() if v is not None} + image = get_image_by_id(connection, image_id) if image is None: raise Ec2AmiFailure(f"Image {image_id} does not exist") @@ -747,13 +801,18 @@ def set_tags(connection, module, tags, image_id): def set_launch_permissions(connection, launch_permissions, image_id): if not launch_permissions: return - + # remove any keys with value=None + launch_permissions = {k: v for k, v in launch_permissions.items() if v is not None} try: params = {"Attribute": "LaunchPermission", "ImageId": image_id, "LaunchPermission": {"Add": []}} for group_name in launch_permissions.get("group_names", []): params["LaunchPermission"]["Add"].append(dict(Group=group_name)) for user_id in launch_permissions.get("user_ids", []): params["LaunchPermission"]["Add"].append(dict(UserId=str(user_id))) + for org_arn in launch_permissions.get("org_arns", []): + params["LaunchPermission"]["Add"].append(dict(OrganizationArn=org_arn)) + for org_unit_arn in launch_permissions.get("org_unit_arns", []): + params["LaunchPermission"]["Add"].append(dict(OrganizationalUnitArn=org_unit_arn)) if params["LaunchPermission"]["Add"]: connection.modify_image_attribute(aws_retry=True, **params) except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: @@ -897,7 +956,15 @@ def main(): image_location={}, instance_id={}, kernel_id={}, - launch_permissions={"type": "dict"}, + launch_permissions=dict( + type="dict", + options=dict( + user_ids=dict(type="list", elements="str"), + group_names=dict(type="list", elements="str"), + org_arns=dict(type="list", elements="str"), + org_unit_arns=dict(type="list", elements="str"), + ), + ), name={}, no_reboot={"default": False, "type": "bool"}, purge_tags={"type": "bool", "default": True}, diff --git a/tests/integration/targets/ec2_ami/tasks/main.yml b/tests/integration/targets/ec2_ami/tasks/main.yml index 06e9e3f7b4a..6d61e0b1f94 100644 --- a/tests/integration/targets/ec2_ami/tasks/main.yml +++ b/tests/integration/targets/ec2_ami/tasks/main.yml @@ -697,6 +697,48 @@ - ami_facts_result_boot_tpm.images[0].boot_mode == 'uefi' - ami_facts_result_boot_tpm.images[0].tpm_support == 'v2.0' + # === Test modify launch permissions org_arns and org_unit_arns========================= + + - name: create an image from the instance + ec2_ami: + instance_id: '{{ ec2_instance_id }}' + state: present + name: '{{ ec2_ami_name }}_permissions' + description: '{{ ec2_ami_description }}' + tags: + Name: '{{ ec2_ami_name }}_permissions' + wait: yes + root_device_name: '{{ ec2_ami_root_disk }}' + register: permissions_create_result + + - name: modify the AMI launch permissions + ec2_ami: + state: present + image_id: '{{ permissions_create_result.image_id }}' + name: '{{ ec2_ami_name }}_permissions' + tags: + Name: '{{ ec2_ami_name }}_permissions' + launch_permissions: + org_arns: ['arn:aws:organizations::123456789012:organization/o-123ab4cdef'] + org_unit_arns: ['arn:aws:organizations::123456789012:ou/o-123example/ou-1234-5exampld'] + register: permissions_update_result + + - name: Get ami info + amazon.aws.ec2_ami_info: + image_ids: '{{ permissions_create_result.image_id }}' + describe_image_attributes: true + register: permissions_info_result + + - name: assert that launch permissions have changed + assert: + that: + - "permissions_update_result.changed" + - "'organization_arn' in permissions_info_result.images[0].launch_permissions[0]" + - "permissions_info_result.images[0].launch_permissions[0]['organization_arn'] == 'arn:aws:organizations::123456789012:organization/o-123ab4cdef'" + - "'organizational_unit_arn' in permissions_info_result.images[0].launch_permissions[1]" + - "permissions_info_result.images[0].launch_permissions[1]['organizational_unit_arn'] == 'arn:aws:organizations::123456789012:ou/o-123example/ou-1234-5exampld'" + + # ============================================================ always: @@ -737,6 +779,14 @@ wait: yes ignore_errors: yes + - name: delete ami + ec2_ami: + state: absent + image_id: "{{ ec2_ami_image_id }}" + name: '{{ ec2_ami_name }}_permissions' + wait: yes + ignore_errors: yes + - name: remove setup snapshot of ec2 instance ec2_snapshot: state: absent