From f381cc328b83b1a95b284006846fc3b7479fb3bf Mon Sep 17 00:00:00 2001
From: "James E. Blair" <jim@acmegating.com>
Date: Fri, 24 Mar 2023 07:59:19 -0700
Subject: [PATCH] Update promote-container-image to copy from intermediate
 registry

Change-Id: Ia24bbd101e01ab371ceacfed006b5ff806418a97
---
 roles/promote-container-image/README.rst      | 23 ++++++++++++++
 .../defaults/main.yaml                        |  1 +
 roles/promote-container-image/tasks/main.yaml | 30 ++++++++++++++++---
 .../promote-from-intermediate-registry.yaml   | 21 +++++++++++++
 .../tasks/promote-from-tag.yaml               | 25 ----------------
 .../tasks/promote-registry.yaml               | 24 +++++++++++++++
 6 files changed, 95 insertions(+), 29 deletions(-)
 create mode 100644 roles/promote-container-image/tasks/promote-from-intermediate-registry.yaml
 delete mode 100644 roles/promote-container-image/tasks/promote-from-tag.yaml
 create mode 100644 roles/promote-container-image/tasks/promote-registry.yaml

diff --git a/roles/promote-container-image/README.rst b/roles/promote-container-image/README.rst
index dc97afd8d..29dc039d7 100644
--- a/roles/promote-container-image/README.rst
+++ b/roles/promote-container-image/README.rst
@@ -10,3 +10,26 @@ Promote one or more previously uploaded container images.
    by the upload-container-image role.  Set to
    ``intermediate-registry`` to have this role copy an image created
    and pushed to an intermediate registry by the build-container-role.
+   In that case, the variables below provide the extra information
+   needed to perform the query.
+
+.. zuul:rolevar:: promote_container_image_api
+
+   Only required for the ``intermediate-registry`` method.
+   The Zuul API endpoint to use.  Example: ``https://zuul.example.org/api/tenant/{{ zuul.tenant }}``
+
+.. zuul:rolevar:: promote_container_image_pipeline
+
+   Only required for the ``intermediate-registry`` method.
+   The pipeline in which the previous build ran.
+
+.. zuul:rolevar:: promote_container_image_job
+
+   Only required for the ``intermediate-registry`` method.
+   The job of the previous build.
+
+.. zuul:rolevar:: promote_container_image_query
+   :default: change={{ zuul.change }}&patchset={{ zuul.patchset }}&pipeline={{ promote_container_image_pipeline }}&job_name={{ promote_container_image_job }}
+
+   Only required for the ``intermediate-registry`` method.
+   The query to use to find the build.  Normally the default is used.
diff --git a/roles/promote-container-image/defaults/main.yaml b/roles/promote-container-image/defaults/main.yaml
index 9739eb171..deda4a500 100644
--- a/roles/promote-container-image/defaults/main.yaml
+++ b/roles/promote-container-image/defaults/main.yaml
@@ -1 +1,2 @@
 zuul_work_dir: "{{ zuul.project.src_dir }}"
+promote_container_image_query: "change={{ zuul.change }}&patchset={{ zuul.patchset }}&pipeline={{ promote_container_image_pipeline }}&job_name={{ promote_container_image_job }}"
diff --git a/roles/promote-container-image/tasks/main.yaml b/roles/promote-container-image/tasks/main.yaml
index aea1b0a22..19c21e2ea 100644
--- a/roles/promote-container-image/tasks/main.yaml
+++ b/roles/promote-container-image/tasks/main.yaml
@@ -1,8 +1,30 @@
-- name: Promote container image with tags
+- name: Verify repository names
+  when: |
+    container_registry_credentials is defined
+    and zj_image.registry not in container_registry_credentials
+  loop: "{{ container_images }}"
+  loop_control:
+    loop_var: zj_image
+  fail:
+    msg: "{{ zj_image.registry }} credentials not found"
+
+- name: Verify repository permission
+  when: |
+    container_registry_credentials[zj_image.registry].repository is defined and
+    not zj_image.repository | regex_search(container_registry_credentials[zj_image.registry].repository)
+  loop: "{{ container_images }}"
+  loop_control:
+    loop_var: zj_image
+  fail:
+    msg: "{{ zj_image.repository }} not permitted by {{ container_registry_credentials[zj_image.registry].repository }}"
+
+- name: Promote image
   when: promote_container_image_method|default('tag') == 'tag'
-  include_tasks: promote-from-tag.yaml
+  loop: "{{ container_images }}"
+  loop_control:
+    loop_var: zj_image
+  include_tasks: promote-retag.yaml
 
 - name: Promote container image with intermediate registry
   when: promote_container_image_method|default('tag') == 'intermediate-registry'
-  fail:
-    msg: 'The intermediate-registry promote role is not yet complete'
+  include_tasks: promote-from-intermediate-registry.yaml
diff --git a/roles/promote-container-image/tasks/promote-from-intermediate-registry.yaml b/roles/promote-container-image/tasks/promote-from-intermediate-registry.yaml
new file mode 100644
index 000000000..93058420e
--- /dev/null
+++ b/roles/promote-container-image/tasks/promote-from-intermediate-registry.yaml
@@ -0,0 +1,21 @@
+- name: Query Zuul API for image information
+  uri:
+    url: "{{ promote_container_image_api }}/builds?{{ promote_container_image_query }}"
+  register: build
+
+- name: Parse build response
+  set_fact:
+    build: "{{ build.json[0] }}"
+
+- name: Map image artifacts
+  set_fact:
+    zj_artifact_map: "{{ zj_artifact_map | default({}) | combine({zj_map_item.metadata.repository + ':' + zj_map_item.metadata.tag: zj_map_item.url}) }}"
+  loop_control:
+    loop_var: zj_map_item
+  loop: "{{ build | json_query(\"artifacts[?metadata.type=='container_image']\")}}"
+
+- name: Promote image
+  loop: "{{ container_images }}"
+  loop_control:
+    loop_var: zj_image
+  include_tasks: promote-registry.yaml
diff --git a/roles/promote-container-image/tasks/promote-from-tag.yaml b/roles/promote-container-image/tasks/promote-from-tag.yaml
deleted file mode 100644
index fb4a96d6c..000000000
--- a/roles/promote-container-image/tasks/promote-from-tag.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-- name: Verify repository names
-  when: |
-    container_registry_credentials is defined
-    and zj_image.registry not in container_registry_credentials
-  loop: "{{ container_images }}"
-  loop_control:
-    loop_var: zj_image
-  fail:
-    msg: "{{ zj_image.registry }} credentials not found"
-
-- name: Verify repository permission
-  when: |
-    container_registry_credentials[zj_image.registry].repository is defined and
-    not zj_image.repository | regex_search(container_registry_credentials[zj_image.registry].repository)
-  loop: "{{ container_images }}"
-  loop_control:
-    loop_var: zj_image
-  fail:
-    msg: "{{ zj_image.repository }} not permitted by {{ container_registry_credentials[zj_image.registry].repository }}"
-
-- name: Promote image
-  loop: "{{ container_images }}"
-  loop_control:
-    loop_var: zj_image
-  include_tasks: promote-retag.yaml
diff --git a/roles/promote-container-image/tasks/promote-registry.yaml b/roles/promote-container-image/tasks/promote-registry.yaml
new file mode 100644
index 000000000..4ca542708
--- /dev/null
+++ b/roles/promote-container-image/tasks/promote-registry.yaml
@@ -0,0 +1,24 @@
+- name: Log in to registry
+  no_log: true
+  command: >-
+    skopeo login {{ zj_image.registry }} -u {{ container_registry_credentials[zj_image.registry].username }} -p {{ container_registry_credentials[zj_image.registry].password }}
+  register: result
+  until: result.rc == 0
+  retries: 3
+  delay: 30
+
+- name: Copy image
+  block:
+    - name: Copy image
+      loop: "{{ zj_image.tags | default(['latest']) }}"
+      loop_control:
+        loop_var: zj_image_tag
+      command: >-
+        skopeo --insecure-policy copy --all {{ zj_artifact_map[zj_image.repository + ':' + zj_image_tag] }} docker://{{ zj_image.repository }}:{{ zj_image_tag }}
+      register: result
+      until: result.rc == 0
+      retries: 3
+      delay: 30
+  always:
+    - name: Log out of registry
+      command: "skopeo logout {{ zj_image.registry }}"