From a4d4d2a80734d756f0aeed230f5b2e80f94d9cd4 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Tue, 28 Jul 2026 14:48:05 +0200 Subject: [PATCH 1/2] Add start Y release script --- scripts/start-y-stream-release.sh | 286 ++++++++++++++++++ scripts/templates/release-kustomization.yaml | 35 +++ .../release-plan-admission-prod.yaml | 48 +++ .../release-plan-admission-stage.yaml | 48 +++ 4 files changed, 417 insertions(+) create mode 100755 scripts/start-y-stream-release.sh create mode 100644 scripts/templates/release-kustomization.yaml create mode 100644 scripts/templates/release-plan-admission-prod.yaml create mode 100644 scripts/templates/release-plan-admission-stage.yaml diff --git a/scripts/start-y-stream-release.sh b/scripts/start-y-stream-release.sh new file mode 100755 index 0000000..858012a --- /dev/null +++ b/scripts/start-y-stream-release.sh @@ -0,0 +1,286 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +TEMPLATE_DIR="${SCRIPT_DIR}/templates" + +DRY_RUN=false +VERSION="" +KONFLUX_DATA_REPO="" +VERSION_DASHED="" + +usage() { + cat < [--dry-run] + +Start a Y-stream release of agentic cluster security suite. + +Arguments: + version Version in X.Y format (e.g., 1.5) + konflux-data-repo Path to the konflux-release-data repository + +Options: + --dry-run Create everything locally but do not push to remote repos + -h, --help Show this help message + +Example: + $(basename "$0") 1.5 /path/to/konflux-release-data + $(basename "$0") 1.5 /path/to/konflux-release-data --dry-run +EOF + exit 0 +} + +log() { + echo "==> $*" +} + +error() { + echo "ERROR: $*" >&2 + exit 1 +} + +parse_args() { + local positional=() + while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) + DRY_RUN=true + shift + ;; + -h|--help) + usage + ;; + -*) + error "Unknown option: $1" + ;; + *) + positional+=("$1") + shift + ;; + esac + done + + if [[ ${#positional[@]} -ne 2 ]]; then + error "Expected 2 positional arguments: . Got ${#positional[@]}." + fi + + VERSION="${positional[0]}" + KONFLUX_DATA_REPO="${positional[1]}" + VERSION_DASHED="${VERSION//./-}" +} + +validate_inputs() { + if ! [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+$ ]]; then + error "Version must be in X.Y format (e.g., 1.5). Got: ${VERSION}" + fi + + if ! command -v yq &>/dev/null; then + error "yq is required but not found in PATH" + fi + + if ! command -v git &>/dev/null; then + error "git is required but not found in PATH" + fi + + if [[ ! -d "${KONFLUX_DATA_REPO}" ]]; then + error "konflux-data-repo directory does not exist: ${KONFLUX_DATA_REPO}" + fi + + if [[ ! -d "${KONFLUX_DATA_REPO}/.git" ]]; then + error "konflux-data-repo is not a git repository: ${KONFLUX_DATA_REPO}" + fi + + if [[ ! -d "${TEMPLATE_DIR}" ]]; then + error "Templates directory not found: ${TEMPLATE_DIR}" + fi + + local yq_version + yq_version=$(yq --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + # Get yq version, sort with 4.52.1 -> check that oldest version is 4.52.1. + if ! printf '%s\n' "4.52.1" "${yq_version}" | sort -V | head -n1 | grep -q "4.52.1"; then + error "yq version 4.52.1 or higher is required. Found: ${yq_version}" + fi +} + +prepare_stackrox_mcp_branch() { + local branch="release-${VERSION}" + + log "Fetching origin in stackrox-mcp..." + git -C "${SCRIPT_DIR}/.." fetch origin + + log "Creating branch ${branch} from origin/main..." + git -C "${SCRIPT_DIR}/.." checkout -b "${branch}" origin/main +} + +update_tekton_pipelines() { + local tekton_dir="${SCRIPT_DIR}/../.tekton" + local files=( + "${tekton_dir}/acs-mcp-server-pull-request.yaml" + "${tekton_dir}/acs-mcp-server-push-main.yaml" + "${tekton_dir}/acs-mcp-server-push-release.yaml" + ) + + log "Updating Tekton pipeline files..." + for file in "${files[@]}"; do + if [[ ! -f "${file}" ]]; then + error "Tekton file not found: ${file}" + fi + + log " Updating $(basename "${file}")..." + + yq -i --yaml-compact-seq-indent '.metadata.labels."appstudio.openshift.io/application" = "agentic-cluster-security-suite-'"${VERSION_DASHED}"'"' "${file}" + yq -i --yaml-compact-seq-indent '.metadata.labels."appstudio.openshift.io/component" = "acs-mcp-server-'"${VERSION_DASHED}"'"' "${file}" + yq -i --yaml-compact-seq-indent '.spec.taskRunTemplate.serviceAccountName = "build-pipeline-acs-mcp-server-'"${VERSION_DASHED}"'"' "${file}" + yq -i --yaml-compact-seq-indent '(.spec.params[] | select(.name == "extra-labels") | .value[0]) = "cpe=cpe:/a:redhat:agentic_cluster_security_suite:'"${VERSION}"'::el9"' "${file}" + done +} + +commit_and_push_stackrox_mcp() { + local repo_dir="${SCRIPT_DIR}/.." + local branch="release-${VERSION}" + + log "Committing Tekton pipeline changes..." + git -C "${repo_dir}" add .tekton/ + git -C "${repo_dir}" commit -m "Configure Konflux pipelines for release ${VERSION}" + + if [[ "${DRY_RUN}" == true ]]; then + log "[DRY-RUN] Skipping push of branch ${branch} in stackrox-mcp" + else + log "Pushing branch ${branch}..." + git -C "${repo_dir}" push origin "${branch}" + fi +} + +prepare_konflux_data_branch() { + local branch="agentic-suite-release-${VERSION_DASHED}" + + log "Fetching origin in konflux-release-data..." + git -C "${KONFLUX_DATA_REPO}" fetch origin + + log "Creating branch ${branch} from origin/main..." + git -C "${KONFLUX_DATA_REPO}" checkout -b "${branch}" origin/main +} + +create_release_plan_admissions() { + local rpa_dir="${KONFLUX_DATA_REPO}/config/kflux-prd-rh02.0fk9.p1/product/ReleasePlanAdmission/agentic-cluster-security-suite" + + if [[ ! -d "${rpa_dir}" ]]; then + error "ReleasePlanAdmission directory not found: ${rpa_dir}" + fi + + create_rpa_file "stage" + create_rpa_file "prod" +} + +create_rpa_file() { + local env="$1" + local rpa_dir="${KONFLUX_DATA_REPO}/config/kflux-prd-rh02.0fk9.p1/product/ReleasePlanAdmission/agentic-cluster-security-suite" + local template="${TEMPLATE_DIR}/release-plan-admission-${env}.yaml" + local target="${rpa_dir}/agentic-cluster-security-suite-${env}-${VERSION_DASHED}.yaml" + + log "Creating ${env} ReleasePlanAdmission..." + cp "${template}" "${target}" + + yq -i '.metadata.name = "agentic-cluster-security-suite-'"${env}-${VERSION_DASHED}"'"' "${target}" + yq -i '.spec.applications[0] = "agentic-cluster-security-suite-'"${VERSION_DASHED}"'"' "${target}" + yq -i '.spec.data.releaseNotes.product_version = "'"${VERSION}"'"' "${target}" + yq -i '.spec.data.mapping.components[0].name = "acs-mcp-server-'"${VERSION_DASHED}"'"' "${target}" + yq -i '.spec.data.mapping.defaults.tags[2] = "'"${VERSION}"'"' "${target}" + yq -i '.spec.data.mapping.defaults.tags[3] = "'"${VERSION}"'-{{ timestamp }}"' "${target}" +} + +create_release_kustomization() { + local tenant_base="${KONFLUX_DATA_REPO}/tenants-config/cluster/kflux-prd-rh02/tenants/agentic-cluster-security-suite-tenant/agentic-cluster-security-suite" + local release_dir="${tenant_base}/release-${VERSION}" + + log "Creating release-${VERSION} kustomization directory..." + mkdir -p "${release_dir}" + + cp "${TEMPLATE_DIR}/release-kustomization.yaml" "${release_dir}/kustomization.yaml" + + local target="${release_dir}/kustomization.yaml" + yq -i '(.patches[0].patch | select(. == "*")) |= sub("release-X.Y", "release-'"${VERSION}"'")' "${target}" + yq -i '.transformers[0] |= sub("-X-Y", "-'"${VERSION_DASHED}"'")' "${target}" +} + +update_parent_kustomization() { + local parent_kustomization="${KONFLUX_DATA_REPO}/tenants-config/cluster/kflux-prd-rh02/tenants/agentic-cluster-security-suite-tenant/agentic-cluster-security-suite/kustomization.yaml" + + if [[ ! -f "${parent_kustomization}" ]]; then + error "Parent kustomization.yaml not found: ${parent_kustomization}" + fi + + log "Adding release-${VERSION} to parent kustomization.yaml..." + yq -i '.resources += ["release-'"${VERSION}"'"]' "${parent_kustomization}" +} + +run_kustomize_build() { + local tenants_config_dir="${KONFLUX_DATA_REPO}/tenants-config" + local venv_dir="${KONFLUX_DATA_REPO}/.venv" + + if [[ ! -f "${tenants_config_dir}/build-single.sh" ]]; then + error "build-single.sh not found in: ${tenants_config_dir}" + fi + + log "Running kustomize build validation..." + + if [[ -d "${venv_dir}" ]]; then + log "Activating Python virtual environment..." + # shellcheck disable=SC1091 + source "${venv_dir}/bin/activate" + fi + + (cd "${tenants_config_dir}" && bash ./build-single.sh agentic-cluster-security-suite-tenant) +} + +commit_and_push_konflux_data() { + local branch="agentic-suite-release-${VERSION_DASHED}" + + log "Committing all changes in konflux-release-data..." + git -C "${KONFLUX_DATA_REPO}" add -A + git -C "${KONFLUX_DATA_REPO}" commit -m "Add agentic-cluster-security-suite release ${VERSION} configuration" + + if [[ "${DRY_RUN}" == true ]]; then + log "[DRY-RUN] Skipping push of branch ${branch} in konflux-release-data" + else + log "Pushing branch ${branch}..." + git -C "${KONFLUX_DATA_REPO}" push origin "${branch}" + fi +} + +main() { + parse_args "$@" + validate_inputs + + if [[ "${DRY_RUN}" == true ]]; then + log "Running in DRY-RUN mode (no pushes)" + fi + + log "Starting Y-stream release ${VERSION} (dashed: ${VERSION_DASHED})" + + log "" + log "=== Part 1: stackrox-mcp ===" + prepare_stackrox_mcp_branch + update_tekton_pipelines + commit_and_push_stackrox_mcp + + log "" + log "=== Part 2: konflux-release-data ===" + prepare_konflux_data_branch + create_release_plan_admissions + create_release_kustomization + update_parent_kustomization + run_kustomize_build + commit_and_push_konflux_data + + log "" + log "Y-stream release ${VERSION} setup complete!" + if [[ "${DRY_RUN}" == true ]]; then + log "DRY-RUN: Review branches locally before pushing manually." + log " stackrox-mcp: release-${VERSION}" + log " konflux-release-data: agentic-suite-release-${VERSION_DASHED}" + fi +} + +main "$@" diff --git a/scripts/templates/release-kustomization.yaml b/scripts/templates/release-kustomization.yaml new file mode 100644 index 0000000..1a1c9ca --- /dev/null +++ b/scripts/templates/release-kustomization.yaml @@ -0,0 +1,35 @@ +--- +resources: + - ../_base + - ../_release-plans + +patches: + - target: + kind: Component + name: acs-mcp-server + patch: | + apiVersion: appstudio.redhat.com/v1alpha1 + kind: Component + metadata: + name: component + spec: + source: + git: + revision: release-X.Y + +transformers: + - |- + apiVersion: builtin + kind: PrefixSuffixTransformer + metadata: + name: suffixTransformer + suffix: -X-Y + fieldSpecs: + - path: /spec/application + - path: /spec/displayName + - path: /spec/componentName + - path: /metadata/name + - path: /metadata/labels/release.appstudio.openshift.io\/releasePlanAdmission + - path: /metadata/labels/appstudio.redhat.com\/application + - path: /metadata/labels/appstudio.redhat.com\/component + - path: /subjects/name diff --git a/scripts/templates/release-plan-admission-prod.yaml b/scripts/templates/release-plan-admission-prod.yaml new file mode 100644 index 0000000..3ad1e2a --- /dev/null +++ b/scripts/templates/release-plan-admission-prod.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: appstudio.redhat.com/v1alpha1 +kind: ReleasePlanAdmission +metadata: + annotations: + rhel_target: "el9" + labels: + release.appstudio.openshift.io/block-releases: "false" + pp.engineering.redhat.com/business-unit: hybrid-platforms + name: agentic-cluster-security-suite-prod-X-Y + namespace: rhtap-releng-tenant +spec: + applications: [agentic-cluster-security-suite-X-Y] + origin: agentic-cluster-security-suite-tenant + policy: registry-standard + data: + releaseNotes: + product_id: [1126] + product_name: "agentic cluster security suite" + product_version: "X.Y" + mapping: + components: + - name: acs-mcp-server-X-Y + repositories: + - url: "registry.redhat.io/agentic-cluster-security-suite-tech-preview/acs-mcp-server-rhel9" + defaults: + tags: + - "{{ git_sha }}" + - "{{ git_short_sha }}" + - "X.Y" + - "X.Y-{{ timestamp }}" + pushSourceContainer: true + intention: production + enforceContainerFirstSecurityLabels: true + pipeline: + pipelineRef: + resolver: git + params: + - name: url + value: "https://github.com/konflux-ci/release-service-catalog.git" + - name: revision + value: production + - name: pathInRepo + value: "pipelines/managed/rh-advisories/rh-advisories.yaml" + serviceAccountName: release-registry-prod + timeouts: + pipeline: "01h0m0s" + tasks: 01h0m0s diff --git a/scripts/templates/release-plan-admission-stage.yaml b/scripts/templates/release-plan-admission-stage.yaml new file mode 100644 index 0000000..5555eeb --- /dev/null +++ b/scripts/templates/release-plan-admission-stage.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: appstudio.redhat.com/v1alpha1 +kind: ReleasePlanAdmission +metadata: + annotations: + rhel_target: "el9" + labels: + release.appstudio.openshift.io/block-releases: "false" + pp.engineering.redhat.com/business-unit: hybrid-platforms + name: agentic-cluster-security-suite-stage-X-Y + namespace: rhtap-releng-tenant +spec: + applications: [agentic-cluster-security-suite-X-Y] + origin: agentic-cluster-security-suite-tenant + policy: registry-standard-stage + data: + releaseNotes: + product_id: [1126] + product_name: "agentic cluster security suite" + product_version: "X.Y" + mapping: + components: + - name: acs-mcp-server-X-Y + repositories: + - url: "registry.stage.redhat.io/agentic-cluster-security-suite-tech-preview/acs-mcp-server-rhel9" + defaults: + tags: + - "{{ git_sha }}" + - "{{ git_short_sha }}" + - "X.Y" + - "X.Y-{{ timestamp }}" + pushSourceContainer: true + intention: staging + enforceContainerFirstSecurityLabels: true + pipeline: + pipelineRef: + resolver: git + params: + - name: url + value: "https://github.com/konflux-ci/release-service-catalog.git" + - name: revision + value: production + - name: pathInRepo + value: "pipelines/managed/rh-advisories/rh-advisories.yaml" + serviceAccountName: release-registry-staging + timeouts: + pipeline: "01h0m0s" + tasks: 01h0m0s From d8d039e02568d5f1944bf8216b930299015ffc9e Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Wed, 29 Jul 2026 09:28:49 +0200 Subject: [PATCH 2/2] CodeRabbit reviews addressed --- scripts/start-y-stream-release.sh | 43 ++++++++++++++++--------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/scripts/start-y-stream-release.sh b/scripts/start-y-stream-release.sh index 858012a..b51d444 100755 --- a/scripts/start-y-stream-release.sh +++ b/scripts/start-y-stream-release.sh @@ -65,9 +65,9 @@ parse_args() { error "Expected 2 positional arguments: . Got ${#positional[@]}." fi - VERSION="${positional[0]}" + export VERSION="${positional[0]}" KONFLUX_DATA_REPO="${positional[1]}" - VERSION_DASHED="${VERSION//./-}" + export VERSION_DASHED="${VERSION//./-}" } validate_inputs() { @@ -109,8 +109,9 @@ prepare_stackrox_mcp_branch() { log "Fetching origin in stackrox-mcp..." git -C "${SCRIPT_DIR}/.." fetch origin + # -B resets the branch if it already exists, so reruns discard local unpushed commits. log "Creating branch ${branch} from origin/main..." - git -C "${SCRIPT_DIR}/.." checkout -b "${branch}" origin/main + git -C "${SCRIPT_DIR}/.." checkout -B "${branch}" origin/main } update_tekton_pipelines() { @@ -129,10 +130,10 @@ update_tekton_pipelines() { log " Updating $(basename "${file}")..." - yq -i --yaml-compact-seq-indent '.metadata.labels."appstudio.openshift.io/application" = "agentic-cluster-security-suite-'"${VERSION_DASHED}"'"' "${file}" - yq -i --yaml-compact-seq-indent '.metadata.labels."appstudio.openshift.io/component" = "acs-mcp-server-'"${VERSION_DASHED}"'"' "${file}" - yq -i --yaml-compact-seq-indent '.spec.taskRunTemplate.serviceAccountName = "build-pipeline-acs-mcp-server-'"${VERSION_DASHED}"'"' "${file}" - yq -i --yaml-compact-seq-indent '(.spec.params[] | select(.name == "extra-labels") | .value[0]) = "cpe=cpe:/a:redhat:agentic_cluster_security_suite:'"${VERSION}"'::el9"' "${file}" + yq -i --yaml-compact-seq-indent '.metadata.labels."appstudio.openshift.io/application" = "agentic-cluster-security-suite-" + env(VERSION_DASHED)' "${file}" + yq -i --yaml-compact-seq-indent '.metadata.labels."appstudio.openshift.io/component" = "acs-mcp-server-" + env(VERSION_DASHED)' "${file}" + yq -i --yaml-compact-seq-indent '.spec.taskRunTemplate.serviceAccountName = "build-pipeline-acs-mcp-server-" + env(VERSION_DASHED)' "${file}" + yq -i --yaml-compact-seq-indent '(.spec.params[] | select(.name == "extra-labels") | .value[] | select(test("X\\.Y"))) |= sub("X\\.Y", env(VERSION))' "${file}" done } @@ -158,8 +159,9 @@ prepare_konflux_data_branch() { log "Fetching origin in konflux-release-data..." git -C "${KONFLUX_DATA_REPO}" fetch origin + # -B resets the branch if it already exists, so reruns discard local unpushed commits. log "Creating branch ${branch} from origin/main..." - git -C "${KONFLUX_DATA_REPO}" checkout -b "${branch}" origin/main + git -C "${KONFLUX_DATA_REPO}" checkout -B "${branch}" origin/main } create_release_plan_admissions() { @@ -174,20 +176,19 @@ create_release_plan_admissions() { } create_rpa_file() { - local env="$1" + export RPA_ENV="$1" local rpa_dir="${KONFLUX_DATA_REPO}/config/kflux-prd-rh02.0fk9.p1/product/ReleasePlanAdmission/agentic-cluster-security-suite" - local template="${TEMPLATE_DIR}/release-plan-admission-${env}.yaml" - local target="${rpa_dir}/agentic-cluster-security-suite-${env}-${VERSION_DASHED}.yaml" + local template="${TEMPLATE_DIR}/release-plan-admission-${RPA_ENV}.yaml" + local target="${rpa_dir}/agentic-cluster-security-suite-${RPA_ENV}-${VERSION_DASHED}.yaml" - log "Creating ${env} ReleasePlanAdmission..." + log "Creating ${RPA_ENV} ReleasePlanAdmission..." cp "${template}" "${target}" - yq -i '.metadata.name = "agentic-cluster-security-suite-'"${env}-${VERSION_DASHED}"'"' "${target}" - yq -i '.spec.applications[0] = "agentic-cluster-security-suite-'"${VERSION_DASHED}"'"' "${target}" - yq -i '.spec.data.releaseNotes.product_version = "'"${VERSION}"'"' "${target}" - yq -i '.spec.data.mapping.components[0].name = "acs-mcp-server-'"${VERSION_DASHED}"'"' "${target}" - yq -i '.spec.data.mapping.defaults.tags[2] = "'"${VERSION}"'"' "${target}" - yq -i '.spec.data.mapping.defaults.tags[3] = "'"${VERSION}"'-{{ timestamp }}"' "${target}" + yq -i '.metadata.name = "agentic-cluster-security-suite-" + env(RPA_ENV) + "-" + env(VERSION_DASHED)' "${target}" + yq -i '.spec.applications[0] = "agentic-cluster-security-suite-" + env(VERSION_DASHED)' "${target}" + yq -i '.spec.data.releaseNotes.product_version = env(VERSION)' "${target}" + yq -i '.spec.data.mapping.components[0].name = "acs-mcp-server-" + env(VERSION_DASHED)' "${target}" + yq -i '(.spec.data.mapping.defaults.tags[] | select(test("X.Y"))) |= sub("X.Y", env(VERSION))' "${target}" } create_release_kustomization() { @@ -200,8 +201,8 @@ create_release_kustomization() { cp "${TEMPLATE_DIR}/release-kustomization.yaml" "${release_dir}/kustomization.yaml" local target="${release_dir}/kustomization.yaml" - yq -i '(.patches[0].patch | select(. == "*")) |= sub("release-X.Y", "release-'"${VERSION}"'")' "${target}" - yq -i '.transformers[0] |= sub("-X-Y", "-'"${VERSION_DASHED}"'")' "${target}" + yq -i '.patches[0].patch |= sub("release-X.Y", "release-" + env(VERSION))' "${target}" + yq -i '.transformers[0] |= sub("-X-Y", "-" + env(VERSION_DASHED))' "${target}" } update_parent_kustomization() { @@ -212,7 +213,7 @@ update_parent_kustomization() { fi log "Adding release-${VERSION} to parent kustomization.yaml..." - yq -i '.resources += ["release-'"${VERSION}"'"]' "${parent_kustomization}" + yq -i '.resources += ["release-" + env(VERSION)]' "${parent_kustomization}" } run_kustomize_build() {