diff --git a/cmd/tssc/main.go b/cmd/tssc/main.go index 99ace1199..27e3d1a13 100644 --- a/cmd/tssc/main.go +++ b/cmd/tssc/main.go @@ -48,6 +48,7 @@ func main() { cwd, framework.WithIntegrations(appIntegrations...), framework.WithMCPImage(mcpImage), + framework.WithDistributedInstallerMergeLayout(), ) if err != nil { fmt.Fprintf(os.Stderr, "failed to create application: %v\n", err) diff --git a/go.mod b/go.mod index e6c7bddb9..cee37d6ee 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/redhat-appstudio/tssc-cli go 1.25.7 require ( - github.com/redhat-appstudio/helmet v0.0.0-20260319215325-e665a08127fc + github.com/redhat-appstudio/helmet v0.0.0-20260420150238-7035f5b16d8e golang.org/x/term v0.41.0 ) @@ -134,7 +134,7 @@ require ( golang.org/x/time v0.15.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect - google.golang.org/grpc v1.79.2 // indirect + google.golang.org/grpc v1.79.3 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -164,3 +164,5 @@ replace ( github.com/Microsoft/hcsshim => github.com/Microsoft/hcsshim v0.13.0 github.com/imdario/mergo => github.com/imdario/mergo v1.0.2 ) + +replace github.com/redhat-appstudio/helmet => github.com/redhat-appstudio/helmet v0.1.1-0.20260511150447-a082a8928a85 diff --git a/go.sum b/go.sum index c05c5d58c..aa586d098 100644 --- a/go.sum +++ b/go.sum @@ -304,8 +304,8 @@ github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4 github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/quay/claircore v1.5.50 h1:ASYcI4YCdSdSoaNYJvYbihtIzs9cbS9FRjVyUeKjI9k= github.com/quay/claircore v1.5.50/go.mod h1:MUpoIaI2d83GMn7OwiTJMAXC/11KsEQQJZ8GhDsWFcI= -github.com/redhat-appstudio/helmet v0.0.0-20260319215325-e665a08127fc h1:IiR19UXmPxMObHG5tbMyx6uPSqgtCxL4XZ7++EwwLWQ= -github.com/redhat-appstudio/helmet v0.0.0-20260319215325-e665a08127fc/go.mod h1:oAUkb8FwrsIzI/vlLkqoWd+zcVpekVwz5QYSML+5VJw= +github.com/redhat-appstudio/helmet v0.1.1-0.20260511150447-a082a8928a85 h1:ID8MQydcFy5mPaoxjCjPq+LPKPXbDPr+bxSASh7CQIU= +github.com/redhat-appstudio/helmet v0.1.1-0.20260511150447-a082a8928a85/go.mod h1:pIu+WcaZOJHaIpH3DGJoRSS7ffT/m2iMNuUd+KIGREs= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= @@ -500,8 +500,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c h1: google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c/go.mod h1:X2gu9Qwng7Nn009s/r3RUxqkzQNqOrAy79bluY7ojIg= google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c h1:xgCzyF2LFIO/0X2UAoVRiXKU5Xg6VjToG4i2/ecSswk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= -google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/hack/cluster-cleanup-tssc.sh b/hack/cluster-cleanup-tssc.sh new file mode 100755 index 000000000..7f3669325 --- /dev/null +++ b/hack/cluster-cleanup-tssc.sh @@ -0,0 +1,363 @@ +#!/usr/bin/env bash +# +# Tear down TSSC Helm releases (and optionally application namespaces) on an OpenShift +# cluster so you can re-run a fresh install vs upgrade tests without provisioning a new cluster. +# +# Installs are distributed: Helmet/composable bundles emit multiple tssc-* Helm releases across +# installer and product namespaces (e.g. tssc-dh, tssc-keycloak). This script uses helm list -A +# and uninstalls every release whose name starts with tssc- — not a single umbrella chart. +# +# Requires: oc (logged in), helm, jq +# +# Usage: +# ./hack/cluster-cleanup-tssc.sh +# ./hack/cluster-cleanup-tssc.sh -d +# ./hack/cluster-cleanup-tssc.sh -n my-installer-ns -a -o +# +# Does NOT delete openshift-* system namespaces (openshift-operators, openshift-pipelines, …). +# With -a/--aggressive it CAN delete operator hub namespaces created for this product (rhacs-operator, +# rhbk-operator, …); use only when you intend to remove those operators from the cluster. +# +# Closest to an unused cluster (recommended full reset): ./hack/cluster-cleanup-tssc.sh -a -o +# (-o removes Subscription CRs Helm keeps; -a removes rhacs/rhbk operator hub namespaces.) +# +# Subscription CRs use helm.sh/resource-policy: keep — Helm uninstall leaves them; use -o/--olm +# to delete the Operator Hub Subscriptions this installer typically creates. +# +# Idempotent: safe to run multiple times; exits 0 when there is nothing left to remove or only +# recoverable warnings (re-run if Helm releases remain due to transient errors). + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +INSTALLER_NS="${INSTALLER_NS:-tssc}" +DRY_RUN=0 +DELETE_NAMESPACES=1 +AGGRESSIVE=0 +DELETE_OLM=0 +MAX_PASSES=12 +HELM_TIMEOUT="15m" +SKIP_HELM=0 + +usage() { + sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//' + cat </dev/null 2>&1; } + +require_cmds() { + local missing=() + for c in oc helm jq; do + have "$c" || missing+=("$c") + done + if ((${#missing[@]})); then + log "[ERROR] Missing commands: ${missing[*]}" + exit 1 + fi +} + +# Normalize helm list JSON; empty or invalid input becomes [] so jq never aborts the script. +helm_list_json() { + local raw + raw="$(helm list -A -o json 2>/dev/null || true)" + if [[ -z "$raw" ]] || ! echo "$raw" | jq -e . >/dev/null 2>&1; then + echo '[]' + return 0 + fi + echo "$raw" +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + -n | --installer-namespace) + INSTALLER_NS="$2" + shift 2 + ;; + -d | --dry-run) + DRY_RUN=1 + shift + ;; + -H | --helm-only) + DELETE_NAMESPACES=0 + shift + ;; + -S | --skip-helm) + SKIP_HELM=1 + shift + ;; + -a | --aggressive) + AGGRESSIVE=1 + shift + ;; + -o | --olm) + DELETE_OLM=1 + shift + ;; + --delete-olm-subscriptions) + DELETE_OLM=1 + shift + ;; + -p | --passes) + MAX_PASSES="$2" + shift 2 + ;; + -t | --timeout) + HELM_TIMEOUT="$2" + shift 2 + ;; + -h | --help) + usage + exit 0 + ;; + *) + log "[ERROR] Unknown argument: $1" + usage + exit 1 + ;; + esac + done +} + +# Prefer uninstalling leaf / application charts before subscriptions and global helpers. +# Higher score = uninstall earlier in each pass (helm list order otherwise arbitrary). +uninstall_weight() { + local name="$1" + case "$name" in + tssc-dh | tssc-tpa | tssc-acs | tssc-tas | tssc-gitops | tssc-pipelines | tssc-iam) echo 90 ;; + tssc-acs-test) echo 85 ;; + tssc-pipelines-config) echo 84 ;; + tssc-dh-access) echo 83 ;; + *-subscriptions) echo 50 ;; + *-openshift) echo 40 ;; + tssc-app-namespaces | tssc-gitops-integrations | tssc-acs-integrations) echo 10 ;; + *-pgsql) echo 70 ;; + *) echo 60 ;; + esac +} + +helm_uninstall_all_tssc() { + local pass releases_json n w name ns + + for ((pass = 1; pass <= MAX_PASSES; pass++)); do + releases_json="$(helm_list_json)" + n="$(echo "$releases_json" | jq '[.[] | select(.name | startswith("tssc-"))] | length')" + if [[ "$n" -eq 0 ]]; then + log "# Helm: no tssc-* releases left." + return 0 + fi + log "### Helm pass $pass/$MAX_PASSES ($n tssc-* releases)" + + while IFS=$'\t' read -r w name ns; do + [[ -z "${name:-}" ]] && continue + if [[ "$DRY_RUN" -eq 1 ]]; then + log "[dry-run] helm uninstall $name -n $ns --wait --timeout $HELM_TIMEOUT" + continue + fi + log "helm uninstall $name -n $ns" + if ! helm uninstall "$name" -n "$ns" --wait --timeout "$HELM_TIMEOUT" 2>/dev/null; then + log "# [WARNING] uninstall failed or partial: $name ($ns) — will retry next pass" + fi + done < <( + echo "$releases_json" | jq -r '.[] | select(.name | startswith("tssc-")) | "\(.name)\t\(.namespace)"' | + while IFS=$'\t' read -r name ns; do + w="$(uninstall_weight "$name")" + printf '%05d\t%s\t%s\n' "$w" "$name" "$ns" + done | sort -rn + ) + [[ "$DRY_RUN" -eq 1 ]] && return 0 + done + + releases_json="$(helm_list_json)" + n="$(echo "$releases_json" | jq '[.[] | select(.name | startswith("tssc-"))] | length')" + if [[ "$n" -gt 0 ]]; then + log "# [WARNING] After $MAX_PASSES passes, $n tssc-* Helm release(s) remain (safe to re-run):" + echo "$releases_json" | jq -r '.[] | select(.name | startswith("tssc-")) | " - \(.name) (ns \(.namespace))"' + fi + return 0 +} + +# Subscription metadata.name + namespace as defined in bundles/*/tssc-*-subscriptions/values.yaml +# (union). Helm leaves these due to resource-policy: keep. +delete_olm_subscriptions() { + local pair ns name + # namespacesubscriptionName + local -a pairs=( + $'openshift-operators\topenshift-gitops-operator' + $'openshift-operators\topenshift-pipelines-operator-rh' + $'openshift-operators\trhtas-operator' + $'openshift-operators\trhdh' + $'rhbk-operator\trhbk-operator' + $'rhacs-operator\trhacs-operator' + $'tssc-tpa\trhtpa-operator' + ) + + if [[ "$DRY_RUN" -eq 1 ]]; then + log "[dry-run] Would delete Operator Hub Subscriptions (if present):" + for pair in "${pairs[@]}"; do + ns="${pair%%$'\t'*}" + name="${pair#*$'\t'}" + log " subscription $name -n $ns" + done + log "[dry-run] (Does not delete openshift-operators-wide OperatorGroups.)" + return 0 + fi + + log "# Deleting TSSC Operator Hub Subscriptions (keep-policy leftovers)..." + for pair in "${pairs[@]}"; do + ns="${pair%%$'\t'*}" + name="${pair#*$'\t'}" + if ! oc get subscription "$name" -n "$ns" >/dev/null 2>&1; then + continue + fi + log "oc delete subscription $name -n $ns" + oc delete subscription "$name" -n "$ns" --ignore-not-found 2>/dev/null || true + done + + # Dedicated operator namespaces: Subscription chart uses the same metadata.name for OperatorGroup. + for pair in $'rhacs-operator\trhacs-operator' $'rhbk-operator\trhbk-operator' $'tssc-tpa\trhtpa-operator'; do + ns="${pair%%$'\t'*}" + og="${pair#*$'\t'}" + if oc get operatorgroup "$og" -n "$ns" >/dev/null 2>&1; then + log "oc delete operatorgroup $og -n $ns" + oc delete operatorgroup "$og" -n "$ns" --ignore-not-found 2>/dev/null || true + fi + done +} + +# Namespaces named exactly "tssc" or any "tssc-*" (installer project, bundle openshift.projects, overrides). +tssc_namespace_pattern() { + grep -E '^tssc($|-)' || true +} + +# Delete every cluster namespace matching ^tssc($|-). Do not skip names from an earlier fixed list — +# the previous straggler skipped bundle namespaces still present after a failed delete. +delete_all_tssc_prefixed_namespaces() { + local ns + log "# Delete all namespaces matching ^tssc($|-) (covers bundle defaults and custom names)..." + while IFS= read -r ns; do + [[ -z "$ns" ]] && continue + log "oc delete namespace $ns --wait=false --ignore-not-found" + oc delete namespace "$ns" --wait=false --ignore-not-found 2>/dev/null || true + done < <(oc get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null | tssc_namespace_pattern) +} + +delete_namespace_if_present() { + local name="$1" + [[ -z "$name" ]] && return 0 + if ! oc get namespace "$name" >/dev/null 2>&1; then + return 0 + fi + log "oc delete namespace $name --wait=false --ignore-not-found" + oc delete namespace "$name" --wait=false --ignore-not-found 2>/dev/null || true +} + +delete_optional_namespaces() { + local ns + + if [[ "$DRY_RUN" -eq 1 ]]; then + log "[dry-run] Would delete installer namespaces if they do NOT match ^tssc($|-):" + log " $INSTALLER_NS ${INSTALLER_NS}-app (when custom project name)" + [[ "$AGGRESSIVE" -eq 1 ]] && log "[dry-run] (aggressive) rhacs-operator rhbk-operator" + log "[dry-run] Then delete every namespace matching ^tssc($|-) (tssc, tssc-keycloak, tssc-gitops, …)" + return 0 + fi + + # Installer / app namespaces that are not matched by ^tssc($|-) (e.g. myproj / myproj-app). + # No bash arrays here: empty "${arr[@]}" under bash 3.2 + set -u can error as unbound. + if [[ ! "$INSTALLER_NS" =~ ^tssc($|-) ]]; then + delete_namespace_if_present "$INSTALLER_NS" + fi + if [[ ! "${INSTALLER_NS}-app" =~ ^tssc($|-) ]]; then + delete_namespace_if_present "${INSTALLER_NS}-app" + fi + + if [[ "$AGGRESSIVE" -eq 1 ]]; then + for ns in rhacs-operator rhbk-operator; do + if ! oc get namespace "$ns" >/dev/null 2>&1; then + continue + fi + log "oc delete namespace $ns --wait=false --ignore-not-found" + oc delete namespace "$ns" --wait=false --ignore-not-found 2>/dev/null || true + done + fi + + delete_all_tssc_prefixed_namespaces + + log "# Waiting for removed namespaces to finish terminating (best-effort, 120s)..." + local wait_start=$SECONDS + while ((SECONDS - wait_start < 120)); do + local pending=0 + while IFS= read -r ns; do + [[ -z "$ns" ]] && continue + oc get namespace "$ns" >/dev/null 2>&1 && pending=1 + done < <(oc get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null | tssc_namespace_pattern) + if [[ ! "$INSTALLER_NS" =~ ^tssc($|-) ]] && oc get namespace "$INSTALLER_NS" >/dev/null 2>&1; then + pending=1 + fi + if [[ ! "${INSTALLER_NS}-app" =~ ^tssc($|-) ]] && oc get namespace "${INSTALLER_NS}-app" >/dev/null 2>&1; then + pending=1 + fi + if [[ "$AGGRESSIVE" -eq 1 ]]; then + for ns in rhacs-operator rhbk-operator; do + oc get namespace "$ns" >/dev/null 2>&1 && pending=1 + done + fi + [[ "$pending" -eq 0 ]] && break + sleep 3 + done +} + +main() { + parse_args "$@" + require_cmds + + log "# TSSC cluster cleanup (installer namespace: $INSTALLER_NS)" + log "# Current context: $(oc whoami --show-context 2>/dev/null || echo '?')" + + if [[ "$SKIP_HELM" -eq 0 ]]; then + helm_uninstall_all_tssc + else + log "# Skipping Helm (-S/--skip-helm)" + fi + + if [[ "$DELETE_OLM" -eq 1 ]]; then + delete_olm_subscriptions + fi + + if [[ "$DELETE_NAMESPACES" -eq 1 ]]; then + delete_optional_namespaces + else + log "# Skipping namespace deletion (-H/--helm-only)" + fi + + log "# Done." + log "# Tip: if a namespace is stuck Terminating, see finalizer patching in hack/reset.sh" +} + +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + main "$@" +fi diff --git a/hack/code-freeze.sh b/hack/code-freeze.sh index 4434beaa2..ac011aba9 100755 --- a/hack/code-freeze.sh +++ b/hack/code-freeze.sh @@ -68,9 +68,26 @@ cleanup() { fi } +subscription_values_for_channel_lookup() { + case "$1" in + openshiftKeycloak) echo "installer/bundles/iam/charts/tssc-iam-subscriptions/values.yaml" ;; + openshiftGitOps) echo "installer/bundles/gitops/charts/tssc-gitops-subscriptions/values.yaml" ;; + openshiftPipelines) echo "installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/values.yaml" ;; + openshiftTrustedArtifactSigner) echo "installer/bundles/tas/charts/tssc-tas-subscriptions/values.yaml" ;; + advancedClusterSecurity) echo "installer/bundles/acs/charts/tssc-acs-subscriptions/values.yaml" ;; + developerHub) echo "installer/bundles/dh/charts/tssc-dh-subscriptions/values.yaml" ;; + *) + echo "[ERROR] Unknown subscription key for channel lookup: $1" >&2 + exit 1 + ;; + esac +} + get_version() { + local values_file + values_file="$(subscription_values_for_channel_lookup "$1")" VERSION_XY="$( - yq '.subscriptions.'"$1"'.channel' installer/charts/tssc-subscriptions/values.yaml \ + yq '.subscriptions.'"$1"'.channel' "$values_file" \ | grep --extended-regexp "[0-9.]*" --only-matching )" VERSION_XYZ="$VERSION_XY.0" @@ -81,22 +98,22 @@ update_charts() { # Bump "version" in all charts get_version "developerHub" export RELEASE_BRANCH="release-$VERSION_XY" - find installer/charts/ -name Chart.yaml | while read -r CHART; do + find installer/charts installer/bundles -name Chart.yaml | while read -r CHART; do yq -i '.version = strenv(VERSION_XYZ)' "$CHART" done # Bump "appVersion" in all charts get_version "openshiftGitOps" - yq -i '.appVersion = strenv(VERSION_XY)' "installer/charts/tssc-gitops/Chart.yaml" + yq -i '.appVersion = strenv(VERSION_XY)' "installer/bundles/gitops/charts/tssc-gitops/Chart.yaml" get_version "openshiftPipelines" - yq -i '.appVersion = strenv(VERSION_XY)' "installer/charts/tssc-pipelines/Chart.yaml" + yq -i '.appVersion = strenv(VERSION_XY)' "installer/bundles/pipelines/charts/tssc-pipelines/Chart.yaml" get_version "openshiftTrustedArtifactSigner" - yq -i '.appVersion = strenv(VERSION_XY)' "installer/charts/tssc-tas/Chart.yaml" + yq -i '.appVersion = strenv(VERSION_XY)' "installer/bundles/tas/charts/tssc-tas/Chart.yaml" get_version "advancedClusterSecurity" - yq -i '.appVersion = strenv(VERSION_XY)' "installer/charts/tssc-acs/Chart.yaml" - yq -i '.appVersion = strenv(VERSION_XY)' "installer/charts/tssc-acs-test/Chart.yaml" + yq -i '.appVersion = strenv(VERSION_XY)' "installer/bundles/acs/charts/tssc-acs/Chart.yaml" + yq -i '.appVersion = strenv(VERSION_XY)' "installer/bundles/acs/charts/tssc-acs-test/Chart.yaml" get_version "developerHub" - yq -i '.appVersion = strenv(VERSION_XY)' "installer/charts/tssc-dh/Chart.yaml" + yq -i '.appVersion = strenv(VERSION_XY)' "installer/bundles/dh/charts/tssc-dh/Chart.yaml" } update_template() { diff --git a/hack/pre-release/pre-release-common.sh b/hack/pre-release/pre-release-common.sh index b79d5f47e..bbe151bee 100644 --- a/hack/pre-release/pre-release-common.sh +++ b/hack/pre-release/pre-release-common.sh @@ -17,11 +17,29 @@ get_cluster_version() { oc get clusterversion version -o jsonpath='{.status.desired.version}' | cut -d. -f1,2 } +# Path to values.yaml that owns .subscriptions. (bundle-local subscription charts). +subscription_values_file_for() { + case "$1" in + openshiftKeycloak) echo "$PROJECT_DIR/installer/bundles/iam/charts/tssc-iam-subscriptions/values.yaml" ;; + openshiftGitOps) echo "$PROJECT_DIR/installer/bundles/gitops/charts/tssc-gitops-subscriptions/values.yaml" ;; + openshiftPipelines) echo "$PROJECT_DIR/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/values.yaml" ;; + openshiftTrustedArtifactSigner) echo "$PROJECT_DIR/installer/bundles/tas/charts/tssc-tas-subscriptions/values.yaml" ;; + advancedClusterSecurity) echo "$PROJECT_DIR/installer/bundles/acs/charts/tssc-acs-subscriptions/values.yaml" ;; + developerHub) echo "$PROJECT_DIR/installer/bundles/dh/charts/tssc-dh-subscriptions/values.yaml" ;; + trustedProfileAnalyzer) echo "$PROJECT_DIR/installer/bundles/tpa/charts/tssc-tpa-subscriptions/values.yaml" ;; + *) + echo "[ERROR] Unknown subscription key: $1" >&2 + return 1 + ;; + esac +} + # Configure subscription in values.yaml # This function should be called after SUBSCRIPTION, CHANNEL, and SOURCE are set configure_subscription() { # Prepare for pre-release install capabilities - local subscription_values_file="$PROJECT_DIR/installer/charts/tssc-subscriptions/values.yaml" + local subscription_values_file + subscription_values_file="$(subscription_values_file_for "${SUBSCRIPTION:-}")" # For TAS, set source to rhtas-operator (without 's' at the end) if [[ "${SUBSCRIPTION:-}" == "openshiftTrustedArtifactSigner" ]]; then diff --git a/hack/pre-release/pre-release-tas.sh b/hack/pre-release/pre-release-tas.sh index 6a2197fd3..9ca8e84fe 100755 --- a/hack/pre-release/pre-release-tas.sh +++ b/hack/pre-release/pre-release-tas.sh @@ -534,7 +534,7 @@ configure_tas() { # Update values.yaml to use the correct source and channel from the pre-release subscription # This ensures the installer references match what was actually installed - values_file="$PROJECT_DIR/installer/charts/tssc-subscriptions/values.yaml" + values_file="$PROJECT_DIR/installer/bundles/tas/charts/tssc-tas-subscriptions/values.yaml" if [[ -f "$values_file" ]]; then echo "[INFO] Updating values.yaml to use pre-release TAS catalog source and channel" >&2 echo "[INFO] Setting source to: $SOURCE, channel to: $CHANNEL" >&2 diff --git a/hack/pre-release/pre-release-tpa.sh b/hack/pre-release/pre-release-tpa.sh index ddebf7fd5..5ca6e9401 100755 --- a/hack/pre-release/pre-release-tpa.sh +++ b/hack/pre-release/pre-release-tpa.sh @@ -154,7 +154,7 @@ EOF # with targetNamespaces: [tssc-tpa] - no manual OperatorGroup/Subscription creation needed echo "[INFO] Step 4: Updating tssc-cli configuration for pre-release TPA..." >&2 - values_file="$PROJECT_DIR/installer/charts/tssc-subscriptions/values.yaml" + values_file="$PROJECT_DIR/installer/bundles/tpa/charts/tssc-tpa-subscriptions/values.yaml" if [[ -f "$values_file" ]]; then echo "[INFO] Updating values.yaml to use pre-release TPA catalog source and channel" >&2 echo "[INFO] Setting source to: rhtpa-fbc-pre, channel to: stable-v1.1" >&2 diff --git a/hack/pre-release/pre-release.sh b/hack/pre-release/pre-release.sh index 520a57fed..62df84764 100755 --- a/hack/pre-release/pre-release.sh +++ b/hack/pre-release/pre-release.sh @@ -270,7 +270,7 @@ main() { echo done echo "Updated subscription values.yaml" - cat "$PROJECT_DIR/installer/charts/tssc-subscriptions/values.yaml" + cat "$PROJECT_DIR/installer/bundles/dh/charts/tssc-dh-subscriptions/values.yaml" } if [ "${BASH_SOURCE[0]}" == "$0" ]; then diff --git a/hack/vendor-helmet-fork.sh b/hack/vendor-helmet-fork.sh new file mode 100755 index 000000000..be6f5fff3 --- /dev/null +++ b/hack/vendor-helmet-fork.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Sync vendor/github.com/redhat-appstudio/helmet from a branch on your fork. +# The canonical module path stays github.com/redhat-appstudio/helmet; go.mod uses +# replace => your fork (go get github.com/dperaza4dustbit/helmet@… does not work +# because the module self-declares as redhat-appstudio/helmet). +# +# Usage (from repo root): +# ./hack/vendor-helmet-fork.sh +# HELMET_FORK=github.com/you/helmet HELMET_BRANCH=my-feature ./hack/vendor-helmet-fork.sh +# +# Alternative for day-to-day work with a local clone (no revisions at all): +# go mod edit -replace=github.com/redhat-appstudio/helmet=../helmet +# go mod tidy && go mod vendor + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$REPO_ROOT" + +FORK="${HELMET_FORK:-github.com/redhat-appstudio/helmet}" +BRANCH="${HELMET_BRANCH:-composable_bundles}" +DOMAIN="${HELMET_FORK_DOMAIN:-github.com/redhat-appstudio}" + +# Avoid sumdb/proxy quirks for private forks (no-op for public repos). +export GOPRIVATE="${GOPRIVATE:-${DOMAIN}/*}" + +if ! command -v git >/dev/null 2>&1; then + echo "git is required (for ls-remote)" >&2 + exit 1 +fi + +REMOTE="https://${FORK}.git" +TIP="$(git ls-remote "${REMOTE}" "refs/heads/${BRANCH}" | awk '{print $1}')" +if [[ -z "${TIP}" ]]; then + echo "Could not resolve branch \"${BRANCH}\" on ${FORK} (${REMOTE})." >&2 + exit 1 +fi + +go mod edit -replace="github.com/redhat-appstudio/helmet=${FORK}@${TIP}" +go mod tidy +go mod vendor + +echo "Helmet vendor synced: ${FORK}@${BRANCH} => ${TIP:0:12}… (full SHA in go.mod replace line)." diff --git a/installer/charts/tssc-acs-test/.helmignore b/installer/bundles/acs/charts/tssc-acs-integrations/.helmignore similarity index 100% rename from installer/charts/tssc-acs-test/.helmignore rename to installer/bundles/acs/charts/tssc-acs-integrations/.helmignore diff --git a/installer/bundles/acs/charts/tssc-acs-integrations/Chart.yaml b/installer/bundles/acs/charts/tssc-acs-integrations/Chart.yaml new file mode 100644 index 000000000..4a5fdd7d8 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-integrations/Chart.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v2 +name: tssc-acs-integrations +description: TSSC ACS integrations (registry integrations via ACS API) +type: application +version: "1.9.0" +annotations: + helmet.redhat-appstudio.github.com/depends-on: tssc-acs + helmet.redhat-appstudio.github.com/install-release-in-installer-namespace: "true" diff --git a/installer/bundles/acs/charts/tssc-acs-integrations/scripts/acs-integration-helper.sh b/installer/bundles/acs/charts/tssc-acs-integrations/scripts/acs-integration-helper.sh new file mode 100755 index 000000000..0669399ff --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-integrations/scripts/acs-integration-helper.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env bash +# +# Script to setup the integration to the Quay registry. +# + +shopt -s inherit_errexit +set -o errexit +set -o errtrace +set -o nounset +set -o pipefail + +usage() { + echo " +Usage: + ${0##*/} [options] REGISTRY + +Optional arguments: + --insecure + Disable TLS certificate validation in the integration + -n, --namespace NAMESPACE + TSSC installation namespace (default: tssc) + -d, --debug + Activate tracing/debug mode. + -h, --help + Display this message. + +Example: + ${0##*/} quay +" >&2 +} + +parse_args() { + NAMESPACE="${NAMESPACE:-tssc}" + INSECURE="false" + while [[ $# -gt 0 ]]; do + case $1 in + --insecure) + INSECURE="true" + ;; + -n | --namespace) + NAMESPACE="$2" + shift + ;; + -d | --debug) + set -x + DEBUG="--debug" + export DEBUG + info "Running script as: $(id)" + ;; + -h | --help) + usage + exit 0 + ;; + *) + REGISTRY="$1" + REGISTRY_SECRET="tssc-$REGISTRY-integration" + if ! oc get secrets -n "$NAMESPACE" "$REGISTRY_SECRET" >/dev/null 2>&1; then + fail "Unsupported registry: Could not find '$REGISTRY_SECRET' secret in '$NAMESPACE'." + fi + ;; + esac + shift + done + if [ -z "${REGISTRY:-}" ]; then + fail "Missing registry" + fi +} + +fail() { + echo "# [ERROR] ${*}" >&2 + exit 1 +} + +info() { + echo "# [INFO] ${*}" +} + +# +# Functions +# + +get_variables() { + ACS_SECRET="tssc-acs-integration" + if [ -z "${ROX_CENTRAL_ENDPOINT:-}" ]; then + ROX_CENTRAL_ENDPOINT="$(oc get secrets -n "$NAMESPACE" "$ACS_SECRET" -o json | jq -r '.data.endpoint | @base64d')" + fi + if [ -z "${ROX_API_TOKEN:-}" ]; then + ROX_API_TOKEN="$(oc get secrets -n "$NAMESPACE" "$ACS_SECRET" -o json | jq -r '.data.token | @base64d')" + fi + # For Nexus and Artifactory, they have two urls, one for UI and one for image registry. When TSSC integrate with them, + # it needs to pass UI url as `--url` parameter to the integration command. If passing image registry url as `--url` parameter, + # images pushed to Nexus and Artifactory will not apear in the Image Registry page. + if [ -z "${REGISTRY_ENDPOINT:-}" ]; then + REGISTRY_ENDPOINT="$( + oc get secrets -n "$NAMESPACE" "$REGISTRY_SECRET" -o json \ + | jq -r '.data.".dockerconfigjson" | @base64d' \ + | jq -r '.auths | to_entries[0].key' + )" + fi + + if [ -z "${REGISTRY_USERNAME:-}" ]; then + REGISTRY_USERNAME="$( + oc get secrets -n "$NAMESPACE" "$REGISTRY_SECRET" -o json \ + | jq -r '.data.".dockerconfigjson" | @base64d' \ + | jq -r '.auths | to_entries[0].value.auth | @base64d' \ + | cut -d: -f1 + )" + fi + if [ -z "${REGISTRY_PASSWORD:-}" ]; then + REGISTRY_PASSWORD="$( + oc get secrets -n "$NAMESPACE" "$REGISTRY_SECRET" -o json \ + | jq -r '.data.".dockerconfigjson" | @base64d' \ + | jq -r '.auths | to_entries[0].value.auth | @base64d' \ + | cut -d: -f2- + )" + fi + + case "$REGISTRY" in + quay) + INTEGRATION_TYPE="quay" + REGISTRY_CREDENTIALS='"registryRobotCredentials": { + "username": "'"$REGISTRY_USERNAME"'", + "password": "'"$REGISTRY_PASSWORD"'" + }' + ;; + *) + INTEGRATION_TYPE="docker" + REGISTRY_CREDENTIALS='"username": "'"$REGISTRY_USERNAME"'", + "password": "'"$REGISTRY_PASSWORD"'"' + ;; + esac + REGISTRY_TYPE="$REGISTRY" +} + +get_integration_id() { + ID="$( + curl --insecure --silent \ + --header "Authorization: Bearer $ROX_API_TOKEN" \ + --request GET "$URL" \ + | jq -r --arg name "$REGISTRY_ENDPOINT" '.integrations[] | select(.name == $name) | .id' + )" +} + +integration() { + URL="https://$ROX_CENTRAL_ENDPOINT/v1/imageintegrations" + + get_integration_id + if [ -z "${ID:-}" ]; then + HTTP_METHOD=POST + else + HTTP_METHOD=PUT + URL="$URL/$ID" + fi + + PAYLOAD="$(cat <{{ $script }} + chmod +x {{ $script }} + {{- end }} + volumeMounts: + - name: scripts + mountPath: /scripts + securityContext: + runAsNonRoot: false + allowPrivilegeEscalation: false +{{- end -}} diff --git a/installer/bundles/acs/charts/tssc-acs-integrations/templates/_helpers.tpl b/installer/bundles/acs/charts/tssc-acs-integrations/templates/_helpers.tpl new file mode 100644 index 000000000..80ca550c8 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-integrations/templates/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "common.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "common.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "common.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "common.labels" -}} +helm.sh/chart: {{ include "common.chart" . }} +{{ include "common.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Need deleted labels +*/}} +{{- define "common.postDeployDeleteLabels" -}} +helmet.redhat-appstudio.github.com/post-deploy: delete +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "common.selectorLabels" -}} +app.kubernetes.io/name: {{ include "common.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "common.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "common.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/installer/bundles/acs/charts/tssc-acs-integrations/templates/_service-account.tpl b/installer/bundles/acs/charts/tssc-acs-integrations/templates/_service-account.tpl new file mode 100644 index 000000000..9c8e70886 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-integrations/templates/_service-account.tpl @@ -0,0 +1,109 @@ +{{- define "common.serviceAccount" -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +{{- end }} +{{- define "common.secretRWRoleBinding" -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Release.Name }}-secret-rw + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - create + - delete + - update + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Release.Name }}-secret-rw + namespace: {{ .notdefaultnamespace | default .Release.Namespace }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Release.Name }}-secret-rw +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Release.Name }}-secret-rw-installer-ns + namespace: {{ .integrationsecretnamespace | default "tssc" }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Release.Name }}-secret-rw +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +{{- end }} +{{- define "common.clusterRoleBinding" -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Release.Name }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +rules: + - apiGroups: + - "*" + resources: + - pods + - jobs + - customresourcedefinitions + - deployments + - statefulsets + - routes + - keycloakrealmimports + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ .Release.Name }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Release.Name }} +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +{{- end }} +{{- define "common.RoleBinding" -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ .Release.Name }} +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/installer/charts/tssc-integrations/templates/acs/pod.yaml b/installer/bundles/acs/charts/tssc-acs-integrations/templates/acs/pod.yaml similarity index 99% rename from installer/charts/tssc-integrations/templates/acs/pod.yaml rename to installer/bundles/acs/charts/tssc-acs-integrations/templates/acs/pod.yaml index 2ba5379fd..213983fc7 100644 --- a/installer/charts/tssc-integrations/templates/acs/pod.yaml +++ b/installer/bundles/acs/charts/tssc-acs-integrations/templates/acs/pod.yaml @@ -17,7 +17,7 @@ metadata: helm.sh/hook-delete-policy: before-hook-creation, hook-succeeded labels: {{- include "common.labels" . | nindent 4 }} - name: tssc-acs-integrations + name: {{ .Release.Name }} spec: serviceAccountName: {{ .Release.Name }} restartPolicy: Never diff --git a/installer/bundles/acs/charts/tssc-acs-integrations/templates/cluster-access.yaml b/installer/bundles/acs/charts/tssc-acs-integrations/templates/cluster-access.yaml new file mode 100644 index 000000000..99852f431 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-integrations/templates/cluster-access.yaml @@ -0,0 +1 @@ +{{- include "common.clusterRoleBinding" . }} diff --git a/installer/charts/tssc-integrations/templates/service-account.yaml b/installer/bundles/acs/charts/tssc-acs-integrations/templates/secret-rw.yaml similarity index 62% rename from installer/charts/tssc-integrations/templates/service-account.yaml rename to installer/bundles/acs/charts/tssc-acs-integrations/templates/secret-rw.yaml index 9bc88cc69..9b0dc22a9 100644 --- a/installer/charts/tssc-integrations/templates/service-account.yaml +++ b/installer/bundles/acs/charts/tssc-acs-integrations/templates/secret-rw.yaml @@ -1,9 +1,4 @@ ---- -{{- include "common.serviceAccount" . }} ---- {{- $namespaces := dict "integrationsecretnamespace" .Values.integrations.tssc.namespace }} {{- include "common.secretRWRoleBinding" (merge $namespaces .) }} ---- -{{- include "common.clusterRoleBinding" . }} diff --git a/installer/bundles/acs/charts/tssc-acs-integrations/templates/serviceaccount.yaml b/installer/bundles/acs/charts/tssc-acs-integrations/templates/serviceaccount.yaml new file mode 100644 index 000000000..dbf5978d9 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-integrations/templates/serviceaccount.yaml @@ -0,0 +1 @@ +{{- include "common.serviceAccount" . }} diff --git a/installer/bundles/acs/charts/tssc-acs-integrations/values.yaml b/installer/bundles/acs/charts/tssc-acs-integrations/values.yaml new file mode 100644 index 000000000..72a31bc7f --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-integrations/values.yaml @@ -0,0 +1,14 @@ +--- +# Defaults for helm lint / tests; installer root values.yaml.tpl supplies production values. +argoCD: + enabled: false + namespace: "" + +integrations: + acs: + enabled: false + argoCD: + enabled: false + namespace: "" + tssc: + namespace: "" diff --git a/installer/charts/tssc-acs/.helmignore b/installer/bundles/acs/charts/tssc-acs-openshift/.helmignore similarity index 100% rename from installer/charts/tssc-acs/.helmignore rename to installer/bundles/acs/charts/tssc-acs-openshift/.helmignore diff --git a/installer/bundles/acs/charts/tssc-acs-openshift/Chart.yaml b/installer/bundles/acs/charts/tssc-acs-openshift/Chart.yaml new file mode 100644 index 000000000..5555468d8 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-openshift/Chart.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: v2 +name: tssc-acs-openshift +description: OpenShift project requests for the Advanced Cluster Security bundle +type: application +version: "1.9.0" diff --git a/installer/charts/tssc-openshift/templates/NOTES.txt b/installer/bundles/acs/charts/tssc-acs-openshift/templates/NOTES.txt similarity index 100% rename from installer/charts/tssc-openshift/templates/NOTES.txt rename to installer/bundles/acs/charts/tssc-acs-openshift/templates/NOTES.txt diff --git a/installer/charts/tssc-openshift/templates/projects.yaml b/installer/bundles/acs/charts/tssc-acs-openshift/templates/projects.yaml similarity index 100% rename from installer/charts/tssc-openshift/templates/projects.yaml rename to installer/bundles/acs/charts/tssc-acs-openshift/templates/projects.yaml diff --git a/installer/charts/tssc-openshift/values.yaml b/installer/bundles/acs/charts/tssc-acs-openshift/values.yaml similarity index 100% rename from installer/charts/tssc-openshift/values.yaml rename to installer/bundles/acs/charts/tssc-acs-openshift/values.yaml diff --git a/installer/charts/tssc-subscriptions/.helmignore b/installer/bundles/acs/charts/tssc-acs-subscriptions/.helmignore similarity index 100% rename from installer/charts/tssc-subscriptions/.helmignore rename to installer/bundles/acs/charts/tssc-acs-subscriptions/.helmignore diff --git a/installer/bundles/acs/charts/tssc-acs-subscriptions/Chart.yaml b/installer/bundles/acs/charts/tssc-acs-subscriptions/Chart.yaml new file mode 100644 index 000000000..cc2e96ff4 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-subscriptions/Chart.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v2 +name: tssc-acs-subscriptions +description: TSSC OpenShift Operator Hub Subscriptions +type: application +version: "1.9.0" +annotations: + helmet.redhat-appstudio.github.com/depends-on-bundle-charts: tssc-acs-openshift diff --git a/installer/bundles/acs/charts/tssc-acs-subscriptions/README.md b/installer/bundles/acs/charts/tssc-acs-subscriptions/README.md new file mode 100644 index 000000000..b9c80cd36 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-subscriptions/README.md @@ -0,0 +1,8 @@ +`tssc-acs-subscriptions` +--------------------- + +This Helm Chart manages OpenShift Operator Hub subscriptions required for TSSC. It leverages `Subscription` and `OperatorGroup` resources. + +During installation, it checks for the existence of the necessary CRDs (Custom Resource Definitions) and waits for the Operator to become available before proceeding. + +The list of subscriptions is defined in the [`values.yaml`](values.yaml) file. diff --git a/installer/charts/tssc-subscriptions/hack/find-os-subscription.sh b/installer/bundles/acs/charts/tssc-acs-subscriptions/hack/find-os-subscription.sh similarity index 100% rename from installer/charts/tssc-subscriptions/hack/find-os-subscription.sh rename to installer/bundles/acs/charts/tssc-acs-subscriptions/hack/find-os-subscription.sh diff --git a/installer/bundles/acs/charts/tssc-acs-subscriptions/scripts/test-rollout-status.sh b/installer/bundles/acs/charts/tssc-acs-subscriptions/scripts/test-rollout-status.sh new file mode 120000 index 000000000..17af939cd --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-subscriptions/scripts/test-rollout-status.sh @@ -0,0 +1 @@ +../../../../../scripts/test-rollout-status.sh \ No newline at end of file diff --git a/installer/bundles/acs/charts/tssc-acs-subscriptions/scripts/test-subscriptions.sh b/installer/bundles/acs/charts/tssc-acs-subscriptions/scripts/test-subscriptions.sh new file mode 120000 index 000000000..3788b30ac --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-subscriptions/scripts/test-subscriptions.sh @@ -0,0 +1 @@ +../../../../../scripts/test-subscriptions.sh \ No newline at end of file diff --git a/installer/charts/tssc-subscriptions/templates/NOTES.txt b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/NOTES.txt similarity index 100% rename from installer/charts/tssc-subscriptions/templates/NOTES.txt rename to installer/bundles/acs/charts/tssc-acs-subscriptions/templates/NOTES.txt diff --git a/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/_copy-scripts.tpl b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/_copy-scripts.tpl new file mode 120000 index 000000000..e37b737cc --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/_copy-scripts.tpl @@ -0,0 +1 @@ +../../../../../charts/_common/_copy-scripts.tpl \ No newline at end of file diff --git a/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/_helpers.tpl b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/_helpers.tpl new file mode 120000 index 000000000..a0020022c --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/_helpers.tpl @@ -0,0 +1 @@ +../../../../../charts/_common/_helpers.tpl \ No newline at end of file diff --git a/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/_service-account.tpl b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/_service-account.tpl new file mode 120000 index 000000000..e89b996d3 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/_service-account.tpl @@ -0,0 +1 @@ +../../../../../charts/_common/_service-account.tpl \ No newline at end of file diff --git a/installer/charts/tssc-subscriptions/templates/_subscriptions.tpl b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/_subscriptions.tpl similarity index 100% rename from installer/charts/tssc-subscriptions/templates/_subscriptions.tpl rename to installer/bundles/acs/charts/tssc-acs-subscriptions/templates/_subscriptions.tpl diff --git a/installer/charts/tssc-infrastructure/templates/service-account.yaml b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/service-account.yaml similarity index 100% rename from installer/charts/tssc-infrastructure/templates/service-account.yaml rename to installer/bundles/acs/charts/tssc-acs-subscriptions/templates/service-account.yaml diff --git a/installer/charts/tssc-subscriptions/templates/subscriptions.yaml b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/subscriptions.yaml similarity index 100% rename from installer/charts/tssc-subscriptions/templates/subscriptions.yaml rename to installer/bundles/acs/charts/tssc-acs-subscriptions/templates/subscriptions.yaml diff --git a/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/tests/_test.tpl b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/tests/_test.tpl new file mode 120000 index 000000000..f3c567eac --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/tests/_test.tpl @@ -0,0 +1 @@ +../../../../../../charts/_common/_test.tpl \ No newline at end of file diff --git a/installer/charts/tssc-subscriptions/templates/tests/test.yaml b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/tests/test.yaml similarity index 90% rename from installer/charts/tssc-subscriptions/templates/tests/test.yaml rename to installer/bundles/acs/charts/tssc-acs-subscriptions/templates/tests/test.yaml index dd1c6bb20..38306913d 100644 --- a/installer/charts/tssc-subscriptions/templates/tests/test.yaml +++ b/installer/bundles/acs/charts/tssc-acs-subscriptions/templates/tests/test.yaml @@ -9,7 +9,7 @@ command: - /scripts/test-subscriptions.sh args: -{{- range $sub := include "subscriptions.enabled" . | fromYaml }} +{{- range $sub := include "subscriptions.managed" . | fromYaml }} {{- if (dig "apiResource" "" $sub) }} - {{ $sub.apiResource | quote }} {{- end }} @@ -20,7 +20,7 @@ securityContext: runAsNonRoot: false allowPrivilegeEscalation: false -{{- range $sub := include "subscriptions.enabled" . | fromYaml }} +{{- range $sub := include "subscriptions.managed" . | fromYaml }} # # Tests the {{ $sub.name }} rollout status. # diff --git a/installer/bundles/acs/charts/tssc-acs-subscriptions/values.yaml b/installer/bundles/acs/charts/tssc-acs-subscriptions/values.yaml new file mode 100644 index 000000000..4be107998 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-subscriptions/values.yaml @@ -0,0 +1,15 @@ +--- +# Chart-local defaults for the Advanced Cluster Security operator Subscription only. +# Install-time values come from ../../values.yaml.tpl (merged by Helmet). +subscriptions: + advancedClusterSecurity: + enabled: false + description: Red Hat Advanced Cluster Security Operator + apiResource: centrals.platform.stackrox.io + channel: rhacs-4.10 + namespace: rhacs-operator + name: rhacs-operator + source: redhat-operators + sourceNamespace: openshift-marketplace + operatorGroup: + upgradeStrategy: Default diff --git a/installer/charts/tssc-gitops/.helmignore b/installer/bundles/acs/charts/tssc-acs-test/.helmignore similarity index 100% rename from installer/charts/tssc-gitops/.helmignore rename to installer/bundles/acs/charts/tssc-acs-test/.helmignore diff --git a/installer/charts/tssc-acs-test/Chart.yaml b/installer/bundles/acs/charts/tssc-acs-test/Chart.yaml similarity index 100% rename from installer/charts/tssc-acs-test/Chart.yaml rename to installer/bundles/acs/charts/tssc-acs-test/Chart.yaml diff --git a/installer/charts/tssc-acs-test/README.md b/installer/bundles/acs/charts/tssc-acs-test/README.md similarity index 100% rename from installer/charts/tssc-acs-test/README.md rename to installer/bundles/acs/charts/tssc-acs-test/README.md diff --git a/installer/bundles/acs/charts/tssc-acs-test/scripts/test-acs-image-scan.sh b/installer/bundles/acs/charts/tssc-acs-test/scripts/test-acs-image-scan.sh new file mode 120000 index 000000000..2fe94b229 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-test/scripts/test-acs-image-scan.sh @@ -0,0 +1 @@ +../../../../../scripts/test-acs-image-scan.sh \ No newline at end of file diff --git a/installer/bundles/acs/charts/tssc-acs-test/scripts/test-rollout-status.sh b/installer/bundles/acs/charts/tssc-acs-test/scripts/test-rollout-status.sh new file mode 120000 index 000000000..17af939cd --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-test/scripts/test-rollout-status.sh @@ -0,0 +1 @@ +../../../../../scripts/test-rollout-status.sh \ No newline at end of file diff --git a/installer/charts/tssc-acs-test/templates/NOTES.txt b/installer/bundles/acs/charts/tssc-acs-test/templates/NOTES.txt similarity index 100% rename from installer/charts/tssc-acs-test/templates/NOTES.txt rename to installer/bundles/acs/charts/tssc-acs-test/templates/NOTES.txt diff --git a/installer/bundles/acs/charts/tssc-acs-test/templates/_copy-scripts.tpl b/installer/bundles/acs/charts/tssc-acs-test/templates/_copy-scripts.tpl new file mode 120000 index 000000000..e37b737cc --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-test/templates/_copy-scripts.tpl @@ -0,0 +1 @@ +../../../../../charts/_common/_copy-scripts.tpl \ No newline at end of file diff --git a/installer/bundles/acs/charts/tssc-acs-test/templates/_helpers.tpl b/installer/bundles/acs/charts/tssc-acs-test/templates/_helpers.tpl new file mode 120000 index 000000000..a0020022c --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-test/templates/_helpers.tpl @@ -0,0 +1 @@ +../../../../../charts/_common/_helpers.tpl \ No newline at end of file diff --git a/installer/bundles/acs/charts/tssc-acs-test/templates/_service-account.tpl b/installer/bundles/acs/charts/tssc-acs-test/templates/_service-account.tpl new file mode 120000 index 000000000..e89b996d3 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-test/templates/_service-account.tpl @@ -0,0 +1 @@ +../../../../../charts/_common/_service-account.tpl \ No newline at end of file diff --git a/installer/charts/tssc-acs-test/templates/service-account.yaml b/installer/bundles/acs/charts/tssc-acs-test/templates/service-account.yaml similarity index 100% rename from installer/charts/tssc-acs-test/templates/service-account.yaml rename to installer/bundles/acs/charts/tssc-acs-test/templates/service-account.yaml diff --git a/installer/bundles/acs/charts/tssc-acs-test/templates/tests/_test.tpl b/installer/bundles/acs/charts/tssc-acs-test/templates/tests/_test.tpl new file mode 120000 index 000000000..f3c567eac --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-test/templates/tests/_test.tpl @@ -0,0 +1 @@ +../../../../../../charts/_common/_test.tpl \ No newline at end of file diff --git a/installer/charts/tssc-acs-test/templates/tests/scanner.yaml b/installer/bundles/acs/charts/tssc-acs-test/templates/tests/scanner.yaml similarity index 100% rename from installer/charts/tssc-acs-test/templates/tests/scanner.yaml rename to installer/bundles/acs/charts/tssc-acs-test/templates/tests/scanner.yaml diff --git a/installer/charts/tssc-acs-test/templates/tests/test.yaml b/installer/bundles/acs/charts/tssc-acs-test/templates/tests/test.yaml similarity index 100% rename from installer/charts/tssc-acs-test/templates/tests/test.yaml rename to installer/bundles/acs/charts/tssc-acs-test/templates/tests/test.yaml diff --git a/installer/charts/tssc-acs-test/values.yaml b/installer/bundles/acs/charts/tssc-acs-test/values.yaml similarity index 100% rename from installer/charts/tssc-acs-test/values.yaml rename to installer/bundles/acs/charts/tssc-acs-test/values.yaml diff --git a/installer/bundles/acs/charts/tssc-acs-test/values.yaml.tpl b/installer/bundles/acs/charts/tssc-acs-test/values.yaml.tpl new file mode 100644 index 000000000..a1cdf45e9 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs-test/values.yaml.tpl @@ -0,0 +1,12 @@ +{{- $_ := required "Red Hat ACS settings" .Installer.Products.Advanced_Cluster_Security -}} +--- +# Same acsTest shape as charts/tssc-acs/values.yaml.tpl (this chart uses its own ReadValuesTemplate). +acsTest: + name: stackrox-central-services + integrationSecret: + namespace: {{ .Installer.Namespace }} + test: + scanner: + image: registry.access.redhat.com/ubi10:latest + tssc: + namespace: {{ .Installer.Namespace }} diff --git a/installer/charts/tssc-iam/.helmignore b/installer/bundles/acs/charts/tssc-acs/.helmignore similarity index 100% rename from installer/charts/tssc-iam/.helmignore rename to installer/bundles/acs/charts/tssc-acs/.helmignore diff --git a/installer/charts/tssc-acs/Chart.yaml b/installer/bundles/acs/charts/tssc-acs/Chart.yaml similarity index 72% rename from installer/charts/tssc-acs/Chart.yaml rename to installer/bundles/acs/charts/tssc-acs/Chart.yaml index beb5c7502..dcba2e280 100644 --- a/installer/charts/tssc-acs/Chart.yaml +++ b/installer/bundles/acs/charts/tssc-acs/Chart.yaml @@ -7,5 +7,5 @@ version: "1.9.0" appVersion: "4.10" annotations: helmet.redhat-appstudio.github.com/product-name: Advanced Cluster Security - helmet.redhat-appstudio.github.com/depends-on: tssc-openshift, tssc-subscriptions + helmet.redhat-appstudio.github.com/depends-on-bundle-charts: tssc-acs-openshift, tssc-acs-subscriptions helmet.redhat-appstudio.github.com/integrations-provided: acs diff --git a/installer/charts/tssc-acs/README.md b/installer/bundles/acs/charts/tssc-acs/README.md similarity index 100% rename from installer/charts/tssc-acs/README.md rename to installer/bundles/acs/charts/tssc-acs/README.md diff --git a/installer/bundles/acs/charts/tssc-acs/scripts/stackrox-helper.sh b/installer/bundles/acs/charts/tssc-acs/scripts/stackrox-helper.sh new file mode 120000 index 000000000..b21f8d099 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs/scripts/stackrox-helper.sh @@ -0,0 +1 @@ +../../../../../scripts/stackrox-helper.sh \ No newline at end of file diff --git a/installer/charts/tssc-acs/templates/NOTES.txt b/installer/bundles/acs/charts/tssc-acs/templates/NOTES.txt similarity index 100% rename from installer/charts/tssc-acs/templates/NOTES.txt rename to installer/bundles/acs/charts/tssc-acs/templates/NOTES.txt diff --git a/installer/charts/tssc-acs/templates/_acs.tpl b/installer/bundles/acs/charts/tssc-acs/templates/_acs.tpl similarity index 100% rename from installer/charts/tssc-acs/templates/_acs.tpl rename to installer/bundles/acs/charts/tssc-acs/templates/_acs.tpl diff --git a/installer/bundles/acs/charts/tssc-acs/templates/_copy-scripts.tpl b/installer/bundles/acs/charts/tssc-acs/templates/_copy-scripts.tpl new file mode 120000 index 000000000..e37b737cc --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs/templates/_copy-scripts.tpl @@ -0,0 +1 @@ +../../../../../charts/_common/_copy-scripts.tpl \ No newline at end of file diff --git a/installer/bundles/acs/charts/tssc-acs/templates/_helpers.tpl b/installer/bundles/acs/charts/tssc-acs/templates/_helpers.tpl new file mode 120000 index 000000000..a0020022c --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs/templates/_helpers.tpl @@ -0,0 +1 @@ +../../../../../charts/_common/_helpers.tpl \ No newline at end of file diff --git a/installer/bundles/acs/charts/tssc-acs/templates/_service-account.tpl b/installer/bundles/acs/charts/tssc-acs/templates/_service-account.tpl new file mode 120000 index 000000000..e89b996d3 --- /dev/null +++ b/installer/bundles/acs/charts/tssc-acs/templates/_service-account.tpl @@ -0,0 +1 @@ +../../../../../charts/_common/_service-account.tpl \ No newline at end of file diff --git a/installer/charts/tssc-acs/templates/acs-central.yaml b/installer/bundles/acs/charts/tssc-acs/templates/acs-central.yaml similarity index 100% rename from installer/charts/tssc-acs/templates/acs-central.yaml rename to installer/bundles/acs/charts/tssc-acs/templates/acs-central.yaml diff --git a/installer/charts/tssc-acs/templates/job-stackrox-api.yaml b/installer/bundles/acs/charts/tssc-acs/templates/job-stackrox-api.yaml similarity index 100% rename from installer/charts/tssc-acs/templates/job-stackrox-api.yaml rename to installer/bundles/acs/charts/tssc-acs/templates/job-stackrox-api.yaml diff --git a/installer/charts/tssc-acs/templates/service-account.yaml b/installer/bundles/acs/charts/tssc-acs/templates/service-account.yaml similarity index 100% rename from installer/charts/tssc-acs/templates/service-account.yaml rename to installer/bundles/acs/charts/tssc-acs/templates/service-account.yaml diff --git a/installer/charts/tssc-acs/values.yaml b/installer/bundles/acs/charts/tssc-acs/values.yaml similarity index 100% rename from installer/charts/tssc-acs/values.yaml rename to installer/bundles/acs/charts/tssc-acs/values.yaml diff --git a/installer/bundles/acs/config.yaml b/installer/bundles/acs/config.yaml new file mode 100644 index 000000000..c4ffe8d73 --- /dev/null +++ b/installer/bundles/acs/config.yaml @@ -0,0 +1,4 @@ +name: Advanced Cluster Security +namespace: tssc-acs +properties: + manageSubscription: true diff --git a/installer/bundles/acs/values.yaml.tpl b/installer/bundles/acs/values.yaml.tpl new file mode 100644 index 000000000..510035e2a --- /dev/null +++ b/installer/bundles/acs/values.yaml.tpl @@ -0,0 +1,30 @@ +{{- $acs := required "Red Hat ACS settings" .Installer.Products.Advanced_Cluster_Security -}} +{{- $ingressDomain := required "OpenShift ingress domain" .OpenShift.Ingress.Domain -}} +{{- $ingressRouterCA := required "OpenShift RouterCA" .OpenShift.Ingress.RouterCA -}} +{{- $acsActive := true -}} +{{- if and (kindIs "bool" $acs.Enabled) (eq $acs.Enabled false) }}{{- $acsActive = false -}}{{- end -}} +{{- $acsNs := default .Installer.Namespace $acs.Namespace -}} +# Bundle-scoped values for tssc-acs-openshift / tssc-acs-subscriptions (see root values.yaml.tpl for globals). +openshift: + projects: + - {{ $acsNs }} + {{- if and $acsActive $acs.Properties.manageSubscription }} + - rhacs-operator + {{- end }} +subscriptions: + advancedClusterSecurity: + enabled: {{ $acsActive }} + managed: {{ and $acsActive $acs.Properties.manageSubscription }} +acs: &acs + enabled: {{ $acsActive }} + name: &acsName stackrox-central-services + ingressDomain: {{ $ingressDomain }} + ingressRouterCA: {{ $ingressRouterCA }} + integrationSecret: + namespace: {{ .Installer.Namespace }} + test: + scanner: + image: registry.access.redhat.com/ubi10:latest + tssc: + namespace: {{ .Installer.Namespace }} +acsTest: *acs diff --git a/installer/charts/tssc-dh/.helmignore b/installer/bundles/dh/charts/tssc-dh-access/.helmignore similarity index 100% rename from installer/charts/tssc-dh/.helmignore rename to installer/bundles/dh/charts/tssc-dh-access/.helmignore diff --git a/installer/bundles/dh/charts/tssc-dh-access/Chart.yaml b/installer/bundles/dh/charts/tssc-dh-access/Chart.yaml new file mode 100644 index 000000000..605d7b49a --- /dev/null +++ b/installer/bundles/dh/charts/tssc-dh-access/Chart.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v2 +name: tssc-dh-access +description: Developer Hub Kubernetes plugin service account and cluster RBAC +type: application +version: "1.9.0" +annotations: + helmet.redhat-appstudio.github.com/use-product-namespace: Developer Hub diff --git a/installer/charts/tssc-infrastructure/templates/developer-hub/serviceaccount.yaml b/installer/bundles/dh/charts/tssc-dh-access/templates/serviceaccount.yaml similarity index 96% rename from installer/charts/tssc-infrastructure/templates/developer-hub/serviceaccount.yaml rename to installer/bundles/dh/charts/tssc-dh-access/templates/serviceaccount.yaml index 3aee3d2f1..e43c07674 100644 --- a/installer/charts/tssc-infrastructure/templates/developer-hub/serviceaccount.yaml +++ b/installer/bundles/dh/charts/tssc-dh-access/templates/serviceaccount.yaml @@ -11,7 +11,7 @@ metadata: type: kubernetes.io/service-account-token --- -{{- $sa := lookup "v1" "serviceAccount" .Release.Name $saName -}} +{{- $sa := lookup "v1" "ServiceAccount" .Release.Namespace $saName -}} apiVersion: v1 kind: ServiceAccount metadata: diff --git a/installer/bundles/dh/charts/tssc-dh-access/values.yaml b/installer/bundles/dh/charts/tssc-dh-access/values.yaml new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/installer/bundles/dh/charts/tssc-dh-access/values.yaml @@ -0,0 +1 @@ +{} diff --git a/installer/charts/tssc-integrations/.helmignore b/installer/bundles/dh/charts/tssc-dh-openshift/.helmignore similarity index 100% rename from installer/charts/tssc-integrations/.helmignore rename to installer/bundles/dh/charts/tssc-dh-openshift/.helmignore diff --git a/installer/bundles/dh/charts/tssc-dh-openshift/Chart.yaml b/installer/bundles/dh/charts/tssc-dh-openshift/Chart.yaml new file mode 100644 index 000000000..1ffc0d2b5 --- /dev/null +++ b/installer/bundles/dh/charts/tssc-dh-openshift/Chart.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: v2 +name: tssc-dh-openshift +description: OpenShift project requests for the Developer Hub bundle +type: application +version: "1.9.0" diff --git a/installer/bundles/dh/charts/tssc-dh-openshift/templates/NOTES.txt b/installer/bundles/dh/charts/tssc-dh-openshift/templates/NOTES.txt new file mode 100644 index 000000000..e13ce3dd1 --- /dev/null +++ b/installer/bundles/dh/charts/tssc-dh-openshift/templates/NOTES.txt @@ -0,0 +1,4 @@ +OpenShift Projects: +{{- range $p := .Values.openshift.projects | sortAlpha }} + - {{ $p | quote }} +{{- end }} diff --git a/installer/bundles/dh/charts/tssc-dh-openshift/templates/projects.yaml b/installer/bundles/dh/charts/tssc-dh-openshift/templates/projects.yaml new file mode 100644 index 000000000..aa8af33b2 --- /dev/null +++ b/installer/bundles/dh/charts/tssc-dh-openshift/templates/projects.yaml @@ -0,0 +1,21 @@ +{{- $projects := list -}} +{{- range $p := .Values.openshift.projects -}} + {{- if not (has $p $projects ) -}} + {{- $projects = append $projects $p -}} + {{- end -}} +{{- end -}} + +{{- range $p := $projects }} + {{- if and + (not (hasPrefix "openshift-" $p)) + (not (lookup "project.openshift.io/v1" "Project" "" $p )) + }} +--- +apiVersion: project.openshift.io/v1 +kind: ProjectRequest +description: {{ $p }} +displayName: {{ $p }} +metadata: + name: {{ $p }} + {{- end }} +{{- end }} diff --git a/installer/bundles/dh/charts/tssc-dh-openshift/values.yaml b/installer/bundles/dh/charts/tssc-dh-openshift/values.yaml new file mode 100644 index 000000000..9cda7f4e3 --- /dev/null +++ b/installer/bundles/dh/charts/tssc-dh-openshift/values.yaml @@ -0,0 +1,3 @@ +--- +openshift: + projects: [] diff --git a/installer/bundles/dh/charts/tssc-dh-subscriptions/.helmignore b/installer/bundles/dh/charts/tssc-dh-subscriptions/.helmignore new file mode 100644 index 000000000..97d9f7337 --- /dev/null +++ b/installer/bundles/dh/charts/tssc-dh-subscriptions/.helmignore @@ -0,0 +1,19 @@ +.bzr/ +.bzrignore +.DS_Store +.git/ +.gitignore +.hg/ +.hgignore +.idea/ +.project +.svn/ +.vscode/ +*.bak +*.orig +*.swp +*.tmp +*.tmproj +*~ +*.md +hack/ diff --git a/installer/bundles/dh/charts/tssc-dh-subscriptions/Chart.yaml b/installer/bundles/dh/charts/tssc-dh-subscriptions/Chart.yaml new file mode 100644 index 000000000..a7736f31f --- /dev/null +++ b/installer/bundles/dh/charts/tssc-dh-subscriptions/Chart.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v2 +name: tssc-dh-subscriptions +description: TSSC OpenShift Operator Hub Subscriptions +type: application +version: "1.9.0" +annotations: + helmet.redhat-appstudio.github.com/depends-on-bundle-charts: tssc-dh-openshift diff --git a/installer/charts/tssc-subscriptions/README.md b/installer/bundles/dh/charts/tssc-dh-subscriptions/README.md similarity index 94% rename from installer/charts/tssc-subscriptions/README.md rename to installer/bundles/dh/charts/tssc-dh-subscriptions/README.md index 5ffe6b220..354f9e174 100644 --- a/installer/charts/tssc-subscriptions/README.md +++ b/installer/bundles/dh/charts/tssc-dh-subscriptions/README.md @@ -1,4 +1,4 @@ -`tssc-subscriptions` +`tssc-dh-subscriptions` --------------------- This Helm Chart manages OpenShift Operator Hub subscriptions required for TSSC. It leverages `Subscription` and `OperatorGroup` resources. diff --git a/installer/bundles/dh/charts/tssc-dh-subscriptions/hack/find-os-subscription.sh b/installer/bundles/dh/charts/tssc-dh-subscriptions/hack/find-os-subscription.sh new file mode 100755 index 000000000..d5f883c36 --- /dev/null +++ b/installer/bundles/dh/charts/tssc-dh-subscriptions/hack/find-os-subscription.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# +# Finds the subscription for the given catalog name, shows the information needed +# to add the OpenShift operator subscription on this chart. +# + +# OpenShift operator catalog name, for instance "openshift-pipelines-operator-rh". +declare -r CATALOG_NAME="${1:-}" + +if [[ -z "${CATALOG_NAME}" ]]; then + echo "# Usage: $$ $0 " + exit 1 +fi + +# Inspecting the manifests installed on the cluster, looking for the catalog name +# given in parameter. The availability of the operators depends on the OpenShift cluster +# version. +if ! oc get packagemanifests "${CATALOG_NAME}" >/dev/null; then + echo "# ERROR: No subscription found for '${CATALOG_NAME}'!" + exit 1 +fi + +# Extracing the source name, and the namespace where the catalog is available +source=$(oc get packagemanifests "${CATALOG_NAME}" \ + --output="jsonpath={.status.catalogSource}") +namespace=$(oc get packagemanifests "${CATALOG_NAME}" \ + --output="jsonpath={.status.catalogSourceNamespace}") + +# Listing the channels and the startingCSVs available for the catalog. +mapfile -t channels < <(echo -en "$(oc get packagemanifests "${CATALOG_NAME}" \ + --output jsonpath='{range .status.channels[*]}{.name}\n{end}')") +mapfile -t startingCSVs < <(echo -en "$(oc get packagemanifests "${CATALOG_NAME}" \ + --output jsonpath='{range .status.channels[*]}{.currentCSV}\n{end}')") + +cat <" + exit 1 +fi + +# Inspecting the manifests installed on the cluster, looking for the catalog name +# given in parameter. The availability of the operators depends on the OpenShift cluster +# version. +if ! oc get packagemanifests "${CATALOG_NAME}" >/dev/null; then + echo "# ERROR: No subscription found for '${CATALOG_NAME}'!" + exit 1 +fi + +# Extracing the source name, and the namespace where the catalog is available +source=$(oc get packagemanifests "${CATALOG_NAME}" \ + --output="jsonpath={.status.catalogSource}") +namespace=$(oc get packagemanifests "${CATALOG_NAME}" \ + --output="jsonpath={.status.catalogSourceNamespace}") + +# Listing the channels and the startingCSVs available for the catalog. +mapfile -t channels < <(echo -en "$(oc get packagemanifests "${CATALOG_NAME}" \ + --output jsonpath='{range .status.channels[*]}{.name}\n{end}')") +mapfile -t startingCSVs < <(echo -en "$(oc get packagemanifests "${CATALOG_NAME}" \ + --output jsonpath='{range .status.channels[*]}{.currentCSV}\n{end}')") + +cat <{{ $script }} + chmod +x {{ $script }} + {{- end }} + volumeMounts: + - name: scripts + mountPath: /scripts + securityContext: + runAsNonRoot: false + allowPrivilegeEscalation: false +{{- end -}} diff --git a/installer/bundles/iam/charts/_common/_helpers.tpl b/installer/bundles/iam/charts/_common/_helpers.tpl new file mode 100644 index 000000000..80ca550c8 --- /dev/null +++ b/installer/bundles/iam/charts/_common/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "common.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "common.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "common.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "common.labels" -}} +helm.sh/chart: {{ include "common.chart" . }} +{{ include "common.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Need deleted labels +*/}} +{{- define "common.postDeployDeleteLabels" -}} +helmet.redhat-appstudio.github.com/post-deploy: delete +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "common.selectorLabels" -}} +app.kubernetes.io/name: {{ include "common.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "common.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "common.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/installer/bundles/iam/charts/_common/_service-account.tpl b/installer/bundles/iam/charts/_common/_service-account.tpl new file mode 100644 index 000000000..9c8e70886 --- /dev/null +++ b/installer/bundles/iam/charts/_common/_service-account.tpl @@ -0,0 +1,109 @@ +{{- define "common.serviceAccount" -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +{{- end }} +{{- define "common.secretRWRoleBinding" -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Release.Name }}-secret-rw + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - create + - delete + - update + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Release.Name }}-secret-rw + namespace: {{ .notdefaultnamespace | default .Release.Namespace }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Release.Name }}-secret-rw +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Release.Name }}-secret-rw-installer-ns + namespace: {{ .integrationsecretnamespace | default "tssc" }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Release.Name }}-secret-rw +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +{{- end }} +{{- define "common.clusterRoleBinding" -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Release.Name }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +rules: + - apiGroups: + - "*" + resources: + - pods + - jobs + - customresourcedefinitions + - deployments + - statefulsets + - routes + - keycloakrealmimports + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ .Release.Name }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Release.Name }} +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +{{- end }} +{{- define "common.RoleBinding" -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ .Release.Name }} +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/installer/bundles/iam/charts/_common/_test.tpl b/installer/bundles/iam/charts/_common/_test.tpl new file mode 100644 index 000000000..7d972acc7 --- /dev/null +++ b/installer/bundles/iam/charts/_common/_test.tpl @@ -0,0 +1,24 @@ +{{- define "common.test" -}} +apiVersion: v1 +kind: Pod +metadata: + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + labels: + {{- include "common.labels" . | nindent 4 }} + name: test-{{ .name | default .Chart.Name }} + namespace: {{ .namespace | default .Release.Namespace }} +spec: + restartPolicy: Never + serviceAccountName: {{ .serviceAccount | default .Release.Name }} + initContainers: + # + # Copying the scripts that will be used on the subsequent containers, the + # scripts are shared via the "/scripts" volume. + # +{{- include "common.copyScripts" . | nindent 4 }} + volumes: + - name: scripts + emptyDir: {} +{{- end -}} diff --git a/installer/bundles/iam/charts/tssc-iam-openshift/.helmignore b/installer/bundles/iam/charts/tssc-iam-openshift/.helmignore new file mode 100644 index 000000000..51b1fe1c3 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-openshift/.helmignore @@ -0,0 +1,17 @@ +.bzr/ +.bzrignore +.DS_Store +.git/ +.gitignore +.hg/ +.hgignore +.idea/ +.project +.svn/ +.vscode/ +*.bak +*.orig +*.swp +*.tmp +*.tmproj +*~ diff --git a/installer/bundles/iam/charts/tssc-iam-openshift/Chart.yaml b/installer/bundles/iam/charts/tssc-iam-openshift/Chart.yaml new file mode 100644 index 000000000..12bae3748 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-openshift/Chart.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: v2 +name: tssc-iam-openshift +description: OpenShift project requests for the IAM (Keycloak) bundle +type: application +version: "1.9.0" diff --git a/installer/bundles/iam/charts/tssc-iam-openshift/templates/NOTES.txt b/installer/bundles/iam/charts/tssc-iam-openshift/templates/NOTES.txt new file mode 100644 index 000000000..e13ce3dd1 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-openshift/templates/NOTES.txt @@ -0,0 +1,4 @@ +OpenShift Projects: +{{- range $p := .Values.openshift.projects | sortAlpha }} + - {{ $p | quote }} +{{- end }} diff --git a/installer/bundles/iam/charts/tssc-iam-openshift/templates/projects.yaml b/installer/bundles/iam/charts/tssc-iam-openshift/templates/projects.yaml new file mode 100644 index 000000000..aa8af33b2 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-openshift/templates/projects.yaml @@ -0,0 +1,21 @@ +{{- $projects := list -}} +{{- range $p := .Values.openshift.projects -}} + {{- if not (has $p $projects ) -}} + {{- $projects = append $projects $p -}} + {{- end -}} +{{- end -}} + +{{- range $p := $projects }} + {{- if and + (not (hasPrefix "openshift-" $p)) + (not (lookup "project.openshift.io/v1" "Project" "" $p )) + }} +--- +apiVersion: project.openshift.io/v1 +kind: ProjectRequest +description: {{ $p }} +displayName: {{ $p }} +metadata: + name: {{ $p }} + {{- end }} +{{- end }} diff --git a/installer/bundles/iam/charts/tssc-iam-openshift/values.yaml b/installer/bundles/iam/charts/tssc-iam-openshift/values.yaml new file mode 100644 index 000000000..9cda7f4e3 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-openshift/values.yaml @@ -0,0 +1,3 @@ +--- +openshift: + projects: [] diff --git a/installer/charts/tssc-pipelines-config/.helmignore b/installer/bundles/iam/charts/tssc-iam-pgsql/.helmignore similarity index 100% rename from installer/charts/tssc-pipelines-config/.helmignore rename to installer/bundles/iam/charts/tssc-iam-pgsql/.helmignore diff --git a/installer/bundles/iam/charts/tssc-iam-pgsql/Chart.yaml b/installer/bundles/iam/charts/tssc-iam-pgsql/Chart.yaml new file mode 100644 index 000000000..9e4ffad99 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-pgsql/Chart.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v2 +name: tssc-iam-pgsql +description: PostgreSQL instance for Keycloak (IAM bundle) +type: application +version: "1.9.0" +annotations: + helmet.redhat-appstudio.github.com/use-product-namespace: IAM + helmet.redhat-appstudio.github.com/install-release-in-installer-namespace: "true" diff --git a/installer/bundles/iam/charts/tssc-iam-pgsql/scripts/test-rollout-status.sh b/installer/bundles/iam/charts/tssc-iam-pgsql/scripts/test-rollout-status.sh new file mode 100755 index 000000000..1b9595e15 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-pgsql/scripts/test-rollout-status.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +# +# Runs "oc rollout status" for configured namespace, resource type, and selectors. +# +shopt -s inherit_errexit +set -o errexit +set -o errtrace +set -o nounset +set -o pipefail + +usage() { + echo " +Usage: + ${0##*/} + +Optional arguments: + -d, --debug + Activate tracing/debug mode. + -h, --help + Display this message. + +Example: + \$ export NAMESPACE=\"namespace\" + \$ export RESOURCE_TYPE=\"deployment\" + ${0##*/} +" >&2 +} + +parse_args() { + RESOURCE_SELECTORS=() + while [[ $# -gt 0 ]]; do + case "$1" in + -d | --debug) + set -x + DEBUG="--debug" + export DEBUG + info "Running script as: $(id)" + ;; + -h | --help) + usage + exit 0 + ;; + *) + # The "rollout status" selectors, to find the actual resource to check for + # successful rollout. + RESOURCE_SELECTORS+=("$1") + ;; + esac + shift + done +} + +fail() { + echo "# [ERROR] ${*}" >&2 + exit 1 +} + +warning() { + echo "# [WARNING] ${*}" +} + +info() { + echo "# [INFO] ${*}" +} + +# +# Functions +# + +rollout_status() { + oc rollout status "${RESOURCE_TYPE}" \ + --namespace="${NAMESPACE}" \ + --watch \ + --timeout=10s \ + --selector="${1}" +} + +assert_resource_exists() { + local selector="${1}" + local output + output=$( + oc get "${RESOURCE_TYPE}" \ + --namespace="${NAMESPACE}" \ + --selector="${selector}" 2>&1 + ) + local status=${?} + if [[ $status -eq 0 && $output != "No resources found"* ]]; then + info "Resource of type '${RESOURCE_TYPE}' with selector" \ + "'${selector}' exists in namespace '${NAMESPACE}'!" + return 0 + fi + + warning "Resource of type '${RESOURCE_TYPE}' with selector" \ + "'${selector}' does not exist in namespace '${NAMESPACE}'." + return 1 +} + +wait_for_resource() { + for s in "${RESOURCE_SELECTORS[@]}"; do + echo "# Checking if ${RESOURCE_TYPE} with selector '${s}' exists..." + if ! assert_resource_exists "${s}"; then + return 1 + fi + + echo "# Checking if ${RESOURCE_TYPE} with selector '${s}' is ready..." + if ! rollout_status "${s}"; then + echo -en "#\n# WARNING: ${RESOURCE_TYPE} '${s}' is not ready!\n#\n" + return 1 + fi + info "${RESOURCE_TYPE} objects with '${s}' selector are ready!" + done + return 0 +} + +test_rollout_status() { + [[ -z "${NAMESPACE}" ]] && usage + [[ -z "${RESOURCE_TYPE}" ]] && usage + [[ ${#RESOURCE_SELECTORS[@]} -eq 0 ]] && usage + + for i in $(seq 1 "${RETRIES}"); do + wait=$((i * 5)) + [[ $wait -gt 30 ]] && wait=30 + echo "### [${i}/${RETRIES}] Waiting for ${wait} seconds before retrying..." + sleep ${wait} + + if wait_for_resource; then + info "${RESOURCE_TYPE} objects ready: '${RESOURCE_SELECTORS[*]}'" + return 0 + fi + done + + fail "'${RESOURCE_TYPE}' are not ready!" +} + +main() { + parse_args "$@" + + # Namespace to check for "rollout status". + declare -r NAMESPACE="${NAMESPACE:-}" + # Resource type for "rollout status", as in "statefulset" or "deployment". + declare -r RESOURCE_TYPE="${RESOURCE_TYPE:-statefulset}" + # Number of retries to attempt before giving up. + declare -r RETRIES=${RETRIES:-20} + + test_rollout_status +} + +if [ "${BASH_SOURCE[0]}" == "$0" ]; then + main "$@" + echo + echo "Success" +fi \ No newline at end of file diff --git a/installer/bundles/iam/charts/tssc-iam-pgsql/templates/_copy-scripts.tpl b/installer/bundles/iam/charts/tssc-iam-pgsql/templates/_copy-scripts.tpl new file mode 100644 index 000000000..91c34ada8 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-pgsql/templates/_copy-scripts.tpl @@ -0,0 +1,26 @@ +{{/* + + POD container spec to copy scripts. + +*/}} +{{- define "common.copyScripts" -}} +- name: copy-scripts + image: registry.access.redhat.com/ubi10/ubi-minimal:latest + workingDir: /scripts + command: + - /bin/bash + - -c + - | + set -x -e + {{- range $path, $content := .Files.Glob "scripts/*.sh" -}} + {{- $script := trimPrefix "scripts/" $path }} + printf '%s' "{{ $content | toString | b64enc }}" | base64 -d >{{ $script }} + chmod +x {{ $script }} + {{- end }} + volumeMounts: + - name: scripts + mountPath: /scripts + securityContext: + runAsNonRoot: false + allowPrivilegeEscalation: false +{{- end -}} diff --git a/installer/bundles/iam/charts/tssc-iam-pgsql/templates/_helpers.tpl b/installer/bundles/iam/charts/tssc-iam-pgsql/templates/_helpers.tpl new file mode 100644 index 000000000..80ca550c8 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-pgsql/templates/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "common.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "common.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "common.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "common.labels" -}} +helm.sh/chart: {{ include "common.chart" . }} +{{ include "common.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Need deleted labels +*/}} +{{- define "common.postDeployDeleteLabels" -}} +helmet.redhat-appstudio.github.com/post-deploy: delete +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "common.selectorLabels" -}} +app.kubernetes.io/name: {{ include "common.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "common.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "common.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/installer/charts/tssc-infrastructure/templates/postgres/pgsql-service.yaml b/installer/bundles/iam/charts/tssc-iam-pgsql/templates/postgres/pgsql-service.yaml similarity index 97% rename from installer/charts/tssc-infrastructure/templates/postgres/pgsql-service.yaml rename to installer/bundles/iam/charts/tssc-iam-pgsql/templates/postgres/pgsql-service.yaml index ea710730e..82f8ec9c2 100644 --- a/installer/charts/tssc-infrastructure/templates/postgres/pgsql-service.yaml +++ b/installer/bundles/iam/charts/tssc-iam-pgsql/templates/postgres/pgsql-service.yaml @@ -1,5 +1,5 @@ {{- $currentNamespace := .Release.Namespace }} -{{- with $pgsql := .Values.infrastructure.pgsqlService }} +{{- with $pgsql := .Values.pgsqlService }} {{- range $inst := $pgsql.instances }} {{- if $inst.enabled }} {{- $secretName := printf "%s-%s" $inst.name $pgsql.secretName }} @@ -71,7 +71,7 @@ metadata: app.kubernetes.io/part-of: tssc spec: selector: - matchLabels: + matchLabels: app: {{ $podName }} replicas: 1 template: diff --git a/installer/bundles/iam/charts/tssc-iam-pgsql/templates/tests/_test.tpl b/installer/bundles/iam/charts/tssc-iam-pgsql/templates/tests/_test.tpl new file mode 100644 index 000000000..f8d7cc631 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-pgsql/templates/tests/_test.tpl @@ -0,0 +1,24 @@ +{{- define "common.test" -}} +apiVersion: v1 +kind: Pod +metadata: + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + labels: + {{- include "common.labels" . | nindent 4 }} + name: test-{{ .name | default .Chart.Name }} + namespace: {{ .namespace | default .Release.Namespace }} +spec: + restartPolicy: Never + serviceAccountName: {{ .serviceAccount | default "default" }} + initContainers: + # + # Copying the scripts that will be used on the subsequent containers, the + # scripts are shared via the "/scripts" volume. + # +{{- include "common.copyScripts" . | nindent 4 }} + volumes: + - name: scripts + emptyDir: {} +{{- end -}} diff --git a/installer/charts/tssc-infrastructure/templates/tests/test.yaml b/installer/bundles/iam/charts/tssc-iam-pgsql/templates/tests/test.yaml similarity index 84% rename from installer/charts/tssc-infrastructure/templates/tests/test.yaml rename to installer/bundles/iam/charts/tssc-iam-pgsql/templates/tests/test.yaml index 7ca19a052..6d7b97bf0 100644 --- a/installer/charts/tssc-infrastructure/templates/tests/test.yaml +++ b/installer/bundles/iam/charts/tssc-iam-pgsql/templates/tests/test.yaml @@ -1,7 +1,14 @@ +{{- $any := false -}} +{{- range $inst := .Values.pgsqlService.instances -}} +{{- if $inst.enabled -}} +{{- $any = true -}} +{{- end -}} +{{- end -}} +{{- if $any -}} --- {{- include "common.test" . }} containers: -{{- with $pgsql := .Values.infrastructure.pgsqlService }} +{{- with $pgsql := .Values.pgsqlService }} {{- range $inst := $pgsql.instances }} {{- if $inst.enabled }} {{- $podName := printf "test-%s-%s" $inst.name $pgsql.podName }} @@ -31,3 +38,4 @@ {{- end }} {{- end }} {{- end }} +{{- end }} diff --git a/installer/bundles/iam/charts/tssc-iam-pgsql/values.yaml b/installer/bundles/iam/charts/tssc-iam-pgsql/values.yaml new file mode 100644 index 000000000..e4dc3d551 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-pgsql/values.yaml @@ -0,0 +1,22 @@ +--- +pgsqlService: + podName: pgsql-bee + secretName: pgsql-user + storageSize: 50Gi + image: + name: postgresql-16 + repository: "registry.redhat.io/rhel9/" + pullPolicy: IfNotPresent + tag: "1-1754433677" + service: + name: pgsql + type: ClusterIP + port: 5432 + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: 250m + memory: 512Mi + instances: [] diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/.helmignore b/installer/bundles/iam/charts/tssc-iam-subscriptions/.helmignore new file mode 100644 index 000000000..97d9f7337 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/.helmignore @@ -0,0 +1,19 @@ +.bzr/ +.bzrignore +.DS_Store +.git/ +.gitignore +.hg/ +.hgignore +.idea/ +.project +.svn/ +.vscode/ +*.bak +*.orig +*.swp +*.tmp +*.tmproj +*~ +*.md +hack/ diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/Chart.yaml b/installer/bundles/iam/charts/tssc-iam-subscriptions/Chart.yaml new file mode 100644 index 000000000..37ac3e736 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/Chart.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v2 +name: tssc-iam-subscriptions +description: Keycloak operator subscription (IAM bundle) +type: application +version: "1.9.0" +annotations: + helmet.redhat-appstudio.github.com/depends-on-bundle-charts: tssc-iam-openshift diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/README.md b/installer/bundles/iam/charts/tssc-iam-subscriptions/README.md new file mode 100644 index 000000000..c21edd0a7 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/README.md @@ -0,0 +1,8 @@ +`tssc-iam-subscriptions` (IAM bundle; Keycloak / RHBK operator Subscription) +--------------------- + +This Helm Chart manages OpenShift Operator Hub subscriptions required for TSSC. It leverages `Subscription` and `OperatorGroup` resources. + +During installation, it checks for the existence of the necessary CRDs (Custom Resource Definitions) and waits for the Operator to become available before proceeding. + +The list of subscriptions is defined in the [`values.yaml`](values.yaml) file. diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/hack/find-os-subscription.sh b/installer/bundles/iam/charts/tssc-iam-subscriptions/hack/find-os-subscription.sh new file mode 100755 index 000000000..d5f883c36 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/hack/find-os-subscription.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# +# Finds the subscription for the given catalog name, shows the information needed +# to add the OpenShift operator subscription on this chart. +# + +# OpenShift operator catalog name, for instance "openshift-pipelines-operator-rh". +declare -r CATALOG_NAME="${1:-}" + +if [[ -z "${CATALOG_NAME}" ]]; then + echo "# Usage: $$ $0 " + exit 1 +fi + +# Inspecting the manifests installed on the cluster, looking for the catalog name +# given in parameter. The availability of the operators depends on the OpenShift cluster +# version. +if ! oc get packagemanifests "${CATALOG_NAME}" >/dev/null; then + echo "# ERROR: No subscription found for '${CATALOG_NAME}'!" + exit 1 +fi + +# Extracing the source name, and the namespace where the catalog is available +source=$(oc get packagemanifests "${CATALOG_NAME}" \ + --output="jsonpath={.status.catalogSource}") +namespace=$(oc get packagemanifests "${CATALOG_NAME}" \ + --output="jsonpath={.status.catalogSourceNamespace}") + +# Listing the channels and the startingCSVs available for the catalog. +mapfile -t channels < <(echo -en "$(oc get packagemanifests "${CATALOG_NAME}" \ + --output jsonpath='{range .status.channels[*]}{.name}\n{end}')") +mapfile -t startingCSVs < <(echo -en "$(oc get packagemanifests "${CATALOG_NAME}" \ + --output jsonpath='{range .status.channels[*]}{.currentCSV}\n{end}')") + +cat < +" >&2 +} + +parse_args() { + RESOURCE_SELECTORS=() + while [[ $# -gt 0 ]]; do + case "$1" in + -d | --debug) + set -x + DEBUG="--debug" + export DEBUG + info "Running script as: $(id)" + ;; + -h | --help) + usage + exit 0 + ;; + *) + # The "rollout status" selectors, to find the actual resource to check for + # successful rollout. + RESOURCE_SELECTORS+=("$1") + ;; + esac + shift + done +} + +fail() { + echo "# [ERROR] ${*}" >&2 + exit 1 +} + +warning() { + echo "# [WARNING] ${*}" +} + +info() { + echo "# [INFO] ${*}" +} + +# +# Functions +# + +rollout_status() { + oc rollout status "${RESOURCE_TYPE}" \ + --namespace="${NAMESPACE}" \ + --watch \ + --timeout=10s \ + --selector="${1}" +} + +assert_resource_exists() { + local selector="${1}" + local output + output=$( + oc get "${RESOURCE_TYPE}" \ + --namespace="${NAMESPACE}" \ + --selector="${selector}" 2>&1 + ) + local status=${?} + if [[ $status -eq 0 && $output != "No resources found"* ]]; then + info "Resource of type '${RESOURCE_TYPE}' with selector" \ + "'${selector}' exists in namespace '${NAMESPACE}'!" + return 0 + fi + + warning "Resource of type '${RESOURCE_TYPE}' with selector" \ + "'${selector}' does not exist in namespace '${NAMESPACE}'." + return 1 +} + +wait_for_resource() { + for s in "${RESOURCE_SELECTORS[@]}"; do + echo "# Checking if ${RESOURCE_TYPE} with selector '${s}' exists..." + if ! assert_resource_exists "${s}"; then + return 1 + fi + + echo "# Checking if ${RESOURCE_TYPE} with selector '${s}' is ready..." + if ! rollout_status "${s}"; then + echo -en "#\n# WARNING: ${RESOURCE_TYPE} '${s}' is not ready!\n#\n" + return 1 + fi + info "${RESOURCE_TYPE} objects with '${s}' selector are ready!" + done + return 0 +} + +test_rollout_status() { + [[ -z "${NAMESPACE}" ]] && usage + [[ -z "${RESOURCE_TYPE}" ]] && usage + [[ ${#RESOURCE_SELECTORS[@]} -eq 0 ]] && usage + + for i in $(seq 1 "${RETRIES}"); do + wait=$((i * 5)) + [[ $wait -gt 30 ]] && wait=30 + echo "### [${i}/${RETRIES}] Waiting for ${wait} seconds before retrying..." + sleep ${wait} + + if wait_for_resource; then + info "${RESOURCE_TYPE} objects ready: '${RESOURCE_SELECTORS[*]}'" + return 0 + fi + done + + fail "'${RESOURCE_TYPE}' are not ready!" +} + +main() { + parse_args "$@" + + # Namespace to check for "rollout status". + declare -r NAMESPACE="${NAMESPACE:-}" + # Resource type for "rollout status", as in "statefulset" or "deployment". + declare -r RESOURCE_TYPE="${RESOURCE_TYPE:-statefulset}" + # Number of retries to attempt before giving up. + declare -r RETRIES=${RETRIES:-20} + + test_rollout_status +} + +if [ "${BASH_SOURCE[0]}" == "$0" ]; then + main "$@" + echo + echo "Success" +fi \ No newline at end of file diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/scripts/test-subscriptions.sh b/installer/bundles/iam/charts/tssc-iam-subscriptions/scripts/test-subscriptions.sh new file mode 100755 index 000000000..6a34bcda7 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/scripts/test-subscriptions.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# +# Tests if the requested CRDs are available on the cluster. +# +shopt -s inherit_errexit +set -o errexit +set -o errtrace +set -o nounset +set -o pipefail + +usage() { + echo " +Usage: + ${0##*/} + +Optional arguments: + -d, --debug + Activate tracing/debug mode. + -h, --help + Display this message. + +Example: + ${0##*/} +" >&2 +} + +parse_args() { + CRDS=() + while [[ $# -gt 0 ]]; do + case "$1" in + -d | --debug) + set -x + DEBUG="--debug" + export DEBUG + info "Running script as: $(id)" + ;; + -h | --help) + usage + exit 0 + ;; + *) + CRDS+=("$1") + ;; + esac + shift + done +} + +fail() { + echo "# [ERROR] ${*}" >&2 + exit 1 +} + +info() { + echo "# [INFO] ${*}" +} + +# +# Functions +# + +# Tests if the CRDs are available on the cluster, returns true when all CRDs are +# found, otherwise false. +api_resources_available() { + SUCCESS=0 + for crd in "${CRDS[@]}"; do + if oc get customresourcedefinitions "${crd}" >/dev/null 2>&1; then + echo "# CRD '${crd}' is installed." + else + echo "# ERROR: CRD '${crd}' not visible to this Pod (missing or RBAC)." + oc get customresourcedefinitions "${crd}" 2>&1 | sed 's/^/# oc: /' || true + SUCCESS=1 + fi + done + return "$SUCCESS" +} + +# Verifies the availability of the CRDs, retrying a few times. +test_subscriptions() { + if [[ ${#CRDS[@]} -eq 0 ]]; then + echo "# No operator Subscriptions are Helm-managed for this release; skipping CRD wait." + return 0 + fi + + echo "# Waiting for CRDs to be available: '${CRDS[*]}'" + for i in {1..40}; do + echo "# Check ${i}/40" + if api_resources_available; then + info "# CRDs are available: '${CRDS[*]}'" + return 0 + fi + wait=$((i * 3)) + if [[ ${wait} -gt 60 ]]; then wait=60; fi + echo "# Waiting for ${wait} seconds before retrying..." + sleep ${wait} + done + fail "CRDs not available!" +} + +# +# Main +# +main() { + parse_args "$@" + test_subscriptions +} + +if [ "${BASH_SOURCE[0]}" == "$0" ]; then + main "$@" + echo + echo "Success" +fi diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/NOTES.txt b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/NOTES.txt new file mode 100644 index 000000000..c0c78e410 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/NOTES.txt @@ -0,0 +1,6 @@ +OpenShift Subscriptions: +{{- range $sub := include "subscriptions.enabled" . | fromYaml }} + - Subscription: "{{ $sub.name }}" ({{ $sub.namespace }}) + API-Resource: "{{ $sub.apiResource }}" + Channel: "{{ $sub.channel }}" +{{- end }} diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/_copy-scripts.tpl b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/_copy-scripts.tpl new file mode 100644 index 000000000..91c34ada8 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/_copy-scripts.tpl @@ -0,0 +1,26 @@ +{{/* + + POD container spec to copy scripts. + +*/}} +{{- define "common.copyScripts" -}} +- name: copy-scripts + image: registry.access.redhat.com/ubi10/ubi-minimal:latest + workingDir: /scripts + command: + - /bin/bash + - -c + - | + set -x -e + {{- range $path, $content := .Files.Glob "scripts/*.sh" -}} + {{- $script := trimPrefix "scripts/" $path }} + printf '%s' "{{ $content | toString | b64enc }}" | base64 -d >{{ $script }} + chmod +x {{ $script }} + {{- end }} + volumeMounts: + - name: scripts + mountPath: /scripts + securityContext: + runAsNonRoot: false + allowPrivilegeEscalation: false +{{- end -}} diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/_helpers.tpl b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/_helpers.tpl new file mode 100644 index 000000000..80ca550c8 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "common.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "common.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "common.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "common.labels" -}} +helm.sh/chart: {{ include "common.chart" . }} +{{ include "common.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Need deleted labels +*/}} +{{- define "common.postDeployDeleteLabels" -}} +helmet.redhat-appstudio.github.com/post-deploy: delete +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "common.selectorLabels" -}} +app.kubernetes.io/name: {{ include "common.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "common.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "common.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/_service-account.tpl b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/_service-account.tpl new file mode 100644 index 000000000..9c8e70886 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/_service-account.tpl @@ -0,0 +1,109 @@ +{{- define "common.serviceAccount" -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +{{- end }} +{{- define "common.secretRWRoleBinding" -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Release.Name }}-secret-rw + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - create + - delete + - update + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Release.Name }}-secret-rw + namespace: {{ .notdefaultnamespace | default .Release.Namespace }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Release.Name }}-secret-rw +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Release.Name }}-secret-rw-installer-ns + namespace: {{ .integrationsecretnamespace | default "tssc" }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Release.Name }}-secret-rw +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +{{- end }} +{{- define "common.clusterRoleBinding" -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Release.Name }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +rules: + - apiGroups: + - "*" + resources: + - pods + - jobs + - customresourcedefinitions + - deployments + - statefulsets + - routes + - keycloakrealmimports + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ .Release.Name }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Release.Name }} +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +{{- end }} +{{- define "common.RoleBinding" -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.postDeployDeleteLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ .Release.Name }} +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/_subscriptions.tpl b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/_subscriptions.tpl new file mode 100644 index 000000000..91fd31187 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/_subscriptions.tpl @@ -0,0 +1,24 @@ +{{- /* + Select the enabled subscriptions. +*/ -}} +{{- define "subscriptions.enabled" -}} + {{- $enabled := dict -}} + {{- range $k, $v := .Values.subscriptions -}} + {{- if $v.enabled -}} + {{- $enabled = merge $enabled (dict $k $v) -}} + {{- end -}} + {{- end -}} + {{- $enabled | toYaml -}} +{{- end -}} +{{- /* + Select the managed subscriptions. +*/ -}} +{{- define "subscriptions.managed" -}} + {{- $managed := dict -}} + {{- range $k, $v := .Values.subscriptions -}} + {{- if $v.managed -}} + {{- $managed = merge $managed (dict $k $v) -}} + {{- end -}} + {{- end -}} + {{- $managed | toYaml -}} +{{- end -}} diff --git a/installer/charts/tssc-tas/templates/service-account.yaml b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/service-account.yaml similarity index 100% rename from installer/charts/tssc-tas/templates/service-account.yaml rename to installer/bundles/iam/charts/tssc-iam-subscriptions/templates/service-account.yaml diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/subscriptions.yaml b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/subscriptions.yaml new file mode 100644 index 000000000..5001ebba2 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/subscriptions.yaml @@ -0,0 +1,41 @@ +{{- range $s := include "subscriptions.managed" . | fromYaml }} + {{- if $s.operatorGroup }} +--- +apiVersion: operators.coreos.com/v1 +kind: OperatorGroup +metadata: + annotations: + helm.sh/resource-policy: keep + namespace: {{ $s.namespace }} + name: {{ $s.name }} +spec: + {{- $s.operatorGroup | toYaml | nindent 2 }} + {{- end }} +--- +apiVersion: operators.coreos.com/v1alpha1 +kind: Subscription +metadata: + annotations: + helm.sh/resource-policy: keep + namespace: {{ $s.namespace }} + name: {{ $s.name }} +spec: + name: {{ $s.name }} + channel: {{ $s.channel }} + installPlanApproval: Automatic + source: {{ $s.source }} + sourceNamespace: {{ $s.sourceNamespace }} + {{- with $s.startingCSV }} + startingCSV: {{ . }} + {{- end }} + {{- if $s.config }} + config: + {{- if $s.config.argoCDClusterNamespace }} + env: + - name: ARGOCD_CLUSTER_CONFIG_NAMESPACES + value: "openshift-gitops,{{ $s.config.argoCDClusterNamespace }}" + {{- else }} + {{- $s.config | toYaml | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/tests/_test.tpl b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/tests/_test.tpl new file mode 100644 index 000000000..a5ef42091 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/tests/_test.tpl @@ -0,0 +1,24 @@ +{{- define "common.test" -}} +apiVersion: v1 +kind: Pod +metadata: + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + labels: + {{- include "common.labels" . | nindent 4 }} + name: test-{{ .name | default .Chart.Name }} + namespace: {{ .namespace | default .Release.Namespace }} +spec: + restartPolicy: Never + serviceAccountName: {{ .serviceAccount | default .Release.Name }} + initContainers: + # + # Copying the scripts that will be used on the subsequent containers, the + # scripts are shared via the "/scripts" volume. + # +{{- include "common.copyScripts" . | nindent 4 }} + volumes: + - name: scripts + emptyDir: {} +{{- end -}} \ No newline at end of file diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/tests/test.yaml b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/tests/test.yaml new file mode 100644 index 000000000..38306913d --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/templates/tests/test.yaml @@ -0,0 +1,46 @@ +--- +{{- include "common.test" . }} + containers: + # + # Tests the subcriptions CRDs. + # + - name: test-subscriptions-crds + image: registry.redhat.io/openshift4/ose-tools-rhel9:v4.19 + command: + - /scripts/test-subscriptions.sh + args: +{{- range $sub := include "subscriptions.managed" . | fromYaml }} + {{- if (dig "apiResource" "" $sub) }} + - {{ $sub.apiResource | quote }} + {{- end }} +{{- end }} + volumeMounts: + - name: scripts + mountPath: /scripts + securityContext: + runAsNonRoot: false + allowPrivilegeEscalation: false +{{- range $sub := include "subscriptions.managed" . | fromYaml }} + # + # Tests the {{ $sub.name }} rollout status. + # + - name: {{ printf "test-%s" ($sub.name | lower) }} + image: registry.redhat.io/openshift4/ose-tools-rhel9:v4.19 + env: + - name: NAMESPACE + value: {{ $sub.namespace }} + - name: RESOURCE_TYPE + value: "deployment" + - name: RETRIES + value: "15" + command: + - /scripts/test-rollout-status.sh + args: + - olm.managed=true + volumeMounts: + - name: scripts + mountPath: /scripts + securityContext: + runAsNonRoot: false + allowPrivilegeEscalation: false +{{- end }} diff --git a/installer/bundles/iam/charts/tssc-iam-subscriptions/values.yaml b/installer/bundles/iam/charts/tssc-iam-subscriptions/values.yaml new file mode 100644 index 000000000..9bd8ace7b --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam-subscriptions/values.yaml @@ -0,0 +1,16 @@ +--- +# Chart-local defaults for the Red Hat Keycloak operator Subscription only. +# Install-time values come from ../../values.yaml.tpl (merged by Helmet). +subscriptions: + openshiftKeycloak: + enabled: false + description: Red Hat Keycloak Operator + apiResource: keycloaks.k8s.keycloak.org + namespace: rhbk-operator + name: rhbk-operator + channel: stable-v26.4 + source: redhat-operators + sourceNamespace: openshift-marketplace + operatorGroup: + upgradeStrategy: Default + targetNamespaces: [] diff --git a/installer/bundles/iam/charts/tssc-iam/.helmignore b/installer/bundles/iam/charts/tssc-iam/.helmignore new file mode 100644 index 000000000..51b1fe1c3 --- /dev/null +++ b/installer/bundles/iam/charts/tssc-iam/.helmignore @@ -0,0 +1,17 @@ +.bzr/ +.bzrignore +.DS_Store +.git/ +.gitignore +.hg/ +.hgignore +.idea/ +.project +.svn/ +.vscode/ +*.bak +*.orig +*.swp +*.tmp +*.tmproj +*~ diff --git a/installer/charts/tssc-iam/CONTRIBUTING.md b/installer/bundles/iam/charts/tssc-iam/CONTRIBUTING.md similarity index 100% rename from installer/charts/tssc-iam/CONTRIBUTING.md rename to installer/bundles/iam/charts/tssc-iam/CONTRIBUTING.md diff --git a/installer/charts/tssc-iam/Chart.yaml b/installer/bundles/iam/charts/tssc-iam/Chart.yaml similarity index 50% rename from installer/charts/tssc-iam/Chart.yaml rename to installer/bundles/iam/charts/tssc-iam/Chart.yaml index 6aa9da668..d958d4e7a 100644 --- a/installer/charts/tssc-iam/Chart.yaml +++ b/installer/bundles/iam/charts/tssc-iam/Chart.yaml @@ -5,5 +5,6 @@ description: TSSC IAM type: application version: "1.9.0" annotations: - helmet.redhat-appstudio.github.com/depends-on: tssc-openshift, tssc-subscriptions, tssc-infrastructure + helmet.redhat-appstudio.github.com/product-name: IAM + helmet.redhat-appstudio.github.com/depends-on-bundle-charts: tssc-iam-openshift, tssc-iam-subscriptions, tssc-iam-pgsql helmet.redhat-appstudio.github.com/integrations-provided: trustificationauth diff --git a/installer/charts/tssc-iam/README.md b/installer/bundles/iam/charts/tssc-iam/README.md similarity index 100% rename from installer/charts/tssc-iam/README.md rename to installer/bundles/iam/charts/tssc-iam/README.md diff --git a/installer/charts/tssc-iam/hack/generate-keycloakrealmimport.py b/installer/bundles/iam/charts/tssc-iam/hack/generate-keycloakrealmimport.py similarity index 100% rename from installer/charts/tssc-iam/hack/generate-keycloakrealmimport.py rename to installer/bundles/iam/charts/tssc-iam/hack/generate-keycloakrealmimport.py diff --git a/installer/charts/tssc-iam/scripts/test-keycloakrealmimport.sh b/installer/bundles/iam/charts/tssc-iam/scripts/test-keycloakrealmimport.sh similarity index 100% rename from installer/charts/tssc-iam/scripts/test-keycloakrealmimport.sh rename to installer/bundles/iam/charts/tssc-iam/scripts/test-keycloakrealmimport.sh diff --git a/installer/charts/tssc-acs-test/scripts/test-rollout-status.sh b/installer/bundles/iam/charts/tssc-iam/scripts/test-rollout-status.sh similarity index 100% rename from installer/charts/tssc-acs-test/scripts/test-rollout-status.sh rename to installer/bundles/iam/charts/tssc-iam/scripts/test-rollout-status.sh diff --git a/installer/charts/tssc-iam/templates/NOTES.txt b/installer/bundles/iam/charts/tssc-iam/templates/NOTES.txt similarity index 100% rename from installer/charts/tssc-iam/templates/NOTES.txt rename to installer/bundles/iam/charts/tssc-iam/templates/NOTES.txt diff --git a/installer/charts/tssc-acs-test/templates/_copy-scripts.tpl b/installer/bundles/iam/charts/tssc-iam/templates/_copy-scripts.tpl similarity index 100% rename from installer/charts/tssc-acs-test/templates/_copy-scripts.tpl rename to installer/bundles/iam/charts/tssc-iam/templates/_copy-scripts.tpl diff --git a/installer/charts/tssc-acs-test/templates/_helpers.tpl b/installer/bundles/iam/charts/tssc-iam/templates/_helpers.tpl similarity index 100% rename from installer/charts/tssc-acs-test/templates/_helpers.tpl rename to installer/bundles/iam/charts/tssc-iam/templates/_helpers.tpl diff --git a/installer/charts/tssc-iam/templates/_keycloak.tpl b/installer/bundles/iam/charts/tssc-iam/templates/_keycloak.tpl similarity index 100% rename from installer/charts/tssc-iam/templates/_keycloak.tpl rename to installer/bundles/iam/charts/tssc-iam/templates/_keycloak.tpl diff --git a/installer/charts/tssc-iam/templates/_realm.tpl b/installer/bundles/iam/charts/tssc-iam/templates/_realm.tpl similarity index 100% rename from installer/charts/tssc-iam/templates/_realm.tpl rename to installer/bundles/iam/charts/tssc-iam/templates/_realm.tpl diff --git a/installer/charts/tssc-acs-test/templates/_service-account.tpl b/installer/bundles/iam/charts/tssc-iam/templates/_service-account.tpl similarity index 100% rename from installer/charts/tssc-acs-test/templates/_service-account.tpl rename to installer/bundles/iam/charts/tssc-iam/templates/_service-account.tpl diff --git a/installer/charts/tssc-iam/templates/keycloak.yaml b/installer/bundles/iam/charts/tssc-iam/templates/keycloak.yaml similarity index 100% rename from installer/charts/tssc-iam/templates/keycloak.yaml rename to installer/bundles/iam/charts/tssc-iam/templates/keycloak.yaml diff --git a/installer/charts/tssc-iam/templates/realm.yaml b/installer/bundles/iam/charts/tssc-iam/templates/realm.yaml similarity index 97% rename from installer/charts/tssc-iam/templates/realm.yaml rename to installer/bundles/iam/charts/tssc-iam/templates/realm.yaml index d00ad2cd3..9bd6f3ae4 100644 --- a/installer/charts/tssc-iam/templates/realm.yaml +++ b/installer/bundles/iam/charts/tssc-iam/templates/realm.yaml @@ -212,12 +212,10 @@ spec: authenticationExecutions: - authenticator: idp-email-verification authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: ALTERNATIVE userSetupAllowed: false - authenticatorFlow: true - autheticatorFlow: true flowAlias: Verify Existing Account by Re-authentication priority: 20 requirement: ALTERNATIVE @@ -230,19 +228,16 @@ spec: authenticationExecutions: - authenticator: basic-auth authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false - authenticator: basic-auth-otp authenticatorFlow: false - autheticatorFlow: false priority: 20 requirement: DISABLED userSetupAllowed: false - authenticator: auth-spnego authenticatorFlow: false - autheticatorFlow: false priority: 30 requirement: DISABLED userSetupAllowed: false @@ -254,13 +249,11 @@ spec: authenticationExecutions: - authenticator: conditional-user-configured authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false - authenticator: auth-otp-form authenticatorFlow: false - autheticatorFlow: false priority: 20 requirement: REQUIRED userSetupAllowed: false @@ -272,13 +265,11 @@ spec: authenticationExecutions: - authenticator: conditional-user-configured authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false - authenticator: direct-grant-validate-otp authenticatorFlow: false - autheticatorFlow: false priority: 20 requirement: REQUIRED userSetupAllowed: false @@ -290,13 +281,11 @@ spec: authenticationExecutions: - authenticator: conditional-user-configured authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false - authenticator: auth-otp-form authenticatorFlow: false - autheticatorFlow: false priority: 20 requirement: REQUIRED userSetupAllowed: false @@ -308,12 +297,10 @@ spec: authenticationExecutions: - authenticator: idp-confirm-link authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false - authenticatorFlow: true - autheticatorFlow: true flowAlias: Account verification options priority: 20 requirement: REQUIRED @@ -328,13 +315,11 @@ spec: authenticationExecutions: - authenticator: conditional-user-configured authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false - authenticator: reset-otp authenticatorFlow: false - autheticatorFlow: false priority: 20 requirement: REQUIRED userSetupAllowed: false @@ -349,12 +334,10 @@ spec: - authenticator: idp-create-user-if-unique authenticatorConfig: create unique user config authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: ALTERNATIVE userSetupAllowed: false - authenticatorFlow: true - autheticatorFlow: true flowAlias: Handle Existing Account priority: 20 requirement: ALTERNATIVE @@ -367,12 +350,10 @@ spec: authenticationExecutions: - authenticator: idp-username-password-form authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false - authenticatorFlow: true - autheticatorFlow: true flowAlias: First broker login - Conditional OTP priority: 20 requirement: CONDITIONAL @@ -385,24 +366,20 @@ spec: authenticationExecutions: - authenticator: auth-cookie authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: ALTERNATIVE userSetupAllowed: false - authenticator: auth-spnego authenticatorFlow: false - autheticatorFlow: false priority: 20 requirement: DISABLED userSetupAllowed: false - authenticator: identity-provider-redirector authenticatorFlow: false - autheticatorFlow: false priority: 25 requirement: ALTERNATIVE userSetupAllowed: false - authenticatorFlow: true - autheticatorFlow: true flowAlias: forms priority: 30 requirement: ALTERNATIVE @@ -415,25 +392,21 @@ spec: authenticationExecutions: - authenticator: client-secret authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: ALTERNATIVE userSetupAllowed: false - authenticator: client-jwt authenticatorFlow: false - autheticatorFlow: false priority: 20 requirement: ALTERNATIVE userSetupAllowed: false - authenticator: client-secret-jwt authenticatorFlow: false - autheticatorFlow: false priority: 30 requirement: ALTERNATIVE userSetupAllowed: false - authenticator: client-x509 authenticatorFlow: false - autheticatorFlow: false priority: 40 requirement: ALTERNATIVE userSetupAllowed: false @@ -445,18 +418,15 @@ spec: authenticationExecutions: - authenticator: direct-grant-validate-username authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false - authenticator: direct-grant-validate-password authenticatorFlow: false - autheticatorFlow: false priority: 20 requirement: REQUIRED userSetupAllowed: false - authenticatorFlow: true - autheticatorFlow: true flowAlias: Direct Grant - Conditional OTP priority: 30 requirement: CONDITIONAL @@ -469,7 +439,6 @@ spec: authenticationExecutions: - authenticator: docker-http-basic-authenticator authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false @@ -482,12 +451,10 @@ spec: - authenticator: idp-review-profile authenticatorConfig: review profile config authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false - authenticatorFlow: true - autheticatorFlow: true flowAlias: User creation or linking priority: 20 requirement: REQUIRED @@ -502,12 +469,10 @@ spec: authenticationExecutions: - authenticator: auth-username-password-form authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false - authenticatorFlow: true - autheticatorFlow: true flowAlias: Browser - Conditional OTP priority: 20 requirement: CONDITIONAL @@ -520,12 +485,10 @@ spec: authenticationExecutions: - authenticator: no-cookie-redirect authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false - authenticatorFlow: true - autheticatorFlow: true flowAlias: Authentication Options priority: 20 requirement: REQUIRED @@ -540,7 +503,6 @@ spec: authenticationExecutions: - authenticator: registration-page-form authenticatorFlow: true - autheticatorFlow: true flowAlias: registration form priority: 10 requirement: REQUIRED @@ -553,25 +515,21 @@ spec: authenticationExecutions: - authenticator: registration-user-creation authenticatorFlow: false - autheticatorFlow: false priority: 20 requirement: REQUIRED userSetupAllowed: false - authenticator: registration-profile-action authenticatorFlow: false - autheticatorFlow: false priority: 40 requirement: REQUIRED userSetupAllowed: false - authenticator: registration-password-action authenticatorFlow: false - autheticatorFlow: false priority: 50 requirement: REQUIRED userSetupAllowed: false - authenticator: registration-recaptcha-action authenticatorFlow: false - autheticatorFlow: false priority: 60 requirement: DISABLED userSetupAllowed: false @@ -583,24 +541,20 @@ spec: authenticationExecutions: - authenticator: reset-credentials-choose-user authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false - authenticator: reset-credential-email authenticatorFlow: false - autheticatorFlow: false priority: 20 requirement: REQUIRED userSetupAllowed: false - authenticator: reset-password authenticatorFlow: false - autheticatorFlow: false priority: 30 requirement: REQUIRED userSetupAllowed: false - authenticatorFlow: true - autheticatorFlow: true flowAlias: Reset - Conditional OTP priority: 40 requirement: CONDITIONAL @@ -613,7 +567,6 @@ spec: authenticationExecutions: - authenticator: http-basic-authenticator authenticatorFlow: false - autheticatorFlow: false priority: 10 requirement: REQUIRED userSetupAllowed: false diff --git a/installer/charts/tssc-iam/templates/route.yaml b/installer/bundles/iam/charts/tssc-iam/templates/route.yaml similarity index 100% rename from installer/charts/tssc-iam/templates/route.yaml rename to installer/bundles/iam/charts/tssc-iam/templates/route.yaml diff --git a/installer/charts/tssc-iam/templates/service-account.yaml b/installer/bundles/iam/charts/tssc-iam/templates/service-account.yaml similarity index 100% rename from installer/charts/tssc-iam/templates/service-account.yaml rename to installer/bundles/iam/charts/tssc-iam/templates/service-account.yaml diff --git a/installer/charts/tssc-iam/templates/service.yaml b/installer/bundles/iam/charts/tssc-iam/templates/service.yaml similarity index 100% rename from installer/charts/tssc-iam/templates/service.yaml rename to installer/bundles/iam/charts/tssc-iam/templates/service.yaml diff --git a/installer/charts/tssc-acs-test/templates/tests/_test.tpl b/installer/bundles/iam/charts/tssc-iam/templates/tests/_test.tpl similarity index 100% rename from installer/charts/tssc-acs-test/templates/tests/_test.tpl rename to installer/bundles/iam/charts/tssc-iam/templates/tests/_test.tpl diff --git a/installer/charts/tssc-iam/templates/tests/test.yaml b/installer/bundles/iam/charts/tssc-iam/templates/tests/test.yaml similarity index 100% rename from installer/charts/tssc-iam/templates/tests/test.yaml rename to installer/bundles/iam/charts/tssc-iam/templates/tests/test.yaml diff --git a/installer/charts/tssc-iam/values.yaml b/installer/bundles/iam/charts/tssc-iam/values.yaml similarity index 100% rename from installer/charts/tssc-iam/values.yaml rename to installer/bundles/iam/charts/tssc-iam/values.yaml diff --git a/installer/bundles/iam/config.yaml b/installer/bundles/iam/config.yaml new file mode 100644 index 000000000..ed9cf0355 --- /dev/null +++ b/installer/bundles/iam/config.yaml @@ -0,0 +1,6 @@ +name: IAM +namespace: tssc-keycloak +properties: + # When false, Keycloak operator Subscription/OperatorGroup are not managed by + # tssc-iam-subscriptions (use if they already exist from a prior tssc-subscriptions release). + manageSubscription: true diff --git a/installer/bundles/iam/scripts/test-keycloakrealmimport.sh b/installer/bundles/iam/scripts/test-keycloakrealmimport.sh new file mode 100755 index 000000000..655d59ba8 --- /dev/null +++ b/installer/bundles/iam/scripts/test-keycloakrealmimport.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# +# Tests if the given KeycloakRealmImports are imported without errors. +# +shopt -s inherit_errexit +set -o errexit +set -o errtrace +set -o nounset +set -o pipefail + +usage() { + echo " +Usage: + ${0##*/} + +Optional arguments: + -d, --debug + Activate tracing/debug mode. + -h, --help + Display this message. + +Example: + ${0##*/} +" >&2 +} + +parse_args() { + KEYCLOAKREALMIMPORT_NAMES=() + while [[ $# -gt 0 ]]; do + case "$1" in + -d | --debug) + set -x + DEBUG="--debug" + export DEBUG + info "Running script as: $(id)" + ;; + -h | --help) + usage + exit 0 + ;; + *) + # List of KeycloakRealmImports to test. + KEYCLOAKREALMIMPORT_NAMES+=("$1") + ;; + esac + shift + done +} + +fail() { + echo "# [ERROR] ${*}" >&2 + exit 1 +} + +info() { + echo "# [INFO] ${*}" +} + +# +# Functions +# + +keycloakrealmimport_available() { + for r in "${KEYCLOAKREALMIMPORT_NAMES[@]}"; do + info "Checking if KeycloakRealmImport '${r}' has errors..." + if ! oc get keycloakrealmimports "${r}" \ + --namespace="${NAMESPACE}" &>/dev/null; then + echo "# [ERROR] KeycloakRealmImport '${r}' not found!" + return 1 + fi + + has_errors="$( + oc get keycloakrealmimports "${r}" \ + --namespace="${NAMESPACE}" \ + --output=jsonpath='{.status.conditions[?(@.type=="HasErrors")].status}' + )" + info "KeycloakRealmImport '${r}' condition='HasErrors=${has_errors}'" + + if [[ "${has_errors}" == "True" ]]; then + return 1 + fi + done + return 0 +} + +test_keycloakrealmimport() { + if [[ -z "${NAMESPACE}" ]]; then + fail "Usage: NAMESPACE=namespace $0 " + fi + + if [[ ${#KEYCLOAKREALMIMPORT_NAMES[@]} -eq 0 ]]; then + fail "Usage: $0 " + fi + + for i in {1..10}; do + if keycloakrealmimport_available; then + info "KeycloakRealmImports are available: '${KEYCLOAKREALMIMPORT_NAMES[*]}'" + return 0 + fi + wait=$((i * 1)) + echo -e "### [${i}/10] Waiting for ${wait} seconds before retrying...\n" + sleep ${wait} + done + fail "KeycloakRealmImports not available!" +} + +main() { + parse_args "$@" + + # Namespace to check for KeycloakRealmImports. + declare -r NAMESPACE="${NAMESPACE:-}" + + test_keycloakrealmimport +} + +if [ "${BASH_SOURCE[0]}" == "$0" ]; then + main "$@" + echo + echo "Success" +fi diff --git a/installer/bundles/iam/scripts/test-rollout-status.sh b/installer/bundles/iam/scripts/test-rollout-status.sh new file mode 100755 index 000000000..1b9595e15 --- /dev/null +++ b/installer/bundles/iam/scripts/test-rollout-status.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +# +# Runs "oc rollout status" for configured namespace, resource type, and selectors. +# +shopt -s inherit_errexit +set -o errexit +set -o errtrace +set -o nounset +set -o pipefail + +usage() { + echo " +Usage: + ${0##*/} + +Optional arguments: + -d, --debug + Activate tracing/debug mode. + -h, --help + Display this message. + +Example: + \$ export NAMESPACE=\"namespace\" + \$ export RESOURCE_TYPE=\"deployment\" + ${0##*/} +" >&2 +} + +parse_args() { + RESOURCE_SELECTORS=() + while [[ $# -gt 0 ]]; do + case "$1" in + -d | --debug) + set -x + DEBUG="--debug" + export DEBUG + info "Running script as: $(id)" + ;; + -h | --help) + usage + exit 0 + ;; + *) + # The "rollout status" selectors, to find the actual resource to check for + # successful rollout. + RESOURCE_SELECTORS+=("$1") + ;; + esac + shift + done +} + +fail() { + echo "# [ERROR] ${*}" >&2 + exit 1 +} + +warning() { + echo "# [WARNING] ${*}" +} + +info() { + echo "# [INFO] ${*}" +} + +# +# Functions +# + +rollout_status() { + oc rollout status "${RESOURCE_TYPE}" \ + --namespace="${NAMESPACE}" \ + --watch \ + --timeout=10s \ + --selector="${1}" +} + +assert_resource_exists() { + local selector="${1}" + local output + output=$( + oc get "${RESOURCE_TYPE}" \ + --namespace="${NAMESPACE}" \ + --selector="${selector}" 2>&1 + ) + local status=${?} + if [[ $status -eq 0 && $output != "No resources found"* ]]; then + info "Resource of type '${RESOURCE_TYPE}' with selector" \ + "'${selector}' exists in namespace '${NAMESPACE}'!" + return 0 + fi + + warning "Resource of type '${RESOURCE_TYPE}' with selector" \ + "'${selector}' does not exist in namespace '${NAMESPACE}'." + return 1 +} + +wait_for_resource() { + for s in "${RESOURCE_SELECTORS[@]}"; do + echo "# Checking if ${RESOURCE_TYPE} with selector '${s}' exists..." + if ! assert_resource_exists "${s}"; then + return 1 + fi + + echo "# Checking if ${RESOURCE_TYPE} with selector '${s}' is ready..." + if ! rollout_status "${s}"; then + echo -en "#\n# WARNING: ${RESOURCE_TYPE} '${s}' is not ready!\n#\n" + return 1 + fi + info "${RESOURCE_TYPE} objects with '${s}' selector are ready!" + done + return 0 +} + +test_rollout_status() { + [[ -z "${NAMESPACE}" ]] && usage + [[ -z "${RESOURCE_TYPE}" ]] && usage + [[ ${#RESOURCE_SELECTORS[@]} -eq 0 ]] && usage + + for i in $(seq 1 "${RETRIES}"); do + wait=$((i * 5)) + [[ $wait -gt 30 ]] && wait=30 + echo "### [${i}/${RETRIES}] Waiting for ${wait} seconds before retrying..." + sleep ${wait} + + if wait_for_resource; then + info "${RESOURCE_TYPE} objects ready: '${RESOURCE_SELECTORS[*]}'" + return 0 + fi + done + + fail "'${RESOURCE_TYPE}' are not ready!" +} + +main() { + parse_args "$@" + + # Namespace to check for "rollout status". + declare -r NAMESPACE="${NAMESPACE:-}" + # Resource type for "rollout status", as in "statefulset" or "deployment". + declare -r RESOURCE_TYPE="${RESOURCE_TYPE:-statefulset}" + # Number of retries to attempt before giving up. + declare -r RETRIES=${RETRIES:-20} + + test_rollout_status +} + +if [ "${BASH_SOURCE[0]}" == "$0" ]; then + main "$@" + echo + echo "Success" +fi \ No newline at end of file diff --git a/installer/bundles/iam/values.yaml.tpl b/installer/bundles/iam/values.yaml.tpl new file mode 100644 index 000000000..680174c72 --- /dev/null +++ b/installer/bundles/iam/values.yaml.tpl @@ -0,0 +1,152 @@ +{{- $crc := required "CRC settings" .Installer.Settings.crc -}} +{{- $tas := required "TAS settings" .Installer.Products.Trusted_Artifact_Signer -}} +{{- $rhdh := required "RHDH settings" .Installer.Products.Developer_Hub -}} +{{- $ingressDomain := required "OpenShift ingress domain" .OpenShift.Ingress.Domain -}} +{{- $authProvider := required "Auth Provider is required" $rhdh.Properties.authProvider }} +{{- $instNs := .Installer.Namespace -}} +{{- $tasActive := true -}} +{{- if and (kindIs "bool" $tas.Enabled) (eq $tas.Enabled false) }}{{- $tasActive = false -}}{{- end -}} +{{- $rhdhActive := true -}} +{{- if and (kindIs "bool" $rhdh.Enabled) (eq $rhdh.Enabled false) }}{{- $rhdhActive = false -}}{{- end -}} +{{- $tpa := index .Installer.Products "Trusted_Profile_Analyzer" -}} +{{- $tpaIntegration := index .Installer.Integrations "tpa" -}} +{{- $tpaBundleMode := "product" -}} +{{- if not $tpa }}{{- $tpaBundleMode = "integration" -}}{{- end -}} +{{- if $tpa -}} +{{- if and (kindIs "bool" $tpa.Enabled) (eq $tpa.Enabled false) }}{{- $tpaBundleMode = "integration" -}}{{- end -}} +{{- end -}} +{{- $tpaActive := false -}} +{{- if eq $tpaBundleMode "product" -}} +{{- $tpaActive = true -}} +{{- if and $tpa (kindIs "bool" $tpa.Enabled) (eq $tpa.Enabled false) }}{{- $tpaActive = false -}}{{- end -}} +{{- end -}} +{{- $tpaNs := "tssc-tpa" -}} +{{- if eq $tpaBundleMode "product" -}} +{{- $tpaNs = default $instNs $tpa.Namespace -}} +{{- else if $tpa -}} +{{- $tpaNs = default "tssc-tpa" $tpa.Namespace -}} +{{- end -}} +{{- $tpaRealmEnabled := or $tpaActive (not (empty $tpaIntegration)) -}} +{{- $keycloakEnabled := or $tpaActive $tasActive (and $rhdhActive (eq $authProvider "oidc")) (not (empty $tpaIntegration)) }} +{{- $keycloakNamespace := "tssc-keycloak" -}} +{{- $keycloakRouteTLSSecretName := "keycloak-tls" }} +{{- $keycloakRouteHost := printf "tssc-sso.%s" $ingressDomain }} +{{- $realmsName := "tssc-iam" }} +{{- $tpaTestingUsersEnabled := false }} +{{- $protocol := "https" -}} +{{- if $crc }} + {{- $protocol = "http" }} +{{- end }} +{{- $tpaOIDCIssuerURL := printf "%s://%s/realms/%s" $protocol $keycloakRouteHost $realmsName }} +{{- $iam := index .Installer.Products "IAM" }} +{{- $iamManageSub := true -}} +{{- if $iam }} + {{- $props := index $iam "Properties" }} + {{- if and $props (kindIs "map" $props) }} + {{- $ms := index $props "manageSubscription" }} + {{- if kindIs "bool" $ms }} + {{- $iamManageSub = $ms }} + {{- end }} + {{- end }} +{{- end }} +--- +openshift: + projects: +{{- if $keycloakEnabled }} + - {{ $keycloakNamespace }} + {{- if $iamManageSub }} + - rhbk-operator + {{- end }} +{{- end }} + +subscriptions: + openshiftKeycloak: + enabled: {{ $keycloakEnabled }} + managed: {{ and $keycloakEnabled $iamManageSub }} + operatorGroup: + targetNamespaces: + - {{ default "empty" $keycloakNamespace }} + +pgsqlService: + podName: pgsql-bee + secretName: pgsql-user + storageSize: 50Gi + image: + name: postgresql-16 + repository: "registry.redhat.io/rhel9/" + pullPolicy: IfNotPresent + tag: "1-1754433677" + service: + name: pgsql + type: ClusterIP + port: 5432 + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: 250m + memory: 512Mi + instances: + - name: keycloak + enabled: {{ $keycloakEnabled }} + namespace: {{ $keycloakNamespace }} + +iam: + enabled: {{ $keycloakEnabled }} + namespace: {{ $keycloakNamespace }} + integrationSecret: + namespace: {{ .Installer.Namespace }} + instances: 1 + database: + host: keycloak-pgsql + name: keycloak + secretName: keycloak-pgsql-user + route: + host: {{ $keycloakRouteHost }} + tls: + enabled: {{ not $crc }} + secretName: {{ $keycloakRouteTLSSecretName }} + termination: reencrypt +{{- if $crc }} + annotations: + route.openshift.io/termination: reencrypt +{{- end }} + service: + annotations: + service.beta.openshift.io/serving-cert-secret-name: {{ $keycloakRouteTLSSecretName }} + keycloakCR: + namespace: {{ $keycloakNamespace }} + ingressDomain: {{ $ingressDomain }} + rhdhRealm: + enabled: {{ and $rhdhActive (eq $authProvider "oidc") }} + rhdhRedirectUris: + - {{ + printf "%s://backstage-developer-hub-tssc-dh.%s/api/auth/oidc/handler/frame" + $protocol + $ingressDomain + }} + rhdhOriginUris: + - {{ + printf "%s://backstage-developer-hub-tssc-dh.%s" + $protocol + $ingressDomain + }} + trustedArtifactSignerRealm: + enabled: {{ $tasActive }} + trustedProfileAnalyzerRealm: + enabled: {{ $tpaRealmEnabled }} + oidcIssuerURL: {{ $tpaOIDCIssuerURL }} + clients: + cli: + enabled: true + testingManager: + enabled: {{ $tpaTestingUsersEnabled }} + testingUser: + enabled: {{ $tpaTestingUsersEnabled }} + frontendRedirectUris: + - "http://localhost:8080" + {{- range list "server" "sbom" }} + - "{{ printf "%s://%s-%s.%s" $protocol . $tpaNs $ingressDomain }}" + - "{{ printf "%s://%s-%s.%s/*" $protocol . $tpaNs $ingressDomain }}" + {{- end }} diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-config/.helmignore b/installer/bundles/pipelines/charts/tssc-pipelines-config/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-config/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-config/Chart.yaml b/installer/bundles/pipelines/charts/tssc-pipelines-config/Chart.yaml new file mode 100644 index 000000000..0c455a88c --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-config/Chart.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: v2 +name: tssc-pipelines-config +description: TSSC Pipelines Config +type: application +version: "1.9.0" +appVersion: "1.21" +annotations: + helmet.redhat-appstudio.github.com/depends-on-bundle-charts: tssc-pipelines-openshift, tssc-pipelines-subscriptions + helmet.redhat-appstudio.github.com/depends-on-bundles: tas diff --git a/installer/charts/tssc-pipelines-config/README.md b/installer/bundles/pipelines/charts/tssc-pipelines-config/README.md similarity index 100% rename from installer/charts/tssc-pipelines-config/README.md rename to installer/bundles/pipelines/charts/tssc-pipelines-config/README.md diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-config/scripts/test-rollout-status.sh b/installer/bundles/pipelines/charts/tssc-pipelines-config/scripts/test-rollout-status.sh new file mode 120000 index 000000000..17af939cd --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-config/scripts/test-rollout-status.sh @@ -0,0 +1 @@ +../../../../../scripts/test-rollout-status.sh \ No newline at end of file diff --git a/installer/charts/tssc-pipelines-config/templates/NOTES.txt b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/NOTES.txt similarity index 100% rename from installer/charts/tssc-pipelines-config/templates/NOTES.txt rename to installer/bundles/pipelines/charts/tssc-pipelines-config/templates/NOTES.txt diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/_copy-scripts.tpl b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/_copy-scripts.tpl new file mode 120000 index 000000000..e37b737cc --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/_copy-scripts.tpl @@ -0,0 +1 @@ +../../../../../charts/_common/_copy-scripts.tpl \ No newline at end of file diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/_helpers.tpl b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/_helpers.tpl new file mode 120000 index 000000000..a0020022c --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/_helpers.tpl @@ -0,0 +1 @@ +../../../../../charts/_common/_helpers.tpl \ No newline at end of file diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/_service-account.tpl b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/_service-account.tpl new file mode 120000 index 000000000..e89b996d3 --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/_service-account.tpl @@ -0,0 +1 @@ +../../../../../charts/_common/_service-account.tpl \ No newline at end of file diff --git a/installer/charts/tssc-tpa/templates/service-account.yaml b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/service-account.yaml similarity index 100% rename from installer/charts/tssc-tpa/templates/service-account.yaml rename to installer/bundles/pipelines/charts/tssc-pipelines-config/templates/service-account.yaml diff --git a/installer/charts/tssc-pipelines-config/templates/tektonconfig/default._tpl b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/tektonconfig/default._tpl similarity index 100% rename from installer/charts/tssc-pipelines-config/templates/tektonconfig/default._tpl rename to installer/bundles/pipelines/charts/tssc-pipelines-config/templates/tektonconfig/default._tpl diff --git a/installer/charts/tssc-pipelines-config/templates/tektonconfig/patch._tpl b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/tektonconfig/patch._tpl similarity index 100% rename from installer/charts/tssc-pipelines-config/templates/tektonconfig/patch._tpl rename to installer/bundles/pipelines/charts/tssc-pipelines-config/templates/tektonconfig/patch._tpl diff --git a/installer/charts/tssc-pipelines-config/templates/tektonconfig/tektonconfig.yaml b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/tektonconfig/tektonconfig.yaml similarity index 100% rename from installer/charts/tssc-pipelines-config/templates/tektonconfig/tektonconfig.yaml rename to installer/bundles/pipelines/charts/tssc-pipelines-config/templates/tektonconfig/tektonconfig.yaml diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/tests/_test.tpl b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/tests/_test.tpl new file mode 120000 index 000000000..f3c567eac --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/tests/_test.tpl @@ -0,0 +1 @@ +../../../../../../charts/_common/_test.tpl \ No newline at end of file diff --git a/installer/charts/tssc-pipelines-config/templates/tests/test.yaml b/installer/bundles/pipelines/charts/tssc-pipelines-config/templates/tests/test.yaml similarity index 100% rename from installer/charts/tssc-pipelines-config/templates/tests/test.yaml rename to installer/bundles/pipelines/charts/tssc-pipelines-config/templates/tests/test.yaml diff --git a/installer/charts/tssc-pipelines-config/values.yaml b/installer/bundles/pipelines/charts/tssc-pipelines-config/values.yaml similarity index 100% rename from installer/charts/tssc-pipelines-config/values.yaml rename to installer/bundles/pipelines/charts/tssc-pipelines-config/values.yaml diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-openshift/.helmignore b/installer/bundles/pipelines/charts/tssc-pipelines-openshift/.helmignore new file mode 100644 index 000000000..51b1fe1c3 --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-openshift/.helmignore @@ -0,0 +1,17 @@ +.bzr/ +.bzrignore +.DS_Store +.git/ +.gitignore +.hg/ +.hgignore +.idea/ +.project +.svn/ +.vscode/ +*.bak +*.orig +*.swp +*.tmp +*.tmproj +*~ diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-openshift/Chart.yaml b/installer/bundles/pipelines/charts/tssc-pipelines-openshift/Chart.yaml new file mode 100644 index 000000000..1adca747a --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-openshift/Chart.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: v2 +name: tssc-pipelines-openshift +description: OpenShift project requests for the OpenShift Pipelines bundle +type: application +version: "1.9.0" diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-openshift/templates/NOTES.txt b/installer/bundles/pipelines/charts/tssc-pipelines-openshift/templates/NOTES.txt new file mode 100644 index 000000000..e13ce3dd1 --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-openshift/templates/NOTES.txt @@ -0,0 +1,4 @@ +OpenShift Projects: +{{- range $p := .Values.openshift.projects | sortAlpha }} + - {{ $p | quote }} +{{- end }} diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-openshift/templates/projects.yaml b/installer/bundles/pipelines/charts/tssc-pipelines-openshift/templates/projects.yaml new file mode 100644 index 000000000..aa8af33b2 --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-openshift/templates/projects.yaml @@ -0,0 +1,21 @@ +{{- $projects := list -}} +{{- range $p := .Values.openshift.projects -}} + {{- if not (has $p $projects ) -}} + {{- $projects = append $projects $p -}} + {{- end -}} +{{- end -}} + +{{- range $p := $projects }} + {{- if and + (not (hasPrefix "openshift-" $p)) + (not (lookup "project.openshift.io/v1" "Project" "" $p )) + }} +--- +apiVersion: project.openshift.io/v1 +kind: ProjectRequest +description: {{ $p }} +displayName: {{ $p }} +metadata: + name: {{ $p }} + {{- end }} +{{- end }} diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-openshift/values.yaml b/installer/bundles/pipelines/charts/tssc-pipelines-openshift/values.yaml new file mode 100644 index 000000000..9cda7f4e3 --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-openshift/values.yaml @@ -0,0 +1,3 @@ +--- +openshift: + projects: [] diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/.helmignore b/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/.helmignore new file mode 100644 index 000000000..97d9f7337 --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/.helmignore @@ -0,0 +1,19 @@ +.bzr/ +.bzrignore +.DS_Store +.git/ +.gitignore +.hg/ +.hgignore +.idea/ +.project +.svn/ +.vscode/ +*.bak +*.orig +*.swp +*.tmp +*.tmproj +*~ +*.md +hack/ diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/Chart.yaml b/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/Chart.yaml new file mode 100644 index 000000000..b9437d013 --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/Chart.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v2 +name: tssc-pipelines-subscriptions +description: TSSC OpenShift Operator Hub Subscriptions +type: application +version: "1.9.0" +annotations: + helmet.redhat-appstudio.github.com/depends-on-bundle-charts: tssc-pipelines-openshift diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/README.md b/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/README.md new file mode 100644 index 000000000..274dc859e --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/README.md @@ -0,0 +1,8 @@ +`tssc-pipelines-subscriptions` +--------------------- + +This Helm Chart manages OpenShift Operator Hub subscriptions required for TSSC. It leverages `Subscription` and `OperatorGroup` resources. + +During installation, it checks for the existence of the necessary CRDs (Custom Resource Definitions) and waits for the Operator to become available before proceeding. + +The list of subscriptions is defined in the [`values.yaml`](values.yaml) file. diff --git a/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/hack/find-os-subscription.sh b/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/hack/find-os-subscription.sh new file mode 100755 index 000000000..d5f883c36 --- /dev/null +++ b/installer/bundles/pipelines/charts/tssc-pipelines-subscriptions/hack/find-os-subscription.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# +# Finds the subscription for the given catalog name, shows the information needed +# to add the OpenShift operator subscription on this chart. +# + +# OpenShift operator catalog name, for instance "openshift-pipelines-operator-rh". +declare -r CATALOG_NAME="${1:-}" + +if [[ -z "${CATALOG_NAME}" ]]; then + echo "# Usage: $$ $0 " + exit 1 +fi + +# Inspecting the manifests installed on the cluster, looking for the catalog name +# given in parameter. The availability of the operators depends on the OpenShift cluster +# version. +if ! oc get packagemanifests "${CATALOG_NAME}" >/dev/null; then + echo "# ERROR: No subscription found for '${CATALOG_NAME}'!" + exit 1 +fi + +# Extracing the source name, and the namespace where the catalog is available +source=$(oc get packagemanifests "${CATALOG_NAME}" \ + --output="jsonpath={.status.catalogSource}") +namespace=$(oc get packagemanifests "${CATALOG_NAME}" \ + --output="jsonpath={.status.catalogSourceNamespace}") + +# Listing the channels and the startingCSVs available for the catalog. +mapfile -t channels < <(echo -en "$(oc get packagemanifests "${CATALOG_NAME}" \ + --output jsonpath='{range .status.channels[*]}{.name}\n{end}')") +mapfile -t startingCSVs < <(echo -en "$(oc get packagemanifests "${CATALOG_NAME}" \ + --output jsonpath='{range .status.channels[*]}{.currentCSV}\n{end}')") + +cat <" + exit 1 +fi + +# Inspecting the manifests installed on the cluster, looking for the catalog name +# given in parameter. The availability of the operators depends on the OpenShift cluster +# version. +if ! oc get packagemanifests "${CATALOG_NAME}" >/dev/null; then + echo "# ERROR: No subscription found for '${CATALOG_NAME}'!" + exit 1 +fi + +# Extracing the source name, and the namespace where the catalog is available +source=$(oc get packagemanifests "${CATALOG_NAME}" \ + --output="jsonpath={.status.catalogSource}") +namespace=$(oc get packagemanifests "${CATALOG_NAME}" \ + --output="jsonpath={.status.catalogSourceNamespace}") + +# Listing the channels and the startingCSVs available for the catalog. +mapfile -t channels < <(echo -en "$(oc get packagemanifests "${CATALOG_NAME}" \ + --output jsonpath='{range .status.channels[*]}{.name}\n{end}')") +mapfile -t startingCSVs < <(echo -en "$(oc get packagemanifests "${CATALOG_NAME}" \ + --output jsonpath='{range .status.channels[*]}{.currentCSV}\n{end}')") + +cat < +" >&2 +} + +parse_args() { + RESOURCE_SELECTORS=() + while [[ $# -gt 0 ]]; do + case "$1" in + -d | --debug) + set -x + DEBUG="--debug" + export DEBUG + info "Running script as: $(id)" + ;; + -h | --help) + usage + exit 0 + ;; + *) + # The "rollout status" selectors, to find the actual resource to check for + # successful rollout. + RESOURCE_SELECTORS+=("$1") + ;; + esac + shift + done +} + +fail() { + echo "# [ERROR] ${*}" >&2 + exit 1 +} + +warning() { + echo "# [WARNING] ${*}" +} + +info() { + echo "# [INFO] ${*}" +} + +# +# Functions +# + +rollout_status() { + oc rollout status "${RESOURCE_TYPE}" \ + --namespace="${NAMESPACE}" \ + --watch \ + --timeout=10s \ + --selector="${1}" +} + +assert_resource_exists() { + local selector="${1}" + local output + output=$( + oc get "${RESOURCE_TYPE}" \ + --namespace="${NAMESPACE}" \ + --selector="${selector}" 2>&1 + ) + local status=${?} + if [[ $status -eq 0 && $output != "No resources found"* ]]; then + info "Resource of type '${RESOURCE_TYPE}' with selector" \ + "'${selector}' exists in namespace '${NAMESPACE}'!" + return 0 + fi + + warning "Resource of type '${RESOURCE_TYPE}' with selector" \ + "'${selector}' does not exist in namespace '${NAMESPACE}'." + return 1 +} + +wait_for_resource() { + for s in "${RESOURCE_SELECTORS[@]}"; do + echo "# Checking if ${RESOURCE_TYPE} with selector '${s}' exists..." + if ! assert_resource_exists "${s}"; then + return 1 + fi + + echo "# Checking if ${RESOURCE_TYPE} with selector '${s}' is ready..." + if ! rollout_status "${s}"; then + echo -en "#\n# WARNING: ${RESOURCE_TYPE} '${s}' is not ready!\n#\n" + return 1 + fi + info "${RESOURCE_TYPE} objects with '${s}' selector are ready!" + done + return 0 +} + +test_rollout_status() { + [[ -z "${NAMESPACE}" ]] && usage + [[ -z "${RESOURCE_TYPE}" ]] && usage + [[ ${#RESOURCE_SELECTORS[@]} -eq 0 ]] && usage + + for i in $(seq 1 "${RETRIES}"); do + wait=$((i * 5)) + [[ $wait -gt 30 ]] && wait=30 + echo "### [${i}/${RETRIES}] Waiting for ${wait} seconds before retrying..." + sleep ${wait} + + if wait_for_resource; then + info "${RESOURCE_TYPE} objects ready: '${RESOURCE_SELECTORS[*]}'" + return 0 + fi + done + + fail "'${RESOURCE_TYPE}' are not ready!" +} + +main() { + parse_args "$@" + + # Namespace to check for "rollout status". + declare -r NAMESPACE="${NAMESPACE:-}" + # Resource type for "rollout status", as in "statefulset" or "deployment". + declare -r RESOURCE_TYPE="${RESOURCE_TYPE:-statefulset}" + # Number of retries to attempt before giving up. + declare -r RETRIES=${RETRIES:-20} + + test_rollout_status +} + +if [ "${BASH_SOURCE[0]}" == "$0" ]; then + main "$@" + echo + echo "Success" +fi \ No newline at end of file diff --git a/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/_copy-scripts.tpl b/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/_copy-scripts.tpl new file mode 100644 index 000000000..91c34ada8 --- /dev/null +++ b/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/_copy-scripts.tpl @@ -0,0 +1,26 @@ +{{/* + + POD container spec to copy scripts. + +*/}} +{{- define "common.copyScripts" -}} +- name: copy-scripts + image: registry.access.redhat.com/ubi10/ubi-minimal:latest + workingDir: /scripts + command: + - /bin/bash + - -c + - | + set -x -e + {{- range $path, $content := .Files.Glob "scripts/*.sh" -}} + {{- $script := trimPrefix "scripts/" $path }} + printf '%s' "{{ $content | toString | b64enc }}" | base64 -d >{{ $script }} + chmod +x {{ $script }} + {{- end }} + volumeMounts: + - name: scripts + mountPath: /scripts + securityContext: + runAsNonRoot: false + allowPrivilegeEscalation: false +{{- end -}} diff --git a/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/_helpers.tpl b/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/_helpers.tpl new file mode 100644 index 000000000..80ca550c8 --- /dev/null +++ b/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "common.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "common.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "common.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "common.labels" -}} +helm.sh/chart: {{ include "common.chart" . }} +{{ include "common.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Need deleted labels +*/}} +{{- define "common.postDeployDeleteLabels" -}} +helmet.redhat-appstudio.github.com/post-deploy: delete +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "common.selectorLabels" -}} +app.kubernetes.io/name: {{ include "common.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "common.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "common.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/postgres/pgsql-service.yaml b/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/postgres/pgsql-service.yaml new file mode 100644 index 000000000..82f8ec9c2 --- /dev/null +++ b/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/postgres/pgsql-service.yaml @@ -0,0 +1,128 @@ +{{- $currentNamespace := .Release.Namespace }} +{{- with $pgsql := .Values.pgsqlService }} +{{- range $inst := $pgsql.instances }} +{{- if $inst.enabled }} +{{- $secretName := printf "%s-%s" $inst.name $pgsql.secretName }} +{{- $serviceName := printf "%s-%s" $inst.name $pgsql.service.name }} +{{- $podName := printf "%s-%s" $inst.name $pgsql.podName }} +{{- $pvcName := printf "%s-pgsql-data" $inst.name }} +{{- $secret := lookup "v1" "Secret" $inst.namespace $secretName }} +{{- $password := randAlphaNum 16 }} +{{- if $secret }} + {{- $password = index $secret.data "password" | b64dec }} +{{- end }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ $pvcName }} + namespace: {{ $inst.namespace }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ $pgsql.storageSize }} +--- +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ $secretName }} + namespace: {{ $inst.namespace }} +stringData: + dbname: {{ $inst.name }} + host: {{ printf "%s.%s.svc" $serviceName $inst.namespace }} + user: {{ $inst.name }} + port: "{{ $pgsql.service.port | toString }}" + password: {{ $password | quote }} +{{- if ne $currentNamespace $inst.namespace }} +--- +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ $secretName }} + namespace: {{ $currentNamespace }} +stringData: + password: {{ $password | quote }} +{{- end }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ $serviceName }} + namespace: {{ $inst.namespace }} +spec: + type: {{ $pgsql.service.type }} + ports: + - name: data + port: {{ $pgsql.service.port }} + targetPort: {{ $pgsql.service.port }} + selector: + app: {{ $podName }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $podName }} + namespace: {{ $inst.namespace }} + annotations: + app.kubernetes.io/part-of: tssc +spec: + selector: + matchLabels: + app: {{ $podName }} + replicas: 1 + template: + metadata: + labels: + app: {{ $podName }} + phase: reference + spec: + volumes: + - name: pgsql-storage + persistentVolumeClaim: + claimName: {{ $pvcName }} + securityContext: + runAsNonRoot: true + containers: + - name: {{ $pgsql.podName }} + image: '{{ $pgsql.image.repository }}{{ $pgsql.image.name }}:{{ $pgsql.image.tag }}' + imagePullPolicy: {{ $pgsql.image.pullPolicy }} + env: + - name: POSTGRESQL_USER + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: user + - name: POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: password + - name: POSTGRESQL_DATABASE + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: dbname + volumeMounts: + - name: pgsql-storage + mountPath: "/var/lib/pgsql/data" + ports: + - containerPort: {{ $pgsql.service.port }} + resources: + limits: + cpu: {{ $pgsql.resources.limits.cpu }} + memory: {{ $pgsql.resources.limits.memory }} + requests: + cpu: {{ $pgsql.resources.requests.cpu }} + memory: {{ $pgsql.resources.requests.memory }} + readinessProbe: + tcpSocket: + port: {{ $pgsql.service.port }} + initialDelaySeconds: 15 + periodSeconds: 20 +{{- end }} +{{- end }} +{{- end }} diff --git a/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/tests/_test.tpl b/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/tests/_test.tpl new file mode 100644 index 000000000..f8d7cc631 --- /dev/null +++ b/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/tests/_test.tpl @@ -0,0 +1,24 @@ +{{- define "common.test" -}} +apiVersion: v1 +kind: Pod +metadata: + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + labels: + {{- include "common.labels" . | nindent 4 }} + name: test-{{ .name | default .Chart.Name }} + namespace: {{ .namespace | default .Release.Namespace }} +spec: + restartPolicy: Never + serviceAccountName: {{ .serviceAccount | default "default" }} + initContainers: + # + # Copying the scripts that will be used on the subsequent containers, the + # scripts are shared via the "/scripts" volume. + # +{{- include "common.copyScripts" . | nindent 4 }} + volumes: + - name: scripts + emptyDir: {} +{{- end -}} diff --git a/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/tests/test.yaml b/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/tests/test.yaml new file mode 100644 index 000000000..6d7b97bf0 --- /dev/null +++ b/installer/bundles/tpa/charts/tssc-tpa-pgsql/templates/tests/test.yaml @@ -0,0 +1,41 @@ +{{- $any := false -}} +{{- range $inst := .Values.pgsqlService.instances -}} +{{- if $inst.enabled -}} +{{- $any = true -}} +{{- end -}} +{{- end -}} +{{- if $any -}} +--- +{{- include "common.test" . }} + containers: +{{- with $pgsql := .Values.pgsqlService }} +{{- range $inst := $pgsql.instances }} +{{- if $inst.enabled }} +{{- $podName := printf "test-%s-%s" $inst.name $pgsql.podName }} +{{- $serviceName := printf "%s-%s" $inst.name $pgsql.service.name }} +{{- $secretName := printf "%s-%s" $inst.name $pgsql.secretName }} +{{- $hostName := printf "%s.%s.svc" $serviceName $inst.namespace }} + - name: {{ $podName }} + image: '{{ $pgsql.image.repository }}{{ $pgsql.image.name }}:{{ $pgsql.image.tag }}' + imagePullPolicy: {{ $pgsql.image.pullPolicy }} + env: + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: password + command: + - /bin/bash + - -ec + - | + for i in {1..30}; do + psql -d {{ $inst.name }} -h {{ $hostName }} -p {{ $pgsql.service.port | toString }} -U {{ $inst.name }} -c "select 1" && exit 0 + echo "pgsql service not ready yet, retrying ($i/30)..." + sleep 2 + done + echo "ERROR: psql readiness check failed after 30 retries" + exit 1 +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/installer/bundles/tpa/charts/tssc-tpa-pgsql/values.yaml b/installer/bundles/tpa/charts/tssc-tpa-pgsql/values.yaml new file mode 100644 index 000000000..e4dc3d551 --- /dev/null +++ b/installer/bundles/tpa/charts/tssc-tpa-pgsql/values.yaml @@ -0,0 +1,22 @@ +--- +pgsqlService: + podName: pgsql-bee + secretName: pgsql-user + storageSize: 50Gi + image: + name: postgresql-16 + repository: "registry.redhat.io/rhel9/" + pullPolicy: IfNotPresent + tag: "1-1754433677" + service: + name: pgsql + type: ClusterIP + port: 5432 + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: 250m + memory: 512Mi + instances: [] diff --git a/installer/bundles/tpa/charts/tssc-tpa-subscriptions/.helmignore b/installer/bundles/tpa/charts/tssc-tpa-subscriptions/.helmignore new file mode 100644 index 000000000..97d9f7337 --- /dev/null +++ b/installer/bundles/tpa/charts/tssc-tpa-subscriptions/.helmignore @@ -0,0 +1,19 @@ +.bzr/ +.bzrignore +.DS_Store +.git/ +.gitignore +.hg/ +.hgignore +.idea/ +.project +.svn/ +.vscode/ +*.bak +*.orig +*.swp +*.tmp +*.tmproj +*~ +*.md +hack/ diff --git a/installer/bundles/tpa/charts/tssc-tpa-subscriptions/Chart.yaml b/installer/bundles/tpa/charts/tssc-tpa-subscriptions/Chart.yaml new file mode 100644 index 000000000..5e14e2a5b --- /dev/null +++ b/installer/bundles/tpa/charts/tssc-tpa-subscriptions/Chart.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v2 +name: tssc-tpa-subscriptions +description: TSSC OpenShift Operator Hub Subscriptions +type: application +version: "1.9.0" +annotations: + helmet.redhat-appstudio.github.com/depends-on-bundle-charts: tssc-tpa-openshift diff --git a/installer/bundles/tpa/charts/tssc-tpa-subscriptions/README.md b/installer/bundles/tpa/charts/tssc-tpa-subscriptions/README.md new file mode 100644 index 000000000..6ddbfa2fa --- /dev/null +++ b/installer/bundles/tpa/charts/tssc-tpa-subscriptions/README.md @@ -0,0 +1,8 @@ +`tssc-tpa-subscriptions` +--------------------- + +This Helm Chart manages OpenShift Operator Hub subscriptions required for TSSC. It leverages `Subscription` and `OperatorGroup` resources. + +During installation, it checks for the existence of the necessary CRDs (Custom Resource Definitions) and waits for the Operator to become available before proceeding. + +The list of subscriptions is defined in the [`values.yaml`](values.yaml) file. diff --git a/installer/bundles/tpa/charts/tssc-tpa-subscriptions/hack/find-os-subscription.sh b/installer/bundles/tpa/charts/tssc-tpa-subscriptions/hack/find-os-subscription.sh new file mode 100755 index 000000000..d5f883c36 --- /dev/null +++ b/installer/bundles/tpa/charts/tssc-tpa-subscriptions/hack/find-os-subscription.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# +# Finds the subscription for the given catalog name, shows the information needed +# to add the OpenShift operator subscription on this chart. +# + +# OpenShift operator catalog name, for instance "openshift-pipelines-operator-rh". +declare -r CATALOG_NAME="${1:-}" + +if [[ -z "${CATALOG_NAME}" ]]; then + echo "# Usage: $$ $0 " + exit 1 +fi + +# Inspecting the manifests installed on the cluster, looking for the catalog name +# given in parameter. The availability of the operators depends on the OpenShift cluster +# version. +if ! oc get packagemanifests "${CATALOG_NAME}" >/dev/null; then + echo "# ERROR: No subscription found for '${CATALOG_NAME}'!" + exit 1 +fi + +# Extracing the source name, and the namespace where the catalog is available +source=$(oc get packagemanifests "${CATALOG_NAME}" \ + --output="jsonpath={.status.catalogSource}") +namespace=$(oc get packagemanifests "${CATALOG_NAME}" \ + --output="jsonpath={.status.catalogSourceNamespace}") + +# Listing the channels and the startingCSVs available for the catalog. +mapfile -t channels < <(echo -en "$(oc get packagemanifests "${CATALOG_NAME}" \ + --output jsonpath='{range .status.channels[*]}{.name}\n{end}')") +mapfile -t startingCSVs < <(echo -en "$(oc get packagemanifests "${CATALOG_NAME}" \ + --output jsonpath='{range .status.channels[*]}{.currentCSV}\n{end}')") + +cat </dev/null 2>&1); then - echo -e "# ERROR: CRD '${crd}' not found." - SUCCESS=1 - else + if oc get customresourcedefinitions "${crd}" >/dev/null 2>&1; then echo "# CRD '${crd}' is installed." + else + echo "# ERROR: CRD '${crd}' not visible to this Pod (missing or RBAC)." + oc get customresourcedefinitions "${crd}" 2>&1 | sed 's/^/# oc: /' || true + SUCCESS=1 fi done return "$SUCCESS" @@ -77,18 +78,19 @@ api_resources_available() { # Verifies the availability of the CRDs, retrying a few times. test_subscriptions() { if [[ ${#CRDS[@]} -eq 0 ]]; then - echo "Usage: $0 " - exit 1 + echo "# No operator Subscriptions are Helm-managed for this release; skipping CRD wait." + return 0 fi echo "# Waiting for CRDs to be available: '${CRDS[*]}'" - for i in {1..20}; do - echo "# Check ${i}/20" + for i in {1..40}; do + echo "# Check ${i}/40" if api_resources_available; then info "# CRDs are available: '${CRDS[*]}'" return 0 fi wait=$((i * 3)) + if [[ ${wait} -gt 60 ]]; then wait=60; fi echo "# Waiting for ${wait} seconds before retrying..." sleep ${wait} done diff --git a/installer/values.yaml.tpl b/installer/values.yaml.tpl index 7f57c3896..bb4eb3e57 100644 --- a/installer/values.yaml.tpl +++ b/installer/values.yaml.tpl @@ -1,199 +1,26 @@ -{{- $crc := required "CRC settings" .Installer.Settings.crc -}} -{{- $tas := required "TAS settings" .Installer.Products.Trusted_Artifact_Signer -}} -{{- $tpa := required "TPA settings" .Installer.Products.Trusted_Profile_Analyzer -}} {{- $acs := required "Red Hat ACS settings" .Installer.Products.Advanced_Cluster_Security -}} {{- $gitops := required "GitOps settings" .Installer.Products.OpenShift_GitOps -}} {{- $pipelines := required "Pipelines settings" .Installer.Products.OpenShift_Pipelines -}} -{{- $pipelinesNamespace := "openshift-pipelines" -}} -{{- $rhdh := required "RHDH settings" .Installer.Products.Developer_Hub -}} -{{- $ingressDomain := required "OpenShift ingress domain" .OpenShift.Ingress.Domain -}} -{{- $ingressRouterCA := required "OpenShift RouterCA" .OpenShift.Ingress.RouterCA -}} -{{- $openshiftMinorVersion := required "OpenShift Version" .OpenShift.MinorVersion -}} -{{- $authProvider := required "Auth Provider is required" $rhdh.Properties.authProvider }} -{{- $keycloakEnabled := or $tpa.Enabled $tas.Enabled (and $rhdh.Enabled (eq $authProvider "oidc"))}} -{{- $keycloakNamespace := "tssc-keycloak" -}} +{{- $instNs := .Installer.Namespace -}} +{{- $acsActive := true -}} +{{- if and (kindIs "bool" $acs.Enabled) (eq $acs.Enabled false) }}{{- $acsActive = false -}}{{- end -}} +{{- $gitopsActive := true -}} +{{- if and (kindIs "bool" $gitops.Enabled) (eq $gitops.Enabled false) }}{{- $gitopsActive = false -}}{{- end -}} +{{- $gitopsNs := default $instNs $gitops.Namespace -}} +{{- $pipelinesActive := true -}} +{{- if and (kindIs "bool" $pipelines.Enabled) (eq $pipelines.Enabled false) }}{{- $pipelinesActive = false -}}{{- end -}} +{{- $settings := .Installer.Settings | default dict -}} +{{- $ci := index $settings "ci" | default dict -}} --- debug: - ci: {{ dig "ci" "debug" false .Installer.Settings }} + ci: {{ default false (index $ci "debug") }} # -# tssc-openshift +# Global charts: tssc-app-namespaces. Bundle charts: tssc-gitops-integrations (gitops), tssc-acs-integrations (acs). +# Quay: use `tssc integration quay` for tssc-quay-integration Secret. +# Keycloak / IAM (openshift projects, subscription, PostgreSQL, realm) live under bundles/iam/. # -openshift: - projects: -{{- if $keycloakEnabled }} - - {{ $keycloakNamespace }} - - rhbk-operator -{{- end }} -{{- if $acs.Enabled }} - - {{ $acs.Namespace }} - {{- if $acs.Properties.manageSubscription }} - - rhacs-operator - {{- end }} -{{- end }} -{{- if $tpa.Enabled }} - - {{ $tpa.Namespace }} - {{- if $tpa.Properties.manageSubscription }} - - rhtpa-operator - {{- end }} -{{- end }} -{{- if $gitops.Enabled }} - - {{ $gitops.Namespace }} -{{- end }} -{{- if $tas.Enabled }} - - {{ $tas.Namespace }} -{{- end }} -{{- if $tpa.Enabled }} - - {{ $tpa.Namespace }} -{{- end }} -{{- if $rhdh.Enabled }} - - {{ $rhdh.Namespace }} -{{- end }} - -# -# tssc-subscriptions -# - - -subscriptions: - openshiftGitOps: - enabled: {{ $gitops.Enabled }} - managed: {{ and $gitops.Enabled $gitops.Properties.manageSubscription }} - config: - argoCDClusterNamespace: {{ $gitops.Namespace }} - openshiftKeycloak: - enabled: {{ $keycloakEnabled }} - managed: {{ $keycloakEnabled }} - operatorGroup: - targetNamespaces: - - {{ default "empty" $keycloakNamespace }} - openshiftPipelines: - enabled: {{ $pipelines.Enabled }} - managed: {{ and $pipelines.Enabled $pipelines.Properties.manageSubscription }} - openshiftTrustedArtifactSigner: - enabled: {{ $tas.Enabled }} - managed: {{ and $tas.Enabled $tas.Properties.manageSubscription }} - trustedProfileAnalyzer: - enabled: {{ $tpa.Enabled }} - managed: {{ and $tpa.Enabled $tpa.Properties.manageSubscription }} - advancedClusterSecurity: - enabled: {{ $acs.Enabled }} - managed: {{ and $acs.Enabled $acs.Properties.manageSubscription }} - developerHub: - enabled: {{ $rhdh.Enabled }} - managed: {{ and $rhdh.Enabled $rhdh.Properties.manageSubscription }} - -# -# tssc-infrastructure -# - -infrastructure: - developerHub: - namespace: {{ $rhdh.Namespace }} - pgsqlService: - instances: - - name: tpa - enabled: {{ $tpa.Enabled }} - namespace: {{ $tpa.Namespace }} - - name: keycloak - enabled: {{ $keycloakEnabled }} - namespace: {{ $keycloakNamespace }} - openShiftPipelines: - enabled: {{ $pipelines.Enabled }} - namespace: {{ $pipelinesNamespace }} - -# -# tssc-iam -# - -{{- $keycloakRouteTLSSecretName := "keycloak-tls" }} -{{- $keycloakRouteHost := printf "tssc-sso.%s" $ingressDomain }} -{{- $realmsName := "tssc-iam" }} -{{- $tpaTestingUsersEnabled := false }} -{{- $protocol := "https" -}} -{{- if $crc }} - {{- $protocol = "http" }} -{{- end }} -{{- $tpaOIDCIssuerURL := printf "%s://%s/realms/%s" $protocol $keycloakRouteHost $realmsName }} - -iam: - enabled: {{ $keycloakEnabled }} - namespace: {{ $keycloakNamespace }} - integrationSecret: - namespace: {{ .Installer.Namespace }} - instances: 1 - database: - host: keycloak-pgsql - name: keycloak - secretName: keycloak-pgsql-user - route: - host: {{ $keycloakRouteHost }} - tls: - enabled: {{ not $crc }} - secretName: {{ $keycloakRouteTLSSecretName }} - termination: reencrypt -{{- if $crc }} - annotations: - route.openshift.io/termination: reencrypt -{{- end }} - service: - annotations: - service.beta.openshift.io/serving-cert-secret-name: {{ $keycloakRouteTLSSecretName }} - keycloakCR: - namespace: {{ $keycloakNamespace }} - ingressDomain: {{ $ingressDomain }} - rhdhRealm: - enabled: {{ and $rhdh.Enabled (eq $authProvider "oidc") }} - rhdhRedirectUris: - - {{ - printf "%s://backstage-developer-hub-tssc-dh.%s/api/auth/oidc/handler/frame" - $protocol - $ingressDomain - }} - rhdhOriginUris: - - {{ - printf "%s://backstage-developer-hub-tssc-dh.%s" - $protocol - $ingressDomain - }} - trustedArtifactSignerRealm: - enabled: {{ $tas.Enabled }} - trustedProfileAnalyzerRealm: - enabled: {{ $tpa.Enabled }} - oidcIssuerURL: {{ $tpaOIDCIssuerURL }} - clients: - cli: - enabled: true - testingManager: - enabled: {{ $tpaTestingUsersEnabled }} - testingUser: - enabled: {{ $tpaTestingUsersEnabled }} - frontendRedirectUris: - - "http://localhost:8080" - {{- range list "server" "sbom" }} - - "{{ printf "%s://%s-%s.%s" $protocol . $tpa.Namespace $ingressDomain }}" - - "{{ printf "%s://%s-%s.%s/*" $protocol . $tpa.Namespace $ingressDomain }}" - {{- end }} - -# -# tssc-acs -# - -acs: &acs - enabled: {{ $acs.Enabled }} - name: &acsName stackrox-central-services - ingressDomain: {{ $ingressDomain }} - ingressRouterCA: {{ $ingressRouterCA }} - integrationSecret: - namespace: {{ .Installer.Namespace }} - test: - scanner: - image: registry.access.redhat.com/ubi10:latest - tssc: - namespace: {{ .Installer.Namespace }} -acsTest: *acs - # # tssc-app-namespaces # @@ -206,51 +33,22 @@ appNamespaces: namespace_prefixes: - {{ printf "%s-app" .Installer.Namespace }} pipelines: - enabled: {{ $pipelines.Enabled}} + enabled: {{ $pipelinesActive }} # -# tssc-gitops +# tssc-gitops-integrations / tssc-acs-integrations (top-level .Values.argoCD + .Values.integrations) # argoCD: - enabled: {{ $gitops.Enabled }} - name: {{ $argoCDName }} - namespace: {{ $gitops.Namespace }} - integrationSecret: - name: tssc-argocd-integration - namespace: {{ .Installer.Namespace }} - ingressDomain: {{ $ingressDomain }} - tssc: - namespace: {{ .Installer.Namespace }} - -# -# tssc-pipelines-config -# - -pipelines_config: - namespace: {{ $pipelinesNamespace }} - integrationSecret: - namespace: {{ .Installer.Namespace }} - -# -# tssc-pipelines -# - -pipelines: - namespace: {{ $pipelinesNamespace }} - tssc: - namespace: {{ .Installer.Namespace }} - -# -# tssc-integrations -# + enabled: {{ $gitopsActive }} + namespace: {{ $gitopsNs }} integrations: acs: - enabled: {{ $acs.Enabled }} + enabled: {{ $acsActive }} argoCD: - enabled: {{ $gitops.Enabled }} - namespace: {{ $gitops.Namespace }} + enabled: {{ $gitopsActive }} + namespace: {{ $gitopsNs }} tssc: namespace: {{ .Installer.Namespace }} # github: @@ -265,150 +63,3 @@ integrations: # webhookSecret: "" # gitlab: # token: "" - -# -# tssc-dh -# - -{{- $catalogURL := required "Red Hat Developer Hub Catalog URL is required" - $rhdh.Properties.catalogURL }} - - -developerHub: - namespace: {{ $rhdh.Namespace }} - ingressDomain: {{ $ingressDomain }} - catalogURL: {{ $catalogURL }} - authProvider: {{ $authProvider }} - integrationSecrets: - namespace: {{ .Installer.Namespace }} - RBAC: - enabled: {{ dig "Properties" "RBAC" "enabled" false $rhdh }} -{{- if eq $authProvider "github" }} - adminUsers: -{{ dig "Properties" "RBAC" "adminUsers" (list "${GITHUB__USERNAME}") $rhdh | toYaml | indent 6 }} - orgs: -{{ dig "Properties" "RBAC" "orgs" (list "${GITHUB__ORG}") $rhdh | toYaml | indent 6 }} -{{- else if eq $authProvider "gitlab" }} - adminUsers: -{{ dig "Properties" "RBAC" "adminUsers" (list "${GITLAB__USERNAME}") $rhdh | toYaml | indent 6 }} -{{- else if eq $authProvider "oidc" }} - oidc: - secretNamespace: {{ .Installer.Namespace }} - enabled: true - clientID: rhdh - metadataURL: {{ printf "%s://%s/realms/%s/.well-known/openid-configuration" $protocol $keycloakRouteHost $realmsName }} - baseURL: {{ printf "%s://%s" $protocol $keycloakRouteHost }} - loginRealm: {{ $realmsName }} - realm: {{ $realmsName }} -{{- end }} - -# -# tssc-tpa -# - -{{- $tpaDatabaseSecretName := "tpa-pgsql-user" }} -{{- $tpaAppDomain := printf "-%s.%s" $tpa.Namespace $ingressDomain }} -{{- $tpaOIDCClientsSecretName := "tpa-realm-clients" }} - -trustedProfileAnalyzer: - enabled: {{ $tpa.Enabled }} - cycloneDXVersion: "1.4" - oidcIssuerURL: {{ $tpaOIDCIssuerURL }} - namespace: "{{ $tpa.Namespace }}" - appDomain: "{{ $tpaAppDomain }}" - integrationSecret: - namespace: {{ .Installer.Namespace }} - ingress: &tpaIngress - className: openshift-default - openshift: &tpaOpenShift - # In practice it toggles "https" vs. "http" for TPA components, for CRC it's - # easier to focus on "http" communication only. - useServiceCa: {{ not $crc }} - database: &tpaDatabase - name: - valueFrom: - secretKeyRef: - name: {{ $tpaDatabaseSecretName }} - key: dbname - host: - valueFrom: - secretKeyRef: - name: {{ $tpaDatabaseSecretName }} - key: host - port: - valueFrom: - secretKeyRef: - name: {{ $tpaDatabaseSecretName}} - key: port - username: - valueFrom: - secretKeyRef: - name: {{ $tpaDatabaseSecretName }} - key: user - password: - valueFrom: - secretKeyRef: - name: {{ $tpaDatabaseSecretName }} - key: password - createDatabase: *tpaDatabase - migrateDatabase: *tpaDatabase - storage: &tpaStorage - type: filesystem - size: 32Gi - oidc: &tpaOIDC - issuerUrl: {{ $tpaOIDCIssuerURL }} - clients: - cli: - clientSecret: - valueFrom: - secretKeyRef: - name: {{ $tpaOIDCClientsSecretName }} - key: cli -{{- if $tpaTestingUsersEnabled }} - testingUser: - clientSecret: - valueFrom: - secretKeyRef: - name: {{ $tpaOIDCClientsSecretName }} - key: testingUser - testingManager: - clientSecret: - valueFrom: - secretKeyRef: - name: {{ $tpaOIDCClientsSecretName }} - key: testingManager -{{- end }} - -trustification: - name: trustedprofileanalyzer - namespace: "{{ $tpa.Namespace }}" - appDomain: "{{ $tpaAppDomain }}" - openshift: *tpaOpenShift - storage: *tpaStorage - oidc: *tpaOIDC - ingress: *tpaIngress - tls: - serviceEnabled: "{{ not $crc }}" - -# -# tssc-tas -# - -{{- $tasRealmPath := printf "realms/%s" $realmsName }} - -trustedArtifactSigner: - enabled: {{ $tas.Enabled }} - ingressDomain: "{{ $ingressDomain }}" - secureSign: - enabled: {{ $tas.Enabled }} - namespace: {{ $tas.Namespace }} - fulcio: - oidc: - clientID: trusted-artifact-signer - issuerURL: {{ printf "%s://%s/%s" $protocol $keycloakRouteHost $tasRealmPath }} - certificate: - # TODO: promopt the user for organization email/name input! - organizationEmail: trusted-artifact-signer@company.dev - organizationName: TSSC - integrationSecret: - namespace: {{ .Installer.Namespace }} diff --git a/integration-tests/tasks/test.sh b/integration-tests/tasks/test.sh new file mode 100644 index 000000000..a43873789 --- /dev/null +++ b/integration-tests/tasks/test.sh @@ -0,0 +1,331 @@ + #!/usr/bin/env bash + set -euo pipefail + + echo "Starting pipelines in mode: $MODE" + echo "Using Konflux metadata: namespace=$KONFLUX_NAMESPACE, app=$KONFLUX_APPLICATION_NAME, component=$KONFLUX_COMPONENT_NAME" + # Decode base64 encoded rhads-config + echo "Decoding base64 encoded rhads-config..." + DECODED_RHADS_CONFIG=$(echo "$RHADS_CONFIG" | base64 -d) + echo "Decoded rhads-config content:" + echo "$DECODED_RHADS_CONFIG" + # Extract images from snapshot + tssc_image=$(echo "${SNAPSHOT}" | jq -r '.components[] |select(.name=="tssc-cli").containerImage') + tssc_test_image=$(echo "${SNAPSHOT}" | jq -r '.components[] |select(.name=="tssc-test").containerImage') + + GIT_REPO="$(jq -r '.git.repo // empty' <<< $JOB_SPEC)" + KONFLUX_URL="https://konflux-ui.apps.stone-prd-rh01.pg1f.p1.openshiftapps.com" + + if [[ "${GIT_REPO}" = "tssc-cli" ]]; then + REPO_ORG=$(jq -r '.git.source_repo_org' <<< $JOB_SPEC) + # Extract the repository name from the source_repo_url + SOURCE_REPO_URL=$(jq -r '.git.source_repo_url' <<< $JOB_SPEC) + REPO=$(basename "$SOURCE_REPO_URL" .git) + # Extract the branch name from the source_repo_branch + SOURCE_REPO_BRANCH=$(jq -r '.git.source_repo_branch' <<< $JOB_SPEC) + BRANCH="${SOURCE_REPO_BRANCH##refs/heads/}" + else + REPO_ORG="redhat-appstudio" + REPO="tssc-cli" + BRANCH="main" + fi + if [[ "$MODE" == "periodic" ]]; then + # use always main from redhat-appstudio for periodic jobs + REPO_ORG="redhat-appstudio" + BRANCH="main" + fi + echo "REPO_ORG: $REPO_ORG" + echo "BRANCH: $BRANCH" + URL_ORG=$(jq -r '.git.org' <<< $JOB_SPEC) + echo "URL_ORG: $URL_ORG" + URL_REPOSITORY=$(jq -r '.git.repo' <<< $JOB_SPEC) + echo "URL_REPOSITORY: $URL_REPOSITORY" + GIT_REVISION=$(jq -r '.git.commit_sha // empty' <<< $JOB_SPEC) + echo "GIT_REVISION: $GIT_REVISION" + PR_NUMBER=$(jq -r '.git.pull_request_number // empty' <<< $JOB_SPEC) + echo "PR_NUMBER: $PR_NUMBER" + EVENT_TYPE=$(jq -r '.event_type // .git.event_type // empty' <<< $JOB_SPEC) + echo "EVENT_TYPE: $EVENT_TYPE" + + PIPELINERUNS_ARRAY=() + + # Function to start a single pipeline + start_single_pipeline() { + local ocp_version="$1" + local config="$2" + local testplan_b64="$3" + local config_suffix="$4" + echo "Starting pipeline for OCP version: $ocp_version" + local pipeline_run + # Build the tkn command with proper parameter handling + local tkn_params=( + "--param" "ocp-version=$ocp_version" + "--param" "job-spec=$JOB_SPEC" + "--param" "konflux-test-infra-secret=$KONFLUX_TEST_INFRA_SECRET" + "--param" "rhads-config=$config" + "--param" "testplan=${testplan_b64:-}" + "--param" "cloud-credential-key=$CLOUD_CREDENTIAL_KEY" + "--param" "machine-type=$OCP_INSTANCE_TYPE" + "--param" "replicas=$OCP_REPLICAS" + ) + + # Add optional image parameters + if [[ "${tssc_image}" != "" ]]; then + tkn_params+=("--param" "tssc-image=${tssc_image}") + fi + if [[ "${tssc_test_image}" != "" ]]; then + tkn_params+=("--param" "tssc-test-image=${tssc_test_image}") + fi + + # Add labels and other flags + tkn_params+=( + "--use-param-defaults" + "--labels" "appstudio.openshift.io/component=${KONFLUX_COMPONENT_NAME}" + "--labels" "appstudio.openshift.io/application=${KONFLUX_APPLICATION_NAME}" + "--labels" "pipelines.appstudio.openshift.io/type=${CONTEXT_PIPELINE_RUN_NAME}" + "--labels" "test.appstudio.openshift.io/scenario=pr-e2e-tests" + "--labels" "pac.test.appstudio.openshift.io/url-org=${URL_ORG}" + "--labels" "pac.test.appstudio.openshift.io/url-repository=${URL_REPOSITORY}" + "--labels" "pac.test.appstudio.openshift.io/git-revision=${GIT_REVISION}" + "--labels" "pac.test.appstudio.openshift.io/pull-request=${PR_NUMBER}" + "--labels" "pac.test.appstudio.openshift.io/event-type=${EVENT_TYPE}" + "--labels" "custom.appstudio.openshift.io/main-pipeline-run=${CONTEXT_PIPELINE_RUN_NAME}" + "--prefix-name" "e2e-$ocp_version$config_suffix" + "-o" "name" + "--serviceaccount" "konflux-integration-runner" + "--pipeline-timeout" "2h0m" + ) + + echo "Starting pipeline with testplan (${#testplan_b64} chars): ${testplan_b64:0:50}..." + pipeline_run=$(tkn pipeline start -f https://raw.githubusercontent.com/$REPO_ORG/$REPO/refs/heads/$BRANCH/integration-tests/pipelines/tssc-cli-e2e.yaml "${tkn_params[@]}") + #Verifying Timeout + pipeline_timeout=$(oc get pipelinerun "${pipeline_run}" -n "${KONFLUX_NAMESPACE}" -o jsonpath='{.spec.timeouts.pipeline}') + echo "Pipeline run timeout set to ${pipeline_timeout}" + # Construct console URL for the new PipelineRun + CONSOLE_URL="${KONFLUX_URL}/ns/${KONFLUX_NAMESPACE}/applications/${KONFLUX_APPLICATION_NAME}/pipelineruns/${pipeline_run}" + echo "Started new pipelinerun: ${CONSOLE_URL}" + # Set console URL as annotation for store-pipeline-status task + # Annotations can contain full URLs, unlike labels which have restrictions + oc annotate pipelinerun "${pipeline_run}" -n "${KONFLUX_NAMESPACE}" "pac.test.appstudio.openshift.io/log-url=${CONSOLE_URL}" --overwrite + PIPELINERUNS_ARRAY+=($pipeline_run) + } + + if [[ "$MODE" == "periodic" ]]; then + echo "Processing periodic configurations..." + echo "RHADS Config data:" + echo "$DECODED_RHADS_CONFIG" + # Validate that we have some content + if [[ -z "$DECODED_RHADS_CONFIG" ]]; then + echo "ERROR: No RHADS configuration data found" + exit 1 + fi + # Count configurations + if echo "$DECODED_RHADS_CONFIG" | grep -q "---CONFIG_SEPARATOR---"; then + SEPARATOR_COUNT=$(echo "$DECODED_RHADS_CONFIG" | grep -o -e "---CONFIG_SEPARATOR---" | wc -l) + echo "DEBUG: Found $SEPARATOR_COUNT separators using grep" + else + SEPARATOR_COUNT=0 + echo "DEBUG: No separators found, setting SEPARATOR_COUNT=0" + fi + CONFIG_COUNT=$((SEPARATOR_COUNT + 1)) # Add 1 because there's one more config than separators + echo "Found $CONFIG_COUNT configurations to process" + # Handle single configuration case + if [[ $CONFIG_COUNT -eq 1 ]]; then + echo "Processing single configuration in periodic mode..." + FULL_CONFIG="$DECODED_RHADS_CONFIG" + # Trim whitespace from config + FULL_CONFIG=$(echo "$FULL_CONFIG" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + CONFIG_INDEX=1 + echo "==================== Configuration $CONFIG_INDEX ====================" + echo "$FULL_CONFIG" + echo "================================================================" + # Split RHADS config and testplan + if [[ "$FULL_CONFIG" == *"---TESTPLAN_START---"* ]]; then + # Extract RHADS config (before testplan) + CONFIG="${FULL_CONFIG%%---TESTPLAN_START---*}" + # Extract testplan (between markers) + TESTPLAN_PART="${FULL_CONFIG#*---TESTPLAN_START---}" + TESTPLAN="${TESTPLAN_PART%%---TESTPLAN_END---*}" + # Trim whitespace from both parts + CONFIG=$(echo "$CONFIG" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + TESTPLAN=$(echo "$TESTPLAN" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + echo "RHADS Config:" + echo "$CONFIG" + echo "Testplan:" + echo "$TESTPLAN" + # Encode testplan to base64 + TESTPLAN_B64=$(echo "$TESTPLAN" | base64 -w 0) + echo "Testplan base64: $TESTPLAN_B64" + else + # No testplan found, use config as-is + CONFIG="$FULL_CONFIG" + TESTPLAN_B64="" + echo "No testplan found for this configuration" + fi + # Extract OCP version from this configuration + if [[ $CONFIG =~ OCP=\"([^\"]+)\" ]]; then + OCP_VERSION="${BASH_REMATCH[1]}" + echo "Successfully extracted OCP version: $OCP_VERSION from configuration $CONFIG_INDEX" + # Start pipeline using shared function + start_single_pipeline "$OCP_VERSION" "$CONFIG" "$TESTPLAN_B64" "-config$CONFIG_INDEX" + else + echo "ERROR: Could not extract OCP version from configuration $CONFIG_INDEX" + echo "Configuration content: $CONFIG" + exit 1 + fi + else + # Multiple configurations + echo "Processing multiple configurations using separator logic..." + # Create a pipeline for every RHADS config using pure bash string manipulation + CONFIG_INDEX=0 + # Split the configurations using bash string manipulation + REMAINING_CONFIG="$DECODED_RHADS_CONFIG" + while [[ -n "$REMAINING_CONFIG" ]]; do + # Find the next separator + if [[ "$REMAINING_CONFIG" == *"---CONFIG_SEPARATOR---"* ]]; then + # Extract config before separator + FULL_CONFIG="${REMAINING_CONFIG%%---CONFIG_SEPARATOR---*}" + # Remove processed config and separator from remaining + REMAINING_CONFIG="${REMAINING_CONFIG#*---CONFIG_SEPARATOR---}" + else + # Last config (no separator after it) + FULL_CONFIG="$REMAINING_CONFIG" + REMAINING_CONFIG="" + fi + # Trim whitespace from config + FULL_CONFIG=$(echo "$FULL_CONFIG" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + CONFIG_INDEX=$(( $CONFIG_INDEX + 1 )) + echo "==================== Configuration $CONFIG_INDEX ====================" + echo "$FULL_CONFIG" + echo "================================================================" + # Split RHADS config and testplan + if [[ "$FULL_CONFIG" == *"---TESTPLAN_START---"* ]]; then + # Extract RHADS config (before testplan) + CONFIG="${FULL_CONFIG%%---TESTPLAN_START---*}" + # Extract testplan (between markers) + TESTPLAN_PART="${FULL_CONFIG#*---TESTPLAN_START---}" + TESTPLAN="${TESTPLAN_PART%%---TESTPLAN_END---*}" + # Trim whitespace from both parts + CONFIG=$(echo "$CONFIG" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + TESTPLAN=$(echo "$TESTPLAN" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + echo "RHADS Config:" + echo "$CONFIG" + echo "Testplan:" + echo "$TESTPLAN" + # Encode testplan to base64 + TESTPLAN_B64=$(echo "$TESTPLAN" | base64 -w 0) + echo "Testplan base64: $TESTPLAN_B64" + else + # No testplan found, use config as-is + CONFIG="$FULL_CONFIG" + TESTPLAN_B64="" + echo "No testplan found for this configuration" + fi + # Extract OCP version from this configuration + if [[ $CONFIG =~ OCP=\"([^\"]+)\" ]]; then + OCP_VERSION="${BASH_REMATCH[1]}" + echo "Successfully extracted OCP version: $OCP_VERSION from configuration $CONFIG_INDEX" + # Start pipeline using shared function + start_single_pipeline "$OCP_VERSION" "$CONFIG" "$TESTPLAN_B64" "-config$CONFIG_INDEX" + else + echo "ERROR: Could not extract OCP version from configuration $CONFIG_INDEX" + echo "Configuration content: $CONFIG" + exit 1 + fi + done + echo "Started $CONFIG_INDEX sub-pipelines for periodic testing" + fi + else + echo "Processing single configuration..." + RHADS_CONFIG_CONTENT=$(echo "$DECODED_RHADS_CONFIG") + echo "Configuration:" + echo "$RHADS_CONFIG_CONTENT" + # Extract OCP versions from RHADS config + [[ $RHADS_CONFIG_CONTENT =~ OCP=\"([^\"]+)\" ]] && IFS=',' read -ra OCP_VERSIONS <<< "${BASH_REMATCH[1]}" + echo "Running tests for OCP versions: ${OCP_VERSIONS[*]}" + # Loop through each OCP version and start a sub-pipeline for each + for OCP_VERSION in "${OCP_VERSIONS[@]}"; do + echo "Starting sub-pipeline for OCP version: $OCP_VERSION" + # Start pipeline using shared function + start_single_pipeline "$OCP_VERSION" "$RHADS_CONFIG_CONTENT" "" "" + done + fi + + echo "Total pipelines started: ${#PIPELINERUNS_ARRAY[@]}" + # Validate that we started at least one pipeline + if [[ ${#PIPELINERUNS_ARRAY[@]} -eq 0 ]]; then + echo "ERROR: No pipelines were started. Check the configuration data and OCP version extraction." + exit 1 + fi + + # Store results + printf '%s\n' "${PIPELINERUNS_ARRAY[@]}" | jq -R . | jq -s . > $(results.pipeline-runs.path) + + declare -A PIPELINE_RESULTS + + # Wait for completion function + waitFor() { + local CONDITION="$1" + local MESSAGE_RUNNING="$2" + local MESSAGE_DONE="$3" + export CONDITION MESSAGE_RUNNING MESSAGE_DONE + timeout --foreground 120m /bin/bash -c ' + #deserialize plrs array + set -x + read -r -a PIPELINERUNS_ARRAY <<< "$PIPELINERUNS_ARRAY_SERIALIZED" + echo "${PIPELINERUNS_ARRAY[*]}" + while [[ ${#PIPELINERUNS_ARRAY[@]} -gt 0 ]]; do + # Create a new array to store pipelines that are still running + STILL_RUNNING=() + for PIPELINE_RUN in "${PIPELINERUNS_ARRAY[@]}"; do + if eval "$CONDITION"; then + # Pipeline is still running, keep it in the array + STILL_RUNNING+=("$PIPELINE_RUN") + else + # Pipeline is finished, remove it from the array + echo "Pipeline $PIPELINE_RUN has finished" + JSON_PATH='{.status.conditions[?(@.type=="Succeeded")].status}' + RESULT=$(oc get pipelinerun/${PIPELINE_RUN} -n ${KONFLUX_NAMESPACE} -o jsonpath="$JSON_PATH") + PIPELINE_RESULTS["$PIPELINE_RUN"]="$RESULT" + echo "Succeeded: $RESULT" + fi + done + # Update the array with only running pipelines + PIPELINERUNS_ARRAY=("${STILL_RUNNING[@]}") + if [[ ${#PIPELINERUNS_ARRAY[@]} -gt 0 ]]; then + echo "$MESSAGE_RUNNING (${#PIPELINERUNS_ARRAY[@]} pipelines remaining)" + sleep 60 + else + echo "$MESSAGE_DONE" + break + fi + done + ' + } + + #Serialize plrs array to be able to export it as var + export PIPELINERUNS_ARRAY_SERIALIZED="${PIPELINERUNS_ARRAY[*]}" + + waitFor '! oc get pipelinerun/$PIPELINE_RUN -n ${KONFLUX_NAMESPACE} &>/dev/null' "Pipelines are still starting. Waiting 1 minute" "All pipelines have started" + waitFor '[[ $(oc get pipelinerun/$PIPELINE_RUN -n ${KONFLUX_NAMESPACE} -o jsonpath="{.status.conditions[?(@.type==\"Succeeded\")].status}") == "" ]]' "Waiting for nested pipelinerun status to be set. Waiting 1 minute" "Nested pipelinerun status is set" + waitFor '[[ $(oc get pipelinerun/$PIPELINE_RUN -n ${KONFLUX_NAMESPACE} -o jsonpath="{.status.conditions[?(@.type==\"Succeeded\")].status}") == "Unknown" ]]' "Nested pipelines are still running. Waiting 1 minute" "All nested pipelines finished" + + # Explore and report status of all failed pipelineruns. Fail if anything failed + SOME_PIPELINE_FAILED=false + SOME_PIPELINE_SUCCEEDED=false + for PIPELINE_RUN in "${PIPELINERUNS_ARRAY[@]}"; do + STATUS="${PIPELINE_RESULTS[$PIPELINE_RUN]}" + if [[ "$STATUS" == "False" ]]; then + if ! $SOME_PIPELINE_FAILED ; then + echo "List of failed PLRs:" + fi + echo "${KONFLUX_URL}/ns/${KONFLUX_NAMESPACE}/applications/${KONFLUX_APPLICATION_NAME}/pipelineruns/${PIPELINE_RUN}" + SOME_PIPELINE_FAILED=true + elif [[ "$STATUS" == "True" ]]; then + SOME_PIPELINE_SUCCEEDED=true + fi + done + if $SOME_PIPELINE_SUCCEEDED ; then + exit 0 + fi + # If we reach here, no pipelines succeeded - fail the main pipeline + exit 1 \ No newline at end of file diff --git a/vendor/github.com/redhat-appstudio/helmet/framework/app.go b/vendor/github.com/redhat-appstudio/helmet/framework/app.go index 41db9d9d4..00ae78b47 100644 --- a/vendor/github.com/redhat-appstudio/helmet/framework/app.go +++ b/vendor/github.com/redhat-appstudio/helmet/framework/app.go @@ -6,6 +6,7 @@ import ( "github.com/redhat-appstudio/helmet/api" "github.com/redhat-appstudio/helmet/internal/chartfs" + "github.com/redhat-appstudio/helmet/internal/config" "github.com/redhat-appstudio/helmet/internal/flags" "github.com/redhat-appstudio/helmet/internal/integrations" "github.com/redhat-appstudio/helmet/internal/k8s" @@ -32,6 +33,9 @@ type App struct { mcpToolsBuilder mcptools.MCPToolsBuilder // tools builder mcpImage string // installer image installerTarball []byte // embedded installer tarball + + loadCreateConfig config.CreateConfigLoader // optional config --create loader + mergedInstallerConfig bool // empty path means merge fragments } // Command exposes the Cobra command. @@ -83,9 +87,14 @@ func (a *App) setupRootCmd() error { return fmt.Errorf("failed to load modules: %w", err) } + if a.mergedInstallerConfig && a.loadCreateConfig == nil { + return fmt.Errorf( + "merged installer layout requires WithLoadCreateConfig (see framework options)") + } + // Register standard subcommands. a.rootCmd.AddCommand(subcmd.NewIntegration( - a.AppCtx, runCtx, a.integrationManager, + a.AppCtx, runCtx, a.integrationManager, a.flags, )) // Use default builder if none provided. @@ -102,7 +111,13 @@ func (a *App) setupRootCmd() error { // Other subcommands via api.Runner. subs := []api.SubCommand{ - subcmd.NewConfig(a.AppCtx, runCtx, a.flags), + subcmd.NewConfig( + a.AppCtx, + runCtx, + a.flags, + a.mergedInstallerConfig, + a.loadCreateConfig, + ), subcmd.NewDeploy(a.AppCtx, runCtx, a.flags, a.integrationManager, a.installerTarball), subcmd.NewInstaller(a.AppCtx, runCtx, a.flags, a.installerTarball), subcmd.NewMCPServer(a.AppCtx, runCtx, a.flags, a.integrationManager, mcpBuilder, a.mcpImage), @@ -181,6 +196,22 @@ func NewAppFromTarball( return NewApp(appCtx, cfs, opts...) } +func distributedInstallerMergeLoader( + cfs *chartfs.ChartFS, + explicitPath string, + namespace string, + appIdentifier string, +) (*config.Config, error) { + if explicitPath != "" { + return config.NewConfigFromFile(cfs, explicitPath, namespace, appIdentifier) + } + payload, err := config.MergeDistributedInstallerYAML(cfs, appIdentifier) + if err != nil { + return nil, err + } + return config.NewConfigFromBytes(payload, namespace, appIdentifier) +} + // StandardIntegrations returns the list of standard integration modules. // This exposes the standard integrations (GitHub, GitLab, Quay, etc.) // through the public API for use with WithIntegrations option. diff --git a/vendor/github.com/redhat-appstudio/helmet/framework/integrations.go b/vendor/github.com/redhat-appstudio/helmet/framework/integrations.go index a4e4531c7..40f21b222 100644 --- a/vendor/github.com/redhat-appstudio/helmet/framework/integrations.go +++ b/vendor/github.com/redhat-appstudio/helmet/framework/integrations.go @@ -1,7 +1,10 @@ package framework import ( + "fmt" "log/slog" + "slices" + "strings" "github.com/redhat-appstudio/helmet/api" "github.com/redhat-appstudio/helmet/internal/integration" @@ -11,6 +14,47 @@ import ( "github.com/redhat-appstudio/helmet/internal/subcmd" ) +// SelectIntegrations returns the subset of modules whose names appear in names, +// in first-seen name order after duplicates are removed. If names is empty, it +// returns modules unchanged (same slice reference, no reordering). It returns an +// error if any name is not present in modules. +func SelectIntegrations(modules []api.IntegrationModule, names ...string) ([]api.IntegrationModule, error) { + if len(names) == 0 { + return modules, nil + } + byName := make(map[string]api.IntegrationModule, len(modules)) + for _, m := range modules { + byName[m.Name] = m + } + seen := make(map[string]struct{}, len(names)) + unique := make([]string, 0, len(names)) + for _, n := range names { + if strings.TrimSpace(n) == "" { + return nil, fmt.Errorf("integration name cannot be empty") + } + if _, ok := seen[n]; ok { + continue + } + seen[n] = struct{}{} + unique = append(unique, n) + } + var unknown []string + for _, n := range unique { + if _, ok := byName[n]; !ok { + unknown = append(unknown, n) + } + } + if len(unknown) > 0 { + slices.Sort(unknown) + return nil, fmt.Errorf("unknown integration names: %s", strings.Join(unknown, ", ")) + } + out := make([]api.IntegrationModule, 0, len(unique)) + for _, n := range unique { + out = append(out, byName[n]) + } + return out, nil +} + // GitHubModuleWithURLProvider returns a GitHub integration module that uses the // given URLProvider for webhook, homepage, and optional callback URLs when not // set by flags. Use this in custom installers (e.g. helmet-ex) to supply URLs diff --git a/vendor/github.com/redhat-appstudio/helmet/framework/options.go b/vendor/github.com/redhat-appstudio/helmet/framework/options.go index 9406e4787..2bf35c62b 100644 --- a/vendor/github.com/redhat-appstudio/helmet/framework/options.go +++ b/vendor/github.com/redhat-appstudio/helmet/framework/options.go @@ -2,6 +2,7 @@ package framework import ( "github.com/redhat-appstudio/helmet/api" + "github.com/redhat-appstudio/helmet/internal/config" "github.com/redhat-appstudio/helmet/internal/mcptools" ) @@ -17,6 +18,24 @@ func WithIntegrations(modules ...api.IntegrationModule) Option { } } +// WithLoadCreateConfig replaces the default "config --create" loader. +func WithLoadCreateConfig(fn config.CreateConfigLoader) Option { + return func(a *App) { + a.loadCreateConfig = fn + } +} + +// WithDistributedInstallerMergeLayout configures "config --create" without a file +// argument to merge installer/config/settings.yaml, installer/helmet.yaml, +// and charts//config.yaml fragments (see config.MergeDistributedInstallerYAML). +// Overrides the default single-file loader. +func WithDistributedInstallerMergeLayout() Option { + return func(a *App) { + a.mergedInstallerConfig = true + a.loadCreateConfig = distributedInstallerMergeLoader + } +} + // WithMCPImage sets the container image for the MCP server. func WithMCPImage(image string) Option { return func(a *App) { diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/annotations/annotations.go b/vendor/github.com/redhat-appstudio/helmet/internal/annotations/annotations.go index 79789b5bd..fe45bda50 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/annotations/annotations.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/annotations/annotations.go @@ -8,10 +8,32 @@ const RepoURI = "helmet.redhat-appstudio.github.com" const ( ProductName = RepoURI + "/product-name" DependsOn = RepoURI + "/depends-on" + // DependsOnGlobalCharts lists charts under the installer charts/ directory (global charts). + DependsOnGlobalCharts = RepoURI + "/depends-on-global-charts" + // DependsOnBundleCharts lists sibling charts in the same bundles//charts/ directory. + DependsOnBundleCharts = RepoURI + "/depends-on-bundle-charts" + // DependsOnBundles lists bundle directory names (bundles/) whose charts must be ordered before this chart. + DependsOnBundles = RepoURI + "/depends-on-bundles" Weight = RepoURI + "/weight" UseProductNamespace = RepoURI + "/use-product-namespace" + // InstallReleaseInInstallerNamespace, when "true", forces the Helm release + // namespace to the installer namespace even when the chart is tied to a product + // that uses a different product namespace. Use when chart templates place + // resources (e.g. integration Secrets) in the installer namespace so Helm + // ownership metadata stays consistent. + InstallReleaseInInstallerNamespace = RepoURI + "/install-release-in-installer-namespace" IntegrationsProvided = RepoURI + "/integrations-provided" + // InstallerIntegrationID documents a chart id suffix (local:// in helmet.yaml; + // must match Chart name tssc- when set). Optional. + InstallerIntegrationID = RepoURI + "/installer-integration-id" + // IntegrationDisplayName is an optional human-readable title (defaults to Chart description). + IntegrationDisplayName = RepoURI + "/integration-display-name" IntegrationsRequired = RepoURI + "/integrations-required" - PostDeploy = RepoURI + "/post-deploy" + // BundleTypesSupported lists product vs legacy integration tokens (comma-separated, + // or the keyword "both"). Only product placement is used for Helm installs. + BundleTypesSupported = RepoURI + "/bundle-types-supported" + // BundleType is legacy; use BundleTypesSupported. Values: integration, product, dual. + BundleType = RepoURI + "/bundle-type" + PostDeploy = RepoURI + "/post-deploy" Config = RepoURI + "/config" ) diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/annotations/bundle_types.go b/vendor/github.com/redhat-appstudio/helmet/internal/annotations/bundle_types.go new file mode 100644 index 000000000..0f0b79eac --- /dev/null +++ b/vendor/github.com/redhat-appstudio/helmet/internal/annotations/bundle_types.go @@ -0,0 +1,67 @@ +package annotations + +import ( + "fmt" + "strings" +) + +// ParseBundleTypesSupported reports whether a chart declares product bundle support +// and/or legacy "integration" bundle tokens (integration Helm installs are unused; +// prefer listing charts under installer.products only). +// +// New annotation helmet.../bundle-types-supported (preferred): +// - "product" — may appear under installer.products +// - "integration" — legacy token (charts are not installed via installer.integrations) +// - "integration,product" or "both" — product listing allowed; integration token ignored for topology +// +// Legacy helmet.../bundle-type (used when bundle-types-supported is empty): +// - (empty) — product-only (same as charts without annotations) +// - "integration" — legacy integration-only token +// - "product" — product-only +// - "dual" — legacy dual token (product install still uses product support bits) +func ParseBundleTypesSupported(bundleTypesSupported, legacyBundleType string) (integration, product bool, err error) { + s := strings.TrimSpace(bundleTypesSupported) + if s != "" { + return parseNewBundleTypesSupported(s) + } + return parseLegacyBundleType(strings.TrimSpace(legacyBundleType)) +} + +func parseLegacyBundleType(legacy string) (integration, product bool, err error) { + switch strings.ToLower(legacy) { + case "": + return false, true, nil + case "integration": + return true, false, nil + case "dual": + return true, true, nil + case "product": + return false, true, nil + default: + return false, false, fmt.Errorf("unknown legacy %s value %q", BundleType, legacy) + } +} + +func parseNewBundleTypesSupported(s string) (integration, product bool, err error) { + parts := strings.Split(s, ",") + for _, p := range parts { + tok := strings.ToLower(strings.TrimSpace(p)) + if tok == "" { + continue + } + switch tok { + case "integration": + integration = true + case "product": + product = true + case "both": + integration, product = true, true + default: + return false, false, fmt.Errorf("unknown %s token %q", BundleTypesSupported, tok) + } + } + if !integration && !product { + return false, false, fmt.Errorf("%s must list at least one of integration, product, or both", BundleTypesSupported) + } + return integration, product, nil +} diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/chartfs/chartfs.go b/vendor/github.com/redhat-appstudio/helmet/internal/chartfs/chartfs.go index 0d5d77da5..a45b73052 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/chartfs/chartfs.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/chartfs/chartfs.go @@ -1,9 +1,15 @@ package chartfs import ( + "errors" + "fmt" "io/fs" "os" + "path" "path/filepath" + "strings" + + "github.com/redhat-appstudio/helmet/internal/constants" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" @@ -35,11 +41,135 @@ func (c *ChartFS) ReadFile(name string) ([]byte, error) { return fs.ReadFile(c.fsys, name) } +// LoadedChart pairs a Helm chart with its source directory path relative to the +// ChartFS root (e.g. "charts/product-a"). +type LoadedChart struct { + Path string + Chart *chart.Chart +} + +// ReadValuesTemplate loads values for chartDir: first bundles//values.yaml.tpl +// when chartDir lives under bundles//charts/, then chartDir/values.yaml.tpl, +// otherwise rootValuesPath. +func (c *ChartFS) ReadValuesTemplate(chartDir, rootValuesPath string) ([]byte, string, error) { + if chartDir != "" { + if br := bundleRootFromChartDir(chartDir); br != "" { + p := path.Join(br, constants.ValuesFilename) + b, err := c.ReadFile(p) + if err == nil { + return b, p, nil + } + if !errors.Is(err, fs.ErrNotExist) && !errors.Is(err, os.ErrNotExist) { + return nil, "", err + } + } + p := path.Join(chartDir, constants.ValuesFilename) + b, err := c.ReadFile(p) + if err == nil { + return b, p, nil + } + if !errors.Is(err, fs.ErrNotExist) && !errors.Is(err, os.ErrNotExist) { + return nil, "", err + } + } + b, err := c.ReadFile(rootValuesPath) + if err != nil { + return nil, "", err + } + return b, rootValuesPath, nil +} + +func bundleRootFromChartDir(chartDir string) string { + chartDir = filepath.ToSlash(chartDir) + const sep = "/charts/" + i := strings.Index(chartDir, sep) + if i < 0 { + return "" + } + return chartDir[:i] +} + +// FindChartDir returns the directory containing Chart.yaml for a chart named chartName +// (Helm metadata name), searching charts/ then bundles/*/charts/. +func (c *ChartFS) FindChartDir(chartName string) (string, error) { + if chartName == "" || strings.Contains(chartName, "..") { + return "", fmt.Errorf("invalid chart name %q", chartName) + } + primary := path.Join("charts", chartName) + cy := path.Join(primary, chartutil.ChartfileName) + if _, err := fs.Stat(c.fsys, cy); err == nil { + return filepath.ToSlash(primary), nil + } + entries, err := fs.ReadDir(c.fsys, "bundles") + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return "", fmt.Errorf("%w: chart %q: not found under charts/ or bundles/*/charts/", fs.ErrNotExist, chartName) + } + return "", err + } + for _, e := range entries { + if !e.IsDir() { + continue + } + bid := e.Name() + p := path.Join("bundles", bid, "charts", chartName) + if _, err := fs.Stat(c.fsys, path.Join(p, chartutil.ChartfileName)); err == nil { + return filepath.ToSlash(p), nil + } + } + return "", fmt.Errorf("%w: chart %q: not found under charts/ or bundles/*/charts/", fs.ErrNotExist, chartName) +} + +// ResolveChartDir maps a user-provided chart reference to the chart directory path +// relative to the ChartFS root. It accepts: +// - an exact path where Chart.yaml exists (e.g. charts/tssc-quay or bundles/tpa/charts/tssc-tpa); +// - a path whose last segment is the Helm chart metadata name (e.g. charts/tssc-tpa); +// - a chart metadata name alone (e.g. tssc-tpa); +// - a short product id when the chart is named tssc- (e.g. tpa → tssc-tpa, charts/tpa). +func (c *ChartFS) ResolveChartDir(arg string) (string, error) { + arg = filepath.ToSlash(strings.TrimSpace(arg)) + arg = strings.TrimSuffix(arg, "/") + if arg == "" { + return "", fmt.Errorf("empty chart path") + } + if _, err := c.GetChartFiles(arg); err == nil { + return arg, nil + } + base := path.Base(arg) + if base == "." || base == "" { + return "", fmt.Errorf("invalid chart path %q", arg) + } + if dir, err := c.FindChartDir(base); err == nil { + return dir, nil + } + if !strings.HasPrefix(base, "tssc-") { + if dir, err := c.FindChartDir("tssc-" + base); err == nil { + return dir, nil + } + } + return "", fmt.Errorf("%w: chart %q: use path under charts/ or bundles/*/charts/, or chart metadata name", fs.ErrNotExist, arg) +} + +// ListChartsUnder returns chart directory paths under relRoot that contain Chart.yaml. +func (c *ChartFS) ListChartsUnder(relRoot string) ([]string, error) { + relRoot = filepath.ToSlash(strings.Trim(relRoot, "/")) + if relRoot == "" { + return nil, fmt.Errorf("empty chart root") + } + return c.walkAndFindChartDirs(c.fsys, relRoot) +} + // Open opens the named file. Implements "fs.FS" interface. func (c *ChartFS) Open(name string) (fs.File, error) { return c.fsys.Open(name) } +// WalkDir walks file names under root (delegates to io/fs.WalkDir on the chart tree). +func (c *ChartFS) WalkDir(root string, fn fs.WalkDirFunc) error { + root = filepath.ToSlash(strings.TrimPrefix(strings.TrimSpace(root), "/")) + return fs.WalkDir(c.fsys, root, fn) +} + // walkChartDir walks through the chart directory, and loads the chart files. func (c *ChartFS) walkChartDir(fsys fs.FS, chartPath string) (*chart.Chart, error) { bf := NewBufferedFiles(fsys, chartPath) @@ -83,18 +213,21 @@ func (c *ChartFS) walkAndFindChartDirs( } // GetAllCharts retrieves all Helm charts from the filesystem. -func (c *ChartFS) GetAllCharts() ([]chart.Chart, error) { - charts := []chart.Chart{} +func (c *ChartFS) GetAllCharts() ([]LoadedChart, error) { + charts := []LoadedChart{} chartDirs, err := c.walkAndFindChartDirs(c.fsys, ".") if err != nil { return nil, err } for _, chartDir := range chartDirs { - chart, err := c.GetChartFiles(chartDir) + hc, err := c.GetChartFiles(chartDir) if err != nil { return nil, err } - charts = append(charts, *chart) + charts = append(charts, LoadedChart{ + Path: filepath.ToSlash(chartDir), + Chart: hc, + }) } return charts, nil } diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/config/bundle_integration_outputs.go b/vendor/github.com/redhat-appstudio/helmet/internal/config/bundle_integration_outputs.go new file mode 100644 index 000000000..e4331fa66 --- /dev/null +++ b/vendor/github.com/redhat-appstudio/helmet/internal/config/bundle_integration_outputs.go @@ -0,0 +1,280 @@ +package config + +import ( + "errors" + "fmt" + "io/fs" + "path" + "path/filepath" + "regexp" + "sort" + "strings" + + "github.com/redhat-appstudio/helmet/internal/chartfs" + "github.com/redhat-appstudio/helmet/internal/constants" +) + +const ( + distributedBundlesRelDir = "bundles" + distributedChartsRelDir = "charts" +) + +// OutputTypeIntegrationSecret is the bundle output kind for integration Secrets — +// Secrets downstream charts consume through the integrations framework (names like +// tssc--integration), not unrelated operator Secrets. +const OutputTypeIntegrationSecret = "integration_secret" + +var integrationSecretFullNamePattern = regexp.MustCompile( + `^tssc-[a-z0-9](?:[a-z0-9-]*[a-z0-9])?-integration$`, +) + +var ( + integrationSecretMetadataNameRX = regexp.MustCompile( + `(?m)^\s*name:\s*(tssc-[a-z0-9-]+-integration)\s*$`, + ) + kindSecretLine = regexp.MustCompile(`(?mi)^kind:\s*Secret\s*$`) + yamlDocSplit = regexp.MustCompile(`(?m)^---\s*$`) + + fromLiteralKeys = regexp.MustCompile(`--from-literal=(?:"([^"]+)=|([^=]+)=)`) + + argocdEnvKeysRX = regexp.MustCompile(`(?m)^(ARGOCD_[A-Z0-9_]+)=\$\{`) +) + +func looksLikeBundledDiscoveryFile(rel string) bool { + rel = filepath.ToSlash(rel) + switch { + case strings.Contains(rel, "/templates/"): + return strings.HasSuffix(rel, ".yaml") || + strings.HasSuffix(rel, ".yml") || + strings.HasSuffix(rel, ".tpl") + case strings.Contains(rel, "/scripts/"): + return strings.HasSuffix(rel, ".sh") + default: + return false + } +} + +func mergeSecretsFromScripts(content string, out map[string][]string) { + if gn := guessStackroxIntegrationSecret(content); gn != "" { + from := extractFromLiteralsAcrossScript(content) + appendKeysMerge(out, gn, from) + } + if strings.Contains(content, "argocd-helper.sh") || + strings.Contains(content, "argocd_store_credentials") { + for _, mk := range argocdEnvKeysRX.FindAllStringSubmatch(content, -1) { + if len(mk) > 1 { + appendKeysMerge(out, "tssc-argocd-integration", []string{mk[1]}) + } + } + } +} + +func guessStackroxIntegrationSecret(content string) string { + if strings.Contains(content, "stackrox-helper.sh") || + strings.Contains(content, "StackRox API token") || + strings.Contains(content, "store_api_token_in_secret") { + return "tssc-acs-integration" + } + return "" +} + +func extractFromLiteralsAcrossScript(content string) []string { + set := map[string]struct{}{} + for _, m := range fromLiteralKeys.FindAllStringSubmatch(content, -1) { + var k string + switch { + case len(m) > 1 && m[1] != "": + k = m[1] + case len(m) > 2 && m[2] != "": + k = strings.TrimSpace(m[2]) + default: + continue + } + if k != "" && !strings.ContainsAny(k, "$`\"\t ") { + set[k] = struct{}{} + } + } + return sortedStrings(set) +} + +func sortedStrings(set map[string]struct{}) []string { + out := make([]string, 0, len(set)) + for k := range set { + out = append(out, k) + } + sort.Strings(out) + return out +} + +func appendKeysMerge(dest map[string][]string, secretName string, keys []string) { + if secretName == "" || len(keys) == 0 { + return + } + have := dest[secretName] + seen := map[string]struct{}{} + for _, k := range have { + seen[k] = struct{}{} + } + for _, k := range keys { + if _, ok := seen[k]; !ok { + have = append(have, k) + seen[k] = struct{}{} + } + } + sort.Strings(have) + dest[secretName] = have +} + +func mergeSecretsFromYAMLDocs(content string, dest map[string][]string) { + blocks := yamlDocSplit.Split(content, -1) + for _, blk := range blocks { + raw := strings.TrimSpace(blk) + if raw == "" || !kindSecretLine.MatchString(raw) { + continue + } + nameMatch := integrationSecretMetadataNameRX.FindStringSubmatch(raw) + if len(nameMatch) < 2 { + continue + } + appendKeysMerge(dest, nameMatch[1], extractSecretManifestKeys(raw)) + } +} + +func extractSecretManifestKeys(secretDoc string) []string { + lower := strings.ToLower(secretDoc) + var remainder string + if i := strings.Index(lower, "\nstringdata:"); i >= 0 { + remainder = secretDoc[i+1:] + } else if i := strings.Index(lower, "\ndata:"); i >= 0 { + remainder = secretDoc[i+1:] + } else { + return nil + } + keyLine := regexp.MustCompile(`^ ([a-zA-Z0-9_.-]+):\s`) + set := map[string]struct{}{} + for _, ln := range strings.Split(remainder, "\n")[1:] { + if ts := strings.TrimSpace(ln); ts == "" || strings.HasPrefix(ts, "#") { + continue + } + if !strings.HasPrefix(ln, " ") || len(ln) < 5 { + break + } + if m := keyLine.FindStringSubmatch(ln); m != nil { + k := m[1] + if strings.Contains(k, "{{") || strings.Contains(k, "}}") { + continue + } + set[k] = struct{}{} + } + } + return sortedStrings(set) +} + +func isMissingPathErr(err error) bool { + return err != nil && errors.Is(err, fs.ErrNotExist) +} + +func scanChartsFSRoot(cfs *chartfs.ChartFS, root string, dest map[string][]string) error { + err := cfs.WalkDir(filepath.ToSlash(strings.TrimPrefix(root, "/")), func(rel string, ent fs.DirEntry, e error) error { + if e != nil { + return e + } + if ent.IsDir() { + return nil + } + if !looksLikeBundledDiscoveryFile(rel) { + return nil + } + body, err := cfs.ReadFile(rel) + if err != nil { + return err + } + s := string(body) + mergeSecretsFromYAMLDocs(s, dest) + mergeSecretsFromScripts(s, dest) + return nil + }) + if err != nil && isMissingPathErr(err) { + return nil + } + return err +} + +// discoverProductIntegrationSecrets scans bundle charts and legacy charts/ +// layouts for manifests that declare integration Secret names (`tssc-*-integration`). +func discoverProductIntegrationSecrets(cfs *chartfs.ChartFS, bundleID string, helmChartName string) (map[string][]string, error) { + dest := map[string][]string{} + roots := []string{ + path.Join(distributedBundlesRelDir, bundleID, "charts"), + path.Join(distributedChartsRelDir, helmChartName), + } + for _, root := range roots { + if err := scanChartsFSRoot(cfs, root, dest); err != nil { + return nil, err + } + } + return dest, nil +} + +func stringSlicesEqualIgnoringOrder(a, b []string) bool { + if len(a) != len(b) { + return false + } + cp := append([]string(nil), a...) + cq := append([]string(nil), b...) + sort.Strings(cp) + sort.Strings(cq) + for i := range cp { + if cp[i] != cq[i] { + return false + } + } + return true +} + +// ValidateProductIntegrationOutputs verifies config.yaml `.outputs` describes only integration +// Secrets (`type` integration_secret) and lists names/keys declared by the product charts. +func ValidateProductIntegrationOutputs(cfs *chartfs.ChartFS, bundleID string, helmChartName string, outputs []BundleOutput) error { + if len(outputs) == 0 { + return nil + } + discovered, err := discoverProductIntegrationSecrets(cfs, bundleID, helmChartName) + if err != nil { + return fmt.Errorf("%s bundles/%s / %s outputs: %w", constants.ConfigFilename, bundleID, helmChartName, err) + } + for i := range outputs { + o := outputs[i] + if strings.TrimSpace(o.Type) != OutputTypeIntegrationSecret { + return fmt.Errorf( + "%s product %s outputs[%d].type=%q invalid: use %q", + constants.ConfigFilename, bundleID, i, strings.TrimSpace(o.Type), OutputTypeIntegrationSecret) + } + name := strings.TrimSpace(o.Name) + if name == "" { + return fmt.Errorf("%s product %s outputs[%d]: empty name", constants.ConfigFilename, bundleID, i) + } + if !integrationSecretFullNamePattern.MatchString(name) { + return fmt.Errorf( + "%s product %s outputs[%d].name=%q must look like tssc--integration", + constants.ConfigFilename, bundleID, i, name) + } + keysSeen, exists := discovered[name] + if !exists || len(keysSeen) == 0 { + return fmt.Errorf( + "%s product %s outputs[%d]: integration secret %q not found under bundles/%s/charts or %s/%s", + constants.ConfigFilename, bundleID, i, name, bundleID, distributedChartsRelDir, helmChartName) + } + if len(o.Data) > 0 && !stringSlicesEqualIgnoringOrder(keysSeen, o.Data) { + return fmt.Errorf( + "%s product %s outputs[%d] secret %q: data keys %#v ≠ discovered %#v", + constants.ConfigFilename, bundleID, i, name, sortedCopy(o.Data), keysSeen) + } + } + return nil +} + +func sortedCopy(s []string) []string { + cp := append([]string(nil), s...) + sort.Strings(cp) + return cp +} diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/config/config.go b/vendor/github.com/redhat-appstudio/helmet/internal/config/config.go index 566d8dd60..55db761fe 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/config/config.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/config/config.go @@ -63,11 +63,23 @@ func (c *Config) GetProduct(name string) (*Product, error) { return nil, fmt.Errorf("product '%s' not found", name) } -// GetEnabledProducts returns a map of enabled products. +// FindProduct returns the product with the given name, or nil if it is not +// listed under installer.products. +func (c *Config) FindProduct(name string) *Product { + for i := range c.Installer.Products { + if c.Installer.Products[i].Name == name { + return &c.Installer.Products[i] + } + } + return nil +} + +// GetEnabledProducts returns products that are active for deployment (omitted +// enabled or true). func (c *Config) GetEnabledProducts() Products { enabled := Products{} for _, product := range c.Installer.Products { - if product.Enabled { + if product.IsActive() { enabled = append(enabled, product) } } diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/config/load_create.go b/vendor/github.com/redhat-appstudio/helmet/internal/config/load_create.go new file mode 100644 index 000000000..200f3d433 --- /dev/null +++ b/vendor/github.com/redhat-appstudio/helmet/internal/config/load_create.go @@ -0,0 +1,30 @@ +package config + +import ( + "github.com/redhat-appstudio/helmet/internal/chartfs" +) + +// CreateConfigLoader loads installer configuration used by the "config --create" +// command. Implementations may merge distributed fragments when explicitPath is +// empty; otherwise they should load that path from ChartFS. +type CreateConfigLoader func( + chartFS *chartfs.ChartFS, + explicitPath string, + namespace string, + appIdentifier string, +) (*Config, error) + +// DefaultCreateConfigLoader loads a single YAML file (embedded or overlay). +// When explicitPath is empty it uses DefaultRelativeConfigPath. +func DefaultCreateConfigLoader( + cfs *chartfs.ChartFS, + explicitPath string, + namespace string, + appIdentifier string, +) (*Config, error) { + path := explicitPath + if path == "" { + path = DefaultRelativeConfigPath + } + return NewConfigFromFile(cfs, path, namespace, appIdentifier) +} diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/config/merge_distributed.go b/vendor/github.com/redhat-appstudio/helmet/internal/config/merge_distributed.go new file mode 100644 index 000000000..470c6c7ba --- /dev/null +++ b/vendor/github.com/redhat-appstudio/helmet/internal/config/merge_distributed.go @@ -0,0 +1,216 @@ +package config + +import ( + "errors" + "fmt" + "io/fs" + "path" + "strings" + + "github.com/redhat-appstudio/helmet/internal/annotations" + "github.com/redhat-appstudio/helmet/internal/chartfs" + "github.com/redhat-appstudio/helmet/internal/constants" + + "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/chartutil" +) + +const ( + distributedSettingsPath = "config/settings.yaml" + distributedBlueprintPath = "helmet.yaml" + distributedChartsDir = "charts" + distributedBundlesDir = "bundles" + localRefPrefix = "local://" +) + +// helmInstallerBlueprint mirrors the installer helmet.yaml blueprint. +type helmInstallerBlueprint struct { + Name string `yaml:"name"` + Products []string `yaml:"products"` +} + +// MergeDistributedInstallerYAML builds one config.yaml payload from settings, +// helmet blueprint, and per-bundle or per-chart config.yaml fragments. +func MergeDistributedInstallerYAML(cfs *chartfs.ChartFS, appIdentifier string) ([]byte, error) { + settingsBytes, err := cfs.ReadFile(distributedSettingsPath) + if err != nil { + return nil, fmt.Errorf("read %s: %w", distributedSettingsPath, err) + } + helmBytes, err := cfs.ReadFile(distributedBlueprintPath) + if err != nil { + return nil, fmt.Errorf("read %s: %w", distributedBlueprintPath, err) + } + + var blueprint helmInstallerBlueprint + if err := yaml.Unmarshal(helmBytes, &blueprint); err != nil { + return nil, fmt.Errorf("parse helmet blueprint: %w", err) + } + + if err := validateProductBundleTypes(cfs, blueprint.Products); err != nil { + return nil, err + } + + var settings map[string]interface{} + if err := yaml.Unmarshal(settingsBytes, &settings); err != nil { + return nil, fmt.Errorf("parse settings: %w", err) + } + + var products []Product + for _, ref := range blueprint.Products { + id, err := integrationIDFromLocalRef(ref) + if err != nil { + return nil, err + } + chartName, err := chartNameFromLocalRef(ref) + if err != nil { + return nil, err + } + cfgPath := path.Join(distributedBundlesDir, id, constants.ConfigFilename) + body, err := cfs.ReadFile(cfgPath) + if err != nil { + cfgPath = path.Join(distributedChartsDir, chartName, constants.ConfigFilename) + body, err = cfs.ReadFile(cfgPath) + if err != nil { + return nil, fmt.Errorf( + "read product config (try bundles/%s/%s or charts/%s/%s): %w", + id, constants.ConfigFilename, chartName, constants.ConfigFilename, err) + } + } + var p Product + if err := yaml.Unmarshal(body, &p); err != nil { + return nil, fmt.Errorf("parse product config %s: %w", cfgPath, err) + } + if err := ValidateProductIntegrationOutputs(cfs, id, chartName, p.Outputs); err != nil { + return nil, err + } + stripped := p + stripped.Outputs = nil + products = append(products, stripped) + } + + doc := map[string]interface{}{ + appIdentifier: map[string]interface{}{ + "settings": settings, + "products": products, + }, + } + + out, err := yaml.Marshal(doc) + if err != nil { + return nil, err + } + return append([]byte("---\n"), out...), nil +} + +func chartNameFromLocalRef(ref string) (string, error) { + id, err := integrationIDFromLocalRef(ref) + if err != nil { + return "", err + } + return fmt.Sprintf("tssc-%s", id), nil +} + +func integrationIDFromLocalRef(ref string) (string, error) { + if !strings.HasPrefix(ref, localRefPrefix) { + return "", fmt.Errorf("expected %q reference, got %q", localRefPrefix, ref) + } + return strings.TrimPrefix(ref, localRefPrefix), nil +} + +// mapAtDotPath returns the mapping at the given dot-separated path (e.g. "quay" +// or "tpa.trustedProfileAnalyzer.integrationSecret"). +func mapAtDotPath(root map[string]interface{}, dotPath string) (map[string]interface{}, error) { + if dotPath == "" { + return nil, fmt.Errorf("empty values path") + } + parts := strings.Split(dotPath, ".") + var cur interface{} = root + for i, seg := range parts { + if seg == "" { + continue + } + m, ok := cur.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("segment %q is not a mapping", strings.Join(parts[:i], ".")) + } + cur, ok = m[seg] + if !ok { + return nil, fmt.Errorf("missing key %q", seg) + } + } + out, ok := cur.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("path must resolve to a mapping, got %T", cur) + } + return out, nil +} + +func readHelmChartYAML(cfs *chartfs.ChartFS, chartName string) (ann map[string]string, description, nameField string, err error) { + dir, err := cfs.FindChartDir(chartName) + if err != nil { + return nil, "", "", err + } + chartPath := path.Join(dir, chartutil.ChartfileName) + body, err := cfs.ReadFile(chartPath) + if err != nil { + return nil, "", "", err + } + return parseHelmChartYAML(body) +} + +func parseHelmChartYAML(body []byte) (ann map[string]string, description, nameField string, err error) { + var doc struct { + Name string `yaml:"name"` + Description string `yaml:"description"` + Metadata struct { + Annotations map[string]string `yaml:"annotations"` + } `yaml:"metadata"` + Annotations map[string]string `yaml:"annotations"` + } + if err := yaml.Unmarshal(body, &doc); err != nil { + return nil, "", "", err + } + ann = map[string]string{} + for k, v := range doc.Metadata.Annotations { + ann[k] = v + } + for k, v := range doc.Annotations { + ann[k] = v + } + return ann, doc.Description, doc.Name, nil +} + +// validateProductBundleTypes rejects charts that do not support a product bundle +// when listed under products (e.g. integration-only charts). +func validateProductBundleTypes(cfs *chartfs.ChartFS, productRefs []string) error { + for _, ref := range productRefs { + chartName, err := chartNameFromLocalRef(ref) + if err != nil { + return err + } + _, supportsProduct, err := readChartBundleSupport(cfs, chartName) + if err != nil { + return fmt.Errorf("read chart %s: %w", chartName, err) + } + if !supportsProduct { + return fmt.Errorf( + "helmet.yaml: product %q (chart %s) does not support a product bundle; set %s to include product", + ref, chartName, annotations.BundleTypesSupported) + } + } + return nil +} + +func readChartBundleSupport(cfs *chartfs.ChartFS, chartName string) (integration, product bool, err error) { + ann, _, _, err := readHelmChartYAML(cfs, chartName) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return false, true, nil + } + return false, false, err + } + return annotations.ParseBundleTypesSupported( + ann[annotations.BundleTypesSupported], + ann[annotations.BundleType], + ) +} diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/config/node.go b/vendor/github.com/redhat-appstudio/helmet/internal/config/node.go index dd57b1d36..af62eacd1 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/config/node.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/config/node.go @@ -58,7 +58,7 @@ func FindNode(node *yaml.Node, key string) (*yaml.Node, error) { // - If the node is a MappingNode, it searches for the specified key. // If found, it marshals the newValue to YAML and unmarshals it back // into a new yaml.Node to preserve type fidelity, then replaces the -// existing value node. If the key is not found, it returns nil. +// existing value node. If the key is not found, it appends a new entry. // - For any other node kind, it returns an error. func UpdateMappingValue(node *yaml.Node, key string, newValue any) error { switch node.Kind { @@ -100,6 +100,26 @@ func UpdateMappingValue(node *yaml.Node, key string, newValue any) error { return nil } + // Key not found: append a new key-value pair (e.g. optional fields like + // "enabled" omitted from YAML until toggled). + var insertDoc yaml.Node + bs, err := yaml.Marshal(newValue) + if err != nil { + return err + } + if err := yaml.Unmarshal(bs, &insertDoc); err != nil { + return err + } + if len(insertDoc.Content) == 0 { + return fmt.Errorf("invalid new value for key %q", key) + } + keyNode := &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!str", + Value: key, + } + insertedValue := insertDoc.Content[0] + node.Content = append(node.Content, keyNode, insertedValue) return nil default: return fmt.Errorf("cannot set value on node kind: %v", node.Kind) diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/config/product.go b/vendor/github.com/redhat-appstudio/helmet/internal/config/product.go index f1381ffde..3c6f4b59a 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/config/product.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/config/product.go @@ -6,17 +6,41 @@ import ( "strings" ) +// BundleOutput documents an integration Secret bundle charts expose to dependents +// (name must look like tssc--integration). Not written to the merged cluster ConfigMap; +// type must be OutputTypeIntegrationSecret and values are validated against chart templates. +type BundleOutput struct { + Name string `yaml:"name"` + Type string `yaml:"type"` + Notes string `yaml:"notes,omitempty"` + Data []string `yaml:"data,omitempty"` +} + +// productYAML is the wire shape for merged/saved YAML: omit "enabled" unless the +// user has explicitly opted out (false). Absent or true means active without a key. +type productYAML struct { + Name string `yaml:"name"` + Enabled *bool `yaml:"enabled,omitempty"` + Namespace *string `yaml:"namespace,omitempty"` + Properties map[string]interface{} `yaml:"properties,omitempty"` +} + // ProductSpec contains the configuration for a specific product. type Product struct { // Name of the product. Name string `yaml:"name"` - // Enabled product toggle. - Enabled bool `yaml:"enabled"` + // Enabled, when set to false, excludes the product from deployment while + // keeping its entry in the config (e.g. MCP / integration flows). When nil + // or omitted in YAML, the product is active—listing in the installer config + // is the primary enablement signal. + Enabled *bool `yaml:"enabled,omitempty"` // Namespace target namespace for product's dependency (Helm chart). If empty, // it defaults to the installer's namespace. Namespace *string `yaml:"namespace,omitempty"` // Properties contains the product specific configuration. Properties map[string]interface{} `yaml:"properties"` + // Outputs lists bundle-provided artifacts for dependents (see BundleOutput). + Outputs []BundleOutput `yaml:"outputs,omitempty"` } // KeyName returns a sanitized key name for the product. @@ -40,6 +64,27 @@ func (p *Product) KeyName() string { return key } +// MarshalYAML emits optional "enabled" only when explicitly false. Omitted key and +// true both mean active; this keeps default merged config free of enabled flags. +func (p Product) MarshalYAML() (interface{}, error) { + out := productYAML{ + Name: p.Name, + Namespace: p.Namespace, + Properties: p.Properties, + } + if p.Enabled != nil && !*p.Enabled { + v := false + out.Enabled = &v + } + return out, nil +} + +// IsActive reports whether this product should be deployed. Omitted or nil +// Enabled means true. +func (p *Product) IsActive() bool { + return p.Enabled == nil || *p.Enabled +} + // GetNamespace returns the product namespace, or an empty string if not set. func (p *Product) GetNamespace() string { if p.Namespace == nil { @@ -50,7 +95,10 @@ func (p *Product) GetNamespace() string { // Validate validates the product configuration, checking for missing fields. func (p *Product) Validate() error { - if p.Enabled && p.GetNamespace() == "" { + if !p.IsActive() { + return nil + } + if p.GetNamespace() == "" { return fmt.Errorf("%w: product %q: missing namespace", ErrInvalidConfig, p.Name) } diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/deployer/helm.go b/vendor/github.com/redhat-appstudio/helmet/internal/deployer/helm.go index 1e7645b89..3cf1e7054 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/deployer/helm.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/deployer/helm.go @@ -44,15 +44,15 @@ var ErrUpgradeFailed = errors.New("upgrade failed") // printRelease prints the Helm release information. func (h *Helm) printRelease(rel *release.Release) { - // In debug mode, print the configuration values using key-value pairs. - if !h.flags.DryRun && h.flags.Debug { + // In verbose mode, print the configuration values using key-value pairs. + if !h.flags.DryRun && h.flags.Verbose { printer.ValuesPrinter("Config", rel.Config) } printer.HelmReleasePrinter(rel) - // Print extended release information only in dry-run or debug mode. This + // Print extended release information only in dry-run or verbose mode. This // allows rendering chart templates (dry-run) while inspecting the release // manifests. - if h.flags.DryRun || h.flags.Debug { + if h.flags.DryRun || h.flags.Verbose { printer.HelmExtendedReleasePrinter(rel) } printer.HelmReleaseNotesPrinter(rel) diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/engine/engine.go b/vendor/github.com/redhat-appstudio/helmet/internal/engine/engine.go index 9087b53ca..9cec29bf4 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/engine/engine.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/engine/engine.go @@ -2,7 +2,7 @@ package engine import ( "bytes" - "html/template" + "text/template" "github.com/redhat-appstudio/helmet/internal/constants" "github.com/redhat-appstudio/helmet/internal/k8s" diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/engine/variables.go b/vendor/github.com/redhat-appstudio/helmet/internal/engine/variables.go index f1b11c5d6..37c4b147a 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/engine/variables.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/engine/variables.go @@ -30,7 +30,13 @@ func (v *Variables) SetInstaller(cfg *config.Config) error { products[product.KeyName()] = product } v.Installer["Products"], err = UnstructuredType(products) - return err + if err != nil { + return err + } + // Templates may reference .Installer.Integrations for backward compatibility; + // runtime integration Secrets come from the integration CLI subcommands. + v.Installer["Integrations"] = map[string]interface{}{} + return nil } func getMinorVersion(version string) (string, error) { diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/flags/flags.go b/vendor/github.com/redhat-appstudio/helmet/internal/flags/flags.go index befea6e36..ae4ed6997 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/flags/flags.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/flags/flags.go @@ -15,8 +15,8 @@ import ( // Flags represents the global flags for the application. type Flags struct { - Debug bool // debug mode DryRun bool // dry-run mode + Verbose bool // verbose output KubeConfigPath string // path to the kubeconfig file LogLevel *slog.Level // log verbosity level Timeout time.Duration // helm client timeout @@ -25,9 +25,15 @@ type Flags struct { // PersistentFlags sets up the global flags. func (f *Flags) PersistentFlags(p *pflag.FlagSet) { - p.BoolVar(&f.Debug, "debug", f.Debug, "enable debug mode") p.BoolVar(&f.DryRun, "dry-run", f.DryRun, "enable dry-run mode") p.BoolVar(&f.Version, "version", f.Version, "show the application version") + p.BoolVarP( + &f.Verbose, + "verbose", + "v", + f.Verbose, + "Enable verbose output", + ) p.StringVar( &f.KubeConfigPath, "kube-config", @@ -60,7 +66,7 @@ func (f *Flags) GetLogger(out io.Writer) *slog.Logger { // LoggerWith returns a logger with contextual information. func (f *Flags) LoggerWith(l *slog.Logger) *slog.Logger { - return l.With("debug", f.Debug, "dry-run", f.DryRun, "timeout", f.Timeout) + return l.With("dry-run", f.DryRun, "timeout", f.Timeout, "verbose", f.Verbose) } // ShowVersion shows the application version. @@ -83,11 +89,11 @@ func NewFlags() *Flags { kubeConfigPath = path.Join(usr.HomeDir, ".kube", "config") } return &Flags{ - Debug: false, DryRun: false, KubeConfigPath: kubeConfigPath, LogLevel: &defaultLogLevel, Timeout: 15 * time.Minute, + Verbose: false, Version: false, } } diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/flags/installer.go b/vendor/github.com/redhat-appstudio/helmet/internal/flags/installer.go index 68b7ed4f6..8292f9fc0 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/flags/installer.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/flags/installer.go @@ -15,6 +15,6 @@ func SetValuesTmplFlag(p *pflag.FlagSet, v *string) { v, ValuesTemplateFlag, constants.ValuesFilename, - "Path to the values template file", + "Path to the default values template; a chart may use /values.yaml.tpl instead", ) } diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/githubapp/githubapp.go b/vendor/github.com/redhat-appstudio/helmet/internal/githubapp/githubapp.go index 48770cbed..e07dfa278 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/githubapp/githubapp.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/githubapp/githubapp.go @@ -152,10 +152,6 @@ func (g *GitHubApp) oAuth2Workflow( // background, this process should obtain the oAuth code. go func() { localhostURL := fmt.Sprintf("http://localhost:%d", g.webServerPort) - fmt.Printf( - "Opening %q, click on the link to create your new GitHub App\n", - localhostURL, - ) go func() { err := webServer.ListenAndServe() if err != nil && !errors.Is(err, http.ErrServerClosed) { @@ -165,7 +161,17 @@ func (g *GitHubApp) oAuth2Workflow( g.log().Debug("opening browser", "url", localhostURL) go func() { if err := OpenInBrowser(localhostURL); err != nil { - g.logger.Error("failed to open browser", "error", err) + g.logger.Debug("failed to open browser", "error", err) + // When using the image, the web browser is not available. + fmt.Printf( + "Open %q in your browser, and click on the link to create your new GitHub App\n", + localhostURL, + ) + } else { + fmt.Printf( + "Opening %q, click on the link to create your new GitHub App\n", + localhostURL, + ) } }() }() diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/installer/installer.go b/vendor/github.com/redhat-appstudio/helmet/internal/installer/installer.go index 7ed2e7efc..f21ff3676 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/installer/installer.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/installer/installer.go @@ -66,7 +66,19 @@ func (i *Installer) RenderValues() error { i.logger.Debug("Preparing rendered values for Helm installation") var err error i.values, err = chartutil.ReadValues(i.valuesBytes) - return err + if err != nil { + return err + } + // Expose CLI dry-run to charts as .Values.helmet.dryRun so templates can skip + // cluster-dependent checks (e.g. lookup integration secrets not created yet). + if i.flags != nil && i.flags.DryRun { + i.values = chartutil.CoalesceTables(i.values, chartutil.Values{ + "helmet": chartutil.Values{ + "dryRun": true, + }, + }) + } + return nil } // PrintValues prints the parsed values to the console. diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/installer/job.go b/vendor/github.com/redhat-appstudio/helmet/internal/installer/job.go index e85a4a9f7..d2eefe862 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/installer/job.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/installer/job.go @@ -191,7 +191,7 @@ func (j *Job) applyClusterRoleBinding( // in-cluster. func (j *Job) createJob( ctx context.Context, - debug, dryRun bool, + verbose, dryRun bool, namespace, image string, ) error { bc, err := j.kube.BatchV1ClientSet("") @@ -201,8 +201,8 @@ func (j *Job) createJob( // Setting up the list of arguments for the deployment job. args := []string{"deploy"} - if debug { - args = append(args, "--debug") + if verbose { + args = append(args, "--verbose") args = append(args, "--log-level=debug") } if dryRun { @@ -273,7 +273,7 @@ func (j *Job) GetJobLogFollowCmd(namespace string) string { // creates the job. func (j *Job) Run( ctx context.Context, - debug, dryRun, force bool, + verbose, dryRun, force bool, namespace, image string, ) error { state, err := j.GetState(ctx) @@ -301,7 +301,7 @@ func (j *Job) Run( return fmt.Errorf("unable to apply the cluster role binding: %w", err) } // Creating the job itself. - return j.createJob(ctx, debug, dryRun, namespace, image) + return j.createJob(ctx, verbose, dryRun, namespace, image) } // NewJob instantiates a new Job object. diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/integration/tas.go b/vendor/github.com/redhat-appstudio/helmet/internal/integration/tas.go index b2044cee7..4ea301ecc 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/integration/tas.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/integration/tas.go @@ -15,8 +15,9 @@ import ( // TrustedArtifactSigner represents the coordinates to connect to // the TrustedArtifactSigner services. type TrustedArtifactSigner struct { - rekorURL string // URL of the rekor server - tufURL string // URL of the TUF server + fulcioURL string // URL of the Fulcio server + rekorURL string // URL of the rekor server + tufURL string // URL of the TUF server } var _ Interface = &TrustedArtifactSigner{} @@ -25,12 +26,15 @@ var _ Interface = &TrustedArtifactSigner{} func (t *TrustedArtifactSigner) PersistentFlags(c *cobra.Command) { p := c.PersistentFlags() + p.StringVar(&t.fulcioURL, "fulcio-url", t.fulcioURL, + "URL of the Fulcio server (e.g. https://fulcio.sigstore.dev)") p.StringVar(&t.rekorURL, "rekor-url", t.rekorURL, "URL of the Rekor server (e.g. https://rekor.sigstore.dev)") p.StringVar(&t.tufURL, "tuf-url", t.tufURL, "URL of the TUF server (e.g. https://tuf.trustification.dev)") for _, f := range []string{ + "fulcio-url", "rekor-url", "tuf-url", } { @@ -48,6 +52,7 @@ func (t *TrustedArtifactSigner) SetArgument(string, string) error { // LoggerWith decorates the logger with the integration flags. func (t *TrustedArtifactSigner) LoggerWith(logger *slog.Logger) *slog.Logger { return logger.With( + "fulcio-url", t.fulcioURL, "rekor-url", t.rekorURL, "tuf-url", t.tufURL, ) @@ -60,6 +65,12 @@ func (t *TrustedArtifactSigner) Type() corev1.SecretType { // Validate checks the informed URLs ensure valid inputs. func (t *TrustedArtifactSigner) Validate() error { + if t.fulcioURL != "" { + // Fulcio URL is optional, but if provided, it must be valid. + if err := ValidateURL(t.fulcioURL); err != nil { + return fmt.Errorf("%w: %q", err, t.fulcioURL) + } + } if t.rekorURL == "" { return fmt.Errorf("rekor-url is required") } @@ -83,8 +94,9 @@ func (t *TrustedArtifactSigner) Data( _ *config.Config, ) (map[string][]byte, error) { return map[string][]byte{ - "rekor_url": []byte(t.rekorURL), - "tuf_url": []byte(t.tufURL), + "fulcio_url": []byte(t.fulcioURL), + "rekor_url": []byte(t.rekorURL), + "tuf_url": []byte(t.tufURL), }, nil } diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/mcptools/configtools.go b/vendor/github.com/redhat-appstudio/helmet/internal/mcptools/configtools.go index 95a68fe60..9d862b157 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/mcptools/configtools.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/mcptools/configtools.go @@ -316,9 +316,9 @@ You must inform the %q argument, with the status of the product %q`, return res, nil } // Toggle the product status. - spec.Enabled = enabled + spec.Enabled = &enabled - if res = c.setProduct(ctx, cfg, name, config.Product{Enabled: enabled}); res != nil { + if res = c.setProduct(ctx, cfg, name, *spec); res != nil { return res, nil } diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/mcptools/deploytools.go b/vendor/github.com/redhat-appstudio/helmet/internal/mcptools/deploytools.go index d9d076d18..ef5012ff4 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/mcptools/deploytools.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/mcptools/deploytools.go @@ -29,8 +29,8 @@ const ( // deploySuffix deploy tool name suffix. deploySuffix = "_deploy" - // DebugArg enables debug mode for the deployment job. - DebugArg = "debug" + // VerboseArg enables verbose mode for the deployment job. + VerboseArg = "verbose" // DryRunArg runs the deployment job on dry-run. DryRunArg = "dry-run" // ForceArg forces the recreation of the deployment job. @@ -61,10 +61,10 @@ place. Inspect the error message below to assess the issue.`, } // Deployment job flags. - var debug, dryRun, force bool + var verbose, dryRun, force bool - if v, ok := ctr.GetArguments()[DebugArg].(bool); ok { - debug = v + if v, ok := ctr.GetArguments()[VerboseArg].(bool); ok { + verbose = v } if v, ok := ctr.GetArguments()[DryRunArg].(bool); ok { dryRun = v @@ -77,7 +77,7 @@ place. Inspect the error message below to assess the issue.`, logsCmd := d.job.GetJobLogFollowCmd(cfg.Namespace()) // Issue the deployment job using the informed flags. - err = d.job.Run(ctx, debug, dryRun, force, cfg.Namespace(), d.image) + err = d.job.Run(ctx, verbose, dryRun, force, cfg.Namespace(), d.image) if err != nil { return mcp.NewToolResultError(fmt.Sprintf(` Unable to issue the deployment Job, it returned the following error: @@ -101,14 +101,14 @@ deployment status; this can take a few minutes depending on cluster performance. Use the tool periodically to verify that the deployment is proceeding as expected. Informed flags: - - debug: %v + - verbose: %v - dry-run: %v - force: %v You can follow the Kubernetes Job logs by running: %s`, - d.appName+statusSuffix, debug, dryRun, force, logsCmd, + d.appName+statusSuffix, verbose, dryRun, force, logsCmd, )), nil } @@ -143,9 +143,9 @@ Job and create a new one regardless of its state.`, mcp.DefaultBool(false), ), mcp.WithBoolean( - DebugArg, + VerboseArg, mcp.Description(` -Sets the debug mode for the deployment job. This will enable verbose logging and +Sets verbose mode for the deployment job. This enables debug logging and additional platform deployment information.`, ), mcp.DefaultBool(false), diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/resolver/collection.go b/vendor/github.com/redhat-appstudio/helmet/internal/resolver/collection.go index 8629cac6f..dd26fd614 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/resolver/collection.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/resolver/collection.go @@ -3,10 +3,13 @@ package resolver import ( "errors" "fmt" + "path" + "path/filepath" "slices" + "strings" "github.com/redhat-appstudio/helmet/api" - "helm.sh/helm/v3/pkg/chart" + "github.com/redhat-appstudio/helmet/internal/chartfs" ) // Collection represents a collection of dependencies the Resolver can utilize. @@ -74,6 +77,21 @@ func (c *Collection) GetProductDependency(product string) (*Dependency, error) { return productDependency, nil } +// FindChartProvidingIntegration returns the first chart that declares the given +// integration in integrations-provided (e.g. "quay", "trustification"). +func (c *Collection) FindChartProvidingIntegration(integrationName string) (*Dependency, error) { + if integrationName == "" { + return nil, fmt.Errorf("%w: empty integration name", ErrDependencyNotFound) + } + for chartName, dep := range c.dependencies { + if slices.Contains(dep.IntegrationsProvided(), integrationName) { + return c.dependencies[chartName], nil + } + } + return nil, fmt.Errorf("%w: no chart provides integration %q", + ErrDependencyNotFound, integrationName) +} + // GetProductNameForIntegration searches and returns the product name by integration name. // It goes though all charts and search for annotation "integrations-provided". // If it matches integration name, then returns product name, which is from @@ -95,15 +113,55 @@ func (c *Collection) GetProductNameForIntegration(integrationName string) string return productName } +// dedupeLoadedChartsPreferChartsDir collapses multiple LoadedCharts that share the +// same Helm chart name (e.g. duplicate paths). charts/ wins so topology has +// a single Dependency per Helm release name. +func dedupeLoadedChartsPreferChartsDir(charts []chartfs.LoadedChart) []chartfs.LoadedChart { + byName := map[string][]chartfs.LoadedChart{} + for _, lc := range charts { + n := lc.Chart.Name() + byName[n] = append(byName[n], lc) + } + out := make([]chartfs.LoadedChart, 0, len(byName)) + for _, group := range byName { + if len(group) == 1 { + out = append(out, group[0]) + continue + } + preferred := path.Join("charts", group[0].Chart.Name()) + var chosen chartfs.LoadedChart + found := false + for _, lc := range group { + if filepath.ToSlash(lc.Path) == preferred { + chosen = lc + found = true + break + } + } + if !found { + slices.SortFunc(group, func(a, b chartfs.LoadedChart) int { + return strings.Compare(filepath.ToSlash(a.Path), filepath.ToSlash(b.Path)) + }) + chosen = group[0] + } + out = append(out, chosen) + } + slices.SortFunc(out, func(a, b chartfs.LoadedChart) int { + return strings.Compare(a.Chart.Name(), b.Chart.Name()) + }) + return out +} + // NewCollection creates a new Collection from the given charts. It returns an // error if there are duplicate charts and product names. -func NewCollection(_ *api.AppContext, charts []chart.Chart) (*Collection, error) { +func NewCollection(_ *api.AppContext, charts []chartfs.LoadedChart) (*Collection, error) { + charts = dedupeLoadedChartsPreferChartsDir(charts) c := &Collection{dependencies: map[string]*Dependency{}} // Stores the product names found in the slice of Helm charts. productNames := []string{} // Populating the collection with dependencies. - for _, hc := range charts { - d := NewDependency(&hc) + for _, lc := range charts { + d := NewDependencyWithChartPath(lc.Chart, lc.Path) // Asserting the weight annotation is a valid integer. if _, err := d.Weight(); err != nil { return nil, fmt.Errorf("%w: %w", ErrInvalidCollection, err) diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/resolver/dependency.go b/vendor/github.com/redhat-appstudio/helmet/internal/resolver/dependency.go index 1ba80219e..93e2dcc07 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/resolver/dependency.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/resolver/dependency.go @@ -4,6 +4,7 @@ import ( "fmt" "log/slog" "strconv" + "strings" "github.com/redhat-appstudio/helmet/internal/annotations" "helm.sh/helm/v3/pkg/chart" @@ -15,6 +16,7 @@ import ( type Dependency struct { chart *chart.Chart // helm chart instance namespace string // target namespace + chartPath string // chart directory relative to ChartFS (for per-chart values.yaml.tpl) } // Dependencies represents a slice of Dependency instances. @@ -25,6 +27,7 @@ func (d *Dependency) LoggerWith(logger *slog.Logger) *slog.Logger { return logger.With( "dependency-name", d.Name(), "dependency-namespace", d.Namespace(), + "dependency-chart-dir", d.ChartPath(), ) } @@ -43,6 +46,12 @@ func (d *Dependency) Namespace() string { return d.namespace } +// ChartPath returns the filesystem path of the chart directory relative to ChartFS, +// used to resolve chart-local values.yaml.tpl. +func (d *Dependency) ChartPath() string { + return d.chartPath +} + // SetNamespace sets the namespace for this dependency. func (d *Dependency) SetNamespace(namespace string) { d.namespace = namespace @@ -57,7 +66,7 @@ func (d *Dependency) getAnnotation(annotation string) string { return "" } -// DependsOn returns a slice of dependencies names from the chart's annotation. +// DependsOn returns legacy depends-on annotation only (full list before bundle split). func (d *Dependency) DependsOn() []string { dependsOn := d.getAnnotation(annotations.DependsOn) if dependsOn == "" { @@ -66,6 +75,26 @@ func (d *Dependency) DependsOn() []string { return commaSeparatedToSlice(dependsOn) } +// DependsOnGlobalCharts lists dependencies on charts under the installer charts/ directory. +func (d *Dependency) DependsOnGlobalCharts() []string { + return commaSeparatedToSlice(d.getAnnotation(annotations.DependsOnGlobalCharts)) +} + +// DependsOnBundleCharts lists sibling charts in the same bundles//charts/ directory. +func (d *Dependency) DependsOnBundleCharts() []string { + return commaSeparatedToSlice(d.getAnnotation(annotations.DependsOnBundleCharts)) +} + +// DependsOnBundles lists bundle ids (bundles/) this chart must follow in topology. +func (d *Dependency) DependsOnBundles() []string { + return commaSeparatedToSlice(d.getAnnotation(annotations.DependsOnBundles)) +} + +// BundleID returns the bundle directory id when the chart lives under bundles//charts/. +func (d *Dependency) BundleID() string { + return bundleIDFromChartPath(d.ChartPath()) +} + // Weight returns the weight of this dependency. If no weight is specified, zero // is returned. The weight must be specified as an integer value. func (d *Dependency) Weight() (int, error) { @@ -90,6 +119,19 @@ func (d *Dependency) UseProductNamespace() string { return d.getAnnotation(annotations.UseProductNamespace) } +// InstallReleaseInInstallerNamespace is true when the chart requests the Helm +// release to be installed in the installer namespace regardless of product namespace. +func (d *Dependency) InstallReleaseInInstallerNamespace() bool { + v := strings.TrimSpace(strings.ToLower( + d.getAnnotation(annotations.InstallReleaseInInstallerNamespace))) + switch v { + case "true", "1", "yes", "y": + return true + default: + return false + } +} + // IntegrationsProvided returns the integrations provided. func (d *Dependency) IntegrationsProvided() []string { provided := d.getAnnotation(annotations.IntegrationsProvided) @@ -101,16 +143,52 @@ func (d *Dependency) IntegrationsRequired() string { return d.getAnnotation(annotations.IntegrationsRequired) } +// BundleType returns the legacy bundle-type annotation (integration, product, dual). +func (d *Dependency) BundleType() string { + return d.getAnnotation(annotations.BundleType) +} + +// BundleSupport returns whether this chart declares legacy bundle-type tokens for +// integration vs product placement (integration bundle installs are no longer used; +// only product placement remains). +func (d *Dependency) BundleSupport() (integration, product bool, err error) { + return annotations.ParseBundleTypesSupported( + d.getAnnotation(annotations.BundleTypesSupported), + d.getAnnotation(annotations.BundleType), + ) +} + +// SupportsProductBundle is true when the chart may be listed under products. +func (d *Dependency) SupportsProductBundle() bool { + _, p, err := d.BundleSupport() + if err != nil { + return false + } + return p +} + // NewDependency creates a new Dependency for the Helm chart and initially using // empty target namespace. func NewDependency(hc *chart.Chart) *Dependency { - return &Dependency{chart: hc} + return NewDependencyWithChartPath(hc, "") +} + +// NewDependencyWithChartPath creates a Dependency that knows its chart directory +// on ChartFS for per-chart values templates. +func NewDependencyWithChartPath(hc *chart.Chart, chartPath string) *Dependency { + return &Dependency{chart: hc, chartPath: chartPath} } // NewDependencyWithNamespace creates a new Dependency for the Helm chart and sets // the target namespace. func NewDependencyWithNamespace(hc *chart.Chart, ns string) *Dependency { - d := NewDependency(hc) + return NewDependencyWithNamespaceAndChartPath(hc, ns, "") +} + +// NewDependencyWithNamespaceAndChartPath creates a Dependency with namespace and +// chart directory path. +func NewDependencyWithNamespaceAndChartPath(hc *chart.Chart, ns, chartPath string) *Dependency { + d := NewDependencyWithChartPath(hc, chartPath) d.SetNamespace(ns) return d } diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/resolver/helper.go b/vendor/github.com/redhat-appstudio/helmet/internal/resolver/helper.go index 740d48e8e..f82c2bbbc 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/resolver/helper.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/resolver/helper.go @@ -1,6 +1,7 @@ package resolver import ( + "path/filepath" "strings" ) @@ -23,3 +24,33 @@ func commaSeparatedToSlice(commaSeparated string) []string { } return slice } + +// bundleIDFromChartPath returns the bundles/ directory name when chartPath is +// under bundles//charts/, otherwise "". +func bundleIDFromChartPath(chartPath string) string { + chartPath = filepath.ToSlash(chartPath) + parts := strings.Split(chartPath, "/") + for i, p := range parts { + if p == "bundles" && i+1 < len(parts) { + return parts[i+1] + } + } + return "" +} + +func dedupeOrdered(parts []string) []string { + seen := map[string]struct{}{} + out := make([]string, 0, len(parts)) + for _, p := range parts { + p = strings.TrimSpace(p) + if p == "" { + continue + } + if _, ok := seen[p]; ok { + continue + } + seen[p] = struct{}{} + out = append(out, p) + } + return out +} diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/resolver/resolver.go b/vendor/github.com/redhat-appstudio/helmet/internal/resolver/resolver.go index 6fe267baa..849e70ea3 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/resolver/resolver.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/resolver/resolver.go @@ -6,14 +6,16 @@ import ( "strings" "text/tabwriter" + "github.com/redhat-appstudio/helmet/internal/annotations" "github.com/redhat-appstudio/helmet/internal/config" ) // Resolver represents the actor that resolves dependencies between charts. type Resolver struct { - cfg *config.Config // installer configuration - collection *Collection // collection of charts - topology *Topology // topology of dependencies + cfg *config.Config // installer configuration + collection *Collection // collection of charts + topology *Topology // topology of dependencies + bundleMembers map[string][]string // bundle id -> chart names (bundles//charts/) } // ErrCircularDependency reports a circular dependency. @@ -43,16 +45,28 @@ func (r *Resolver) setDependencyNamespace(d *Dependency) error { if product == "" { namespace = r.cfg.Namespace() } else { - spec, err := r.cfg.GetProduct(product) - if err != nil { - return err + spec := r.cfg.FindProduct(product) + if spec == nil { + // Chart carries product-name but that product is not under installer.products: + // release in the installer namespace. + namespace = r.cfg.Namespace() + } else { + namespace = *spec.Namespace } - namespace = *spec.Namespace } d.SetNamespace(namespace) + r.maybeInstallReleaseInInstallerNamespace(d) return nil } +// maybeInstallReleaseInInstallerNamespace overrides the dependency namespace to the +// installer namespace when the chart declares install-release-in-installer-namespace. +func (r *Resolver) maybeInstallReleaseInInstallerNamespace(d *Dependency) { + if d != nil && d.InstallReleaseInInstallerNamespace() { + d.SetNamespace(r.cfg.Namespace()) + } +} + // dependsOn checks if the chart has dependencies and resolves them. The // dependencies are prepended to the parent chart and when more dependencies are // found, they are also resolved. @@ -70,19 +84,17 @@ func (r *Resolver) dependsOn( visited[dependencyName] = true defer delete(visited, dependencyName) - for _, dependsOn := range d.DependsOn() { + for _, dependsOn := range r.effectiveDependsOn(d) { // Picking up the dependency from the collection by name. dependsOnDep, err := r.collection.Get(dependsOn) if err != nil { return err } - // Skiping when the next dependency is associated with a disabled product. + // Skip only when the product exists in config and is disabled. If the + // product is not listed in installer.products, still resolve depends-on + // so ordering stays correct; namespace uses installer namespace. if product := dependsOnDep.ProductName(); product != "" { - productSpec, err := r.cfg.GetProduct(product) - if err != nil { - return err - } - if !productSpec.Enabled { + if spec := r.cfg.FindProduct(product); spec != nil && !spec.IsActive() { continue } } @@ -110,6 +122,7 @@ func (r *Resolver) resolveEnabledProducts() error { } // Products uses the namespace specified in the configuration. d.SetNamespace(*product.Namespace) + r.maybeInstallReleaseInInstallerNamespace(d) // Product charts are added to the topology before required charts. r.topology.Append(*d) // Recursively resolving the dependencies, added before this chart. @@ -133,7 +146,7 @@ func (r *Resolver) resolveDependencies() error { // Collecting the last dependency name that is required by the current // chart (dependency), if any. requiredDependency := "" - for _, dependsOn := range d.DependsOn() { + for _, dependsOn := range r.effectiveDependsOn(&d) { // Ensure the required dependency is in the topology, when not in the // topology it is skipped. if !r.topology.Contains(dependsOn) { @@ -168,12 +181,57 @@ func (r *Resolver) resolveDependencies() error { // Resolve resolves the all dependencies in the collection to create the topology. func (r *Resolver) Resolve() error { + r.bundleMembers = r.buildBundleMembersIndex() if err := r.resolveEnabledProducts(); err != nil { return err } return r.resolveDependencies() } +func (r *Resolver) buildBundleMembersIndex() map[string][]string { + idx := map[string][]string{} + _ = r.collection.Walk(func(name string, d Dependency) error { + bid := bundleIDFromChartPath(d.ChartPath()) + if bid != "" { + idx[bid] = append(idx[bid], name) + } + return nil + }) + return idx +} + +// effectiveDependsOn merges global charts, bundle-local charts, expanded bundle +// dependencies, or the legacy depends-on annotation when no split annotations are set. +func (r *Resolver) effectiveDependsOn(d *Dependency) []string { + g := d.DependsOnGlobalCharts() + bc := d.DependsOnBundleCharts() + bb := d.DependsOnBundles() + legacy := d.getAnnotation(annotations.DependsOn) + if len(g) == 0 && len(bc) == 0 && len(bb) == 0 && legacy != "" { + return commaSeparatedToSlice(legacy) + } + var parts []string + parts = append(parts, g...) + parts = append(parts, bc...) + for _, bid := range bb { + bid = strings.TrimSpace(bid) + if bid == "" { + continue + } + parts = append(parts, r.bundleMembers[bid]...) + } + return dedupeOrdered(parts) +} + +func (r *Resolver) isActiveProduct(name string) bool { + for _, p := range r.cfg.Installer.Products { + if p.Name == name && p.IsActive() { + return true + } + } + return false +} + // Print prints the resolved topology to the writer formatted as a table. func (r *Resolver) Print(w io.Writer) { table := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) @@ -189,7 +247,7 @@ func (r *Resolver) Print(w io.Writer) { d.Name(), d.Namespace(), d.ProductName(), - strings.Join(d.DependsOn(), ", "), + strings.Join(r.effectiveDependsOn(&d), ", "), fmt.Sprintf("%d", weight), strings.Join(d.IntegrationsProvided(), ", "), d.IntegrationsRequired(), diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/config.go b/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/config.go index a83bc312d..3fa7208ea 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/config.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/config.go @@ -25,6 +25,9 @@ type Config struct { manager *config.ConfigMapManager // cluster configuration manager configPath string // configuration file relative path + mergedInstaller bool // when set, empty path means "load merged layout" (not an error) + loadCreate config.CreateConfigLoader + namespace string // installer's namespace create bool // create a new configuration force bool // overrides existing configuration @@ -111,10 +114,14 @@ func (c *Config) Complete(args []string) error { } // Storing the configuration file reference, when empty using the embedded // default configuration path. - if len(args) == 1 { + switch { + case len(args) == 1: c.configPath = args[0] c.log().Debug("Using local configuration file") - } else { + case c.mergedInstaller: + c.configPath = "" + c.log().Debug("Using merged installer configuration (no path argument).") + default: c.configPath = config.DefaultRelativeConfigPath c.log().Debug("Using embedded configuration file, default settings.") } @@ -123,7 +130,7 @@ func (c *Config) Complete(args []string) error { // Validate make sure all items are in place. func (c *Config) Validate() error { - if c.create && c.configPath == "" { + if c.create && c.configPath == "" && !c.mergedInstaller { return fmt.Errorf("configuration file is not informed") } if err := c.validateFlags(); err != nil { @@ -136,8 +143,18 @@ func (c *Config) Validate() error { // cluster and update when using the --force flag. func (c *Config) runCreate() error { c.log().Debug("Loading configuration from file") - cfg, err := config.NewConfigFromFile( - c.runCtx.ChartFS, c.configPath, c.namespace, c.appCtx.IdentifierName()) + + loader := c.loadCreate + if loader == nil { + loader = config.DefaultCreateConfigLoader + } + + cfg, err := loader( + c.runCtx.ChartFS, + c.configPath, + c.namespace, + c.appCtx.IdentifierName(), + ) if err != nil { return err } @@ -256,6 +273,8 @@ func NewConfig( appCtx *api.AppContext, runCtx *runcontext.RunContext, f *flags.Flags, + mergedInstaller bool, + loadCreate config.CreateConfigLoader, ) api.SubCommand { configDesc := fmt.Sprintf(` Manages installer's cluster configuration. @@ -283,10 +302,12 @@ retrieved using a unique label selector. Long: configDesc, SilenceUsage: true, }, - appCtx: appCtx, - runCtx: runCtx, - flags: f, - manager: config.NewConfigMapManager(runCtx.Kube, appCtx.Name), + appCtx: appCtx, + runCtx: runCtx, + flags: f, + manager: config.NewConfigMapManager(runCtx.Kube, appCtx.Name), + mergedInstaller: mergedInstaller, + loadCreate: loadCreate, } c.PersistentFlags(c.cmd.PersistentFlags()) diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/deploy.go b/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/deploy.go index 3442ffbdd..4af708e54 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/deploy.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/deploy.go @@ -4,9 +4,12 @@ import ( "errors" "fmt" "log/slog" + "path" + "path/filepath" "strings" "github.com/redhat-appstudio/helmet/api" + "github.com/redhat-appstudio/helmet/internal/chartfs" "github.com/redhat-appstudio/helmet/internal/config" "github.com/redhat-appstudio/helmet/internal/flags" "github.com/redhat-appstudio/helmet/internal/installer" @@ -76,12 +79,6 @@ func (d *Deploy) Validate() error { // Run deploys the enabled dependencies listed on the configuration. func (d *Deploy) Run() error { - d.log().Debug("Reading values template file") - valuesTmpl, err := d.runCtx.ChartFS.ReadFile(d.valuesTemplatePath) - if err != nil { - return err - } - topology, err := d.topologyBuilder.Build(d.cmd.Context(), d.cfg) if err != nil { if errors.Is(err, resolver.ErrMissingIntegrations) || @@ -103,9 +100,20 @@ subcommand to configure them. For example: if d.chartPath == "" { d.log().Debug("Installing all dependencies...") deps = topology.Dependencies() + } else if bundleID, ok := parseBundleDeployArg(d.chartPath); ok { + d.log().Debug("Installing all charts in bundle...", "bundle", bundleID) + var err error + deps, err = dependenciesForBundle(d.runCtx.ChartFS, topology, bundleID) + if err != nil { + return err + } } else { d.log().Debug("Installing a single Helm chart...") - hc, err := d.runCtx.ChartFS.GetChartFiles(d.chartPath) + chartDir, err := d.runCtx.ChartFS.ResolveChartDir(d.chartPath) + if err != nil { + return err + } + hc, err := d.runCtx.ChartFS.GetChartFiles(chartDir) if err != nil { return err } @@ -129,19 +137,28 @@ subcommand to configure them. For example: i := installer.NewInstaller(d.log(), d.flags, d.runCtx.Kube, &dep, d.installerTarball) + valuesTmpl, templatePath, err := d.runCtx.ChartFS.ReadValuesTemplate( + dep.ChartPath(), + d.valuesTemplatePath, + ) + if err != nil { + return err + } + d.log().Debug("Using values template", "path", templatePath) + ctx := d.cmd.Context() - err := i.SetValues(ctx, d.cfg, string(valuesTmpl)) + err = i.SetValues(ctx, d.cfg, string(valuesTmpl)) if err != nil { return err } - if d.flags.Debug { + if d.flags.Verbose { i.PrintRawValues() } if err := i.RenderValues(); err != nil { return err } - if d.flags.Debug { + if d.flags.Verbose { i.PrintValues() } @@ -180,15 +197,19 @@ installed, and the dependencies to be resolved. The deployment configuration file describes the sequence of Helm charts to be applied, on the attribute '%s.dependencies[]'. -The platform configuration is rendered from the values template file -(--values-template), this configuration payload is given to all Helm charts. +The platform configuration is rendered from values templates: each chart may +supply charts//values.yaml.tpl; otherwise the root file (--values-template) +is used for that chart. The installer resources are embedded in the executable, these resources are employed by default. -A single chart can be deployed by specifying its path. E.g.: +Deploy one chart by path or chart name (charts/ and bundles/*/charts/ are resolved automatically). E.g.: %s deploy charts/%s-openshift -`, appCtx.Name, appCtx.IdentifierName(), appCtx.Name, appCtx.IdentifierName()) + %s deploy %s-tpa +Or deploy every chart in a composable bundle (in topology order): + %s deploy bundles/ +`, appCtx.Name, appCtx.IdentifierName(), appCtx.Name, appCtx.IdentifierName(), appCtx.Name, appCtx.IdentifierName(), appCtx.Name) d := &Deploy{ cmd: &cobra.Command{ @@ -207,3 +228,42 @@ A single chart can be deployed by specifying its path. E.g.: flags.SetValuesTmplFlag(d.cmd.PersistentFlags(), &d.valuesTemplatePath) return d } + +// parseBundleDeployArg returns bundleID when arg is exactly bundles/ (deploy all charts in that bundle). +func parseBundleDeployArg(arg string) (bundleID string, ok bool) { + arg = filepath.ToSlash(strings.TrimSpace(arg)) + arg = strings.TrimSuffix(arg, "/") + const p = "bundles/" + if !strings.HasPrefix(arg, p) { + return "", false + } + rest := strings.TrimPrefix(arg, p) + if rest == "" || strings.Contains(rest, "/") { + return "", false + } + return rest, true +} + +// dependenciesForBundle returns topology dependencies whose chart dirs lie under bundles//charts/, in install order. +func dependenciesForBundle(cfs *chartfs.ChartFS, topology *resolver.Topology, bundleID string) (resolver.Dependencies, error) { + prefix := path.Join("bundles", bundleID, "charts") + dirs, err := cfs.ListChartsUnder(prefix) + if err != nil { + return nil, fmt.Errorf("bundle %q: list charts: %w", bundleID, err) + } + inBundle := make(map[string]struct{}, len(dirs)) + for _, dir := range dirs { + inBundle[filepath.ToSlash(dir)] = struct{}{} + } + var out resolver.Dependencies + for _, dep := range topology.Dependencies() { + cp := filepath.ToSlash(dep.ChartPath()) + if _, ok := inBundle[cp]; ok { + out = append(out, dep) + } + } + if len(out) == 0 { + return nil, fmt.Errorf("bundle %q: no charts from this bundle appear in the deployment topology (check config and bundle path)", bundleID) + } + return out, nil +} diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/integration.go b/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/integration.go index 3841724b4..7114b86b6 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/integration.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/integration.go @@ -6,6 +6,7 @@ import ( "github.com/redhat-appstudio/helmet/api" "github.com/redhat-appstudio/helmet/internal/config" + "github.com/redhat-appstudio/helmet/internal/flags" "github.com/redhat-appstudio/helmet/internal/integrations" "github.com/redhat-appstudio/helmet/internal/resolver" "github.com/redhat-appstudio/helmet/internal/runcontext" @@ -50,15 +51,16 @@ func disableProductForIntegration( return nil // no product provides this integration } - spec, err := cfg.GetProduct(productName) - if err != nil { - return err + spec := cfg.FindProduct(productName) + if spec == nil { + return nil // product not in config (e.g. integration-only bundle) } - if !spec.Enabled { + if !spec.IsActive() { return nil // already disabled } - spec.Enabled = false + disabled := false + spec.Enabled = &disabled if err := cfg.SetProduct(productName, *spec); err != nil { return err } @@ -70,6 +72,7 @@ func NewIntegration( appCtx *api.AppContext, runCtx *runcontext.RunContext, manager *integrations.Manager, + f *flags.Flags, ) *cobra.Command { cmd := &cobra.Command{ Use: "integration ", @@ -88,8 +91,15 @@ func NewIntegration( if err != nil { return err } - return disableProductForIntegration( - ctx, appCtx, runCtx, manager, cfg, activeIntegration) + if err := disableProductForIntegration( + ctx, appCtx, runCtx, manager, cfg, activeIntegration); err != nil { + return err + } + if f.Verbose { + _, err := fmt.Fprintln(cmd.OutOrStdout(), "Integration created successfully") + return err + } + return nil }, } diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/mcptools.go b/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/mcptools.go index 8222ddcf4..9e80c2e45 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/mcptools.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/mcptools.go @@ -55,6 +55,7 @@ func standardMCPTools( toolsCtx.AppContext, toolsCtx.RunContext, toolsCtx.IntegrationManager, + toolsCtx.Flags, ) integrationTools := mcptools.NewIntegrationTools( toolsCtx.AppContext.IdentifierName(), toolsCtx.AppContext.Name, diff --git a/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/template.go b/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/template.go index 7b1e60fcb..4d739fa32 100644 --- a/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/template.go +++ b/vendor/github.com/redhat-appstudio/helmet/internal/subcmd/template.go @@ -2,6 +2,8 @@ package subcmd import ( "fmt" + "path/filepath" + "strings" "github.com/redhat-appstudio/helmet/api" "github.com/redhat-appstudio/helmet/internal/config" @@ -46,11 +48,16 @@ func (t *Template) Complete(args []string) error { return fmt.Errorf("expecting one chart, got %d", len(args)) } - hc, err := t.runCtx.ChartFS.GetChartFiles(args[0]) + chartDir, err := t.runCtx.ChartFS.ResolveChartDir(args[0]) if err != nil { return err } - t.dep = *resolver.NewDependencyWithNamespace(hc, t.namespace) + chartDir = filepath.ToSlash(strings.TrimSuffix(chartDir, "/")) + hc, err := t.runCtx.ChartFS.GetChartFiles(chartDir) + if err != nil { + return err + } + t.dep = *resolver.NewDependencyWithNamespaceAndChartPath(hc, t.namespace, chartDir) if t.cfg, err = bootstrapConfig(t.cmd.Context(), t.appCtx, t.runCtx); err != nil { return err @@ -74,7 +81,10 @@ func (t *Template) Validate() error { // Run Renders the templates. func (t *Template) Run() error { - valuesTmplPayload, err := t.runCtx.ChartFS.ReadFile(t.valuesTemplatePath) + valuesTmplPayload, _, err := t.runCtx.ChartFS.ReadValuesTemplate( + t.dep.ChartPath(), + t.valuesTemplatePath, + ) if err != nil { return fmt.Errorf("failed to read values template file: %w", err) } @@ -124,28 +134,41 @@ By using the '--show-manifests=false' flag, only the global values template ('--values-template') will be rendered as YAML, thus the last argument, with the Helm chart directory, optional. -Additionally, the '--debug' flag should be used to display rendered global values, -passed into every Helm Chart installed, as key-value pairs. +During deploy, '--verbose' prints rendered values before and after template +rendering. For this command, use '--show-values' to emit the rendered global values +as YAML. The installer resources are embedded in the executable, these resources are employed by default, to use local files just use the last argument with the path to the local Helm Chart. +Chart path resolution: a bare chart name resolves to charts/ when present, +otherwise FindChartDir scans bundles/*/charts/. Bundle-local charts use a +distinct Helm chart name (e.g. %s-tpa-subscriptions under bundles/tpa/charts/). +Use an explicit path to render a bundle chart so values come from +bundles//values.yaml.tpl. + Examples: # Only showing the global values as YAML. $ %s template --show-manifests=false - # Rendering only the templates of a single Helm Chart. + # Global subscriptions chart (installer/charts/). $ %s template --show-values=false charts/%s-subscriptions + # Bundle chart + bundle values (bundles/tpa/values.yaml.tpl). + $ %s template --show-values=false bundles/tpa/charts/%s-tpa-subscriptions + # Rendering all resources of a Helm Chart. $ %s template charts/%s-subscriptions`, + appCtx.IdentifierName(), appCtx.Name, appCtx.Name, appCtx.IdentifierName(), appCtx.Name, appCtx.IdentifierName(), + appCtx.Name, + appCtx.IdentifierName(), ) t := &Template{ diff --git a/vendor/modules.txt b/vendor/modules.txt index ab93d7fc9..fd6bf84f5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -363,7 +363,7 @@ github.com/pmezard/go-difflib/difflib # github.com/quay/claircore v1.5.50 ## explicit; go 1.25.0 github.com/quay/claircore/pkg/tarfs -# github.com/redhat-appstudio/helmet v0.0.0-20260319215325-e665a08127fc +# github.com/redhat-appstudio/helmet v0.0.0-20260420150238-7035f5b16d8e => github.com/redhat-appstudio/helmet v0.1.1-0.20260511150447-a082a8928a85 ## explicit; go 1.25.7 github.com/redhat-appstudio/helmet/api github.com/redhat-appstudio/helmet/api/integrations @@ -532,7 +532,7 @@ google.golang.org/genproto/googleapis/api/expr/v1alpha1 # google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c ## explicit; go 1.25.0 google.golang.org/genproto/googleapis/rpc/status -# google.golang.org/grpc v1.79.2 +# google.golang.org/grpc v1.79.3 ## explicit; go 1.24.0 google.golang.org/grpc/codes google.golang.org/grpc/connectivity @@ -1177,3 +1177,4 @@ sigs.k8s.io/yaml/goyaml.v3 sigs.k8s.io/yaml/kyaml # github.com/Microsoft/hcsshim => github.com/Microsoft/hcsshim v0.13.0 # github.com/imdario/mergo => github.com/imdario/mergo v1.0.2 +# github.com/redhat-appstudio/helmet => github.com/redhat-appstudio/helmet v0.1.1-0.20260511150447-a082a8928a85