Build Prod Release (Build + Manifest) #93
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Prod Release (Build + Manifest) | |
| # Combined workflow: builds images, updates versions, stitches manifests, creates ONE PR | |
| # Replaces the two-step flow of prod-build-images.yml → (merge PR) → prod-build-manifest.yml | |
| # | |
| # Beta mode: builds images tagged {version}-beta.N, creates a GitHub pre-release | |
| # with manifests attached. N auto-increments from existing {version}-beta.* | |
| # pre-releases, so repeated beta runs coexist (2.0.8-beta.1, .2, .3 ...) instead | |
| # of overwriting. Does NOT bump SPLUNK-VERSION or create a PR. | |
| # Use beta to test a release candidate before committing to the version. | |
| "on": | |
| workflow_dispatch: | |
| inputs: | |
| version_bump: | |
| description: 'Version bump type' | |
| required: true | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| - none (keep current) | |
| - custom | |
| default: 'patch' | |
| custom_version: | |
| description: 'Custom version (only when version_bump=custom, requires single service)' | |
| required: false | |
| type: string | |
| default: '' | |
| services: | |
| description: 'Services to build (comma-separated, or "all")' | |
| required: false | |
| type: string | |
| default: 'all' | |
| beta: | |
| description: 'Beta build — tags images with -beta.N suffix (N auto-increments), creates pre-release, does NOT bump SPLUNK-VERSION' | |
| required: false | |
| type: boolean | |
| default: false | |
| no_cache: | |
| description: 'Disable build cache' | |
| required: false | |
| type: boolean | |
| default: false | |
| jobs: | |
| # ───────────────────────────────────────────── | |
| # Step 1: Determine the version | |
| # ───────────────────────────────────────────── | |
| determine-version: | |
| if: github.repository == 'splunk/opentelemetry-demo' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| base_version: ${{ steps.version.outputs.base_version }} | |
| is_beta: ${{ steps.version.outputs.is_beta }} | |
| is_hotfix: ${{ steps.version.outputs.is_hotfix }} | |
| is_full_release: ${{ steps.version.outputs.is_full_release }} | |
| is_custom: ${{ steps.version.outputs.is_custom }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.x' | |
| - run: pip install pyyaml | |
| - name: Make scripts executable | |
| run: | | |
| chmod +x .github/scripts/bump-version.py | |
| chmod +x .github/scripts/manage-hotfix.py | |
| - name: Determine version | |
| id: version | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| CURRENT_VERSION=$(cat SPLUNK-VERSION) | |
| BUMP_TYPE="${{ inputs.version_bump }}" | |
| SERVICES="${{ inputs.services }}" | |
| CUSTOM_VERSION="${{ inputs.custom_version }}" | |
| echo "Current version: $CURRENT_VERSION" | |
| echo "Services to build: $SERVICES" | |
| # Validate custom version usage | |
| if [[ "$BUMP_TYPE" == "custom" ]]; then | |
| if [[ "$SERVICES" == "all" ]]; then | |
| echo "❌ ERROR: Custom version requires a single service, not 'all'" | |
| exit 1 | |
| fi | |
| if [[ -z "$CUSTOM_VERSION" ]]; then | |
| echo "❌ ERROR: Custom version selected but no version provided" | |
| exit 1 | |
| fi | |
| SERVICE_COUNT=$(echo "$SERVICES" | tr ',' '\n' | wc -l) | |
| if [[ "$SERVICE_COUNT" -gt 1 ]]; then | |
| echo "❌ ERROR: Custom version requires exactly one service, got: $SERVICES" | |
| exit 1 | |
| fi | |
| fi | |
| # Determine if this is a hotfix build | |
| IS_HOTFIX="false" | |
| if [[ "$SERVICES" != "all" ]] && [[ "$BUMP_TYPE" == "none (keep current)" ]]; then | |
| IS_HOTFIX="true" | |
| fi | |
| # Calculate version | |
| if [[ "$BUMP_TYPE" == "custom" ]]; then | |
| VERSION="$CUSTOM_VERSION" | |
| VERSION_STATUS="custom" | |
| elif [[ "$IS_HOTFIX" == "true" ]]; then | |
| SERVICE_NAME=$(echo "$SERVICES" | tr ',' '\n' | head -1 | xargs) | |
| VERSION=$(python3 .github/scripts/manage-hotfix.py add "$SERVICE_NAME") | |
| VERSION_STATUS="hotfix" | |
| elif [[ "$BUMP_TYPE" == "none (keep current)" ]]; then | |
| VERSION="$CURRENT_VERSION" | |
| VERSION_STATUS="unchanged" | |
| else | |
| VERSION=$(python3 .github/scripts/bump-version.py "$CURRENT_VERSION" "$BUMP_TYPE") | |
| VERSION_STATUS="bumped" | |
| fi | |
| IS_FULL_RELEASE="false" | |
| if [[ "$SERVICES" == "all" && "$VERSION_STATUS" == "bumped" ]]; then | |
| IS_FULL_RELEASE="true" | |
| fi | |
| IS_CUSTOM="false" | |
| if [[ "$VERSION_STATUS" == "custom" ]]; then | |
| IS_CUSTOM="true" | |
| fi | |
| # Beta: append -beta.N suffix, don't actually bump anything. | |
| # Iteration N auto-increments from existing v{base}-beta.* pre-releases | |
| # so repeated beta runs coexist (2.0.8-beta.1, .2, .3 ...) rather than | |
| # overwriting. Stateless — derived from GitHub releases, nothing committed. | |
| IS_BETA="${{ inputs.beta }}" | |
| BASE_VERSION="$VERSION" | |
| if [[ "$IS_BETA" == "true" ]]; then | |
| EXISTING=$(gh release list --repo "$GITHUB_REPOSITORY" --limit 200 \ | |
| --json tagName --jq '.[].tagName' 2>/dev/null \ | |
| | grep -E "^v${BASE_VERSION}-beta(\.[0-9]+)?$" || true) | |
| MAXN=0 | |
| while IFS= read -r tag; do | |
| [[ -z "$tag" ]] && continue | |
| # legacy bare "-beta" (no number) counts as iteration 0 | |
| n=$(echo "$tag" | sed -E "s/^v${BASE_VERSION}-beta\.?([0-9]*)$/\1/") | |
| [[ "$n" =~ ^[0-9]+$ ]] || n=0 | |
| (( n > MAXN )) && MAXN=$n | |
| done <<< "$EXISTING" | |
| NEXT=$(( MAXN + 1 )) | |
| VERSION="${BASE_VERSION}-beta.${NEXT}" | |
| IS_FULL_RELEASE="false" # Beta never triggers full release behavior | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "base_version=$BASE_VERSION" >> $GITHUB_OUTPUT | |
| echo "is_beta=$IS_BETA" >> $GITHUB_OUTPUT | |
| echo "is_hotfix=$IS_HOTFIX" >> $GITHUB_OUTPUT | |
| echo "is_full_release=$IS_FULL_RELEASE" >> $GITHUB_OUTPUT | |
| echo "is_custom=$IS_CUSTOM" >> $GITHUB_OUTPUT | |
| echo "### Version Information" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [[ "$IS_BETA" == "true" ]]; then | |
| echo "> 🧪 **BETA BUILD** — images tagged \`${VERSION}\`, no version commit" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "| Type | Version |" >> $GITHUB_STEP_SUMMARY | |
| echo "|------|---------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Current | \`$CURRENT_VERSION\` |" >> $GITHUB_STEP_SUMMARY | |
| if [[ "$VERSION_STATUS" == "bumped" ]]; then | |
| echo "| **New** | **\`$VERSION\`** |" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "| **Using** | **\`$VERSION\`** ($VERSION_STATUS) |" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "| Bump | ${{ inputs.version_bump }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Services | ${{ inputs.services }} |" >> $GITHUB_STEP_SUMMARY | |
| # ───────────────────────────────────────────── | |
| # Step 2: Prepare build matrix | |
| # ───────────────────────────────────────────── | |
| prepare-matrix: | |
| needs: determine-version | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.set-matrix.outputs.matrix }} | |
| registry: ${{ steps.set-matrix.outputs.registry }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.x' | |
| - run: pip install pyyaml | |
| - name: Generate build matrix | |
| id: set-matrix | |
| run: | | |
| python3 << 'PYTHON_SCRIPT' | |
| import yaml, json, os | |
| with open('services.yaml', 'r') as f: | |
| config = yaml.safe_load(f) | |
| prod_registry = config.get('registry', {}).get('prod', 'ghcr.io/splunk/opentelemetry-demo') | |
| requested = "${{ inputs.services }}".strip() | |
| services_to_build = [] | |
| for svc in config.get('services', []): | |
| name = svc.get('name') | |
| if not svc.get('build', False): | |
| continue | |
| if requested == 'all': | |
| include = True | |
| else: | |
| include = name in [s.strip() for s in requested.split(',')] | |
| if include: | |
| platform = svc.get('platform', 'linux/amd64,linux/arm64') | |
| dockerfile = svc.get('dockerfile', f'src/{name}/Dockerfile') | |
| build_target = svc.get('build_target', '') | |
| services_to_build.append({ | |
| 'name': name, 'variant': '', 'platform': platform, | |
| 'dockerfile': dockerfile, 'build_args': '', | |
| 'target': build_target | |
| }) | |
| if not services_to_build: | |
| matrix = {'include': [{'name': '_skip', 'variant': '', 'platform': 'linux/amd64', 'dockerfile': 'none', 'build_args': '', 'target': ''}]} | |
| else: | |
| matrix = {'include': services_to_build} | |
| with open(os.environ['GITHUB_OUTPUT'], 'a') as f: | |
| f.write(f'matrix={json.dumps(matrix)}\n') | |
| f.write(f'registry={prod_registry}\n') | |
| print(f"Registry: {prod_registry}") | |
| print(f"Services to build: {len(services_to_build)}") | |
| for svc in services_to_build: | |
| v = f" (variant: {svc['variant']})" if svc['variant'] else "" | |
| print(f" - {svc['name']}{v}") | |
| PYTHON_SCRIPT | |
| # ───────────────────────────────────────────── | |
| # Step 3: Build and push images | |
| # ───────────────────────────────────────────── | |
| build-images: | |
| needs: [determine-version, prepare-matrix] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.prepare-matrix.outputs.matrix) }} | |
| steps: | |
| - name: Check if skip | |
| id: check-skip | |
| run: | | |
| if [[ "${{ matrix.name }}" == "_skip" ]]; then | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "skip=false" >> $GITHUB_OUTPUT | |
| fi | |
| - uses: actions/checkout@v5 | |
| if: steps.check-skip.outputs.skip != 'true' | |
| - uses: docker/setup-buildx-action@v4 | |
| if: steps.check-skip.outputs.skip != 'true' | |
| - name: Log in to GHCR | |
| if: steps.check-skip.outputs.skip != 'true' | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GHCR_TOKEN }} | |
| - name: Extract image name | |
| if: steps.check-skip.outputs.skip != 'true' | |
| id: image | |
| run: | | |
| REGISTRY="${{ needs.prepare-matrix.outputs.registry }}" | |
| VERSION="${{ needs.determine-version.outputs.version }}" | |
| IMAGE_NAME="otel-${{ matrix.name }}" | |
| if [ -n "${{ matrix.variant }}" ]; then | |
| VERSION_TAG="${VERSION}-$(echo '${{ matrix.variant }}' | tr '[:upper:]' '[:lower:]')" | |
| else | |
| VERSION_TAG="${VERSION}" | |
| fi | |
| echo "full_image=${REGISTRY}/${IMAGE_NAME}:${VERSION_TAG}" >> $GITHUB_OUTPUT | |
| echo "version_tag=${VERSION_TAG}" >> $GITHUB_OUTPUT | |
| - name: Check for Dockerfile | |
| if: steps.check-skip.outputs.skip != 'true' | |
| id: check | |
| run: | | |
| if [ -f "${{ matrix.dockerfile }}" ]; then | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "exists=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Build and push image | |
| if: steps.check.outputs.exists == 'true' | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: ${{ matrix.dockerfile }} | |
| target: ${{ matrix.target }} | |
| platforms: ${{ matrix.platform }} | |
| push: true | |
| tags: ${{ steps.image.outputs.full_image }} | |
| build-args: ${{ matrix.build_args }} | |
| cache-from: type=gha,scope=${{ matrix.name }} | |
| cache-to: type=gha,mode=max,scope=${{ matrix.name }} | |
| no-cache: ${{ inputs.no_cache }} | |
| - name: Image info | |
| if: steps.check.outputs.exists == 'true' | |
| run: | | |
| echo "### ✅ Built: ${{ matrix.name }}${{ matrix.variant && format(' ({0})', matrix.variant) || '' }}" >> $GITHUB_STEP_SUMMARY | |
| echo "Image: \`${{ steps.image.outputs.full_image }}\`" >> $GITHUB_STEP_SUMMARY | |
| # ───────────────────────────────────────────── | |
| # Step 4: Update versions + stitch manifests + PR (or pre-release for beta) | |
| # ───────────────────────────────────────────── | |
| update-and-release: | |
| needs: [determine-version, prepare-matrix, build-images] | |
| if: ${{ success() && github.repository == 'splunk/opentelemetry-demo' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.x' | |
| - run: pip install pyyaml | |
| - name: Make scripts executable | |
| run: | | |
| chmod +x .github/scripts/manage-hotfix.py | |
| chmod +x .github/scripts/update-manifest-images.py | |
| chmod +x .github/scripts/show-image-versions.py | |
| chmod +x .github/scripts/stitch-manifests.sh | |
| chmod +x .github/scripts/get-services.py | |
| chmod +x .github/scripts/stage-values-with-header.sh | |
| - name: Save current version before bump | |
| id: old-version | |
| run: echo "old_version=$(cat SPLUNK-VERSION)" >> $GITHUB_OUTPUT | |
| - name: Update SPLUNK-VERSION | |
| if: ${{ needs.determine-version.outputs.is_beta != 'true' && needs.determine-version.outputs.is_hotfix == 'false' && inputs.version_bump != 'none (keep current)' && inputs.version_bump != 'custom' }} | |
| run: | | |
| CURRENT=$(cat SPLUNK-VERSION) | |
| NEW="${{ needs.determine-version.outputs.base_version }}" | |
| echo "$NEW" > SPLUNK-VERSION | |
| echo "✅ SPLUNK-VERSION: $CURRENT → $NEW" | |
| - name: Clear hotfixes on full releases | |
| if: ${{ needs.determine-version.outputs.is_full_release == 'true' }} | |
| run: | | |
| python3 .github/scripts/manage-hotfix.py clear | |
| echo "✅ Hotfixes cleared" | |
| - name: Update hotfix tracking | |
| if: ${{ needs.determine-version.outputs.is_hotfix == 'true' }} | |
| run: | | |
| # determine-version ran manage-hotfix in a separate job (separate | |
| # runner/filesystem), so .hotfix.yaml does not exist in this job's | |
| # checkout. Regenerate it here with the SAME first-service logic so | |
| # the Create Pull Request step can `git add .hotfix.yaml`. Both jobs | |
| # start from the same committed baseline on main, so the increment is | |
| # deterministic and matches the computed version. | |
| SERVICE_NAME=$(echo "${{ inputs.services }}" | tr ',' '\n' | head -1 | xargs) | |
| python3 .github/scripts/manage-hotfix.py add "$SERVICE_NAME" | |
| - name: Update source manifests with new image references | |
| run: | | |
| REGISTRY="${{ needs.prepare-matrix.outputs.registry }}" | |
| VERSION="${{ needs.determine-version.outputs.version }}" | |
| SERVICES_INPUT="${{ inputs.services }}" | |
| if [ "$SERVICES_INPUT" = "all" ]; then | |
| SERVICES_TO_UPDATE=$(python3 <<'PYTHON_SCRIPT' | |
| import yaml | |
| with open('services.yaml', 'r') as f: | |
| config = yaml.safe_load(f) | |
| services = [svc['name'] for svc in config.get('services', []) if svc.get('build', False)] | |
| print(' '.join(services)) | |
| PYTHON_SCRIPT | |
| ) | |
| else | |
| SERVICES_TO_UPDATE=$(echo "$SERVICES_INPUT" | tr ',' ' ') | |
| fi | |
| echo "Updating source manifests for: $SERVICES_TO_UPDATE" | |
| for SERVICE in $SERVICES_TO_UPDATE; do | |
| if [ "$SERVICE" = "payment" ]; then | |
| NEW_IMAGE="${REGISTRY}/otel-payment:${VERSION}" | |
| for MANIFEST_PATH in src/payment/payment-vA-k8s.yaml src/payment/payment-vB-k8s.yaml; do | |
| if [ -f "$MANIFEST_PATH" ]; then | |
| sed -i.bak -E "s|image:[ ]+[^ ]+/otel-payment:[^ ]+|image: ${NEW_IMAGE}|g" "$MANIFEST_PATH" | |
| rm -f "${MANIFEST_PATH}.bak" | |
| echo " ✅ $MANIFEST_PATH → $NEW_IMAGE" | |
| fi | |
| done | |
| else | |
| python3 .github/scripts/update-manifest-images.py "$SERVICE" "$REGISTRY" "otel-${SERVICE}" "$VERSION" | |
| fi | |
| done | |
| - name: Generate per-service version pinning for partial builds | |
| # For partial builds (services!=all), the stitched manifest must | |
| # pin only the freshly-built services at the new tag and leave | |
| # everything else at the prior stable version. Otherwise pods | |
| # ImagePullBackOff on tags that were never pushed for un-built | |
| # services. The stitcher already supports this via | |
| # .service-versions.yaml; we just need to generate it. | |
| if: ${{ inputs.services != 'all' }} | |
| run: | | |
| OLD_VERSION="${{ steps.old-version.outputs.old_version }}" | |
| NEW_VERSION="${{ needs.determine-version.outputs.version }}" | |
| BUILT_SERVICES="${{ inputs.services }}" | |
| python3 - "$BUILT_SERVICES" "$NEW_VERSION" "$OLD_VERSION" <<'PYEOF' > .service-versions.yaml | |
| import sys, yaml | |
| built = {s.strip() for s in sys.argv[1].split(',') if s.strip()} | |
| new_version, old_version = sys.argv[2], sys.argv[3] | |
| with open('services.yaml') as f: | |
| cfg = yaml.safe_load(f) | |
| print('services:') | |
| for svc in cfg.get('services', []): | |
| if svc.get('build', False): | |
| name = svc['name'] | |
| pin = new_version if name in built else old_version | |
| print(f" {name}: {pin}") | |
| PYEOF | |
| echo "Per-service version pinning (partial build):" | |
| cat .service-versions.yaml | |
| - name: Stitch production manifests | |
| id: stitch | |
| run: | | |
| VERSION="${{ needs.determine-version.outputs.version }}" | |
| OUTPUT_DIR="kubernetes" | |
| # Temporarily set SPLUNK-VERSION to the target version (stitch-manifests.sh reads it) | |
| echo "$VERSION" > SPLUNK-VERSION | |
| echo "Stitching regular manifest (version: $VERSION)..." | |
| .github/scripts/stitch-manifests.sh prod "" "$OUTPUT_DIR" "" | |
| echo "Stitching DIAB manifest..." | |
| .github/scripts/stitch-manifests.sh prod diab "$OUTPUT_DIR" "" | |
| MANIFEST_FILE="${OUTPUT_DIR}/splunk-astronomy-shop-${VERSION}.yaml" | |
| MANIFEST_FILE_DIAB="${OUTPUT_DIR}/splunk-astronomy-shop-${VERSION}-diab.yaml" | |
| MANIFEST_LAMBDA="${OUTPUT_DIR}/splunk-astronomy-shop-${VERSION}-lambda.yaml" | |
| MANIFEST_DC_SHIM="${OUTPUT_DIR}/splunk-astronomy-shop-${VERSION}-dc-shim.yaml" | |
| MANIFEST_SECUREAPP="${OUTPUT_DIR}/splunk-astronomy-shop-${VERSION}-secureapp.yaml" | |
| MANIFEST_THROTTLE_DEMO="${OUTPUT_DIR}/splunk-astronomy-shop-${VERSION}-throttle-demo.yaml" | |
| echo "manifest_file=$MANIFEST_FILE" >> $GITHUB_OUTPUT | |
| echo "manifest_file_diab=$MANIFEST_FILE_DIAB" >> $GITHUB_OUTPUT | |
| echo "manifest_lambda=$MANIFEST_LAMBDA" >> $GITHUB_OUTPUT | |
| echo "manifest_dc_shim=$MANIFEST_DC_SHIM" >> $GITHUB_OUTPUT | |
| echo "manifest_secureapp=$MANIFEST_SECUREAPP" >> $GITHUB_OUTPUT | |
| echo "manifest_throttle_demo=$MANIFEST_THROTTLE_DEMO" >> $GITHUB_OUTPUT | |
| echo "Generated manifests:" | |
| for MFILE in "$MANIFEST_FILE" "$MANIFEST_FILE_DIAB" "$MANIFEST_LAMBDA" "$MANIFEST_DC_SHIM" "$MANIFEST_SECUREAPP" "$MANIFEST_THROTTLE_DEMO"; do | |
| [ -f "$MFILE" ] && echo " [ok] $(basename $MFILE)" || echo " [--] $(basename $MFILE) (not generated)" | |
| done | |
| # For non-beta: restore SPLUNK-VERSION to the base version (will be committed) | |
| # For beta: restore to original (nothing gets committed) | |
| IS_BETA="${{ needs.determine-version.outputs.is_beta }}" | |
| if [[ "$IS_BETA" == "true" ]]; then | |
| echo "${{ steps.old-version.outputs.old_version }}" > SPLUNK-VERSION | |
| else | |
| echo "${{ needs.determine-version.outputs.base_version }}" > SPLUNK-VERSION | |
| fi | |
| # Remove the temporary per-service pinning file so it never gets | |
| # committed by downstream PR/branch steps. | |
| rm -f .service-versions.yaml | |
| - name: Ensure values.yaml exists for this version | |
| if: ${{ needs.determine-version.outputs.is_beta != 'true' }} | |
| id: values | |
| run: | | |
| VERSION=$(cat SPLUNK-VERSION) | |
| OLD_VERSION="${{ steps.old-version.outputs.old_version }}" | |
| VALUES_FILE="kubernetes/splunk-astronomy-shop-${VERSION}-values.yaml" | |
| if [ -f "$VALUES_FILE" ]; then | |
| echo "✅ Values file already exists: $VALUES_FILE" | |
| else | |
| # Find the most recent values file to clone from | |
| # Prefer the previous SPLUNK-VERSION, fall back to latest available | |
| SOURCE="" | |
| # Try the version before bump | |
| if [ -n "$OLD_VERSION" ] && [ -f "kubernetes/splunk-astronomy-shop-${OLD_VERSION}-values.yaml" ]; then | |
| SOURCE="kubernetes/splunk-astronomy-shop-${OLD_VERSION}-values.yaml" | |
| else | |
| # Find the latest values file by version sort | |
| SOURCE=$(ls kubernetes/splunk-astronomy-shop-*-values.yaml 2>/dev/null | sort -V | tail -1) | |
| fi | |
| if [ -n "$SOURCE" ] && [ -f "$SOURCE" ]; then | |
| cp "$SOURCE" "$VALUES_FILE" | |
| echo "📋 Cloned values file: $SOURCE → $VALUES_FILE" | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### 📋 Values File" >> $GITHUB_STEP_SUMMARY | |
| echo "No values.yaml found for \`${VERSION}\`. Cloned from \`$(basename $SOURCE)\`." >> $GITHUB_STEP_SUMMARY | |
| echo "You can update it later via a PR if needed." >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "⚠️ No values file found to clone from — promote workflow will need one manually" | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### ⚠️ Values File Missing" >> $GITHUB_STEP_SUMMARY | |
| echo "No values.yaml found for \`${VERSION}\` and no previous version to clone from." >> $GITHUB_STEP_SUMMARY | |
| echo "Create one manually before running the promote workflow." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| fi | |
| echo "values_file=$VALUES_FILE" >> $GITHUB_OUTPUT | |
| - name: Validate manifest YAML | |
| run: | | |
| for MFILE in "${{ steps.stitch.outputs.manifest_file }}" \ | |
| "${{ steps.stitch.outputs.manifest_file_diab }}" \ | |
| "${{ steps.stitch.outputs.manifest_lambda }}" \ | |
| "${{ steps.stitch.outputs.manifest_dc_shim }}" \ | |
| "${{ steps.stitch.outputs.manifest_secureapp }}" \ | |
| "${{ steps.stitch.outputs.manifest_throttle_demo }}"; do | |
| if [ -f "$MFILE" ]; then | |
| python3 -c "import yaml; docs=list(yaml.safe_load_all(open('$MFILE'))); print(f'$(basename $MFILE): {len(docs)} documents')" | |
| fi | |
| done | |
| - name: Show image version breakdown | |
| run: | | |
| VERSION="${{ needs.determine-version.outputs.version }}" | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### 📦 Image Versions in Manifest" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| python3 .github/scripts/show-image-versions.py \ | |
| --base-version "$VERSION" \ | |
| --format github >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Summary:**" >> $GITHUB_STEP_SUMMARY | |
| python3 .github/scripts/show-image-versions.py \ | |
| --base-version "$VERSION" \ | |
| --summary-only >> $GITHUB_STEP_SUMMARY | |
| - name: Create GitHub pre-release (beta only) | |
| if: ${{ needs.determine-version.outputs.is_beta == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.determine-version.outputs.version }}" | |
| SERVICES="${{ inputs.services }}" | |
| # Collect manifest files | |
| ASSETS="" | |
| for MFILE in "${{ steps.stitch.outputs.manifest_file }}" \ | |
| "${{ steps.stitch.outputs.manifest_file_diab }}" \ | |
| "${{ steps.stitch.outputs.manifest_lambda }}" \ | |
| "${{ steps.stitch.outputs.manifest_dc_shim }}" \ | |
| "${{ steps.stitch.outputs.manifest_secureapp }}" \ | |
| "${{ steps.stitch.outputs.manifest_throttle_demo }}"; do | |
| [ -f "$MFILE" ] && ASSETS="$ASSETS $MFILE" | |
| done | |
| # Also attach the helm values.yaml for this version so | |
| # `helm upgrade --install ... -f splunk-astronomy-shop-${VERSION}-values.yaml` | |
| # can be scripted straight from the release assets. The | |
| # earlier "Ensure values.yaml exists" step guarantees the | |
| # source file is present (cloned from previous version if new). | |
| # Version header is prepended to the release-asset copy (source | |
| # in kubernetes/ stays untouched). | |
| STAGED_VALUES=$(.github/scripts/stage-values-with-header.sh "$VERSION") | |
| [ -n "$STAGED_VALUES" ] && [ -f "$STAGED_VALUES" ] && ASSETS="$ASSETS $STAGED_VALUES" | |
| # Delete existing pre-release if regenerating | |
| if gh release view "v${VERSION}" &>/dev/null; then | |
| echo "Replacing existing pre-release v${VERSION}..." | |
| gh release delete "v${VERSION}" --yes 2>/dev/null || true | |
| git push origin --delete "v${VERSION}" 2>/dev/null || true | |
| fi | |
| # Create pre-release with manifests attached | |
| gh release create "v${VERSION}" $ASSETS \ | |
| --title "v${VERSION} (beta)" \ | |
| --prerelease \ | |
| --notes "$(printf '%s\n' \ | |
| "## Beta Release ${VERSION}" \ | |
| "" \ | |
| "**Base version:** \`${{ needs.determine-version.outputs.base_version }}\`" \ | |
| "**Services:** ${SERVICES}" \ | |
| "**Registry:** \`${{ needs.prepare-matrix.outputs.registry }}\`" \ | |
| "" \ | |
| "### Deploy for testing" \ | |
| "\`\`\`bash" \ | |
| "# Download manifests from this release, then:" \ | |
| "kubectl apply -f splunk-astronomy-shop-${VERSION}.yaml" \ | |
| "\`\`\`" \ | |
| "" \ | |
| "> This is a **pre-release** for testing. Run the workflow again without" \ | |
| "> the beta flag to create the final release and bump SPLUNK-VERSION." \ | |
| "" \ | |
| "---" \ | |
| "Generated by Production Release workflow (beta mode)" | |
| )" | |
| RELEASE_URL=$(gh release view "v${VERSION}" --json url -q .url) | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### 🧪 Beta Pre-Release Created" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**[v${VERSION}]($RELEASE_URL)**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Download manifests from the release to deploy and test." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "When satisfied, re-run this workflow **without** the beta flag to finalize." >> $GITHUB_STEP_SUMMARY | |
| - name: Create Pull Request (non-beta) | |
| if: ${{ needs.determine-version.outputs.is_beta != 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| git config --local user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --local user.name "github-actions[bot]" | |
| VERSION="${{ needs.determine-version.outputs.version }}" | |
| IS_HOTFIX="${{ needs.determine-version.outputs.is_hotfix }}" | |
| IS_FULL_RELEASE="${{ needs.determine-version.outputs.is_full_release }}" | |
| IS_CUSTOM="${{ needs.determine-version.outputs.is_custom }}" | |
| SERVICES="${{ inputs.services }}" | |
| BRANCH_NAME="release/${VERSION}" | |
| # Stage source changes only (no stitched manifests) | |
| if [[ "$IS_HOTFIX" == "true" ]]; then | |
| git add .hotfix.yaml | |
| fi | |
| if [[ "$IS_HOTFIX" == "false" && "$IS_CUSTOM" == "false" && "${{ inputs.version_bump }}" != "none (keep current)" ]]; then | |
| git add SPLUNK-VERSION | |
| fi | |
| if [[ "$IS_FULL_RELEASE" == "true" ]]; then | |
| if ! [ -f .hotfix.yaml ]; then | |
| git rm --ignore-unmatch .hotfix.yaml 2>/dev/null || true | |
| fi | |
| fi | |
| # Stage source manifests and values files only | |
| # Stitched manifests are gitignored — they go to GitHub Release assets | |
| git add src/*/*-k8s.yaml | |
| git add kubernetes/splunk-astronomy-shop-*-values.yaml 2>/dev/null || true | |
| if git diff --staged --quiet; then | |
| echo "No changes to commit" | |
| exit 0 | |
| fi | |
| git checkout -b "$BRANCH_NAME" | |
| # Commit message | |
| if [[ "$IS_HOTFIX" == "true" ]]; then | |
| COMMIT_TITLE="hotfix: ${VERSION} (${SERVICES})" | |
| PR_TITLE="Hotfix: ${SERVICES} @ ${VERSION}" | |
| elif [[ "$IS_CUSTOM" == "true" ]]; then | |
| COMMIT_TITLE="build: ${SERVICES} @ ${VERSION} (custom)" | |
| PR_TITLE="Custom Build: ${SERVICES} @ ${VERSION}" | |
| elif [[ "$IS_FULL_RELEASE" == "true" ]]; then | |
| COMMIT_TITLE="release: ${VERSION}" | |
| PR_TITLE="Release ${VERSION}" | |
| else | |
| COMMIT_TITLE="build: ${VERSION} (${SERVICES})" | |
| PR_TITLE="Production Build: ${VERSION}" | |
| fi | |
| git commit -m "$COMMIT_TITLE" \ | |
| -m "" \ | |
| -m "Services: ${SERVICES}" \ | |
| -m "Bump: ${{ inputs.version_bump }}" \ | |
| -m "" \ | |
| -m "Includes:" \ | |
| -m "- Container images built and pushed" \ | |
| -m "- SPLUNK-VERSION updated" \ | |
| -m "- Source k8s manifests updated" | |
| # Delete remote branch if exists | |
| git push origin --delete "$BRANCH_NAME" 2>/dev/null || true | |
| git push -u origin "$BRANCH_NAME" | |
| # Build PR body | |
| PR_BODY=$(printf '%s\n' \ | |
| "## ${PR_TITLE}" \ | |
| "" \ | |
| "**Version:** \`${VERSION}\`" \ | |
| "**Services:** ${SERVICES}" \ | |
| "**Bump:** ${{ inputs.version_bump }}" \ | |
| "" \ | |
| "### What's included" \ | |
| "- ✅ Container images built and pushed to \`ghcr.io/splunk/opentelemetry-demo\`" \ | |
| "- ✅ SPLUNK-VERSION updated" \ | |
| "- ✅ Source k8s manifests updated with new image references" \ | |
| "" \ | |
| "### Manifests" \ | |
| "Stitched manifests will be attached to the GitHub Release when this PR is merged." \ | |
| "" \ | |
| "See the **Image Versions in Manifest** section in the workflow summary for the full breakdown." \ | |
| "" \ | |
| "---" \ | |
| "Generated by Production Release workflow" | |
| ) | |
| gh pr create \ | |
| --title "$PR_TITLE" \ | |
| --body "$PR_BODY" \ | |
| --base main \ | |
| --head "$BRANCH_NAME" | |
| PR_URL=$(gh pr view "$BRANCH_NAME" --json url -q .url) | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### ✅ Pull Request Created" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**[$PR_TITLE]($PR_URL)**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Branch: \`$BRANCH_NAME\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Merge this PR to apply all changes." >> $GITHUB_STEP_SUMMARY | |
| echo "Stitched manifests will be attached to the release automatically." >> $GITHUB_STEP_SUMMARY | |
| - name: Upload manifests as artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: astronomy-shop-manifests-${{ needs.determine-version.outputs.version }} | |
| path: | | |
| ${{ steps.stitch.outputs.manifest_file }} | |
| ${{ steps.stitch.outputs.manifest_file_diab }} | |
| ${{ steps.stitch.outputs.manifest_lambda }} | |
| ${{ steps.stitch.outputs.manifest_dc_shim }} | |
| ${{ steps.stitch.outputs.manifest_secureapp }} | |
| retention-days: 90 | |
| # ───────────────────────────────────────────── | |
| # Summary | |
| # ───────────────────────────────────────────── | |
| summary: | |
| needs: [determine-version, prepare-matrix, build-images, update-and-release] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Build Summary | |
| run: | | |
| IS_BETA="${{ needs.determine-version.outputs.is_beta }}" | |
| if [[ "$IS_BETA" == "true" ]]; then | |
| echo "## 🧪 Beta Release Complete" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "## Production Release Complete" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Item | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Version** | \`${{ needs.determine-version.outputs.version }}\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Bump** | ${{ inputs.version_bump }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Services** | ${{ inputs.services }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Beta** | ${{ needs.determine-version.outputs.is_beta }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Registry** | \`${{ needs.prepare-matrix.outputs.registry }}\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [[ "$IS_BETA" == "true" ]]; then | |
| echo "✅ Images built → ✅ Manifests stitched → ✅ Pre-release created" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "> **Deploy the beta manifests to test. Re-run without beta to finalize.**" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "✅ Images built → ✅ Versions updated → ✅ Manifests stitched → ✅ PR created" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "> **Merge the PR. Manifests are attached to the release after merge.**" >> $GITHUB_STEP_SUMMARY | |
| fi |