From 29939383e6b8853c70415a4321bf501e5e87cdbc Mon Sep 17 00:00:00 2001 From: Benjamin Brabant Date: Fri, 11 Oct 2019 18:13:13 +0200 Subject: [PATCH] Improve ecs_ecr module to handle image_tag_mutability feature (#62871) Since 2019 jul. 26, AWS Elastic Container Registry can be configured to be immutable that prevent overwritting of an existing tag in the registry. https://aws.amazon.com/fr/about-aws/whats-new/2019/07/amazon-ecr-now-supports-immutable-image-tags/ https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-tag-mutability.html By default, ecr is created MUTABLE, as this was the only option before this feature. This module leverage new capabilities of boto3 to configure image_tag_mutability option. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecr.html#ECR.Client.put_image_tag_mutability The image_tag_mutability option definition was inspired by existing terraform module. https://www.terraform.io/docs/providers/aws/r/ecr_repository.html --- lib/ansible/modules/cloud/amazon/ecs_ecr.py | 53 +++++++++++-- .../targets/ecs_ecr/tasks/main.yml | 76 +++++++++++++++++++ 2 files changed, 121 insertions(+), 8 deletions(-) diff --git a/lib/ansible/modules/cloud/amazon/ecs_ecr.py b/lib/ansible/modules/cloud/amazon/ecs_ecr.py index b16d6b5124b..b0463658fcf 100644 --- a/lib/ansible/modules/cloud/amazon/ecs_ecr.py +++ b/lib/ansible/modules/cloud/amazon/ecs_ecr.py @@ -5,14 +5,13 @@ # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} - DOCUMENTATION = ''' --- module: ecs_ecr @@ -48,6 +47,13 @@ options: required: false default: false type: bool + image_tag_mutability: + description: + - configure whether repository should be mutable (ie. an already existing tag can be overwritten) or not + required: false + choices: [mutable, immutable] + default: 'mutable' + version_added: '2.10' state: description: - create or destroy the repository @@ -97,6 +103,12 @@ EXAMPLES = ''' ecs_ecr: name: needs-no-policy delete_policy: yes + +- name: create immutable ecr-repo + ecs_ecr: + name: super/cool + image_tag_mutability: immutable + ''' RETURN = ''' @@ -190,15 +202,17 @@ class EcsEcr: return None raise - def create_repository(self, registry_id, name): + def create_repository(self, registry_id, name, image_tag_mutability): if registry_id: default_registry_id = self.sts.get_caller_identity().get('Account') if registry_id != default_registry_id: raise Exception('Cannot create repository in registry {0}.' - 'Would be created in {1} instead.'.format( - registry_id, default_registry_id)) + 'Would be created in {1} instead.'.format(registry_id, default_registry_id)) + if not self.check_mode: - repo = self.ecr.create_repository(repositoryName=name).get('repository') + repo = self.ecr.create_repository( + repositoryName=name, + imageTagMutability=image_tag_mutability).get('repository') self.changed = True return repo else: @@ -250,6 +264,23 @@ class EcsEcr: return policy return None + def put_image_tag_mutability(self, registry_id, name, new_mutability_configuration): + repo = self.get_repository(registry_id, name) + current_mutability_configuration = repo.get('imageTagMutability') + + if current_mutability_configuration != new_mutability_configuration: + if not self.check_mode: + self.ecr.put_image_tag_mutability( + repositoryName=name, + imageTagMutability=new_mutability_configuration, + **build_kwargs(registry_id)) + else: + self.skipped = True + self.changed = True + + repo['imageTagMutability'] = new_mutability_configuration + return repo + def sort_lists_of_strings(policy): for statement_index in range(0, len(policy.get('Statement', []))): @@ -270,6 +301,7 @@ def run(ecr, params, verbosity): delete_policy = params['delete_policy'] registry_id = params['registry_id'] force_set_policy = params['force_set_policy'] + image_tag_mutability = params['image_tag_mutability'].upper() # If a policy was given, parse it policy = policy_text and json.loads(policy_text) @@ -281,10 +313,13 @@ def run(ecr, params, verbosity): if state == 'present': result['created'] = False + if not repo: - repo = ecr.create_repository(registry_id, name) + repo = ecr.create_repository(registry_id, name, image_tag_mutability) result['changed'] = True result['created'] = True + else: + repo = ecr.put_image_tag_mutability(registry_id, name, image_tag_mutability) result['repository'] = repo if delete_policy: @@ -358,7 +393,9 @@ def main(): default='present'), force_set_policy=dict(required=False, type='bool', default=False), policy=dict(required=False, type='json'), - delete_policy=dict(required=False, type='bool'))) + delete_policy=dict(required=False, type='bool'), + image_tag_mutability=dict(required=False, choices=['mutable', 'immutable'], + default='mutable'))) module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, diff --git a/test/integration/targets/ecs_ecr/tasks/main.yml b/test/integration/targets/ecs_ecr/tasks/main.yml index e223752242d..7a97dce2ad6 100644 --- a/test/integration/targets/ecs_ecr/tasks/main.yml +++ b/test/integration/targets/ecs_ecr/tasks/main.yml @@ -55,6 +55,11 @@ - result is changed - result.created + - name: it should have been configured as mutable by default + assert: + that: + - result.repository.imageTagMutability == "MUTABLE" + - name: When creating a repository that already exists in check mode ecs_ecr: @@ -329,6 +334,77 @@ that: - result is not changed + - name: When creating an immutable repository + ecs_ecr: + name: '{{ ecr_name }}' + region: '{{ ec2_region }}' + ec2_access_key: '{{ec2_access_key}}' + ec2_secret_key: '{{ec2_secret_key}}' + security_token: '{{security_token}}' + image_tag_mutability: immutable + register: result + + - name: it should change and create + assert: + that: + - result is changed + - result.created + + - name: it should have been configured as immutable + assert: + that: + - result.repository.imageTagMutability == "IMMUTABLE" + + + - name: When configuring an existing immutable repository to be mutable in check mode + ecs_ecr: + name: '{{ ecr_name }}' + region: '{{ ec2_region }}' + ec2_access_key: '{{ec2_access_key}}' + ec2_secret_key: '{{ec2_secret_key}}' + security_token: '{{security_token}}' + image_tag_mutability: mutable + register: result + check_mode: yes + + - name: it should skip, change and configured mutable + assert: + that: + - result is skipped + - result is changed + - result.repository.imageTagMutability == "MUTABLE" + + - name: When configuring an existing immutable repository to be mutable + ecs_ecr: + name: '{{ ecr_name }}' + region: '{{ ec2_region }}' + ec2_access_key: '{{ec2_access_key}}' + ec2_secret_key: '{{ec2_secret_key}}' + security_token: '{{security_token}}' + image_tag_mutability: mutable + register: result + + - name: it should change and configured mutable + assert: + that: + - result is changed + - result.repository.imageTagMutability == "MUTABLE" + + - name: When configuring an already mutable repository to be mutable + ecs_ecr: + name: '{{ ecr_name }}' + region: '{{ ec2_region }}' + ec2_access_key: '{{ec2_access_key}}' + ec2_secret_key: '{{ec2_secret_key}}' + security_token: '{{security_token}}' + image_tag_mutability: mutable + register: result + + - name: it should not change + assert: + that: + - result is not changed + always: - name: Delete lingering ECR repository