Repository: konflux-ci/release-service-catalog
Pull request #817: feat(RELEASE-1243): make create advisory  task idempotent
Author: login: happybhati
Labels: name: lgtm
Created: 2025-02-11T05:57:55Z | Merged: 2025-03-29T03:59:09Z

## Describe your changes

## Relevant Jira

## Checklist before requesting a review
- [x] I have marked as draft or added `do not merge` label if there's a dependency PR
  - If you want reviews on your draft PR, you can add reviewers or add the `release-service-maintainers` handle if you are unsure who to tag
- [x] My commit message includes `Signed-off-by: My name <email>`
- [x] I have bumped the task/pipeline version string and updated changelog in the relevant README
- [x] I read CONTRIBUTING.MD and [commit formatting](CONTRIBUTING.md#commit-message-formatting-and-standards)




Diff:
diff --git a/tasks/internal/create-advisory-task/README.md b/tasks/internal/create-advisory-task/README.md
index e6dd00e40a..631798d9b3 100644
--- a/tasks/internal/create-advisory-task/README.md
+++ b/tasks/internal/create-advisory-task/README.md
@@ -16,6 +16,9 @@ internal request. The success/failure is handled in the task creating the intern
 | errata_secret_name             | The name of the secret that contains the errata service account metadata                               | No       | -             |
 | internalRequestPipelineRunName | Name of the PipelineRun that called this task                                                          | No       | -             |
 
+## Changes in 1.0.0
+* Make create advisory task idempotent
+
 ## Changes in 0.12.0
 * Updated logging
   * Use a STDERR_FILE and tail it into the `result` task result so that the calling task has better
diff --git a/tasks/internal/create-advisory-task/create-advisory-task.yaml b/tasks/internal/create-advisory-task/create-advisory-task.yaml
index 93c8b74d71..b921d78732 100644
--- a/tasks/internal/create-advisory-task/create-advisory-task.yaml
+++ b/tasks/internal/create-advisory-task/create-advisory-task.yaml
@@ -4,7 +4,7 @@ kind: Task
 metadata:
   name: create-advisory-task
   labels:
-    app.kubernetes.io/version: "0.12.0"
+    app.kubernetes.io/version: "1.0.0"
   annotations:
     tekton.dev/pipelines.minVersion: "0.12.1"
     tekton.dev/tags: release
@@ -142,10 +142,63 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            find "${ADVISORY_BASE_DIR}" -mindepth 2 -type d -printf "%T@ %p\n" |  # year/advisory dir with modified time
+            sort -nr |                                                            # sort by latest modified first
+            cut -d' ' -f2- |                                                      # remove the timestamp, keep only path
+            sed "s|^${ADVISORY_BASE_DIR}/||"                                      # keeping year/advisory format
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_SUBDIR in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_SUBDIR}/advisory.yaml"
+              EXISTING_CONTENT=$(yq -o=json '.spec.content.images // []' "${ADVISORY_FILE}")
+
+              echo "Processing advisory: ${ADVISORY_FILE}"
+              echo "Existing images in advisory: $EXISTING_CONTENT"
+
+              # Update CONTENT_IMAGES by removing images that already exist in the advisory
+              CONTENT_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci |
+                  .tags as $tags |
+                  .repository as $repo |
+                  ($existing | map(select(
+                    .containerImage == $ci and .tags == $tags and .repository == $repo
+                  )) | length == 0)
+                ))')
+
+              echo "Remaining images after filtering: $CONTENT_IMAGES"
+          done
+
+          # If after filtering, no images are left, then we can exit early
+          if jq -e 'length == 0' <<< "$CONTENT_IMAGES" >/dev/null; then
+              echo "Matching advisory exists: ${ADVISORY_FILE}. Skipping creation."
+              ADVISORY_URL="${GIT_REPO//\.git/}/-/blob/main/${ADVISORY_FILE}"
+              echo -n "Success" > "$(results.result.path)"
+              echo -n "${ADVISORY_URL}" > "$(results.advisory_url.path)"
+              exit 0
+          fi
+
+          NEW_ADVISORY_JSON=$(echo "$ADVISORY_JSON" | jq --argjson new_images "$CONTENT_IMAGES" \
+            '.content.images = $new_images')
+
           signingKey=$(kubectl get configmap "$(params.config_map_name)" -o jsonpath="{.data.SIG_KEY_NAME}")
           advisoryJsonWithKey=$(jq -c --arg key "$signingKey" \
-            '.content.images[] += {"signingKey": $key}' <<< "$ADVISORY_JSON")
+            '.content.images[] += {"signingKey": $key}' <<< "$NEW_ADVISORY_JSON")
 
           # write keytab to file
           echo -n "${SERVICE_ACCOUNT_KEYTAB}" | base64 --decode > /tmp/keytab
@@ -161,9 +214,6 @@ spec:
           ID=$(curl --retry 3 --negotiate -u : "${ERRATA_API}/advisory/reserve_live_id" -XPOST | jq -r '.live_id')
           ADVISORY_NUM=$(printf "%04d" "$ID")
 
-          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
-          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
-          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
           # group advisories by <origin workspace>/year
           ADVISORY_DIR="data/advisories/$(params.origin)/${YEAR}/${ADVISORY_NUM}"
           mkdir -p "${ADVISORY_DIR}"
diff --git a/tasks/internal/create-advisory-task/tests/mocks.sh b/tasks/internal/create-advisory-task/tests/mocks.sh
index 1cae0b8a12..47fdd0319a 100644
--- a/tasks/internal/create-advisory-task/tests/mocks.sh
+++ b/tasks/internal/create-advisory-task/tests/mocks.sh
@@ -17,6 +17,55 @@ function git() {
   fi
 }
 
+function find() {
+  echo "Mock find called with: $*" >&2
+
+  if echo "$*" | grep -q "${ADVISORY_BASE_DIR}"; then
+    # Simulate directories with timestamps
+    echo "1712012345.0 ${ADVISORY_BASE_DIR}/2025/1602"  # Contains image-beta
+    echo "1712012344.0 ${ADVISORY_BASE_DIR}/2025/1601"  # Contains image-alpha
+    echo "1708012343.0 ${ADVISORY_BASE_DIR}/2024/1452"
+    echo "1704012342.0 ${ADVISORY_BASE_DIR}/2024/1442"
+  else
+    echo "Error: Unexpected find command: $*" >&2
+    exit 1
+  fi
+}
+
+function yq() {
+  echo "Mock yq called with: $*" >&2
+
+  if [[ -z "$3" ]]; then
+    echo "Error: Empty file path in yq command" >&2
+    exit 1
+  fi
+
+  advisory_path="$3"
+  advisory_year=$(echo "$advisory_path" | awk -F'/' '{print $(NF-2)}')  # Extract Year
+  advisory_num=$(echo "$advisory_path" | awk -F'/' '{print $(NF-1)}')   # Extract Advisory Number
+
+  echo "Returning advisory content for ${advisory_year}/${advisory_num}" >&2
+
+  case "$advisory_num" in
+    1601)
+      echo '[{"architecture":"amd64","component":"release-manager-alpha","containerImage":"quay.io/example/release@sha256:alpha123","repository":"example-stream/release","signingKey":"example-sign-key","tags":["v1.0","latest"]}]'
+      ;;
+    1602)
+      echo '[{"architecture":"amd64","component":"release-manager-beta","containerImage":"quay.io/example/release@sha256:beta123","repository":"example-stream/release","signingKey":"example-sign-key","tags":["v2.0","stable"]}]'
+      ;;
+    1442)
+      echo '[{"architecture":"amd64","component":"foo-foo-manager-1-15","containerImage":"quay.io/example/openstack@sha256:abde","repository":"quay.io/example/openstack","signingKey":"example-sign-key","tags":["v1.0","latest"]}]'
+      ;;
+    1452)
+      echo '[{"architecture":"amd64","component":"foo-foo-manager-1-15","containerImage":"quay.io/example/openstack@sha256:lmnop","repository":"quay.io/example/openstack","signingKey":"example-sign-key","tags":["latest"]}]'
+      ;;
+    *)
+      echo "Error: Unexpected advisory number $advisory_num" >&2
+      exit 1
+      ;;
+  esac
+}
+
 function glab() {
   echo "Mock glab called with: $*"
 
diff --git a/tasks/internal/create-advisory-task/tests/test-create-advisory-fail.yaml b/tasks/internal/create-advisory-task/tests/test-create-advisory-fail.yaml
index 1eba3e5809..36bf370a9f 100644
--- a/tasks/internal/create-advisory-task/tests/test-create-advisory-fail.yaml
+++ b/tasks/internal/create-advisory-task/tests/test-create-advisory-fail.yaml
@@ -21,7 +21,7 @@ spec:
             {"product_id":123,"product_name":"Red Hat Product","product_version":"1.2.3","product_stream":"tp1",
             "cpe":"cpe:/a:example:product:el8","type":"RHSA","synopsis":"test synopsis","topic":"test topic",
             "description":"test description","solution":"test solution","references":["https://docs.example.com/notes"],
-            "content":{"images":[{"containerImage":"quay.io/example/openstack@sha256:abde",
+            "content":{"images":[{"containerImage":"quay.io/example/openstack@sha256:abdefail",
             "repository":"rhosp16-rhel8/openstack","tags":["latest"],"architecture":"amd64",
             "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
             "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}}]}}
diff --git a/tasks/internal/create-advisory-task/tests/test-create-advisory-idempotency-multiple-image.yaml b/tasks/internal/create-advisory-task/tests/test-create-advisory-idempotency-multiple-image.yaml
new file mode 100644
index 0000000000..d609452882
--- /dev/null
+++ b/tasks/internal/create-advisory-task/tests/test-create-advisory-idempotency-multiple-image.yaml
@@ -0,0 +1,77 @@
+---
+apiVersion: tekton.dev/v1
+kind: Pipeline
+metadata:
+  name: test-create-advisory-idempotency-multiple-image
+spec:
+  description: |
+    Verifies the idempotency behavior of the advisory creation process when multiple container images are provided
+    in the advisory_json. This test supplies three images (alpha123, beta123, gamma123), of which
+    alpha123 and beta123 are already present in existing advisories(Mock scenraio).
+    The task is expected to recognize these and only create a new advisory for the remaining image (gamma123).
+    The test passes if only the unmatched image is included in the resulting advisory and the advisory URL matches the
+    expected value.
+  tasks:
+    - name: run-task
+      taskRef:
+        name: create-advisory-task
+      params:
+        - name: advisory_json
+          value: >-
+            {"product_id":123,"product_name":"Red Hat Product","product_version":"1.2.3","product_stream":"tp1",
+            "cpe":"cpe:/a:example:product:el8","type":"RHSA","synopsis":"test synopsis","topic":"test topic",
+            "description":"test description","solution":"test solution","references":["https://docs.example.com/notes"],
+            "content":{"images":[{"containerImage":"quay.io/example/release@sha256:alpha123",
+            "repository":"example-stream/release","tags":["v1.0", "latest"],"architecture":"amd64",
+            "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
+            "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}},{"containerImage":"quay.io/example/release@sha256:beta123",
+            "repository":"example-stream/release","tags":["v2.0", "stable"],"architecture":"amd64",
+            "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
+            "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}},{"containerImage":"quay.io/example/release@sha256:gamma123",
+            "repository":"rhosp16-rhel8/openstack","tags":["v3.0", "stable"],"architecture":"amd64",
+            "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
+            "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}}]}}
+        - name: origin
+          value: dev-tenant
+        - name: application
+          value: "test-app"
+        - name: config_map_name
+          value: "create-advisory-test-cm"
+        - name: advisory_secret_name
+          value: "create-advisory-secret"
+        - name: errata_secret_name
+          value: "create-advisory-errata-secret"
+        - name: internalRequestPipelineRunName
+          value: $(context.pipelineRun.name)
+    - name: check-result
+      params:
+        - name: advisory-url
+          value: $(tasks.run-task.results.advisory_url)
+        - name: result-status
+          value: $(tasks.run-task.results.result)
+      runAfter:
+        - run-task
+      taskSpec:
+        params:
+          - name: advisory-url
+            type: string
+          - name: result-status
+            type: string
+        steps:
+          - name: verify-idempotency
+            image: quay.io/konflux-ci/release-service-utils:e633d51cd41d73e4b3310face21bb980af7a662f
+            script: |
+              #!/usr/bin/env bash
+              set -eux
+
+              EXPECTED_URL="https://gitlab.com/org/repo/-/blob/main/data/advisories/dev-tenant/2024/1234/advisory.yaml"
+
+              if [[ "$(params.result-status)" != "Success" ]]; then
+                  echo "Task did not succeed. Status: $(params.result-status)"
+                  exit 1
+              fi
+
+              if [[ "$(params.advisory-url)" != "$EXPECTED_URL" ]]; then
+                  echo "Unexpected advisory URL: $(params.advisory-url)"
+                  exit 1
+              fi
diff --git a/tasks/internal/create-advisory-task/tests/test-create-advisory-idempotency-single-image.yaml b/tasks/internal/create-advisory-task/tests/test-create-advisory-idempotency-single-image.yaml
new file mode 100644
index 0000000000..873402aaec
--- /dev/null
+++ b/tasks/internal/create-advisory-task/tests/test-create-advisory-idempotency-single-image.yaml
@@ -0,0 +1,71 @@
+---
+apiVersion: tekton.dev/v1
+kind: Pipeline
+metadata:
+  name: test-create-advisory-idempotency-single-image
+spec:
+  description: |
+    Verifies the idempotency behavior of the advisory creation process when a single container image is
+    provided in the advisory_json. The input image (abde) is already included in an existing advisory.
+    The task is expected to detect this and reuse the same advisory without duplicating or creating a new one.
+    The test passes if the resulting advisory URL matches the one for the existing advisory.
+    Note: This test does not validate the actual contents of the advisory file. It assumes that if the task
+    succeeded and the advisory was created at the expected URL, then the logic to skip already released images worked.
+  tasks:
+    - name: run-task
+      taskRef:
+        name: create-advisory-task
+      params:
+        - name: advisory_json
+          value: >-
+            {"product_id":123,"product_name":"Red Hat Product","product_version":"1.2.3","product_stream":"tp1",
+            "cpe":"cpe:/a:example:product:el8","type":"RHSA","synopsis":"test synopsis","topic":"test topic",
+            "description":"test description","solution":"test solution","references":["https://docs.example.com/notes"],
+            "content":{"images":[{"containerImage":"quay.io/example/openstack@sha256:abde",
+            "repository":"quay.io/example/openstack","tags":["v1.0", "latest"],"architecture":"amd64",
+            "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
+            "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}}]}}
+        - name: origin
+          value: dev-tenant
+        - name: application
+          value: "test-app"
+        - name: config_map_name
+          value: "create-advisory-test-cm"
+        - name: advisory_secret_name
+          value: "create-advisory-secret"
+        - name: errata_secret_name
+          value: "create-advisory-errata-secret"
+        - name: internalRequestPipelineRunName
+          value: $(context.pipelineRun.name)
+    - name: check-result
+      params:
+        - name: advisory-url
+          value: $(tasks.run-task.results.advisory_url)
+        - name: result-status
+          value: $(tasks.run-task.results.result)
+      runAfter:
+        - run-task
+      taskSpec:
+        params:
+          - name: advisory-url
+            type: string
+          - name: result-status
+            type: string
+        steps:
+          - name: verify-idempotency
+            image: quay.io/konflux-ci/release-service-utils:e633d51cd41d73e4b3310face21bb980af7a662f
+            script: |
+              #!/usr/bin/env bash
+              set -eux
+
+              EXPECTED_URL="https://gitlab.com/org/repo/-/blob/main/data/advisories/dev-tenant/2024/1442/advisory.yaml"
+
+              if [[ "$(params.result-status)" != "Success" ]]; then
+                  echo "Task did not succeed. Status: $(params.result-status)"
+                  exit 1
+              fi
+
+              if [[ "$(params.advisory-url)" != "$EXPECTED_URL" ]]; then
+                  echo "Unexpected advisory URL: $(params.advisory-url)"
+                  exit 1
+              fi
diff --git a/tasks/internal/create-advisory-task/tests/test-create-advisory.yaml b/tasks/internal/create-advisory-task/tests/test-create-advisory.yaml
index 28b9404ae8..b2f3603618 100644
--- a/tasks/internal/create-advisory-task/tests/test-create-advisory.yaml
+++ b/tasks/internal/create-advisory-task/tests/test-create-advisory.yaml
@@ -16,7 +16,7 @@ spec:
             {"product_id":123,"product_name":"Red Hat Product","product_version":"1.2.3","product_stream":"tp1",
             "cpe":"cpe:/a:example:product:el8","type":"RHSA","synopsis":"test synopsis","topic":"test topic",
             "description":"test description","solution":"test solution","references":["https://docs.example.com/notes"],
-            "content":{"images":[{"containerImage":"quay.io/example/openstack@sha256:abde",
+            "content":{"images":[{"containerImage":"quay.io/example/openstack@sha256:abdeNEW",
             "repository":"rhosp16-rhel8/openstack","tags":["latest"],"architecture":"amd64",
             "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
             "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}}]}}


Reviews:
user: login: johnbieren
body: This looks close, but I think it is missing something. Take this scenario for example:
already published advisory:
```
  content:
    images:
    - containerImage: foo@sha256:sha2
      tags:
      - 1.0.0
```
new advisory_json contains
```
  content:
    images:
    - containerImage: foo@sha256:sha2
      tags:
      - 1.0.0
    - containerImage: bar@sha256:sha
      tags:
      - 1.0.0
```
In this case,
```
    - containerImage: bar@sha256:sha
      tags:
      - 1.0.0
```
should be published in a new advisory. That is to say, the entry that already exists with containerImage and tags matching is dropped, but there is still new stuff so an advisory has to be filed, just with less
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-18T15:09:11Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-19T04:19:36Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-19T04:19:49Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-19T04:21:09Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-19T04:22:00Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-19T04:22:16Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-19T04:29:12Z
user: login: johnbieren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-19T16:39:20Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T06:33:21Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T06:12:13Z
user: login: johnbieren
body: Can you add a test for the scenario described here https://github.com/konflux-ci/release-service-catalog/pull/817#pullrequestreview-2623882839 ?
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-27T18:15:48Z
user: login: jinqi7
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T01:41:20Z
user: login: mmalina
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T15:00:47Z
user: login: mmalina
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T15:06:16Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T06:01:37Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T06:01:43Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T06:02:12Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T06:03:00Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T06:03:32Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T06:06:16Z
user: login: mmalina
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T14:19:34Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T14:13:12Z
user: login: mmalina
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T08:58:53Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T04:27:18Z
user: login: mmalina
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T08:47:57Z
user: login: johnbieren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-18T14:05:50Z
user: login: johnbieren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-18T14:09:13Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T14:10:30Z
user: login: mmalina
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T14:15:05Z
user: login: johnbieren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-20T14:24:44Z
user: login: mmalina
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T14:36:24Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T11:56:59Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T18:51:28Z
user: login: mmalina
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T08:44:09Z
user: login: johnbieren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-25T12:53:18Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T13:09:53Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T17:35:19Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T18:53:57Z
user: login: johnbieren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-26T19:54:44Z
user: login: mmalina
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T09:40:17Z
user: login: mmalina
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T09:42:33Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T04:02:25Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T04:04:10Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T04:05:17Z
user: login: happybhati
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T04:14:47Z
user: login: mmalina
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T08:37:16Z
user: login: mmalina
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T08:38:30Z
user: login: johnbieren
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-28T13:06:45Z

Review comments:
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: This is going to be super ugly since it is compact json and just pollute the logs
created_at: 2025-02-18T15:01:52Z
author_association: COLLABORATOR
diff_hunk: @@ -15,6 +15,9 @@ internal request. The success/failure is handled in the task creating the intern
 | advisory_secret_name | The name of the secret that contains the advisory creation metadata                                    | No       | -             |
 | errata_secret_name   | The name of the secret that contains the errata service account metadata                               | No       | -             |
 
+## Changes in 0.12.0
path: tasks/internal/create-advisory-task/README.md
user: login: johnbieren
body: I think 1.0.0 is more appropriate
created_at: 2025-02-18T15:02:01Z
author_association: COLLABORATOR
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
+          echo "Validating JSON format..."
+          echo "$ADVISORY_JSON" | jq empty || { echo "Invalid JSON format"; exit 1; }
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: This entire task breaks if the ADVISORY_JSON isn't json. Do we really need this?
created_at: 2025-02-18T15:02:21Z
author_association: COLLABORATOR
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
+          echo "Validating JSON format..."
+          echo "$ADVISORY_JSON" | jq empty || { echo "Invalid JSON format"; exit 1; }
+
+          ADVISORY_CONTENT=$(
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: Maybe CONTENT_IMAGES makes more sense? There is a lot more "content" to the advisory
created_at: 2025-02-18T15:04:03Z
author_association: COLLABORATOR
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
+          echo "Validating JSON format..."
+          echo "$ADVISORY_JSON" | jq empty || { echo "Invalid JSON format"; exit 1; }
+
+          ADVISORY_CONTENT=$(
+            echo "$ADVISORY_JSON" | jq -c '
+              [.content.images[] | {
+                containerImage: .containerImage, 
+                tags: .tags
+              }]
+            ' || echo "[]"
+          )
+
+          echo "Extracted Advisory Content: $ADVISORY_CONTENT"
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: Not sure we need this echo, but if you want it, I think it would be better to say it is the content images, not just content. I get what you are going for, but content is a pretty ambiguous english word so it is confusing IMO
created_at: 2025-02-18T15:04:53Z
author_association: COLLABORATOR
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
+          echo "Validating JSON format..."
+          echo "$ADVISORY_JSON" | jq empty || { echo "Invalid JSON format"; exit 1; }
+
+          ADVISORY_CONTENT=$(
+            echo "$ADVISORY_JSON" | jq -c '
+              [.content.images[] | {
+                containerImage: .containerImage, 
+                tags: .tags
+              }]
+            ' || echo "[]"
+          )
+
+          echo "Extracted Advisory Content: $ADVISORY_CONTENT"
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)/${YEAR}"
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: hmm I think even if it was released last year, we shouldn't again, so maybe drop $YEAR here?
created_at: 2025-02-18T15:05:32Z
author_association: COLLABORATOR
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Agree, removed!
created_at: 2025-02-19T04:19:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,6 +15,9 @@ internal request. The success/failure is handled in the task creating the intern
 | advisory_secret_name | The name of the secret that contains the advisory creation metadata                                    | No       | -             |
 | errata_secret_name   | The name of the secret that contains the errata service account metadata                               | No       | -             |
 
+## Changes in 0.12.0
path: tasks/internal/create-advisory-task/README.md
user: login: happybhati
body: Fixed, thanks!
created_at: 2025-02-19T04:19:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
+          echo "Validating JSON format..."
+          echo "$ADVISORY_JSON" | jq empty || { echo "Invalid JSON format"; exit 1; }
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Removed!
created_at: 2025-02-19T04:21:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
+          echo "Validating JSON format..."
+          echo "$ADVISORY_JSON" | jq empty || { echo "Invalid JSON format"; exit 1; }
+
+          ADVISORY_CONTENT=$(
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: I agree; I am just bad with naming; thanks for the suggestion!
Fixed!
created_at: 2025-02-19T04:22:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
+          echo "Validating JSON format..."
+          echo "$ADVISORY_JSON" | jq empty || { echo "Invalid JSON format"; exit 1; }
+
+          ADVISORY_CONTENT=$(
+            echo "$ADVISORY_JSON" | jq -c '
+              [.content.images[] | {
+                containerImage: .containerImage, 
+                tags: .tags
+              }]
+            ' || echo "[]"
+          )
+
+          echo "Extracted Advisory Content: $ADVISORY_CONTENT"
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Removed!
created_at: 2025-02-19T04:22:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
+          echo "Validating JSON format..."
+          echo "$ADVISORY_JSON" | jq empty || { echo "Invalid JSON format"; exit 1; }
+
+          ADVISORY_CONTENT=$(
+            echo "$ADVISORY_JSON" | jq -c '
+              [.content.images[] | {
+                containerImage: .containerImage, 
+                tags: .tags
+              }]
+            ' || echo "[]"
+          )
+
+          echo "Extracted Advisory Content: $ADVISORY_CONTENT"
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)/${YEAR}"
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: I agree, on a side note

I am just wondering if we want to add any limit on existing advisory checks? Like the last 10 or 20? because I'm just thinking if some product has 100s of advisories, it will go through each to find if it is duplicate or not, and this will repeat for each containerImage. 

What do you think?
created_at: 2025-02-19T04:29:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
+          echo "Validating JSON format..."
+          echo "$ADVISORY_JSON" | jq empty || { echo "Invalid JSON format"; exit 1; }
+
+          ADVISORY_CONTENT=$(
+            echo "$ADVISORY_JSON" | jq -c '
+              [.content.images[] | {
+                containerImage: .containerImage, 
+                tags: .tags
+              }]
+            ' || echo "[]"
+          )
+
+          echo "Extracted Advisory Content: $ADVISORY_CONTENT"
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)/${YEAR}"
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: That's a fair point. I think because you narrow it down to the params.origin, it won't be too bad, but it could still happen eventually. Maybe don't add a limit here beyond origin, but then create a followup jira to add a limit and we can discuss it as a team in refinement?
created_at: 2025-02-19T16:39:20Z
author_association: COLLABORATOR
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
+          echo "Validating JSON format..."
+          echo "$ADVISORY_JSON" | jq empty || { echo "Invalid JSON format"; exit 1; }
+
+          ADVISORY_CONTENT=$(
+            echo "$ADVISORY_JSON" | jq -c '
+              [.content.images[] | {
+                containerImage: .containerImage, 
+                tags: .tags
+              }]
+            ' || echo "[]"
+          )
+
+          echo "Extracted Advisory Content: $ADVISORY_CONTENT"
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)/${YEAR}"
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Check for the previous year is giving me pain to test properly, so I reverted for now.
I will look into that tomorrow;
the rest is ready for review.
created_at: 2025-02-26T06:33:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,6 +128,55 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
+          echo "Raw Advisory JSON received: $ADVISORY_JSON"
+          echo "Validating JSON format..."
+          echo "$ADVISORY_JSON" | jq empty || { echo "Invalid JSON format"; exit 1; }
+
+          ADVISORY_CONTENT=$(
+            echo "$ADVISORY_JSON" | jq -c '
+              [.content.images[] | {
+                containerImage: .containerImage, 
+                tags: .tags
+              }]
+            ' || echo "[]"
+          )
+
+          echo "Extracted Advisory Content: $ADVISORY_CONTENT"
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)/${YEAR}"
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Done, ready for review, Sir!
created_at: 2025-02-27T06:12:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,10 +128,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: I think you can do
`CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )`
instead
created_at: 2025-02-27T18:08:45Z
author_association: COLLABORATOR
diff_hunk: @@ -128,10 +128,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: I suppose this works, but they are organized by origin workspace.. why not just do `find "${ADVISORY_BASE_DIR} -name "advisory.yaml"`? Am I missing something?
created_at: 2025-02-27T18:11:19Z
author_association: COLLABORATOR
diff_hunk: @@ -128,10 +128,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: Is it so open MRs are checked too or something?
created_at: 2025-02-27T18:12:02Z
author_association: COLLABORATOR
diff_hunk: @@ -128,10 +128,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: I'd think you can use jq's default thing to not need two conditions here
created_at: 2025-02-27T18:13:01Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,66 @@
+---
+apiVersion: tekton.dev/v1
+kind: Pipeline
+metadata:
+  name: test-advisory-idempotency
+spec:
+  description: |
+    Test the idempotency feature in the advisory creation process.
+  tasks:
+    - name: run-task
+      taskRef:
+        name: create-advisory-task
+      params:
+        - name: advisory_json
+          value: >-
+            {"product_id":123,"product_name":"Red Hat Product","product_version":"1.2.3","product_stream":"tp1",
+            "cpe":"cpe:/a:example:product:el8","type":"RHSA","synopsis":"test synopsis","topic":"test topic",
+            "description":"test description","solution":"test solution","references":["https://docs.example.com/notes"],
+            "content":{"images":[{"containerImage":"quay.io/example/openstack@sha256:abde",
+            "repository":"rhosp16-rhel8/openstack","tags":["v1.0", "latest"],"architecture":"amd64",
+            "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
+            "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}}]}}
+        - name: origin
+          value: dev-tenant
+        - name: application
+          value: "test-app"
+        - name: config_map_name
+          value: "create-advisory-test-cm"
+        - name: advisory_secret_name
+          value: "create-advisory-secret"
+        - name: errata_secret_name
+          value: "create-advisory-errata-secret"
+    - name: check-result
+      params:
+        - name: advisory-url
+          value: $(tasks.run-task.results.advisory_url)
+        - name: result-status
+          value: $(tasks.run-task.results.result)
+      runAfter:
+        - run-task
+      taskSpec:
+        params:
+          - name: advisory-url
+            type: string
+          - name: result-status
+            type: string
+        steps:
+          - name: verify-idempotency
+            image: quay.io/konflux-ci/release-service-utils:e633d51cd41d73e4b3310face21bb980af7a662f
+            script: |
+              #!/usr/bin/env bash
+              set -eux
+
+              # The advisory should match one of the existing advisories
path: tasks/internal/create-advisory-task/tests/test-advisory-idempotency.yaml
user: login: johnbieren
body: shouldn't we know which of the two?
created_at: 2025-02-27T18:15:07Z
author_association: COLLABORATOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: jinqi7
body: Is the value of  ADVISORY_BASE_DIR right? I think it should be "data/advisories/$(params.origin)". 
created_at: 2025-03-03T01:41:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: mmalina
body: Why `git show`? Why not just `cat`?
created_at: 2025-03-03T15:00:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: mmalina
body: It's not clear to me how this iteration works. What I see is: If we find an advisory that already contains all of the images, then exit, there is nothing to do. But what if there is an overlap? You will save the remaining images as `FILTERED_IMAGES`, right? But then you proceed to process the next advisory and maybe that one has completely different images. So maybe you found that some images were already released in advisory 1, but then you process advisory 2 which didn't have any. And then the cycle is over and `FILTERED_IMAGES` contains all of the images from this release, even though we were supposed to skip a few. Or am I missing something?
created_at: 2025-03-03T15:06:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,10 +128,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Agree, Done!
TY!
created_at: 2025-03-06T06:01:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Great catch. I was testing locally and forgot to fix the correct path, Thanks @jinqi7 
created_at: 2025-03-06T06:01:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,10 +128,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: I originally used git to ensure retrieving the latest version, but since we are already cloning the repo and checking the advisory locally, I’m fine with switching to `find` and `cat`([Martin's suggestion](https://github.com/konflux-ci/release-service-catalog/pull/817/files#r1977670290))

Fixed!
created_at: 2025-03-06T06:02:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Adjusted, thanks!
created_at: 2025-03-06T06:03:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -128,10 +128,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Agree, thanks!
Fixed
created_at: 2025-03-06T06:03:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: @mmalina, I think I understood your use case. I am writing a test to make sure I am getting it right. Will get back to you once ready
created_at: 2025-03-06T06:06:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: mmalina
body: Yeah, what I think you'll need is start with the full list of images and then on each iteration see if you can remove some from the list and keep doing that. So that for example if you have advisory1 with image1, advisory2 with image2 and this runs in a release that tries to release image1 and image2 and image3, after the for cycle, you will only have image3 left to be released.
created_at: 2025-03-06T14:19:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Yes, I have added the test and your recommended changes. Please take a look whenever you get a chance.
Thanks a lot :)
created_at: 2025-03-11T14:13:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: mmalina
body: OK, this seems correct. But I'm curious about one thing: How do we know when to consider an image already released? I see you're basing this on the combination of `containerImage` and `tags`. How did you arrive at this? I checked the Jira and it doesn't say anything... I wonder if re-releasing an image for the sake of adding a new tag should really mean that we have a new advisory. I think actually in such case the most correct thing to do would be to modify the existing advisory to add the tag. Not sure if that's something we'll implement in the future. But what about the other fields? What if it's released again to a completely new repository, but same tags?
created_at: 2025-03-12T08:58:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Thanks for the feedback!
I consider this comment from slack [thread](https://redhat-internal.slack.com/archives/C031USXS2FJ/p1737491992776329?thread_ts=1737490925.889499&cid=C031USXS2FJ) by Johnny as an initial approach

The current logic considers an image "already released" if its `containerImage` (digest) and` tags` exist in an existing advisory. This was chosen because:

- The digest (containerImage) uniquely identifies an image.

- The tags represent release versions, and re-releasing the same digest under a new tag can be seen as a "new release."

- The Jira ticket did not specify whether we should modify existing advisories instead of creating new ones, so I followed the existing pattern.

You raise a valid point about handling cases where the same image is released with new tags (should we modify the existing advisory instead of creating a new one?).

If modifying the existing advisory to add new tags is the preferred approach, we could explore that in a future update. Let me know if you think we should adjust this logic now or if this should be discussed further.
created_at: 2025-03-13T04:27:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: mmalina
body: Thanks for the details. I think it would be good to also get Johnny's input on this. And he also has some open threads here, so probably we should wait for him anyway? We should probably check with Rogue about updating advisories for added tags and then create a followup Jira if needed.
created_at: 2025-03-13T08:47:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: Updating an existing advisory would be messy. I'm not sure how we would go about it. I mean, we do that via MRs right now. I guess we could just push to the branch directly here? I haven't heard of anyone releasing the exact same digest with the exact same tags to a different repository. That would be weird to me.. you are re-releasing the same snapshot but changed your RPA in between? It could happen though. I think the code as written is good enough to call this task idempotent. @happybhati maybe you can create a spike as a followup to this and we can involve Rogue or Ryan or whoever else to ask if we need to handle the cases Martin brought up
created_at: 2025-03-18T14:05:50Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,72 @@
+---
+apiVersion: tekton.dev/v1
+kind: Pipeline
+metadata:
+  name: test-create-advisory-idempotency-multiple-image
+spec:
+  description: |
+    Test the idempotency feature in the advisory creation process for multiple containerImage in advisory_json.
path: tasks/internal/create-advisory-task/tests/test-create-advisory-idempotency-multiple-image.yaml
user: login: johnbieren
body: It's hard to read json just by looking at it. Can you say here what is testing the idempotent part? As in "advisory_json contained these images but advisory only contains these because others already released"
Same for the other idempotent test
created_at: 2025-03-18T14:09:12Z
author_association: COLLABORATOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: @johnbieren Sounds good to me; I will create a spike with all the necessary details. Thanks for your input.
+1 Johnny's take!
created_at: 2025-03-18T14:10:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: mmalina
body: > I haven't heard of anyone releasing the exact same digest with the exact same tags to a different repository.

@johnbieren that's only the second case I mentioned. The first would be changing tags and I think that will happen from time to time.

But for the different repo - that actually is a use case that also happens - and why we have this for example: https://issues.redhat.com/browse/RELEASE-1491

I'm not sure if there are more users that do this, but the RHEL releases have this - they have two RPAs - one will push all the images to rhel9/... and the other will push to ubi9/... . It's the same images. And because it's two separate releases, we will create two advisories. I think in an ideal world these should be all in one advisory. But maybe it's not really an issue.

A spike sounds good.
created_at: 2025-03-20T14:15:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: Maybe you can clarify what part I missed as I still don't see it

> I wonder if re-releasing an image for the sake of adding a new tag should really mean that we have a new advisory. I think actually in such case the most correct thing to do would be to modify the existing advisory to add the tag. Not sure if that's something we'll implement in the future. 

I meant to address this with "Updating an existing advisory would be messy. I'm not sure how we would go about it. I mean, we do that via MRs right now. I guess we could just push to the branch directly here?"

> But what about the other fields? What if it's released again to a completely new repository, but same tags?

I think this is "the second case I mentioned."

I don't see anything else in your message

I don't have the answer to this though so I think we should proceed with this PR as it is and have Happy open a spike to figure out these questions. We should talk to releng about it IMO
created_at: 2025-03-20T14:24:44Z
author_association: COLLABORATOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: mmalina
body: > I meant to address this with "Updating an existing advisory would be messy. I'm not sure how we would go about it. I mean, we do that via MRs right now. I guess we could just push to the branch directly here?"

The way you continued made me think that you only considered the second scenario. The scenario with updating tags - we'll see, but I think we might have to worry about it in the future. There have been these discussions about how to support updating tags in Konflux - currently releng would manually run a pub task to update tags in registry and then somebody would modify stuff in Pyxis. And then a MR updating the advisory should also be created.

But going forward, the recommended way of doing this would be to update your RPA and release the same snapshot again and the release pipeline will take care of everything - updating tags in Pyxis is already working and we will push the new tags as well (but currently not remove tags that were removed from the RPA - I created a jira for that a few weeks ago). Now what about the advisory? It seems we will just create a new one - I think sooner or later somebody will come and say that's not right.

> I don't have the answer to this though so I think we should proceed with this PR as it is and have Happy open a spike to figure out these questions.

Yes, I said a spike sounds good. Maybe I wasn't clear, but I agree to proceed with this PR in the current form.
created_at: 2025-03-20T14:36:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,72 @@
+---
+apiVersion: tekton.dev/v1
+kind: Pipeline
+metadata:
+  name: test-create-advisory-idempotency-multiple-image
+spec:
+  description: |
+    Test the idempotency feature in the advisory creation process for multiple containerImage in advisory_json.
path: tasks/internal/create-advisory-task/tests/test-create-advisory-idempotency-multiple-image.yaml
user: login: happybhati
body: I agree. I added a description; I hope this helps.
created_at: 2025-03-21T11:56:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,57 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(echo "$ADVISORY_JSON" | jq -c '.content.images' || echo "[]")
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="advisories/data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            git ls-tree --full-tree -r --name-only origin/main "${ADVISORY_BASE_DIR}/" 2>/dev/null |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              EXISTING_CONTENT=$(
+                git show origin/main:"${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              FILTERED_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci | 
+                  .tags as $tags |
+                  $existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0
+                ))')
+
+              if [[ -z "$FILTERED_IMAGES" || "$FILTERED_IMAGES" == "[]" ]]; then
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Thanks for all the input.
Spike Created: https://issues.redhat.com/browse/RELEASE-1500
created_at: 2025-03-24T18:51:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,65 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            find "${ADVISORY_BASE_DIR}" -name "advisory.yaml" |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: mmalina
body: It seems this can be simplified - you want the directories, so why not do something like this?
```
find data/advisories/rhdh-tenant -type d -depth 2
data/advisories/rhdh-tenant/2025/0409
data/advisories/rhdh-tenant/2025/1931
data/advisories/rhdh-tenant/2024/11265
```

You also want to start with newest, right? But the sort doesn't seem to work the way you expect here:
```
find data/advisories/rhdh-tenant -type d -depth 2|sort -nr
data/advisories/rhdh-tenant/2025/1931
data/advisories/rhdh-tenant/2025/0409
data/advisories/rhdh-tenant/2024/11265
```
The newest one is last here. So I think you'll probably need something like what they [suggest](https://superuser.com/questions/294161/unix-linux-find-and-sort-by-date-modified) in SO: 
```
find . -printf "%T@ %p\n"
```
And then sort and then use `cut` or `sed` to remove the number column.
created_at: 2025-03-25T08:44:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,77 @@
+---
+apiVersion: tekton.dev/v1
+kind: Pipeline
+metadata:
+  name: test-create-advisory-idempotency-multiple-image
+spec:
+  description: |
+    Verifies the idempotency behavior of the advisory creation process when multiple container images are provided
+    in the advisory_json. This test supplies three images (alpha123, beta123, gamma123), of which
+    alpha123 and beta123 are already present in existing advisories.
+    The task is expected to recognize these and only create a new advisory for the remaining image (gamma123).
+    The test passes if only the unmatched image is included in the resulting advisory and the advisory URL matches the
+    expected value.
+  tasks:
+    - name: run-task
+      taskRef:
+        name: create-advisory-task
+      params:
+        - name: advisory_json
+          value: >-
+            {"product_id":123,"product_name":"Red Hat Product","product_version":"1.2.3","product_stream":"tp1",
+            "cpe":"cpe:/a:example:product:el8","type":"RHSA","synopsis":"test synopsis","topic":"test topic",
+            "description":"test description","solution":"test solution","references":["https://docs.example.com/notes"],
+            "content":{"images":[{"containerImage":"quay.io/example/release@sha256:alpha123",
+            "repository":"rhosp16-rhel8/openstack","tags":["v1.0", "latest"],"architecture":"amd64",
+            "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
+            "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}},{"containerImage":"quay.io/example/release@sha256:beta123",
+            "repository":"rhosp16-rhel8/openstack","tags":["v2.0", "stable"],"architecture":"amd64",
+            "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
+            "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}},{"containerImage":"quay.io/example/release@sha256:gamma123",
+            "repository":"rhosp16-rhel8/openstack","tags":["v3.0", "stable"],"architecture":"amd64",
+            "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
+            "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}}]}}
+        - name: origin
+          value: dev-tenant
+        - name: application
+          value: "test-app"
+        - name: config_map_name
+          value: "create-advisory-test-cm"
+        - name: advisory_secret_name
+          value: "create-advisory-secret"
+        - name: errata_secret_name
+          value: "create-advisory-errata-secret"
+        - name: internalRequestPipelineRunName
+          value: $(context.pipelineRun.name)
+    - name: check-result
+      params:
+        - name: advisory-url
+          value: $(tasks.run-task.results.advisory_url)
+        - name: result-status
+          value: $(tasks.run-task.results.result)
+      runAfter:
+        - run-task
+      taskSpec:
+        params:
+          - name: advisory-url
+            type: string
+          - name: result-status
+            type: string
+        steps:
+          - name: verify-idempotency
+            image: quay.io/konflux-ci/release-service-utils:e633d51cd41d73e4b3310face21bb980af7a662f
+            script: |
+              #!/usr/bin/env bash
+              set -eux
+
+              EXPECTED_URL="https://gitlab.com/org/repo/-/blob/main/data/advisories/dev-tenant/2024/1234/advisory.yaml"
path: tasks/internal/create-advisory-task/tests/test-create-advisory-idempotency-multiple-image.yaml
user: login: johnbieren
body: I don't see `1234` as the advisory_id anywhere in the mocks so not sure how this is working
created_at: 2025-03-25T12:51:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,77 @@
+---
+apiVersion: tekton.dev/v1
+kind: Pipeline
+metadata:
+  name: test-create-advisory-idempotency-multiple-image
+spec:
+  description: |
+    Verifies the idempotency behavior of the advisory creation process when multiple container images are provided
+    in the advisory_json. This test supplies three images (alpha123, beta123, gamma123), of which
+    alpha123 and beta123 are already present in existing advisories.
+    The task is expected to recognize these and only create a new advisory for the remaining image (gamma123).
+    The test passes if only the unmatched image is included in the resulting advisory and the advisory URL matches the
path: tasks/internal/create-advisory-task/tests/test-create-advisory-idempotency-multiple-image.yaml
user: login: johnbieren
body: This is much better and sounds great. The thing is, the checks are only seeing if the task was successful and the url is what it is expected. Maybe the certain url contains only the gamma123 image, but it isn't clear to me how/why that would be the case
created_at: 2025-03-25T12:52:03Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,77 @@
+---
+apiVersion: tekton.dev/v1
+kind: Pipeline
+metadata:
+  name: test-create-advisory-idempotency-multiple-image
+spec:
+  description: |
+    Verifies the idempotency behavior of the advisory creation process when multiple container images are provided
+    in the advisory_json. This test supplies three images (alpha123, beta123, gamma123), of which
+    alpha123 and beta123 are already present in existing advisories.
+    The task is expected to recognize these and only create a new advisory for the remaining image (gamma123).
+    The test passes if only the unmatched image is included in the resulting advisory and the advisory URL matches the
+    expected value.
+  tasks:
+    - name: run-task
+      taskRef:
+        name: create-advisory-task
+      params:
+        - name: advisory_json
+          value: >-
+            {"product_id":123,"product_name":"Red Hat Product","product_version":"1.2.3","product_stream":"tp1",
+            "cpe":"cpe:/a:example:product:el8","type":"RHSA","synopsis":"test synopsis","topic":"test topic",
+            "description":"test description","solution":"test solution","references":["https://docs.example.com/notes"],
+            "content":{"images":[{"containerImage":"quay.io/example/release@sha256:alpha123",
+            "repository":"rhosp16-rhel8/openstack","tags":["v1.0", "latest"],"architecture":"amd64",
+            "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
+            "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}},{"containerImage":"quay.io/example/release@sha256:beta123",
+            "repository":"rhosp16-rhel8/openstack","tags":["v2.0", "stable"],"architecture":"amd64",
+            "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
+            "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}},{"containerImage":"quay.io/example/release@sha256:gamma123",
+            "repository":"rhosp16-rhel8/openstack","tags":["v3.0", "stable"],"architecture":"amd64",
+            "purl":"pkg:example/openstack@256:abcde?repository_url=quay.io/example/rhosp16-rhel8","cves":{"fixed":{
+            "CVE-2022-1234":{"packages":["pkg:golang/golang.org/x/net/http2@1.11.1"]}}}}]}}
+        - name: origin
+          value: dev-tenant
+        - name: application
+          value: "test-app"
+        - name: config_map_name
+          value: "create-advisory-test-cm"
+        - name: advisory_secret_name
+          value: "create-advisory-secret"
+        - name: errata_secret_name
+          value: "create-advisory-errata-secret"
+        - name: internalRequestPipelineRunName
+          value: $(context.pipelineRun.name)
+    - name: check-result
+      params:
+        - name: advisory-url
+          value: $(tasks.run-task.results.advisory_url)
+        - name: result-status
+          value: $(tasks.run-task.results.result)
+      runAfter:
+        - run-task
+      taskSpec:
+        params:
+          - name: advisory-url
+            type: string
+          - name: result-status
+            type: string
+        steps:
+          - name: verify-idempotency
+            image: quay.io/konflux-ci/release-service-utils:e633d51cd41d73e4b3310face21bb980af7a662f
+            script: |
+              #!/usr/bin/env bash
+              set -eux
+
+              EXPECTED_URL="https://gitlab.com/org/repo/-/blob/main/data/advisories/dev-tenant/2024/1234/advisory.yaml"
path: tasks/internal/create-advisory-task/tests/test-create-advisory-idempotency-multiple-image.yaml
user: login: happybhati
body: May got hidden because that part of the code did not change 

You can find it  here: https://github.com/konflux-ci/release-service-catalog/pull/817/files#diff-6dfdcab61e8b80986656abd5f7a5cf08efe735da6446188751d8906d7be72cedR85
created_at: 2025-03-25T13:09:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,65 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            find "${ADVISORY_BASE_DIR}" -name "advisory.yaml" |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Thanks for the suggestion. I have made changes that are close to what you suggested, PTAL, whenever you get a chance.
created_at: 2025-03-26T17:35:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,77 @@
+---
+apiVersion: tekton.dev/v1
+kind: Pipeline
+metadata:
+  name: test-create-advisory-idempotency-multiple-image
+spec:
+  description: |
+    Verifies the idempotency behavior of the advisory creation process when multiple container images are provided
+    in the advisory_json. This test supplies three images (alpha123, beta123, gamma123), of which
+    alpha123 and beta123 are already present in existing advisories.
+    The task is expected to recognize these and only create a new advisory for the remaining image (gamma123).
+    The test passes if only the unmatched image is included in the resulting advisory and the advisory URL matches the
path: tasks/internal/create-advisory-task/tests/test-create-advisory-idempotency-multiple-image.yaml
user: login: happybhati
body: I've added a note in the description to clarify that the test does not inspect the advisory contents directly — it assumes correctness based on the task result and advisory URL. Hopefully this makes the intent a bit clearer.
created_at: 2025-03-26T18:53:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,65 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            find "${ADVISORY_BASE_DIR}" -mindepth 2 -type d -printf "%T@ %p\n" |  # year/advisory dir with modified time
+            sort -nr |                                                            # sort by latest modified first
+            cut -d' ' -f2- |                                                      # remove the timestamp, keep only path
+            sed "s|^${ADVISORY_BASE_DIR}/||"                                      # keeping year/advisory format
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              # shellcheck disable=SC2002
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: Why? Can't you just switch this to
`yq -o=json '.' "${ADVISORY_FILE}" | jq -c '.spec.content.images' 2>/dev/null || echo "[]"`?
Actually, you could probably just do
`yq -o=json '.spec.content.images // []' "${ADVISORY_FILE}"`, right?
created_at: 2025-03-26T19:50:32Z
author_association: COLLABORATOR
diff_hunk: @@ -142,10 +142,65 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            find "${ADVISORY_BASE_DIR}" -mindepth 2 -type d -printf "%T@ %p\n" |  # year/advisory dir with modified time
+            sort -nr |                                                            # sort by latest modified first
+            cut -d' ' -f2- |                                                      # remove the timestamp, keep only path
+            sed "s|^${ADVISORY_BASE_DIR}/||"                                      # keeping year/advisory format
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              # shellcheck disable=SC2002
+              EXISTING_CONTENT=$(
+                cat "${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              echo "Processing advisory: ${ADVISORY_FILE}"
+              echo "Existing images in advisory: $EXISTING_CONTENT"
+
+              # Update CONTENT_IMAGES by removing images that already exist in the advisory
+              CONTENT_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci |
+                  .tags as $tags |
+                  ($existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0)
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: johnbieren
body: I don't know if you created a spike for it yet, but @mmalina was right (I asked Rogue), we should also compare `.repository` because if that changed (but the other two are the same) we still need to include it in the advisory. If you already filed a spike, we can leave that for that jira though
created_at: 2025-03-26T19:54:25Z
author_association: COLLABORATOR
diff_hunk: @@ -142,10 +142,65 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            find "${ADVISORY_BASE_DIR}" -name "advisory.yaml" |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: mmalina
body: It looks good. But using `-depth 2` seems more correct than `-mindepth 2`, no?
created_at: 2025-03-27T09:40:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,65 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            find "${ADVISORY_BASE_DIR}" -mindepth 2 -type d -printf "%T@ %p\n" |  # year/advisory dir with modified time
+            sort -nr |                                                            # sort by latest modified first
+            cut -d' ' -f2- |                                                      # remove the timestamp, keep only path
+            sed "s|^${ADVISORY_BASE_DIR}/||"                                      # keeping year/advisory format
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: mmalina
body: Maybe `ADVISORY_YEAR_NUM` would be a better var name? Because it's not just the number. Or `ADVISORY_SUBDIR`?
created_at: 2025-03-27T09:42:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,65 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            find "${ADVISORY_BASE_DIR}" -mindepth 2 -type d -printf "%T@ %p\n" |  # year/advisory dir with modified time
+            sort -nr |                                                            # sort by latest modified first
+            cut -d' ' -f2- |                                                      # remove the timestamp, keep only path
+            sed "s|^${ADVISORY_BASE_DIR}/||"                                      # keeping year/advisory format
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Agree. I like `ADVISORY_SUBDIR` 
Fixed. Thanks :)
created_at: 2025-03-28T04:02:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,65 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            find "${ADVISORY_BASE_DIR}" -mindepth 2 -type d -printf "%T@ %p\n" |  # year/advisory dir with modified time
+            sort -nr |                                                            # sort by latest modified first
+            cut -d' ' -f2- |                                                      # remove the timestamp, keep only path
+            sed "s|^${ADVISORY_BASE_DIR}/||"                                      # keeping year/advisory format
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              # shellcheck disable=SC2002
+              EXISTING_CONTENT=$(
+                cat "${ADVISORY_FILE}" | 
+                yq -o=json '.' | 
+                jq -c '.spec.content.images' 2>/dev/null || echo "[]"
+              )
+
+              echo "Processing advisory: ${ADVISORY_FILE}"
+              echo "Existing images in advisory: $EXISTING_CONTENT"
+
+              # Update CONTENT_IMAGES by removing images that already exist in the advisory
+              CONTENT_IMAGES=$(echo "$CONTENT_IMAGES" | jq --argjson existing "$EXISTING_CONTENT" '
+                map(select(
+                  .containerImage as $ci |
+                  .tags as $tags |
+                  ($existing | map(select(.containerImage == $ci and .tags == $tags)) | length == 0)
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Yes, I created a spike 2 days ago

 but also added `.repository` as per your suggestion PTAL.
 Thanks!
created_at: 2025-03-28T04:04:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,65 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            find "${ADVISORY_BASE_DIR}" -mindepth 2 -type d -printf "%T@ %p\n" |  # year/advisory dir with modified time
+            sort -nr |                                                            # sort by latest modified first
+            cut -d' ' -f2- |                                                      # remove the timestamp, keep only path
+            sed "s|^${ADVISORY_BASE_DIR}/||"                                      # keeping year/advisory format
+          )
+
+          if [[ -z "$EXISTING_ADVISORIES" ]]; then
+              echo "No existing advisories found."
+          fi
+
+          for ADVISORY_NUM in $EXISTING_ADVISORIES; do
+              ADVISORY_FILE="${ADVISORY_BASE_DIR}/${ADVISORY_NUM}/advisory.yaml"
+              # shellcheck disable=SC2002
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Thanks for the suggestion; it makes enormous sense :P
Fixed!
created_at: 2025-03-28T04:05:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,65 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            find "${ADVISORY_BASE_DIR}" -name "advisory.yaml" |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: happybhati
body: Thanks! I actually tested both options — `-depth 2` works on macOS, but when I used `gfind`(GNU version) to replicate the Linux environment, it didn't behave as expected and failed to return the correct results. So I went with `-mindepth 2` for better cross-platform compatibility.

With `gfind`:
```
hbhati@hbhati-mac advisories % gfind "$sample" -type d -depth 2
gfind: warning: you have specified the global option -depth after the argument -type, but global options are not positional, i.e., -depth affects tests specified before it as well as those specified after it.  Please specify global options before other arguments.
gfind: paths must precede expression: `2'

```


With `find`:
```
hbhati@hbhati-mac advisories % find "$sample" -type d -depth 2                                                 
data/advisories/rhtas-tenant/2025/1328
data/advisories/rhtas-tenant/2025/1326
data/advisories/rhtas-tenant/2025/1321
data/advisories/rhtas-tenant/2025/1320
data/advisories/rhtas-tenant/2025/1327
data/advisories/rhtas-tenant/2025/1285
data/advisories/rhtas-tenant/2025/1322
data/advisories/rhtas-tenant/2025/1325
data/advisories/rhtas-tenant/2025/1324

```
created_at: 2025-03-28T04:14:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,10 +142,65 @@ spec:
           # This also cds into the git repo
           git_clone_and_checkout --repository "$GIT_REPO" --revision "$REPO_BRANCH"
 
-          # Inject signing key into ADVISORY_JSON
+          CONTENT_IMAGES=$(jq -c '.content.images // []' <<< "$ADVISORY_JSON" )
+
+          # Use ISO 8601 format in UTC/Zulu time, e.g. 2024-03-06T17:27:38Z
+          SHIP_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+          YEAR=${SHIP_DATE%%-*} # derive the year from the ship date
+          # Define advisory directory
+          ADVISORY_BASE_DIR="data/advisories/$(params.origin)"
+          echo "Checking advisories in directory: ${ADVISORY_BASE_DIR}"
+
+          # Check existing advisories across ALL years
+          EXISTING_ADVISORIES=$(
+            find "${ADVISORY_BASE_DIR}" -name "advisory.yaml" |
+            grep -oE '[0-9]{4}/[0-9]{4,6}/advisory.yaml$' |
+            sed 's/\/advisory.yaml$//' |
+            sort -nr || true
path: tasks/internal/create-advisory-task/create-advisory-task.yaml
user: login: mmalina
body: @happybhati I see, thanks for the details, I didn't realize there could be issues with different versions of find.
created_at: 2025-03-28T08:37:16Z
author_association: CONTRIBUTOR

PR comments:
user: login: openshift-ci[bot]
created_at: 2025-02-11T05:58:00Z
body: Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with `/test all`
author_association: NONE
user: login: happybhati
created_at: 2025-02-14T19:55:29Z
body: /retest
author_association: CONTRIBUTOR
user: login: happybhati
created_at: 2025-02-14T21:20:59Z
body: /retest
author_association: CONTRIBUTOR
user: login: happybhati
created_at: 2025-02-15T03:01:03Z
body: /retest
author_association: CONTRIBUTOR
user: login: happybhati
created_at: 2025-02-19T04:24:02Z
body: > This looks close, but I think it is missing something. Take this scenario for example: already published advisory:
> 
> ```
>   content:
>     images:
>     - containerImage: foo@sha256:sha2
>       tags:
>       - 1.0.0
> ```
> 
> new advisory_json contains
> 
> ```
>   content:
>     images:
>     - containerImage: foo@sha256:sha2
>       tags:
>       - 1.0.0
>     - containerImage: bar@sha256:sha
>       tags:
>       - 1.0.0
> ```
> 
> In this case,
> 
> ```
>     - containerImage: bar@sha256:sha
>       tags:
>       - 1.0.0
> ```
> 
> should be published in a new advisory. That is to say, the entry that already exists with containerImage and tags matching is dropped, but there is still new stuff so an advisory has to be filed, just with less

Thanks @johnbieren  for explaining the use case. I am working on fixing and testing it, and I will ping you once it is ready for review.
author_association: CONTRIBUTOR
user: login: happybhati
created_at: 2025-03-06T06:07:35Z
body: > Can you add a test for the scenario described here [#817 (review)](https://github.com/konflux-ci/release-service-catalog/pull/817#pullrequestreview-2623882839) ?

Sure, on it. Almost missed the comment.
author_association: CONTRIBUTOR
user: login: theflockers
created_at: 2025-03-24T10:21:59Z
body: /retest
author_association: CONTRIBUTOR
user: login: happybhati
created_at: 2025-03-24T19:42:30Z
body: /retest
author_association: CONTRIBUTOR
user: login: jinqi7
created_at: 2025-03-28T10:05:39Z
body: /retest
author_association: CONTRIBUTOR
user: login: happybhati
created_at: 2025-03-28T12:44:27Z
body: FYI: After Martin's approval, the only change is adding the missing new line at the end of the file!
author_association: CONTRIBUTOR
user: login: happybhati
created_at: 2025-03-28T18:32:45Z
body: /retest
author_association: CONTRIBUTOR