diff --git a/ansible/Makefile b/ansible/Makefile index b422c7f6e..94bb16724 100644 --- a/ansible/Makefile +++ b/ansible/Makefile @@ -43,7 +43,10 @@ create-build-env-vars: guard-build_label guard-out_dir @poetry run ansible-playbook -i local create-build-env-vars.yml deploy-ecs-proxies: guard-account guard-build_label guard-service_id guard-APIGEE_ENVIRONMENT guard-PROXY_VARS_FILE - @poetry run ansible-playbook -i local deploy-ecs-proxies.yml + @poetry run ansible-playbook -i local deploy-ecs-proxies.yml \ + +deploy-ecs-proxies-retag: guard-build_label guard-service_id guard-PROXY_VARS_FILE + @poetry run ansible-playbook -i local deploy-ecs-proxies-retag.yml deploy-apigee-proxy: guard-FULLY_QUALIFIED_SERVICE_NAME guard-SERVICE_BASE_PATH guard-APIGEE_ENVIRONMENT guard-APIGEE_ORGANIZATION guard-APIGEE_ACCESS_TOKEN guard-PROXY_DIR guard-PING @poetry run ansible-playbook -i local deploy-apigee-proxy.yml diff --git a/ansible/deploy-ecs-proxies-retag.yml b/ansible/deploy-ecs-proxies-retag.yml new file mode 100644 index 000000000..cf1da1706 --- /dev/null +++ b/ansible/deploy-ecs-proxies-retag.yml @@ -0,0 +1,18 @@ +- name: deploy ecs proxies retag + hosts: 127.0.0.1 + connection: local + gather_facts: no + + vars: + service_id: "{{ lookup('env','service_id') }}" + APIGEE_ENVIRONMENT: "{{ lookup('env','APIGEE_ENVIRONMENT') }}" + account: "{{ lookup('env','account') }}" + + pre_tasks: + - name: include container vars + include_vars: + file: "{{ lookup('env', 'CONTAINER_VARS_FILE') | expandvars | expanduser | realpath }}" + + roles: + - setup-facts + - deploy-ecs-proxies-retag diff --git a/ansible/deploy-ecs-proxies.yml b/ansible/deploy-ecs-proxies.yml index 43a54df26..00adfabcb 100644 --- a/ansible/deploy-ecs-proxies.yml +++ b/ansible/deploy-ecs-proxies.yml @@ -42,6 +42,15 @@ - name: include vars include_vars: file: "{{ lookup('env', 'PROXY_VARS_FILE') | expandvars | expanduser | realpath }}" + + - name: load use_ecs_tag from environment + set_fact: + use_ecs_tag: "{{ lookup('env','use_ecs_tag') | default('false') }}" + + - name: normalise use_ecs_tag to boolean + set_fact: + use_ecs_tag: "{{ use_ecs_tag | lower == 'true' }}" + roles: - setup-facts diff --git a/ansible/ecr-lifecycle/ecr_lifecycle.json b/ansible/ecr-lifecycle/ecr_lifecycle.json new file mode 100644 index 000000000..ee111e937 --- /dev/null +++ b/ansible/ecr-lifecycle/ecr_lifecycle.json @@ -0,0 +1,36 @@ +{ + "rules": [ + { + "rulePriority": 1, + "description": "Keep the 6 most recent ECS deployment images tagged ecs- (release images)", + "selection": { + "tagStatus": "tagged", + "tagPrefixList": ["ecs-"], + "countType": "imageCountMoreThan", + "countNumber": 6 + }, + "action": { "type": "expire" } + }, + { + "rulePriority": 2, + "description": "Never expire the 'latest' tag", + "selection": { + "tagStatus": "tagged", + "tagPrefixList": ["latest"], + "countType": "imageCountMoreThan", + "countNumber": 9999 + }, + "action": { "type": "expire" } + }, + { + "rulePriority": 3, + "description": "Keep the 6 most recent build images (all tags)", + "selection": { + "tagStatus": "any", + "countType": "imageCountMoreThan", + "countNumber": 6 + }, + "action": { "type": "expire" } + } + ] +} diff --git a/ansible/roles/build-ecs-proxies/tasks/main.yml b/ansible/roles/build-ecs-proxies/tasks/main.yml index 284b881d3..e6863769d 100644 --- a/ansible/roles/build-ecs-proxies/tasks/main.yml +++ b/ansible/roles/build-ecs-proxies/tasks/main.yml @@ -30,6 +30,29 @@ with_items: "{{ new_repos }}" when: new_repos +# TO DO- Add back in once confirmed lifecycle policy to be applied to all new repos. + +# - name: Read lifecycle policy file +# ansible.builtin.slurp: +# src: "{{ playbook_dir }}/ecr-lifecycle/ecr_lifecycle.json" +# register: desired_policy_raw +# when: new_repos | length > 0 + +# - name: Decode lifecycle policy JSON +# set_fact: +# desired_policy_json: "{{ desired_policy_raw.content | b64decode | from_json }}" +# when: new_repos | length > 0 + +# - name: Apply lifecycle policy to each new repo +# ansible.builtin.command: > +# {{ aws_cmd }} ecr put-lifecycle-policy +# --repository-name {{ item }} +# --lifecycle-policy-text '{{ desired_policy_json | to_json }}' +# with_items: "{{ new_repos }}" +# register: lifecycle_update +# ignore_errors: yes +# when: new_repos | length > 0 + - name: ecr login shell: "eval $({{ aws_cmd }} ecr get-login --no-include-email)" changed_when: no diff --git a/ansible/roles/create-api-deployment-pre-reqs/templates/terraform/iam.tf b/ansible/roles/create-api-deployment-pre-reqs/templates/terraform/iam.tf index 29eb55a3e..775b01a11 100644 --- a/ansible/roles/create-api-deployment-pre-reqs/templates/terraform/iam.tf +++ b/ansible/roles/create-api-deployment-pre-reqs/templates/terraform/iam.tf @@ -69,6 +69,8 @@ data "aws_iam_policy_document" "ecs-execution-role" { "ecr:DescribeRepositories", "ecr:ListImages", "ecr:DescribeImages", + "ecr:GetLifecyclePolicy", + "ecr:PutLifecyclePolicy", "s3:GetObject" ] @@ -173,6 +175,18 @@ data "aws_iam_policy_document" "deploy-user" { } + statement { + actions = [ + "ecr:GetLifecyclePolicy", + "ecr:PutLifecyclePolicy" + ] + + resources = [ + "arn:aws:ecr:${local.region}:${local.account_id}:repository/${var.service_id}", + "arn:aws:ecr:${local.region}:${local.account_id}:repository/${var.service_id}_*" + ] + } + statement { actions = [ "s3:ListBucket", diff --git a/ansible/roles/create-ecr-build-role/vars/main.yml b/ansible/roles/create-ecr-build-role/vars/main.yml index c40db5b1a..817fd7bb0 100644 --- a/ansible/roles/create-ecr-build-role/vars/main.yml +++ b/ansible/roles/create-ecr-build-role/vars/main.yml @@ -44,6 +44,7 @@ aws_ecs_policy: - "ecr:StartImageScan" - "ecr:StartLifecyclePolicyPreview" - "ecr:UploadLayerPart" + - "ecr:PutLifecyclePolicy" Resource: [ "arn:aws:ecr:{{ aws_region }}:{{ aws_account_id }}:repository/{{ service_id }}_*" ] diff --git a/ansible/roles/deploy-ecs-proxies-retag/tasks/main.yml b/ansible/roles/deploy-ecs-proxies-retag/tasks/main.yml new file mode 100644 index 000000000..a96d861d7 --- /dev/null +++ b/ansible/roles/deploy-ecs-proxies-retag/tasks/main.yml @@ -0,0 +1,83 @@ +- name: Ensure docker_containers is loaded + include_vars: + file: "{{ lookup('env', 'CONTAINER_VARS_FILE') }}" + when: docker_containers is not defined + +- name: Login to ECR + shell: > + {{ aws_cmd }} ecr get-login-password --region {{ aws_region }} + | docker login --username AWS --password-stdin {{ ecr_registry }} + +- name: Pulling ECR image + debug: + msg: "Pulling {{ item }}:{{ build_label }}" + loop: "{{ repo_names }}" + loop_control: + label: "{{ item }}" + +- name: Pull existing image + ansible.builtin.command: + cmd: > + docker pull {{ ecr_registry }}/{{ item }}:{{ build_label }} + loop: "{{ repo_names }}" + loop_control: + label: "{{ item }}" + register: pull_results + +- name: Retagging ECR image + debug: + msg: "Retagging {{ item.item }}:{{ build_label }} → ecs-{{ build_label }}" + loop: "{{ pull_results.results }}" + loop_control: + label: "{{ item.item }}" + when: + - item.rc == 0 + - item.item == "canary_canary-api" + +- name: Retag image + ansible.builtin.command: + cmd: > + docker tag + {{ ecr_registry }}/{{ item.item }}:{{ build_label }} + {{ ecr_registry }}/{{ item.item }}:ecs-{{ build_label }} + loop: "{{ pull_results.results }}" + loop_control: + label: "{{ item.item }}" + when: + - item.rc == 0 + - item.item == "canary_canary-api" + +- name: Pushing ECR image + debug: + msg: "Pushing ecs-{{ build_label }} for {{ item.item }}" + loop: "{{ pull_results.results }}" + loop_control: + label: "{{ item.item }}" + when: + - item.rc == 0 + - item.item == "canary_canary-api" + +- name: Push new tag + ansible.builtin.command: + cmd: > + docker push {{ ecr_registry }}/{{ item.item }}:ecs-{{ build_label }} + loop: "{{ pull_results.results }}" + loop_control: + label: "{{ item.item }}" + when: + - item.rc == 0 + - item.item == "canary_canary-api" + +# - name: Delete old tag from ECR +# ansible.builtin.command: +# cmd: > +# aws ecr batch-delete-image +# --repository-name {{ item.item }} +# --image-ids imageTag={{ build_label }} +# --region {{ aws_region }} +# loop: "{{ pull_results.results }}" +# loop_control: +# label: "{{ item.item }}" +# when: +# - item.rc == 0 +# - item.item == "canary_canary-api" diff --git a/ansible/roles/deploy-ecs-proxies-retag/vars/main.yml b/ansible/roles/deploy-ecs-proxies-retag/vars/main.yml new file mode 100644 index 000000000..da9d210b6 --- /dev/null +++ b/ansible/roles/deploy-ecs-proxies-retag/vars/main.yml @@ -0,0 +1,6 @@ +--- +build_label: "{{ lookup('env', 'build_label') }}" +containers: "{{ docker_containers | json_query('[].name') | unique | sort }}" +repo_names: "{{ containers | map('regex_replace', '^(.*)$', service_id + '_\\1') | list }}" +base_dir: "{{ playbook_dir }}/../.." + diff --git a/ansible/roles/deploy-ecs-proxies/tasks/main.yml b/ansible/roles/deploy-ecs-proxies/tasks/main.yml index 668c8cb0e..00281fc94 100644 --- a/ansible/roles/deploy-ecs-proxies/tasks/main.yml +++ b/ansible/roles/deploy-ecs-proxies/tasks/main.yml @@ -71,14 +71,21 @@ with_filetree: "{{ '../templates' }}" when: item.state == 'file' + - name: ansible use_ecs_tag before terraform + debug: + msg: > + ANSIBLE: use_ecs_tag='{{ use_ecs_tag }}' + TYPE={{ use_ecs_tag | type_debug }} + TF_VAR_use_ecs_tag='{{ use_ecs_tag | ternary("true","false") }}' + - name: terraform plan - shell: "make -C {{ out_dir }}/terraform clean plan args='-no-color -lock-timeout=30m -out tfplan.out'" # noqa 305 + shell: "TF_VAR_use_ecs_tag={{ use_ecs_tag | ternary('true','false') }} make -C {{ out_dir }}/terraform clean plan args='-no-color -lock-timeout=30m -out tfplan.out'" # noqa 305 register: tfplan failed_when: tfplan.rc not in (0, 2) when: not do_not_terraform - name: terraform apply - shell: "make -C {{ out_dir }}/terraform apply-plan args='-no-color -lock-timeout=30m --auto-approve tfplan.out'" # noqa 305 + shell: "TF_VAR_use_ecs_tag={{ use_ecs_tag | ternary('true','false') }} make -C {{ out_dir }}/terraform apply-plan args='-no-color -lock-timeout=30m --auto-approve tfplan.out'" # noqa 305 register: tfapply when: not do_not_terraform diff --git a/ansible/roles/deploy-ecs-proxies/templates/terraform/locals.tf b/ansible/roles/deploy-ecs-proxies/templates/terraform/locals.tf index c01c869d5..c1190e947 100644 --- a/ansible/roles/deploy-ecs-proxies/templates/terraform/locals.tf +++ b/ansible/roles/deploy-ecs-proxies/templates/terraform/locals.tf @@ -43,18 +43,34 @@ locals { } - ecs_service = [ - {% for container in ecs_service %} - {{ - ( - container - | combine( - {'image': '${local.account_id}.dkr.ecr.eu-west-2.amazonaws.com/' + service_id + '_' + container.name + ':' + build_label } +ecs_service = [ +{% for container in ecs_service %} + + {% set image_tag = ( + '${local.account_id}.dkr.ecr.eu-west-2.amazonaws.com/' + + service_id + '_' + container.name + + ( + ":ecs-" + build_label + if use_ecs_tag and container.name == "canary-api" + else ":" + build_label ) - ) | to_json - }}, - {% endfor %} - ] + ) + %} + + + {{ + ( + container + | combine( + { + 'image': image_tag + } + ) + ) | to_json + }}, + +{% endfor %} +] exposed_service = element(matchkeys(local.ecs_service, local.ecs_service.*.expose, list(true)), 0) diff --git a/ansible/roles/deploy-ecs-proxies/templates/terraform/variables.tf b/ansible/roles/deploy-ecs-proxies/templates/terraform/variables.tf index 3b88aad59..361c5df98 100644 --- a/ansible/roles/deploy-ecs-proxies/templates/terraform/variables.tf +++ b/ansible/roles/deploy-ecs-proxies/templates/terraform/variables.tf @@ -71,4 +71,9 @@ variable "autoscaling_scale_out_cooldown" { variable "deregistration_delay" { type = number +} + +variable "use_ecs_tag" { + type = bool + description = "Whether to use ecs- prefixed tag for canary-api" } \ No newline at end of file diff --git a/azure/deploy-ecs-proxies-retag.yml b/azure/deploy-ecs-proxies-retag.yml new file mode 100644 index 000000000..02679c720 --- /dev/null +++ b/azure/deploy-ecs-proxies-retag.yml @@ -0,0 +1,22 @@ +parameters: + - name: 'container_vars' + type: string + default: 'ecs-proxies-containers.yml' + + - name: 'env_vars_dir' + type: string + default: './' + + - name: 'utils_dir' + type: string + default: 'utils' + +steps: + - bash: | + set -e + source "${{ parameters.env_vars_dir }}/.build_env_vars" + + export CONTAINER_VARS_FILE="$SERVICE_DIR/ecs-proxies-containers.yml + + make --no-print-directory -C ${{ parameters.utils_dir }}/ansible deploy-ecs-proxies-retag + displayName: "Retag ECS proxies" diff --git a/azure/templates/deploy-service.yml b/azure/templates/deploy-service.yml index a67b52457..45ae358ed 100644 --- a/azure/templates/deploy-service.yml +++ b/azure/templates/deploy-service.yml @@ -191,12 +191,40 @@ steps: displayName: Create ECS Prerequisites condition: and(succeeded(), eq(variables['deploy_containers'], 'true')) + - bash: | + echo "##vso[task.setvariable variable=SERVICE_BASE_PATH_FOR_CONDITION]${{ parameters.service_base_path }}" + displayName: Set SERVICE_BASE_PATH_FOR_CONDITION + + + - bash: | + set -e + + export CONTAINER_VARS_FILE="$SERVICE_DIR/ecs-proxies-containers.yml" + + source $(SERVICE_DIR)/.build_env_vars + export SERVICE_BASE_PATH="${{ parameters.service_base_path }}" + echo "SERVICE_BASE_PATH='${SERVICE_BASE_PATH}'" + + export ASSUMED_VERSION=$(echo $SERVICE_ARTIFACT_NAME | grep -E -o "v[0-9]+\.[0-9]+\.[0-9]+-?[a-z]*" || true | tail -1) + export DEPLOYED_VERSION=${ASSUMED_VERSION:-${{ parameters.fully_qualified_service_name }}} + + account=${{ parameters.aws_account }} \ + CONTAINER_VARS_FILE="${CONTAINER_VARS_FILE}" \ + SOURCE_COMMIT_ID="$(Build.SourceVersion)" \ + RELEASE_RELEASEID="$(Build.BuildId)" \ + make --no-print-directory -C $(UTILS_DIR)/ansible deploy-ecs-proxies-retag + + displayName: Retag ECS image + condition: and(succeeded(), ne(variables['DEPLOY_ROLE'], ''), contains(variables['SERVICE_BASE_PATH_FOR_CONDITION'], 'canary'), not(contains(variables['SERVICE_BASE_PATH_FOR_CONDITION'], '-pr-'))) + + + - template: ../components/aws-assume-role.yml parameters: role: "$(DEPLOY_ROLE)" profile: "$(DEPLOY_ROLE)" aws_account: "${{ parameters.aws_account }}" - + - bash: | set -e proxy_vars_file="$(PROXY_VARS_FILE)" @@ -209,6 +237,18 @@ steps: else export DEPLOYED_VERSION="${{ parameters.fully_qualified_service_name }}" fi + + + if [[ "$SERVICE_BASE_PATH_FOR_CONDITION" == *"-pr-"* ]]; then + export TF_VAR_use_ecs_tag=false + else + export TF_VAR_use_ecs_tag=true + fi + + echo "TF_VAR_use_ecs_tag='${TF_VAR_use_ecs_tag}'" + echo "SERVICE_BASE_PATH='${SERVICE_BASE_PATH}'" + + export use_ecs_tag=$TF_VAR_use_ecs_tag account=${{ parameters.aws_account }} \ PROXY_VARS_FILE="${proxy_vars_file}" \ @@ -216,6 +256,7 @@ steps: RELEASE_RELEASEID="$(Build.BuildId)" \ SERVICE_BASE_PATH=${{ parameters.service_base_path }} \ APIGEE_ENVIRONMENT=${{ parameters.apigee_environment }} \ + use_ecs_tag="${use_ecs_tag}" \ make --no-print-directory -C $(UTILS_DIR)/ansible deploy-ecs-proxies displayName: Deploy ECS proxies @@ -348,6 +389,7 @@ steps: displayName: Disable _status monitoring condition: and(succeeded(), eq(variables['check_and_enable_status'], 'false'), eq(variables['is_pull_request'], 'false')) + - bash: | set -euo pipefail