diff --git a/ansible/inventory/group_vars/operator-pipeline.yml b/ansible/inventory/group_vars/operator-pipeline.yml index 0955c4665..62532022d 100644 --- a/ansible/inventory/group_vars/operator-pipeline.yml +++ b/ansible/inventory/group_vars/operator-pipeline.yml @@ -25,6 +25,7 @@ operator_pipeline_private_cert_local_path: ../../vaults/{{ env }}/operator-pipel operator_hosted_pipeline_registry_auth_path: ../../vaults/{{ env }}/registry-auth/hosted-pipeline.json operator_release_pipeline_registry_auth_pull_path: ../../vaults/{{ env }}/registry-auth/release-pipeline-pull.json +operator_release_pipeline_registry_auth_pull_pending_path: ../../vaults/{{ env }}/registry-auth/release-pipeline-pull-pending.json operator_release_pipeline_registry_auth_push_path: ../../vaults/{{ env }}/registry-auth/release-pipeline-push.json operator_release_pipeline_registry_auth_serve_path: ../../vaults/{{ env }}/registry-auth/release-pipeline-serve.json diff --git a/ansible/roles/operator-pipeline/tasks/community-release-pipeline-trigger.yml b/ansible/roles/operator-pipeline/tasks/community-release-pipeline-trigger.yml index ade2330fd..835330dd2 100644 --- a/ansible/roles/operator-pipeline/tasks/community-release-pipeline-trigger.yml +++ b/ansible/roles/operator-pipeline/tasks/community-release-pipeline-trigger.yml @@ -163,3 +163,6 @@ - name: ssh-dir secret: secretName: github-rh-operator-bundle-bot-ssh-credentials + - name: iib-credentials + secret: + secretName: $(tt.params.quay_push_final_index_secret) diff --git a/ansible/roles/operator-pipeline/tasks/operator-release-pipeline-trigger.yml b/ansible/roles/operator-pipeline/tasks/operator-release-pipeline-trigger.yml index 8c0f0494a..a03316f6c 100644 --- a/ansible/roles/operator-pipeline/tasks/operator-release-pipeline-trigger.yml +++ b/ansible/roles/operator-pipeline/tasks/operator-release-pipeline-trigger.yml @@ -149,3 +149,6 @@ - name: ssh-dir secret: secretName: github-rh-operator-bundle-bot-ssh-credentials + - name: iib-credentials + secret: + secretName: $(tt.params.quay_push_final_index_secret) diff --git a/ansible/roles/operator-pipeline/tasks/pipeline-secrets.yml b/ansible/roles/operator-pipeline/tasks/pipeline-secrets.yml index eaa0bcf5b..f33ef3838 100644 --- a/ansible/roles/operator-pipeline/tasks/pipeline-secrets.yml +++ b/ansible/roles/operator-pipeline/tasks/pipeline-secrets.yml @@ -126,6 +126,27 @@ data: .dockerconfigjson: "{{ lookup('file', operator_release_pipeline_registry_auth_serve_path, rstrip=False) | b64encode }}" +- name: Create Operator release pipeline pull pending registry auth secret + no_log: true + tags: + - secrets + kubernetes.core.k8s: + state: present + force: true + namespace: "{{ oc_namespace }}" + definition: + apiVersion: v1 + kind: Secret + type: opaque + metadata: + name: release-pipeline-pull-pending-registry-auth + labels: + app: operator-pipeline + suffix: "{{ suffix }}" + env: "{{ env }}" + data: + config.json: "{{ lookup('file', operator_release_pipeline_registry_auth_pull_pending_path, rstrip=False) | b64encode }}" + - name: Create Operator pipeline github bot token secret no_log: true tags: diff --git a/ansible/roles/operator-pipeline/templates/openshift/pipelines/operator-release-pipeline.yml b/ansible/roles/operator-pipeline/templates/openshift/pipelines/operator-release-pipeline.yml index 9e663d5ad..0ebe94b10 100644 --- a/ansible/roles/operator-pipeline/templates/openshift/pipelines/operator-release-pipeline.yml +++ b/ansible/roles/operator-pipeline/templates/openshift/pipelines/operator-release-pipeline.yml @@ -65,6 +65,9 @@ spec: - name: signing_pub_secret_key description: The key within the Kubernetes Secret that contains the public key for verifying signatures. default: sig-key.pub + - name: registry_pull_pending_secret_name + description: The name of the Kubernetes Secret that contains registry credentials for pulling from pending repositories. + default: release-pipeline-pull-pending-registry-auth - name: cert_project_required description: >- A flag determines whether a cert project identifier is required @@ -85,6 +88,10 @@ spec: - name: registry-serve-credentials - name: hosted-registry-credentials - name: image-data + - name: iib-credentials + description: | + Quay credentials for IIB to authenticate and overwrite from_index. + Required for IIB to push to pending repositories. tasks: @@ -491,11 +498,22 @@ spec: - name: credentials workspace: hosted-registry-credentials + # Generate timestamp for consistent tagging across IIB builds and publish + - name: generate-build-timestamp + runAfter: + - get-supported-versions + taskRef: + name: generate-build-timestamp + params: + - name: pipeline_image + value: "$(params.pipeline_image)" + # acquire/lease the resource to resolve the conflict of concurrent pipelineruns - name: acquire-lease retries: 8 runAfter: - build-fragment-images + - generate-build-timestamp when: - ¬Undistributed input: "$(tasks.get-pyxis-certification-data.results.operator_distribution)" @@ -525,7 +543,7 @@ spec: - name: pipeline_image value: "$(params.pipeline_image)" - name: index_images - value: "$(tasks.get-supported-versions.results.public_repository_mirrors_with_version)" + value: "$(tasks.get-supported-versions.results.pending_repositories_with_version)" - name: commit_sha value: "$(params.git_commit)" - name: catalogs_with_added_or_modified_operators @@ -542,10 +560,14 @@ spec: value: "$(params.kerberos_keytab_secret_name)" - name: kerberos_keytab_secret_key value: "$(params.kerberos_keytab_secret_key)" + - name: build_tags_suffix + value: "$(tasks.generate-build-timestamp.results.timestamp)" workspaces: - name: output workspace: results subPath: paths + - name: iib-credentials + workspace: iib-credentials # call IIB to add the bundle to index - name: add-bundle-to-index @@ -565,7 +587,7 @@ spec: - name: pipeline_image value: "$(params.pipeline_image)" - name: index_images - value: "$(tasks.get-supported-versions.results.public_repository_mirrors_with_version)" + value: "$(tasks.get-supported-versions.results.pending_repositories_with_version)" - name: bundle_pullspec value: "$(tasks.copy-bundle-image-to-released-registry.results.image_pullspec)" - name: iib_url @@ -578,10 +600,14 @@ spec: value: "$(params.kerberos_keytab_secret_key)" - name: upgrade-graph-mode value: "$(tasks.read-config.results.upgrade-graph-mode)" + - name: build_tags_suffix + value: "$(tasks.generate-build-timestamp.results.timestamp)" workspaces: - name: output workspace: results subPath: paths + - name: iib-credentials + workspace: iib-credentials - name: sign-index-image runAfter: @@ -595,6 +621,8 @@ spec: params: - name: pipeline_image value: "$(params.pipeline_image)" + - name: public_repository_mirror + value: "$(tasks.get-supported-versions.results.public_repository_mirror)" - name: requester value: "araszka" - name: sig_key_id @@ -624,6 +652,9 @@ spec: value: "$(params.signing_pub_secret_name)" - name: signing_pub_secret_key value: "$(params.signing_pub_secret_key)" + + - name: registry_auth_secret_name + value: "$(params.registry_pull_pending_secret_name)" workspaces: - name: results workspace: results @@ -646,6 +677,8 @@ spec: value: "$(tasks.get-supported-versions.results.public_repository_mirror)" - name: quay_push_final_index_secret value: "$(params.quay_push_final_index_secret)" + - name: build_tags_suffix + value: "$(tasks.generate-build-timestamp.results.timestamp)" workspaces: - name: results workspace: results diff --git a/ansible/roles/operator-pipeline/templates/openshift/tasks/add-bundle-to-index.yml b/ansible/roles/operator-pipeline/templates/openshift/tasks/add-bundle-to-index.yml index 928963c38..8b6566c29 100644 --- a/ansible/roles/operator-pipeline/templates/openshift/tasks/add-bundle-to-index.yml +++ b/ansible/roles/operator-pipeline/templates/openshift/tasks/add-bundle-to-index.yml @@ -42,6 +42,11 @@ spec: default: "" description: "A graph update mode that defines how channel graphs are updated" + - name: build_tags_suffix + default: "" + description: | + Timestamp suffix for build tags (used with overwrite to ensure consistent tagging with publish task). + results: - name: status description: Indicates a status of adding a bundle to an index @@ -53,6 +58,12 @@ spec: - name: output - name: credentials optional: true + - name: iib-credentials + description: | + Optional workspace containing Quay credentials for IIB to authenticate and overwrite from_index. + Required for release pipeline to enable IIB to push to pending repositories. + Expected to contain 'username' and 'password' files. + optional: true steps: - name: add-bundle-to-index @@ -67,9 +78,9 @@ spec: mountPath: "/etc/kerberos" script: | #! /usr/bin/env bash - set -xe + # DO NOT USE set -x to avoid revealing credentials in logs! + set -e - ENV=$(params.environment) INDEX_IMAGES="$(params.index_images)" EXTRA_ARGS="" @@ -85,6 +96,17 @@ spec: EXTRA_ARGS+=" --mode $(params.upgrade-graph-mode)" fi + # Add IIB overwrite token if credentials workspace is provided (for release pipeline) + if [[ "$(workspaces.iib-credentials.bound)" == "true" ]]; then + IIB_QUAY_USER=$(cat $(workspaces.iib-credentials.path)/username) + IIB_QUAY_TOKEN=$(cat $(workspaces.iib-credentials.path)/password) + export IIB_OVERWRITE_TOKEN="${IIB_QUAY_USER}:${IIB_QUAY_TOKEN}" + + # Add build tags suffix for consistent tagging with publish task + if [[ "$(params.build_tags_suffix)" != "" ]]; then + EXTRA_ARGS+=" --build-tags-suffix $(params.build_tags_suffix)" + fi + fi # DO NOT use `--verbose` to avoid auth headers appearing in logs index \ @@ -96,4 +118,31 @@ spec: echo -n "success" | tee "$(results.status.path)" + echo cat index-image-paths.txt + echo + + # WORKAROUND: Manually overwriting index images using skopeo (TODO: remove when overwrite token is fixed) + if [[ "$(workspaces.iib-credentials.bound)" == "true" ]]; then + TEMP_IMAGES=$(cat index-image-paths.txt | tr "," " ") + for i in $TEMP_IMAGES + do + SRC_IMAGE=$(echo $i | awk -F '+' '{print $2}') + DEST_IMAGE=$(echo $i | awk -F '+' '{print $1}') + echo "1. Version tag: copying $SRC_IMAGE to $DEST_IMAGE" + skopeo copy --format v2s2 --all --src-no-creds \ + --dest-creds $IIB_QUAY_USER:$IIB_QUAY_TOKEN \ + docker://$SRC_IMAGE \ + docker://$DEST_IMAGE + + # Also copy with permanent tag if build_tags_suffix is set + if [[ "$(params.build_tags_suffix)" != "" ]]; then + DEST_IMAGE_PERMANENT="${DEST_IMAGE}-$(params.build_tags_suffix)" + echo "2. Permanent tag: copying $SRC_IMAGE to $DEST_IMAGE_PERMANENT" + skopeo copy --format v2s2 --all --src-no-creds \ + --dest-creds $IIB_QUAY_USER:$IIB_QUAY_TOKEN \ + docker://$SRC_IMAGE \ + docker://$DEST_IMAGE_PERMANENT + fi + done + fi diff --git a/ansible/roles/operator-pipeline/templates/openshift/tasks/build-fbc-index-images.yml b/ansible/roles/operator-pipeline/templates/openshift/tasks/build-fbc-index-images.yml index 09626b2f3..fb28fb647 100644 --- a/ansible/roles/operator-pipeline/templates/openshift/tasks/build-fbc-index-images.yml +++ b/ansible/roles/operator-pipeline/templates/openshift/tasks/build-fbc-index-images.yml @@ -46,12 +46,23 @@ spec: description: >- The key within the Kubernetes Secret that contains the kerberos keytab for submitting IIB builds. + - name: build_tags_suffix + default: "" + description: | + Timestamp suffix for build tags (used with overwrite to ensure consistent tagging with publish task). + volumes: - name: kerberos-volume secret: secretName: "$(params.kerberos_keytab_secret_name)" workspaces: - name: output + - name: iib-credentials + description: | + Optional workspace containing Quay credentials for IIB to authenticate and overwrite from_index. + Required for release pipeline to enable IIB to push to pending repositories. + Expected to contain 'username' and 'password' files. + optional: true steps: - name: add-fbc-fragments-to-index @@ -66,16 +77,29 @@ spec: mountPath: "/etc/kerberos" script: | #! /usr/bin/env bash - set -xe + # DO NOT USE set -x to avoid revealing credentials in logs! + set -e if [[ "$(params.catalogs_with_added_or_modified_operators)" == "" ]];then echo "No affected catalogs, skipping fragment image build" exit 0 fi - ENV=$(params.environment) INDEX_IMAGES="$(params.index_images)" + # Add IIB overwrite token if credentials workspace is provided (for release pipeline) + EXTRA_ARGS="" + if [[ "$(workspaces.iib-credentials.bound)" == "true" ]]; then + IIB_QUAY_USER=$(cat $(workspaces.iib-credentials.path)/username) + IIB_QUAY_TOKEN=$(cat $(workspaces.iib-credentials.path)/password) + export IIB_OVERWRITE_TOKEN="${IIB_QUAY_USER}:${IIB_QUAY_TOKEN}" + + # Add build tags suffix for consistent tagging with publish task + if [[ "$(params.build_tags_suffix)" != "" ]]; then + EXTRA_ARGS+=" --build-tags-suffix $(params.build_tags_suffix)" + fi + fi + add-fbc-fragments-to-index \ --iib-url "$(params.iib_url)" \ --indices $INDEX_IMAGES \ @@ -83,9 +107,36 @@ spec: --image-repository "$(params.image_repository)" \ --commit-sha "$(params.commit_sha)" \ --image-output index-image-paths.txt \ - --verbose + --verbose \ + $EXTRA_ARGS cat index-image-paths.txt + echo + + # WORKAROUND: Manually overwrite index images using skopeo (TODO: remove when IIB fixed) + if [[ "$(workspaces.iib-credentials.bound)" == "true" ]]; then + TEMP_IMAGES=$(cat index-image-paths.txt | tr "," " ") + for i in $TEMP_IMAGES + do + SRC_IMAGE=$(echo $i | awk -F '+' '{print $2}') + DEST_IMAGE=$(echo $i | awk -F '+' '{print $1}') + echo "1. Version tag: copying $SRC_IMAGE to $DEST_IMAGE" + skopeo copy --format v2s2 --all --src-no-creds \ + --dest-creds $IIB_QUAY_USER:$IIB_QUAY_TOKEN \ + docker://$SRC_IMAGE \ + docker://$DEST_IMAGE + + # Also copy with permanent tag if build_tags_suffix is set + if [[ "$(params.build_tags_suffix)" != "" ]]; then + DEST_IMAGE_PERMANENT="${DEST_IMAGE}-$(params.build_tags_suffix)" + echo "2. Permanent tag: copying $SRC_IMAGE to $DEST_IMAGE_PERMANENT" + skopeo copy --format v2s2 --all --src-no-creds \ + --dest-creds $IIB_QUAY_USER:$IIB_QUAY_TOKEN \ + docker://$SRC_IMAGE \ + docker://$DEST_IMAGE_PERMANENT + fi + done + fi - name: rm-operator-from-index image: "$(params.pipeline_image)" @@ -99,22 +150,62 @@ spec: mountPath: "/etc/kerberos" script: | #! /usr/bin/env bash - set -xe + # DO NOT USE set -x to avoid revealing credentials in logs! + set -e if [[ "$(params.deleted_catalog_operators)" == "" ]];then echo "No deleted operators, skipping operator removal from index" exit 0 fi - ENV=$(params.environment) INDEX_IMAGES="$(params.index_images)" + # Add IIB overwrite token if credentials workspace is provided (for release pipeline) + EXTRA_ARGS="" + if [[ "$(workspaces.iib-credentials.bound)" == "true" ]]; then + IIB_QUAY_USER=$(cat $(workspaces.iib-credentials.path)/username) + IIB_QUAY_TOKEN=$(cat $(workspaces.iib-credentials.path)/password) + export IIB_OVERWRITE_TOKEN="${IIB_QUAY_USER}:${IIB_QUAY_TOKEN}" + + # Add build tags suffix for consistent tagging with publish task + if [[ "$(params.build_tags_suffix)" != "" ]]; then + EXTRA_ARGS+=" --build-tags-suffix $(params.build_tags_suffix)" + fi + fi + rm-operator-from-index \ --iib-url "$(params.iib_url)" \ --indices $INDEX_IMAGES \ --fragment-builds-output index-image-paths.txt \ --rm-catalog-operators "$(params.deleted_catalog_operators)" \ --image-output index-image-paths.txt \ - --verbose + --verbose \ + $EXTRA_ARGS cat index-image-paths.txt + echo + + # WORKAROUND: Manually overwrite index images using skopeo (TODO: remove when IIB fixed) + if [[ "$(workspaces.iib-credentials.bound)" == "true" ]]; then + TEMP_IMAGES=$(cat index-image-paths.txt | tr "," " ") + for i in $TEMP_IMAGES + do + SRC_IMAGE=$(echo $i | awk -F '+' '{print $2}') + DEST_IMAGE=$(echo $i | awk -F '+' '{print $1}') + echo "1. Version tag: copying $SRC_IMAGE to $DEST_IMAGE" + skopeo copy --format v2s2 --all --src-no-creds \ + --dest-creds $IIB_QUAY_USER:$IIB_QUAY_TOKEN \ + docker://$SRC_IMAGE \ + docker://$DEST_IMAGE + + # Also copy with permanent tag if build_tags_suffix is set + if [[ "$(params.build_tags_suffix)" != "" ]]; then + DEST_IMAGE_PERMANENT="${DEST_IMAGE}-$(params.build_tags_suffix)" + echo "2. Permanent tag: copying $SRC_IMAGE to $DEST_IMAGE_PERMANENT" + skopeo copy --format v2s2 --all --src-no-creds \ + --dest-creds $IIB_QUAY_USER:$IIB_QUAY_TOKEN \ + docker://$SRC_IMAGE \ + docker://$DEST_IMAGE_PERMANENT + fi + done + fi diff --git a/ansible/roles/operator-pipeline/templates/openshift/tasks/generate-build-timestamp.yml b/ansible/roles/operator-pipeline/templates/openshift/tasks/generate-build-timestamp.yml new file mode 100644 index 000000000..bd7d79720 --- /dev/null +++ b/ansible/roles/operator-pipeline/templates/openshift/tasks/generate-build-timestamp.yml @@ -0,0 +1,20 @@ +--- +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: generate-build-timestamp +spec: + description: |- + Generate a Unix timestamp to be used for tagging index images consistently + across IIB builds and publish operations. + params: + - name: pipeline_image + results: + - name: timestamp + description: Unix timestamp for tagging index images + steps: + - name: generate-timestamp + image: "$(params.pipeline_image)" + script: | + #!/usr/bin/env bash + date +%s | tee "$(results.timestamp.path)" diff --git a/ansible/roles/operator-pipeline/templates/openshift/tasks/get-supported-versions.yml b/ansible/roles/operator-pipeline/templates/openshift/tasks/get-supported-versions.yml index d1ebde29d..800beead6 100644 --- a/ansible/roles/operator-pipeline/templates/openshift/tasks/get-supported-versions.yml +++ b/ansible/roles/operator-pipeline/templates/openshift/tasks/get-supported-versions.yml @@ -29,13 +29,6 @@ spec: Pull spec for the public repository mirror index image. Example: registry.redhat.io/redhat/community-operator-index - - name: public_repository_mirrors_with_version - description: >- - All known supported public repository mirror pull specs with version tag (space separated). - Example: - registry.redhat.io/redhat/community-operator-index:v4.13 - registry.redhat.io/redhat/community-operator-index:v4.14 - - name: pending_repository description: >- Known supported pending repository pull spec. @@ -56,13 +49,6 @@ spec: Example: quay.io/redhat/redhat----community-operator-index - - name: repositories_with_version - description: >- - All known supported repository urls with version tag (space separated). - Example: - quay.io/redhat/redhat----community-operator-index:v4.13 - quay.io/redhat/redhat----community-operator-index:v4.14 - - name: indices_ocp_versions description: >- All known supported OCP versions (space separated). @@ -122,13 +108,6 @@ spec: | tee $(results.public_repository_mirror.path) echo - echo "All supported public repository mirror pull specs with version:" - echo $VERSION_INFO \ - | jq -r '.indices | map(.public_repository_mirror_with_version) | join(" ")' \ - | tr -d '\n\r' \ - | tee $(results.public_repository_mirrors_with_version.path) - echo - echo "Supported pending repository pull spec:" echo $VERSION_INFO \ | jq -r '.indices[0].pending_repository' \ @@ -150,13 +129,6 @@ spec: | tee $(results.repository.path) echo - echo "All supported repository urls with version:" - echo $VERSION_INFO \ - | jq -r '.indices | map(.repository_with_version) | join(" ")' \ - | tr -d '\n\r' \ - | tee $(results.repositories_with_version.path) - echo - echo "All supported OCP versions:" echo $VERSION_INFO \ | jq -r '.indices | map(.ocp_version) | join(" ")' \ diff --git a/ansible/roles/operator-pipeline/templates/openshift/tasks/publish-to-index.yml b/ansible/roles/operator-pipeline/templates/openshift/tasks/publish-to-index.yml index 609353949..4220f0fdd 100644 --- a/ansible/roles/operator-pipeline/templates/openshift/tasks/publish-to-index.yml +++ b/ansible/roles/operator-pipeline/templates/openshift/tasks/publish-to-index.yml @@ -21,6 +21,8 @@ spec: - iib-quay-credentials (default) - for connect and marketplace repositories - community-push-final-index-quay-credentials - for community repositories default: iib-quay-credentials + - name: build_tags_suffix + description: Timestamp suffix for build tags (same as used in IIB builds for consistency) workspaces: - name: results description: A space for the task inputs and summary output @@ -53,13 +55,14 @@ spec: INDEX_IMAGES_PATHS="$(workspaces.results.path)/paths/index-image-paths.txt" TEMP_IMAGES=$(cat $INDEX_IMAGES_PATHS | tr "," " ") - SUFFIX=`date +%s` + # Use the same timestamp suffix that was used for IIB builds + SUFFIX="$(params.build_tags_suffix)" for i in $TEMP_IMAGES do - SRC_IMAGE=$(echo $i | awk -F '+' '{print $2}') + SRC_IMAGE=$(echo $i | awk -F '+' '{print $1}') echo "Source image: $SRC_IMAGE" - VERSION=$(echo $i | awk -F '+' '{print $1}' | awk -F ':' '{print $2}') + VERSION=$(echo $SRC_IMAGE | awk -F ':' '{print $2}') DEST_REPO_WITH_VERSION_TAG="$(params.destination_repository):${VERSION}" DEST_REPO_WITH_PERMANENT_TAG="${DEST_REPO_WITH_VERSION_TAG}-${SUFFIX}" @@ -73,7 +76,7 @@ spec: copy \ --retry-times 5 \ --format v2s2 --all \ - --src-no-creds \ + --src-creds $QUAY_USER:$QUAY_TOKEN \ --dest-creds $QUAY_USER:$QUAY_TOKEN \ docker://$SRC_IMAGE \ docker://$DEST_REPO_WITH_VERSION_TAG @@ -83,7 +86,7 @@ spec: copy \ --retry-times 5 \ --format v2s2 --all \ - --src-no-creds \ + --src-creds $QUAY_USER:$QUAY_TOKEN \ --dest-creds $QUAY_USER:$QUAY_TOKEN \ docker://$SRC_IMAGE \ docker://$DEST_REPO_WITH_PERMANENT_TAG diff --git a/ansible/roles/operator-pipeline/templates/openshift/tasks/sign-index-image.yml b/ansible/roles/operator-pipeline/templates/openshift/tasks/sign-index-image.yml index 06dcd404b..ae054ef33 100644 --- a/ansible/roles/operator-pipeline/templates/openshift/tasks/sign-index-image.yml +++ b/ansible/roles/operator-pipeline/templates/openshift/tasks/sign-index-image.yml @@ -9,6 +9,12 @@ spec: params: - name: pipeline_image description: A docker image of operator-pipeline-images for the steps to run in. + - name: public_repository_mirror + description: | + The public repository mirror where the index image becomes available. + Used by release pipeline to reference the public mirror in signatures. + Example: registry.redhat.io/redhat/community-operator-index + default: "" # Signing configuration - name: requester @@ -100,17 +106,30 @@ spec: if [[ -f /tmp/registry-auth/config.json ]]; then echo "Registry auth file found. Using it for skopeo." SKOPEO_EXTRA_ARGS="--authfile /tmp/registry-auth/config.json" + else + echo "WARNING: No registry auth found. Skopeo will use no --authfile." fi DOCKER_REFERENCES="" MANIFEST_DIGESTS="" for i in $DIGEST_LIST do - REFERENCE=$(echo $i | awk -F '+' '{print $1}') - - DIGEST=$(echo $i | awk -F '+' '{print $2}') - - MANIFEST_LIST=$(skopeo inspect --retry-times 5 $SKOPEO_EXTRA_ARGS --raw docker://$DIGEST) + INDEX_WITH_VERSION=$(echo $i | awk -F '+' '{print $1}') + INDEX=$(echo $INDEX_WITH_VERSION | awk -F ':' '{print $1}') + VERSION=$(echo $INDEX_WITH_VERSION | awk -F ':' '{print $2}') + DIGEST_SHA=$(echo $i | awk -F '@' '{print $2}') + + if [[ -n "$(params.public_repository_mirror)" ]]; then + # release pipeline + INSPECT_TARGET="${INDEX}@${DIGEST_SHA}" # pending repository + REFERENCE="$(params.public_repository_mirror):${VERSION}" + else + # index image bootstrap signing pipeline + INSPECT_TARGET="${INDEX_WITH_VERSION}" # public repository mirror + REFERENCE="${INDEX_WITH_VERSION}" + fi + + MANIFEST_LIST=$(skopeo inspect --retry-times 5 $SKOPEO_EXTRA_ARGS --raw docker://$INSPECT_TARGET) MANIFEST_LIST=$(echo $MANIFEST_LIST | jq -r '.manifests[].digest') # create comma separated index images that match each digest diff --git a/ansible/vaults/dev/registry-auth/release-pipeline-pull-pending.json b/ansible/vaults/dev/registry-auth/release-pipeline-pull-pending.json new file mode 100644 index 000000000..4847251c5 --- /dev/null +++ b/ansible/vaults/dev/registry-auth/release-pipeline-pull-pending.json @@ -0,0 +1,16 @@ +$ANSIBLE_VAULT;1.1;AES256 +38373539366435613038333238626361653630326462343434613236663037366530306635616665 +3233613038343732363531633365343335336166633130660a356665346139616366393334633936 +35653838613164663137363161393263313566353665383832353464613533346635313262663663 +6338633336613262370a393830346638613032313032323632323636353062353964313234353133 +36376564373636386436393339383965366330313135663466326265356264343666333533643863 +35613965323138303333623232376431623639646130353436343738383761626663313162373838 +63643030343364326238313330373037653732376661646136396537623539343532646134386131 +63666236376434333737346239636634323332323334616331333030633334353237633866373032 +38653436333563376533623966343038376564643039353137303264633237343335366265663866 +39653039623261626132363166316637373139323865386139393231653336343963356236373535 +30306266313863323332366663376466666435613432636564613461646334646133316131323235 +61383133616438613938333739346638373635333932303237653861623835353463306265656338 +38333333626464613732383766326634353966656635666363376334343337376663663837626436 +32343833613838373663376161646332313233323264656262666138323539303863636164393238 +336231643662366237653765663739313639 diff --git a/ansible/vaults/integration-tests/registry-auth/release-pipeline-pull-pending.json b/ansible/vaults/integration-tests/registry-auth/release-pipeline-pull-pending.json new file mode 100644 index 000000000..8ff95d134 --- /dev/null +++ b/ansible/vaults/integration-tests/registry-auth/release-pipeline-pull-pending.json @@ -0,0 +1,16 @@ +$ANSIBLE_VAULT;1.1;AES256 +39613330663635646430326564303037333738366161643462626636656535303365303465666133 +3037323666653139346364336365353633323365386162650a636133353665343662323038646461 +38333539323636336332616233343136303538633131626531333162363764363962343766653931 +3739613965633864650a653935623039323963623330313565333636373731343265653962386530 +63653033306465653961646532666465323363613033346234643634306135376531626331333537 +36373530396266383766353664383961363432376165376532636262626230343031623963616436 +62333763613433623437373039343437343435333261326137363839376163326335336231316439 +62643830613736656137313432323530656433376137366236353435323034303738643038663033 +37613465306661323939313833643133353330353562373066653566323737626531626435306163 +37666464326463396461343566323861653066356334646466623564396235323836376165373733 +37643430306463616463626135373538663863323338623863616236383933373366383137313165 +32663934323035626534663135663261646563303139653263366132323432636165356561613831 +36346463613232393538326262306232383863623334346337393038386561393436356632306161 +65363734386539386663333266653231316530646463383562363938646633386435393566663464 +383233313037626639353562363131353835 diff --git a/ansible/vaults/prod/registry-auth/release-pipeline-pull-pending.json b/ansible/vaults/prod/registry-auth/release-pipeline-pull-pending.json new file mode 100644 index 000000000..305b10a9b --- /dev/null +++ b/ansible/vaults/prod/registry-auth/release-pipeline-pull-pending.json @@ -0,0 +1,15 @@ +$ANSIBLE_VAULT;1.1;AES256 +64653264626537366665383633386436383936323665303961336637366661316237623434643765 +3936633566616235643430363130303563306663633634320a343365663932666330663637393561 +32323961323332623937366233333763306332313364663539386161656234373632336532306430 +3238643465373838310a623863393831323637346637643038393034386362363038363134346330 +30666431393833623636643333353663316537653330343835336165326662333030383162343132 +64633336396564386338316435333364393065663632353732313763343561373137626137313039 +39323134623861396665626532636264373265333731663564336438323730373935376333353964 +33643339363735636334313664313631396334343731613031363030396566313363643934356164 +61373664636433373939343466323664323837636262383233373361333464363333633837336137 +36326338366633333238343731313131633663343636353332663831616563363133616661363464 +65613063373532323261323436316161303238623662366335396266393764626134633665376331 +33333665343761386631323936613334383564316432653932316262386436386261373062613430 +39386532386532666330636633636630313238373036336633323237663663386430656535323965 +3736663334323530306137343563643931633433643030313664 diff --git a/ansible/vaults/qa/registry-auth/release-pipeline-pull-pending.json b/ansible/vaults/qa/registry-auth/release-pipeline-pull-pending.json new file mode 100644 index 000000000..f38fd4f52 --- /dev/null +++ b/ansible/vaults/qa/registry-auth/release-pipeline-pull-pending.json @@ -0,0 +1,16 @@ +$ANSIBLE_VAULT;1.1;AES256 +64623731633436353461356563646165323039356133656638623038356637313537646535663836 +3161373662666137656431626263393962333433313237350a343135326237636433316639353863 +32333061383035616365643335393931333763303938393533373734343361633538393765393737 +6663313637383737620a353464633432393461383439613331376434386166613139323261646165 +33636237326132303734643731363339353432316136383031653035656130383335313335336431 +34323663313432636239656436343233393232363537616462326264346564666466316536653739 +34636239396166313032306433366363356632633262326361303337343864393166326335663662 +37393232343264306562306436643937316133623266386530396333636332666237303061376432 +38343036653365313332303938626439383165363265663337303065613431633838643933326132 +39326332383936356634383236623065643435646536653332303538633437623664643633333465 +39373533333933643464353062636238353164396434313362616438336532613535306638666434 +30306233646135333138636435616439313337633962313837323862373634313139383163656261 +64623762643563346630336664323338353837353837333463383933313866313465636631663565 +65623761383734393666333936666363393833343436306561393533643231653763306233623735 +386164393430303836636563386466623838 diff --git a/ansible/vaults/stage/registry-auth/release-pipeline-pull-pending.json b/ansible/vaults/stage/registry-auth/release-pipeline-pull-pending.json new file mode 100644 index 000000000..fb908c580 --- /dev/null +++ b/ansible/vaults/stage/registry-auth/release-pipeline-pull-pending.json @@ -0,0 +1,16 @@ +$ANSIBLE_VAULT;1.1;AES256 +63343164346539623637623464323635326261303936373933396635326263353366366666353864 +3531343463303233376330643435396566353632616666660a346462343166616263306538666663 +34633430356338636266363236353034663863363336313131616434386136646232366530393661 +3564376330373833640a326162316161303334636238353663633138666239396532653562373831 +32663861346163633339313939336636346439343639366263353163323737376130313830346139 +35616431616661653436373261366231386636363262333666316133656635313766656637346532 +65383530383734396462366462663965623331646431666136313937303234393664353861646533 +63383764323361643865646230313131343732373761656431383633363631663334326531666130 +31306434623031633030336134363336383162623332613330313062366465306361393961643133 +35363233316632336566383733663263363436323332336238663538616231373230663232356436 +61386137373336653364323539353763346230346463363733653363366665623933376565306335 +34313034666332623134303436653236323866313338613833613239343261323362303338306163 +32343338323461633066363165343232316132653533336337313064373762656136336365636364 +36333266646631643938393239326232386134383434396636383263643038633431623435313832 +646438386162303139616561356565643831 diff --git a/operatorcert/entrypoints/add_fbc_fragments_to_index.py b/operatorcert/entrypoints/add_fbc_fragments_to_index.py index 186c0590e..9d4728625 100644 --- a/operatorcert/entrypoints/add_fbc_fragments_to_index.py +++ b/operatorcert/entrypoints/add_fbc_fragments_to_index.py @@ -7,7 +7,7 @@ import os import time from datetime import datetime, timedelta -from typing import Any, Dict, List, Tuple +from typing import Any, Dict, List, Tuple, Optional from operatorcert import iib, utils from operatorcert.logger import setup_logger @@ -61,6 +61,11 @@ def setup_argparser() -> argparse.ArgumentParser: "unpublished index images built by IIB.", ) + parser.add_argument( + "--build-tags-suffix", + help="Timestamp suffix for build tags (used with overwrite to ensure consistent tagging)", + ) + parser.add_argument("--verbose", action="store_true", help="Verbose output") return parser @@ -169,6 +174,8 @@ def add_fbc_fragment_to_index( iib_url: str, index_fragment_mapping: List[Tuple[str, str]], image_output: str, + overwrite_token: Optional[str] = None, + build_tags_suffix: Optional[str] = None, ) -> Any: """ Add a fragment image to index image using IIB @@ -177,6 +184,8 @@ def add_fbc_fragment_to_index( iib_url (str): url of IIB instance index_fragment_mapping (List[Tuple[str, str]]): List of tuples with index and fragment image_output (str): file name to output the location of the newly built images to + overwrite_token (str): Optional token for IIB to authenticate and overwrite from_index + build_tags_suffix (str): Optional timestamp suffix for build tags Returns: List[Any]: Build responses Raises: @@ -185,10 +194,22 @@ def add_fbc_fragment_to_index( request_ids = [] for index, fragment in index_fragment_mapping: - payload = { + payload: dict[str, Any] = { "from_index": index, "fbc_fragment": fragment, } + + if build_tags_suffix: + version = index.split(":")[-1] + payload["build_tags"] = [version, f"{version}-{build_tags_suffix}"] + + if overwrite_token: + # WORKAROUND: Manually overwriting index images using skopeo + # TODO: uncomment when overwrite token is fixed, delete pass + # payload["overwrite_from_index"] = True + # payload["overwrite_from_index_token"] = overwrite_token + pass + resp = iib.add_fbc_build(iib_url, payload) request_ids.append(resp["id"]) @@ -237,6 +258,7 @@ def main() -> None: setup_logger(level=log_level) utils.set_client_keytab(os.environ.get("KRB_KEYTAB_FILE", "/etc/krb5.krb")) + overwrite_token = os.environ.get("IIB_OVERWRITE_TOKEN") index_fragment_mapping = map_index_to_fragment( args.indices, @@ -249,6 +271,8 @@ def main() -> None: args.iib_url, index_fragment_mapping, args.image_output, + overwrite_token, + args.build_tags_suffix, ) diff --git a/operatorcert/entrypoints/index.py b/operatorcert/entrypoints/index.py index 1344554d2..4795662e2 100644 --- a/operatorcert/entrypoints/index.py +++ b/operatorcert/entrypoints/index.py @@ -5,7 +5,7 @@ import argparse import logging import os -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from operatorcert import iib, utils from operatorcert.logger import setup_logger @@ -21,6 +21,8 @@ def setup_argparser() -> argparse.ArgumentParser: # pragma: no cover Any: Initialized argument parser """ parser = argparse.ArgumentParser(description="Add bundle to index image") + + # pylint: disable=duplicate-code parser.add_argument( "--bundle-pullspec", required=True, help="Operator bundle pullspec" ) @@ -59,17 +61,24 @@ def setup_argparser() -> argparse.ArgumentParser: # pragma: no cover ) parser.add_argument("--authfile", help="") + parser.add_argument( + "--build-tags-suffix", + help="Timestamp suffix for build tags (used with overwrite to ensure consistent tagging)", + ) + parser.add_argument("--verbose", action="store_true", help="Verbose output") return parser -def add_bundle_to_index( +def add_bundle_to_index( # pylint: disable=too-many-arguments,too-many-positional-arguments,duplicate-code bundle_pullspec: str, iib_url: str, indices: List[str], image_output: str, mode: str, + overwrite_token: Optional[str] = None, + build_tags_suffix: Optional[str] = None, ) -> Any: """ Add a bundle to index image using IIB @@ -80,6 +89,8 @@ def add_bundle_to_index( indices (List[str]): list of original indices image_output (str): file name to output the location of the newly built images to mode (str): A mode that defines how the update graph will be updated + overwrite_token (str): Optional token for IIB to authenticate and overwrite from_index + build_tags_suffix (str): Optional timestamp suffix for build tags Returns: Any: Build response Raises: @@ -89,13 +100,25 @@ def add_bundle_to_index( payload: Dict[str, Any] = {"build_requests": []} for index in indices: - build_request = { + build_request: Dict[str, Any] = { "from_index": index, "bundles": [bundle_pullspec], "add_arches": ["amd64", "s390x", "ppc64le"], } if mode: build_request["graph_update_mode"] = mode + + if build_tags_suffix: + version = index.split(":")[-1] + build_request["build_tags"] = [version, f"{version}-{build_tags_suffix}"] + + if overwrite_token: + # WORKAROUND: Manually overwriting index images using skopeo + # TODO: uncomment when overwrite token is fixed, delete pass + # build_request["overwrite_from_index"] = True + # build_request["overwrite_from_index_token"] = overwrite_token + pass + payload["build_requests"].append(build_request) resp = iib.add_builds(iib_url, payload) @@ -147,6 +170,7 @@ def main() -> None: # pragma: no cover setup_logger(level=log_level) utils.set_client_keytab(os.environ.get("KRB_KEYTAB_FILE", "/etc/krb5.krb")) + overwrite_token = os.environ.get("IIB_OVERWRITE_TOKEN") iib_response = add_bundle_to_index( args.bundle_pullspec, @@ -154,6 +178,8 @@ def main() -> None: # pragma: no cover args.indices, args.image_output, args.mode, + overwrite_token, + args.build_tags_suffix, ) if args.index_image_destination: utils.copy_images_to_destination( diff --git a/operatorcert/entrypoints/rm_operator_from_index.py b/operatorcert/entrypoints/rm_operator_from_index.py index fcc87db14..9a3feb7e8 100644 --- a/operatorcert/entrypoints/rm_operator_from_index.py +++ b/operatorcert/entrypoints/rm_operator_from_index.py @@ -49,6 +49,11 @@ def setup_argparser() -> argparse.ArgumentParser: help="Base URL for IIB API", ) + parser.add_argument( + "--build-tags-suffix", + help="Timestamp suffix for build tags (used with overwrite to ensure consistent tagging)", + ) + parser.add_argument("--verbose", action="store_true", help="Verbose output") return parser @@ -107,6 +112,8 @@ def __eq__(self, value: object) -> bool: def rm_operator_from_index( index_images: List[IndexImage], iib_url: str, + overwrite_token: Optional[str] = None, + build_tags_suffix: Optional[str] = None, ) -> Any: """ Submit a batch build request to IIB to remove operators from the index images. @@ -114,6 +121,8 @@ def rm_operator_from_index( Args: index_images (List[IndexImage]): List of index images objects iib_url (str): IIb API URL + overwrite_token (str): Optional token for IIB to authenticate and overwrite from_index + build_tags_suffix (str): Optional timestamp suffix for build tags Returns: Any: IIB batch build response @@ -123,10 +132,24 @@ def rm_operator_from_index( if not index_image.operators_to_remove: continue - build_request = { + build_request: Dict[str, Any] = { "from_index": index_image.index_pullspec(), "operators": index_image.operators_to_remove, } + + if build_tags_suffix: + build_request["build_tags"] = [ + index_image.version, + f"{index_image.version}-{build_tags_suffix}", + ] + + if overwrite_token: + # WORKAROUND: Manually overwriting index images using skopeo + # TODO: uncomment when overwrite token is fixed, delete pass + # build_request["overwrite_from_index"] = True + # build_request["overwrite_from_index_token"] = overwrite_token + pass + payload["build_requests"].append(build_request) resp = iib.add_builds(iib_url, payload) @@ -277,6 +300,7 @@ def main() -> None: # pragma: no cover setup_logger(level=log_level) utils.set_client_keytab(os.environ.get("KRB_KEYTAB_FILE", "/etc/krb5.krb")) + overwrite_token = os.environ.get("IIB_OVERWRITE_TOKEN") # In case there was a previous run of fragment builds, read the output and use # it as a base for removal process. In case the file does not exist, set it to @@ -297,7 +321,9 @@ def main() -> None: # pragma: no cover map_operators_to_indices(args.rm_catalog_operators, index_images) # Remove operators from the index images using IIB API - iib_rm_response = rm_operator_from_index(index_images, args.iib_url) + iib_rm_response = rm_operator_from_index( + index_images, args.iib_url, overwrite_token, args.build_tags_suffix + ) # Merge the output from the removal process with the output from the # fragment builds and use only the images that were built by IIB diff --git a/tests/entrypoints/test_add_fbc_fragments_to_index.py b/tests/entrypoints/test_add_fbc_fragments_to_index.py index 743dd0340..79cb4311a 100644 --- a/tests/entrypoints/test_add_fbc_fragments_to_index.py +++ b/tests/entrypoints/test_add_fbc_fragments_to_index.py @@ -207,6 +207,40 @@ def test_add_fbc_fragment_to_index( "test-image-path.txt", ) + # with overwrite_token and build_tags_suffix + mock_iib_build.reset_mock() + mock_results.reset_mock() + mock_iib_build.side_effect = [ + {"state": "pending", "id": 3}, + ] + mock_results.return_value = [ + {"state": "complete", "id": 3}, + ] + index.add_fbc_fragment_to_index( + "https://iib.engineering.redhat.com", + [ + ( + "registry/index:v4.15", + "registry.foo/repo/foo:v4.15-fragment-sha256:1234", + ), + ], + "test-image-path.txt", + "user:token123", + "1711883400", + ) + mock_iib_build.assert_called_once_with( + "https://iib.engineering.redhat.com", + { + "from_index": "registry/index:v4.15", + "fbc_fragment": "registry.foo/repo/foo:v4.15-fragment-sha256:1234", + "build_tags": ["v4.15", "v4.15-1711883400"], + # WORKAROUND: Manually overwriting index images using skopeo + # TODO: uncomment when overwrite token is fixed + # "overwrite_from_index": True, + # "overwrite_from_index_token": "user:token123", + }, + ) + def test_output_index_image_paths() -> None: image_output = "test-image-path.txt" @@ -253,6 +287,8 @@ def test_main( args.iib_url = "https://iib.engineering.redhat.com" args.image_output = "test-image-path.txt" args.hosted = True + args.iib_overwrite_token = None + args.build_tags_suffix = None mock_setup_argparser.return_value.parse_args.return_value = args mock_mapping.return_value = [("foo", "bar")] @@ -270,6 +306,8 @@ def test_main( args.iib_url, mock_mapping.return_value, args.image_output, + args.iib_overwrite_token, + args.build_tags_suffix, ) diff --git a/tests/entrypoints/test_index.py b/tests/entrypoints/test_index.py index 0e340872b..193baef42 100644 --- a/tests/entrypoints/test_index.py +++ b/tests/entrypoints/test_index.py @@ -45,6 +45,39 @@ def test_add_bundle_to_index( "replaces", ) + # with overwrite_token and build_tags_suffix + mock_iib_builds.reset_mock() + mock_results.reset_mock() + mock_iib_builds.return_value = [{"state": "complete", "batch": "batch2"}] + mock_results.return_value = {"items": [{"state": "complete", "batch": "batch2"}]} + index.add_bundle_to_index( + "redhat-isv/some-pullspec", + "https://iib.engineering.redhat.com", + ["registry/index:v4.9"], + "test-image-path.txt", + "replaces", + "user:token123", + "1711883400", + ) + mock_iib_builds.assert_called_once_with( + "https://iib.engineering.redhat.com", + { + "build_requests": [ + { + "from_index": "registry/index:v4.9", + "bundles": ["redhat-isv/some-pullspec"], + "add_arches": ["amd64", "s390x", "ppc64le"], + "graph_update_mode": "replaces", + "build_tags": ["v4.9", "v4.9-1711883400"], + # WORKAROUND: Manually overwriting index images using skopeo + # TODO: uncomment when overwrite token is fixed + # "overwrite_from_index": True, + # "overwrite_from_index_token": "user:token123", + } + ] + }, + ) + def test_output_index_image_paths() -> None: image_output = "test-image-path.txt" diff --git a/tests/entrypoints/test_rm_operator_from_index.py b/tests/entrypoints/test_rm_operator_from_index.py index 2da96960e..7fb9af810 100644 --- a/tests/entrypoints/test_rm_operator_from_index.py +++ b/tests/entrypoints/test_rm_operator_from_index.py @@ -53,6 +53,39 @@ def test_rm_operator_from_index( }, ) + # with overwrite_token and build_tags_suffix + mock_iib_add_builds.reset_mock() + mock_wait.reset_mock() + index_images_with_token = [ + rm_operator_from_index.IndexImage("registry/index:v4.15", "iib-pullspec"), + ] + index_images_with_token[0].operators_to_remove = ["op1"] + mock_iib_add_builds.return_value = [{"batch": "batch2"}] + mock_wait.return_value = {"items": [{"state": "complete"}]} + rm_operator_from_index.rm_operator_from_index( + index_images_with_token, + "https://iib.foo.redhat.com", + "user:token123", + "1711883400", + ) + + mock_iib_add_builds.assert_called_once_with( + "https://iib.foo.redhat.com", + { + "build_requests": [ + { + "from_index": "iib-pullspec", + "operators": ["op1"], + "build_tags": ["v4.15", "v4.15-1711883400"], + # WORKAROUND: Manually overwriting index images using skopeo + # TODO: uncomment when overwrite token is fixed + # "overwrite_from_index": True, + # "overwrite_from_index_token": "user:token123", + } + ] + }, + ) + @patch("operatorcert.entrypoints.rm_operator_from_index.iib.wait_for_batch_results") @patch("operatorcert.entrypoints.rm_operator_from_index.iib.add_builds")