Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 287 additions & 0 deletions scripts/start-y-stream-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
#!/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 <<EOF
Usage: $(basename "$0") <version> <konflux-data-repo> [--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: <version> <konflux-data-repo>. Got ${#positional[@]}."
fi

export VERSION="${positional[0]}"
KONFLUX_DATA_REPO="${positional[1]}"
export 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

# -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
Comment thread
mtodor marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The comment on line 113 documents this as intentional, which is good. But if someone made manual adjustments to the branch and re-runs the script, their work is silently lost. A pre-check for unpushed commits with a warning would make re-runs safer:

if git -C "${repo}" log --oneline "origin/main..${branch}" 2>/dev/null | grep -q .; then
    log "WARNING: Branch ${branch} has unpushed commits that will be discarded"
fi

}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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-" + 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}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good — the escaped dot here (X\\.Y) is the correct pattern. The other call sites (lines 191, 204) should match this.

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

# -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
}

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() {
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-${RPA_ENV}.yaml"
local target="${rpa_dir}/agentic-cluster-security-suite-${RPA_ENV}-${VERSION_DASHED}.yaml"

log "Creating ${RPA_ENV} ReleasePlanAdmission..."
cp "${template}" "${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}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dot in "X.Y" is an unescaped regex wildcard — it matches any character between X and Y, not just a literal period. Compare with line 136 which correctly escapes:

select(test("X\\.Y"))) |= sub("X\\.Y", env(VERSION))

Same issue on line 204 with sub("release-X.Y", ...). All call sites should use consistent escaping.

}

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 |= sub("release-X.Y", "release-" + env(VERSION))' "${target}"
yq -i '.transformers[0] |= sub("-X-Y", "-" + env(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-" + env(VERSION)]' "${parent_kustomization}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+= blindly appends without checking if the entry already exists. If the script is re-run after a previous PR was merged (e.g. to fix a typo), checkout -B resets to origin/main (which now contains the merged entry), then this appends a duplicate. kustomize will reject the duplicate resource declaration with an "already visited" error, blocking the release.

Consider guarding with something like:

yq -i '.resources |= (. + ["release-" + env(VERSION)] | unique)' "${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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: This operates on a user-provided path to a separately cloned repository. git add -A stages everything including untracked files (editor swap files, test outputs, notes). Line 145 in the stackrox-mcp part correctly uses targeted staging (git add .tekton/). Consider staging only the specific paths the script modifies:

git -C "${KONFLUX_DATA_REPO}" add \
    config/kflux-prd-rh02.0fk9.p1/product/ReleasePlanAdmission/ \
    tenants-config/cluster/kflux-prd-rh02/tenants/agentic-cluster-security-suite-tenant/

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 "$@"
35 changes: 35 additions & 0 deletions scripts/templates/release-kustomization.yaml
Original file line number Diff line number Diff line change
@@ -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
48 changes: 48 additions & 0 deletions scripts/templates/release-plan-admission-prod.yaml
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading