Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ec2_ami: Add support for OrganizationArn and OrganizationalUnitArn #1690

Merged
merged 10 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
Copy link
Collaborator

@alinabuzachis alinabuzachis Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be wrong, but these suboptions were already present in launch_permissions, am I wrong? If so, this should be more like a bugfix (meaning we can also backport to stable-5).

Copy link
Contributor Author

@mandar242 mandar242 Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I'm getting the question correctly but

in module ec2_ami.py, the options org_arns and org_unit_arns were not present before this PR

but the options OrganizationArn & OrganizationalUnitArn have been LaunchPermissions supported in boto3 from 1.20.0
https://boto3.amazonaws.com/v1/documentation/api/1.20.0/reference/services/ec2.html?highlight=modify_image_attribute#EC2.Client.modify_image_attribute

please let me know if that answers the question

- "ec2_ami - add support for ``org_arns`` and ``org_unit_arns`` in launch_permissions (https://github.com/ansible-collections/amazon.aws/pull/1690)."
91 changes: 79 additions & 12 deletions plugins/modules/ec2_ami.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
hakbailey marked this conversation as resolved.
Show resolved Hide resolved
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.
Expand Down Expand Up @@ -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"""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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},
Expand Down
50 changes: 50 additions & 0 deletions tests/integration/targets/ec2_ami/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down