From 5a5081f0a30fa4658c2b6dbd8e1b219f1886681f Mon Sep 17 00:00:00 2001 From: Alexey Pustovalov Date: Wed, 25 Mar 2026 14:46:29 +0900 Subject: [PATCH] Optimizations for dependabot and RHEL catalog description --- .github/dependabot.yml | 459 ++---------------- .../scripts/requirements-rhel-description.txt | 2 + .github/scripts/rhel_description.py | 126 +++-- .../workflows/rhel_registry_description.yml | 133 +++-- 4 files changed, 223 insertions(+), 497 deletions(-) create mode 100644 .github/scripts/requirements-rhel-description.txt diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 04ad6eb3..166ce2ac 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,418 +1,45 @@ version: 2 + updates: -- package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: daily - time: "06:00" - open-pull-requests-limit: 10 - -- package-ecosystem: "docker" - directory: /Dockerfiles/agent/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/agent/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/agent/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/agent/rhel - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/agent/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/agent/windows - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/agent2/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/agent2/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/agent2/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/agent2/rhel - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/agent2/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/agent2/windows - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-base/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-base/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-base/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-base/rhel - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-base/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-mysql/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-mysql/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-mysql/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-mysql/rhel - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-mysql/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-pgsql/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-pgsql/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-pgsql/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-pgsql/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-sqlite3/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-sqlite3/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-sqlite3/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-sqlite3/rhel - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/build-sqlite3/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/java-gateway/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/java-gateway/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/java-gateway/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/java-gateway/rhel - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/java-gateway/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/proxy-mysql/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/proxy-mysql/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/proxy-mysql/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/proxy-mysql/rhel - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/proxy-mysql/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/proxy-sqlite3/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/proxy-sqlite3/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/proxy-sqlite3/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/proxy-sqlite3/rhel - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/proxy-sqlite3/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/server-mysql/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/server-mysql/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/server-mysql/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/server-mysql/rhel - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/server-mysql/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/server-pgsql/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/server-pgsql/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/server-pgsql/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/server-pgsql/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/snmptraps/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/snmptraps/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/snmptraps/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/snmptraps/rhel - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/snmptraps/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-apache-mysql/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-apache-mysql/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-apache-mysql/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-apache-mysql/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-apache-pgsql/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-apache-pgsql/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-apache-pgsql/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-apache-pgsql/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-nginx-mysql/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-nginx-mysql/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-nginx-mysql/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-nginx-mysql/rhel - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-nginx-mysql/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-nginx-pgsql/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-nginx-pgsql/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-nginx-pgsql/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-nginx-pgsql/ubuntu - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-service/alpine - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-service/centos - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-service/ol - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-service/rhel - schedule: - interval: daily - -- package-ecosystem: "docker" - directory: /Dockerfiles/web-service/ubuntu - schedule: - interval: daily + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + time: "06:00" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "github-actions" + + - package-ecosystem: "docker" + directories: + - "/Dockerfiles/agent/*" + - "/Dockerfiles/agent2/*" + - "/Dockerfiles/build-base/*" + - "/Dockerfiles/build-mysql/*" + - "/Dockerfiles/build-pgsql/*" + - "/Dockerfiles/build-sqlite3/*" + - "/Dockerfiles/java-gateway/*" + - "/Dockerfiles/proxy-mysql/*" + - "/Dockerfiles/proxy-sqlite3/*" + - "/Dockerfiles/server-mysql/*" + - "/Dockerfiles/server-pgsql/*" + - "/Dockerfiles/snmptraps/*" + - "/Dockerfiles/web-apache-mysql/*" + - "/Dockerfiles/web-apache-pgsql/*" + - "/Dockerfiles/web-nginx-mysql/*" + - "/Dockerfiles/web-nginx-pgsql/*" + - "/Dockerfiles/web-service/*" + schedule: + interval: "weekly" + time: "06:30" + open-pull-requests-limit: 3 + labels: + - "dependencies" + - "docker" + groups: + docker-images: + applies-to: version-updates + patterns: + - "*" + group-by: dependency-name diff --git a/.github/scripts/requirements-rhel-description.txt b/.github/scripts/requirements-rhel-description.txt new file mode 100644 index 00000000..124892ba --- /dev/null +++ b/.github/scripts/requirements-rhel-description.txt @@ -0,0 +1,2 @@ +markdown==3.7 +requests==2.32.3 diff --git a/.github/scripts/rhel_description.py b/.github/scripts/rhel_description.py index 08528561..2fabacc7 100644 --- a/.github/scripts/rhel_description.py +++ b/.github/scripts/rhel_description.py @@ -1,48 +1,96 @@ -import sys -import requests -import json -import markdown import os +import sys +from pathlib import Path -repository_description = None +import markdown +import requests -if ("DESCRIPTION_FILE" not in os.environ or len(os.environ["DESCRIPTION_FILE"]) == 0): - print("::error::Description file environment variable is not specified") - sys.exit(1) -if ("PYXIS_API_TOKEN" not in os.environ or len(os.environ["PYXIS_API_TOKEN"]) == 0): - print("::error::API token environment variable is not specified") - sys.exit(1) -if ("API_URL" not in os.environ or len(os.environ["API_URL"]) == 0): - print("::error::API URL environment variable is not specified") - sys.exit(1) -if ("PROJECT_ID" not in os.environ or len(os.environ["PROJECT_ID"]) == 0): - print("RedHat project ID environment variable is not specified") +MAX_DESCRIPTION_LEN = 32768 +REQUEST_TIMEOUT = 30 + +################## + +def fail(msg: str) -> None: + print(f"::error::{msg}") sys.exit(1) -if (os.path.isfile(os.environ["DESCRIPTION_FILE"] + '.html')): - file = open(os.environ["DESCRIPTION_FILE"] + '.html', mode='r') - repository_description = file.read() - file.close() -elif (os.path.isfile(os.environ["DESCRIPTION_FILE"] + '.md')): - file = open(os.environ["DESCRIPTION_FILE"] + '.md', mode='r') - markdown_data = file.read() - file.close() - repository_description=markdown.markdown(markdown_data) -if (repository_description is None or len(repository_description) == 0): - print("::error::No description file found") - sys.exit(1) +def get_env(name: str) -> str: + value = os.environ.get(name, "").strip() + if not value: + fail(f"{name} environment variable is not specified") + return value -data = dict() -data['container'] = dict() -data['container']['repository_description'] = repository_description[:32768] +################## -headers = {'accept' : 'application/json', 'X-API-KEY' : os.environ["PYXIS_API_TOKEN"], 'Content-Type' : 'application/json'} -result = requests.patch(os.environ["API_URL"] + os.environ["PROJECT_ID"], - headers = headers, - data = json.dumps(data)) +def load_description(file_name: str) -> str: + html_path = Path(f"{file_name}.html") + md_path = Path(f"{file_name}.md") -print("::group::Result") -print("Response code: " + str(result.status_code)) -print("Last update date: " + json.loads(result.content)['last_update_date']) -print("::endgroup::") + if html_path.is_file(): + with html_path.open("r", encoding="utf-8") as f: + return f.read() + + if md_path.is_file(): + with md_path.open("r", encoding="utf-8") as f: + markdown_data = f.read() + return markdown.markdown(markdown_data) + + fail(f"No description file found: expected '{html_path}' or '{md_path}'") + +def main() -> int: + description_file = get_env("DESCRIPTION_FILE") + api_token = get_env("PYXIS_API_TOKEN") + api_url = get_env("API_URL") + project_id = get_env("PROJECT_ID") + + repository_description = load_description(description_file) + + if not repository_description.strip(): + fail("Description file is empty") + + was_truncated = len(repository_description) > MAX_DESCRIPTION_LEN + repository_description = repository_description[:MAX_DESCRIPTION_LEN] + + payload = { + "container": { + "repository_description": repository_description, + } + } + + headers = { + "accept": "application/json", + "X-API-KEY": api_token, + "Content-Type": "application/json", + } + + url = f"{api_url}{project_id}" + + try: + with requests.Session() as session: + response = session.patch( + url, + headers = headers, + json = payload, + timeout = REQUEST_TIMEOUT, + ) + response.raise_for_status() + except requests.RequestException as exc: + fail(f"Request to Pyxis API failed: {exc}") + + try: + response_data = response.json() + except ValueError: + fail(f"API returned non-JSON response: {response.text[:500]}") + + print("::group::Result") + print(f"Response code: {response.status_code}") + if was_truncated: + print(f"Warning: repository_description was truncated to {MAX_DESCRIPTION_LEN} characters") + print(f"Last update date: {response_data.get('last_update_date', '')}") + print("::endgroup::") + + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/workflows/rhel_registry_description.yml b/.github/workflows/rhel_registry_description.yml index 32b92d70..1e57ee46 100644 --- a/.github/workflows/rhel_registry_description.yml +++ b/.github/workflows/rhel_registry_description.yml @@ -9,19 +9,26 @@ on: - 'Dockerfiles/*/README.md' - 'Dockerfiles/*/rhel/README.md' - '.github/workflows/rhel_registry_description.yml' + - '.github/scripts/rhel_description.py' + - '.github/scripts/requirements-rhel-description.txt' workflow_dispatch: +concurrency: + group: rhel-description-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: DOCKERFILES_DIRECTORY: "./Dockerfiles" API_URL: "https://catalog.redhat.com/api/containers/v1/projects/certification/id/" MATRIX_FILE: "build.json" + PYTHON_REQUIREMENTS_FILE: ".github/scripts/requirements-rhel-description.txt" defaults: run: shell: bash permissions: - contents: read + contents: read jobs: init: @@ -32,6 +39,7 @@ jobs: outputs: components: ${{ steps.components.outputs.list }} zabbix_release: ${{ steps.branch_info.outputs.zabbix_release }} + steps: - name: Block egress traffic uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 @@ -40,54 +48,59 @@ jobs: egress-policy: block allowed-endpoints: > github.com:443 + api.github.com:443 - - name: Checkout repository + - name: Checkout repository metadata uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 1 sparse-checkout: ${{ env.MATRIX_FILE }} - name: Check ${{ env.MATRIX_FILE }} file - id: build_exists env: MATRIX_FILE: ${{ env.MATRIX_FILE }} run: | - if [[ ! -f "$MATRIX_FILE" ]]; then - echo "::error::File $MATRIX_FILE is missing" - exit 1 - fi + set -Eeuo pipefail + + if [[ ! -f "$MATRIX_FILE" ]]; then + echo "::error::File $MATRIX_FILE is missing" + exit 1 + fi - name: Get branch info id: branch_info - shell: bash env: - github_ref: ${{ github.ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} run: | - result=false + set -Eeuo pipefail - github_ref=${github_ref##*/} + echo "::group::Branch metadata" + echo "zabbix_release=${GITHUB_REF_NAME//.}" + echo "::endgroup::" - echo "::group::Branch metadata" - echo "zabbix_release=${github_ref//.}" - echo "::endgroup::" - - echo "zabbix_release=${github_ref//.}" >> $GITHUB_OUTPUT + echo "zabbix_release=${GITHUB_REF_NAME//.}" >> "$GITHUB_OUTPUT" - name: Prepare Zabbix component list id: components env: MATRIX_FILE: ${{ env.MATRIX_FILE }} run: | - component_list=$(jq -r '.components | map_values(select(.rhel == true)) | keys | @json' "$MATRIX_FILE") + set -Eeuo pipefail - echo "::group::Zabbix Component List" - echo "$component_list" - echo "::endgroup::" + component_list="$(jq -rc '.components | map_values(select(.rhel == true)) | keys' "$MATRIX_FILE")" - echo "list=$component_list" >> $GITHUB_OUTPUT + if [[ -z "$component_list" || "$component_list" == "[]" ]]; then + echo "::warning::No RHEL-enabled components found in $MATRIX_FILE" + fi - publish: - name: Initialize build + echo "::group::Zabbix Component List" + echo "$component_list" + echo "::endgroup::" + + echo "list=$component_list" >> "$GITHUB_OUTPUT" + + update-description: + name: Update description (${{ matrix.component }}) runs-on: ubuntu-latest needs: init permissions: @@ -96,6 +109,7 @@ jobs: fail-fast: false matrix: component: ${{ fromJson(needs.init.outputs.components) }} + steps: - name: Block egress traffic uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 @@ -103,55 +117,90 @@ jobs: disable-sudo: true egress-policy: block allowed-endpoints: > - catalog.redhat.com:443 - files.pythonhosted.org:443 github.com:443 + api.github.com:443 pypi.org:443 + files.pythonhosted.org:443 + catalog.redhat.com:443 - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 1 - - name: Variables formating + - name: Format matrix variables id: var_format env: MATRIX_BUILD: ${{ matrix.component }} run: | - MATRIX_BUILD=${MATRIX_BUILD^^} - MATRIX_BUILD=${MATRIX_BUILD//-/_} + set -Eeuo pipefail - echo "::group::Result" - echo "matrix_build=${MATRIX_BUILD}" - echo "::endgroup::" - echo "matrix_build=${MATRIX_BUILD}" >> $GITHUB_OUTPUT + MATRIX_BUILD="${MATRIX_BUILD^^}" + MATRIX_BUILD="${MATRIX_BUILD//-/_}" - - name: Setup Python 3.x + echo "::group::Result" + echo "matrix_build=${MATRIX_BUILD}" + echo "::endgroup::" + + echo "matrix_build=${MATRIX_BUILD}" >> "$GITHUB_OUTPUT" + + - name: Check Python requirements file + env: + PYTHON_REQUIREMENTS_FILE: ${{ env.PYTHON_REQUIREMENTS_FILE }} + run: | + set -Eeuo pipefail + + if [[ ! -f "$PYTHON_REQUIREMENTS_FILE" ]]; then + echo "::error::File $PYTHON_REQUIREMENTS_FILE is missing" + exit 1 + fi + + - name: Setup Python 3.12 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: - python-version: '3.x' + python-version: '3.12' + cache: 'pip' + cache-dependency-path: ${{ env.PYTHON_REQUIREMENTS_FILE }} - name: Install Python packages + env: + PYTHON_REQUIREMENTS_FILE: ${{ env.PYTHON_REQUIREMENTS_FILE }} run: | + set -Eeuo pipefail + python -m pip install --upgrade pip - pip install markdown requests + pip install -r "$PYTHON_REQUIREMENTS_FILE" + + - name: Check description source + env: + DESCRIPTION_FILE: ${{ format('{0}/{1}/rhel/README', env.DOCKERFILES_DIRECTORY, matrix.component) }} + run: | + set -Eeuo pipefail + + if [[ ! -f "${DESCRIPTION_FILE}.html" && ! -f "${DESCRIPTION_FILE}.md" ]]; then + echo "::error::No description file found: ${DESCRIPTION_FILE}.html or ${DESCRIPTION_FILE}.md" + exit 1 + fi - name: Update Red Hat project description env: DESCRIPTION_FILE: ${{ format('{0}/{1}/rhel/README', env.DOCKERFILES_DIRECTORY, matrix.component) }} - PROJECT_ID: ${{ secrets[format('RHEL_{0}_{1}_PROJECT', needs.init.outputs.zabbix_release, steps.var_format.outputs.matrix_build)] }} + PROJECT_ID: ${{ secrets[format('RHEL_{0}_{1}_PROJECT', needs.init.outputs.zabbix_release, steps.var_format.outputs.matrix_build)] }} PYXIS_API_TOKEN: ${{ secrets.REDHAT_API_TOKEN }} API_URL: ${{ env.API_URL }} run: | - python ./.github/scripts/rhel_description.py + set -Eeuo pipefail + python ./.github/scripts/rhel_description.py - - name: Red Hat Gatalog URL + - name: Print Red Hat Catalog URL env: COMPONENT: ${{ matrix.component }} MATRIX_FILE: ${{ env.MATRIX_FILE }} run: | - PROJECT_ID=$(jq -r ".components.\"$COMPONENT\".rhel_project" "$MATRIX_FILE") + set -Eeuo pipefail - echo "::group::URL" - echo "https://catalog.redhat.com/software/containers/${PROJECT_ID}" - echo "::endgroup::" + PROJECT_ID="$(jq -r ".components.\"$COMPONENT\".rhel_project" "$MATRIX_FILE")" + + echo "::group::URL" + echo "https://catalog.redhat.com/software/containers/${PROJECT_ID}" + echo "::endgroup::"