From 9245ee8dbfd764d8064514bcbba42cb98b25a69b Mon Sep 17 00:00:00 2001 From: Ewa Ostrowska Date: Tue, 5 May 2026 10:48:13 +0200 Subject: [PATCH] chore: update release workflow for codegen 3.0 --- .github/workflows/prepare-release-3.yml | 123 +++++ .github/workflows/release-codegen-3.yml | 510 ++++++++++++++++++++ .github/workflows/release-full-3.yml | 317 ------------ CI/release/build-codegen-with-generators.sh | 15 + CI/release/common.sh | 158 ++++++ CI/release/post-codegen-snapshot.sh | 41 ++ CI/release/prepare-codegen-release.sh | 110 +++++ CI/release/resolve-release-versions.sh | 156 ++++++ CI/release/update-codegen-release-files.py | 161 ++++++ 9 files changed, 1274 insertions(+), 317 deletions(-) create mode 100644 .github/workflows/prepare-release-3.yml create mode 100644 .github/workflows/release-codegen-3.yml create mode 100644 CI/release/build-codegen-with-generators.sh create mode 100644 CI/release/common.sh create mode 100644 CI/release/post-codegen-snapshot.sh create mode 100644 CI/release/prepare-codegen-release.sh create mode 100644 CI/release/resolve-release-versions.sh create mode 100644 CI/release/update-codegen-release-files.py diff --git a/.github/workflows/prepare-release-3.yml b/.github/workflows/prepare-release-3.yml new file mode 100644 index 00000000000..01df663187e --- /dev/null +++ b/.github/workflows/prepare-release-3.yml @@ -0,0 +1,123 @@ +name: Prepare Release 3.0 + +# Manual pre-release workflow for creating the prepare PR on branch 3.0.0. +on: + workflow_dispatch: + inputs: + codegen_version: + description: 'Release version, for example 3.0.80. Leave empty to derive from current SNAPSHOT.' + required: false + type: string + next_codegen_snapshot_version: + description: 'Next development version, for example 3.0.81-SNAPSHOT. Leave empty to increment codegen_version.' + required: false + type: string + release_generators: + description: 'Prepare for releasing swagger-codegen-generators with codegen' + required: true + default: 'false' + type: choice + options: + - 'false' + - 'true' + generators_version: + description: 'Generator release version when release_generators=true, for example 1.0.61' + required: false + type: string + previous_generators_version: + description: 'Optional bootstrap generator version for circular dependency validation' + required: false + type: string +permissions: + contents: write + pull-requests: write + +jobs: + prepare: + # Resolves release versions, applies file updates, validates build, then opens PR. + runs-on: ubuntu-latest + env: + CODEGEN_VERSION: ${{ inputs.codegen_version }} + NEXT_CODEGEN_SNAPSHOT_VERSION: ${{ inputs.next_codegen_snapshot_version }} + RELEASE_GENERATORS: ${{ inputs.release_generators }} + GENERATORS_VERSION: ${{ inputs.generators_version }} + PREVIOUS_GENERATORS_VERSION: ${{ inputs.previous_generators_version }} + steps: + - name: Checkout swagger-codegen 3.0.0 + uses: actions/checkout@v6 + with: + ref: 3.0.0 + fetch-depth: 0 + + - uses: actions/create-github-app-token@v3 + id: generate-token + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Set up Java and Maven + uses: actions/setup-java@v5 + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + with: + java-version: '17' + distribution: temurin + server-id: central + cache: maven + overwrite-settings: false + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + + - name: Add Central Portal snapshot repository + uses: s4u/maven-settings-action@v4.0.0 + with: + repositories: '[{"id":"central-portal-snapshots","name":"Sonatype Central Portal snapshots","url":"https://central.sonatype.com/repository/maven-snapshots/","releases":{"enabled":false},"snapshots":{"enabled":true}}]' + servers: '[{"id":"central","username":"${{ secrets.MAVEN_CENTRAL_USERNAME }}","password":"${{ secrets.MAVEN_CENTRAL_PASSWORD }}"}]' + + - name: Validate bootstrap generator snapshot + if: inputs.previous_generators_version != '' + run: | + # Optional safeguard for circular dependency bootstrap inputs. + source CI/release/common.sh + require_release_or_snapshot_version "previous_generators_version" "${PREVIOUS_GENERATORS_VERSION}" + if [[ "${PREVIOUS_GENERATORS_VERSION}" =~ SNAPSHOT$ ]]; then + assert_snapshot_metadata_exists "${GENERATORS_ARTIFACT}" "${PREVIOUS_GENERATORS_VERSION}" + elif ! release_artifact_exists "${GENERATORS_ARTIFACT}" "${PREVIOUS_GENERATORS_VERSION}"; then + fail "previous_generators_version ${PREVIOUS_GENERATORS_VERSION} does not exist in Maven Central" + fi + + - name: Prepare release file changes + id: prepare-release + # Performs version bump to release and updates docs/poms/openapi. + run: bash CI/release/prepare-codegen-release.sh + + - name: Build release candidate + # Build with the resolved generators version to catch dependency issues early. + run: | + mvn -B -U clean install -Pdocker \ + -Dswagger-codegen-generators-version="${BUILD_GENERATORS_VERSION}" \ + -DJETTY_TEST_HTTP_PORT=8090 \ + -DJETTY_TEST_STOP_PORT=8089 + + - name: Print generator repo follow-up + if: inputs.release_generators == 'true' + run: | + # Generators repo changes are intentionally handled in a separate repository PR. + echo "::notice::Open a separate PR in swagger-api/swagger-codegen-generators master setting version ${GENERATORS_VERSION} and a usable swagger-codegen-version. This repository workflow does not push cross-repository generator changes." + + - name: Create prepare release pull request + # Opens PR with all prepare-release changes targeting 3.0.0. + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ steps.generate-token.outputs.token }} + commit-message: prepare release ${{ steps.prepare-release.outputs.codegen_version }} + title: prepare release ${{ steps.prepare-release.outputs.codegen_version }} + branch: prepare-release-${{ steps.prepare-release.outputs.codegen_version }} + base: 3.0.0 + body: | + Prepare Swagger Codegen ${{ steps.prepare-release.outputs.codegen_version }}. + + release_generators: ${{ inputs.release_generators }} + swagger-codegen-generators version: ${{ steps.prepare-release.outputs.generators_version }} + next codegen snapshot: ${{ steps.prepare-release.outputs.next_codegen_snapshot_version }} diff --git a/.github/workflows/release-codegen-3.yml b/.github/workflows/release-codegen-3.yml new file mode 100644 index 00000000000..6d8ba32dbba --- /dev/null +++ b/.github/workflows/release-codegen-3.yml @@ -0,0 +1,510 @@ +name: Release Codegen 3.0 + +# Manual release workflow for codegen 3.0.0, with optional generators release. +on: + workflow_dispatch: + inputs: + release_generators: + description: 'Release swagger-codegen-generators together with codegen' + required: true + default: 'false' + type: choice + options: + - 'false' + - 'true' + skip_maven_deploy: + description: 'Skip Maven deploy for recovery when artifacts are already published' + required: true + default: 'false' + type: choice + options: + - 'false' + - 'true' + skip_docker_push: + description: 'Skip Docker build and push' + required: true + default: 'false' + type: choice + options: + - 'false' + - 'true' + skip_rancher_deploy: + description: 'Skip Swagger online generator deployment' + required: true + default: 'false' + type: choice + options: + - 'false' + - 'true' + previous_generators_version: + description: 'Generator release or snapshot version used to bootstrap the first codegen build when releasing generators' + required: false + type: string + codegen_generators_bootstrap_version: + description: 'Codegen release or snapshot version used to build generators' + required: false + type: string + dry_run: + description: 'Run validation and builds without deploy, Docker push, or Rancher deploy (post-release PR is still created)' + required: true + default: 'true' + type: choice + options: + - 'true' + - 'false' + +permissions: + contents: read + +jobs: + validate: + # Single source of truth for resolved versions and release-mode decisions. + runs-on: ubuntu-latest + outputs: + codegen_version: ${{ steps.resolve.outputs.codegen_version }} + generators_version: ${{ steps.resolve.outputs.generators_version }} + codegen_build_generators_version: ${{ steps.resolve.outputs.codegen_build_generators_version }} + bootstrap_generators_version: ${{ steps.resolve.outputs.bootstrap_generators_version }} + bootstrap_codegen_version: ${{ steps.resolve.outputs.bootstrap_codegen_version }} + next_codegen_snapshot_version: ${{ steps.resolve.outputs.next_codegen_snapshot_version }} + generators_deploy_needed: ${{ steps.resolve.outputs.generators_deploy_needed }} + steps: + - name: Checkout swagger-codegen 3.0.0 + uses: actions/checkout@v6 + with: + ref: 3.0.0 + + - name: Checkout swagger-codegen-generators + if: inputs.release_generators == 'true' + uses: actions/checkout@v6 + with: + repository: swagger-api/swagger-codegen-generators + ref: master + path: generators-repo + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Java and Maven + uses: actions/setup-java@v5 + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_PRIVATE_PASSPHRASE }} + with: + java-version: '17' + distribution: temurin + server-id: central + cache: maven + overwrite-settings: false + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.OSSRH_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + + - name: Add Central Portal snapshot repository + uses: s4u/maven-settings-action@v4.0.0 + with: + repositories: '[{"id":"central-portal-snapshots","name":"Sonatype Central Portal snapshots","url":"https://central.sonatype.com/repository/maven-snapshots/","releases":{"enabled":false},"snapshots":{"enabled":true}}]' + servers: '[{"id":"central","username":"${{ secrets.MAVEN_CENTRAL_USERNAME }}","password":"${{ secrets.MAVEN_CENTRAL_PASSWORD }}"}]' + + - name: Validate and resolve versions + id: resolve + # Computes build/deploy coordinates and enforces safe release preconditions. + env: + RELEASE_GENERATORS: ${{ inputs.release_generators }} + PREVIOUS_GENERATORS_VERSION: ${{ inputs.previous_generators_version }} + CODEGEN_GENERATORS_BOOTSTRAP_VERSION: ${{ inputs.codegen_generators_bootstrap_version }} + SKIP_MAVEN_DEPLOY: ${{ inputs.skip_maven_deploy }} + DRY_RUN: ${{ inputs.dry_run }} + run: bash CI/release/resolve-release-versions.sh + + bootstrap_codegen: + # Break circular dependency: build codegen first with previous generators version. + runs-on: ubuntu-latest + needs: validate + if: inputs.release_generators == 'true' && needs.validate.outputs.generators_deploy_needed == 'true' + steps: + - name: Checkout swagger-codegen 3.0.0 + uses: actions/checkout@v6 + with: + ref: 3.0.0 + + - name: Set up Java and Maven + uses: actions/setup-java@v5 + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + with: + java-version: '17' + distribution: temurin + server-id: central + cache: maven + overwrite-settings: false + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + + - name: Add Central Portal snapshot repository + uses: s4u/maven-settings-action@v4.0.0 + with: + repositories: '[{"id":"central-portal-snapshots","name":"Sonatype Central Portal snapshots","url":"https://central.sonatype.com/repository/maven-snapshots/","releases":{"enabled":false},"snapshots":{"enabled":true}}]' + servers: '[{"id":"central","username":"${{ secrets.MAVEN_CENTRAL_USERNAME }}","password":"${{ secrets.MAVEN_CENTRAL_PASSWORD }}"}]' + + - name: Build codegen with bootstrap generators + run: | + mvn -B -U clean install -Pdocker \ + -Dswagger-codegen-generators-version="${{ needs.validate.outputs.bootstrap_generators_version }}" \ + -DJETTY_TEST_HTTP_PORT=8090 \ + -DJETTY_TEST_STOP_PORT=8089 + + generators: + # Build/deploy generators only when this run is responsible for publishing them. + runs-on: ubuntu-latest + needs: + - validate + - bootstrap_codegen + if: inputs.release_generators == 'true' && needs.validate.outputs.generators_deploy_needed == 'true' + steps: + - name: Checkout swagger-codegen-generators + uses: actions/checkout@v6 + with: + repository: swagger-api/swagger-codegen-generators + ref: master + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Java and Maven + uses: actions/setup-java@v5 + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_PRIVATE_PASSPHRASE }} + with: + java-version: '17' + distribution: temurin + server-id: central + cache: maven + overwrite-settings: false + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.OSSRH_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + + - name: Add Central Portal snapshot repository + uses: s4u/maven-settings-action@v4.0.0 + with: + repositories: '[{"id":"central-portal-snapshots","name":"Sonatype Central Portal snapshots","url":"https://central.sonatype.com/repository/maven-snapshots/","releases":{"enabled":false},"snapshots":{"enabled":true}}]' + servers: '[{"id":"central","username":"${{ secrets.MAVEN_CENTRAL_USERNAME }}","password":"${{ secrets.MAVEN_CENTRAL_PASSWORD }}"}]' + + - name: Build swagger-codegen-generators + run: | + mvn -B -U clean install \ + -Dswagger-codegen-version="${{ needs.validate.outputs.bootstrap_codegen_version }}" + + - name: Deploy swagger-codegen-generators + # Recovery controls: deploy can be skipped with dry_run/skip_maven_deploy. + if: needs.validate.outputs.generators_deploy_needed == 'true' && inputs.skip_maven_deploy != 'true' && inputs.dry_run != 'true' + run: mvn --no-transfer-progress -B -Prelease deploy + + - name: Skip swagger-codegen-generators deploy + if: needs.validate.outputs.generators_deploy_needed != 'true' || inputs.skip_maven_deploy == 'true' || inputs.dry_run == 'true' + run: | + echo "Generator deploy skipped." + echo "generators_deploy_needed=${{ needs.validate.outputs.generators_deploy_needed }}" + echo "skip_maven_deploy=${{ inputs.skip_maven_deploy }}" + echo "dry_run=${{ inputs.dry_run }}" + + codegen: + # Main codegen build and Maven release deploy. + runs-on: ubuntu-latest + permissions: + contents: write + needs: + - validate + - generators + if: always() && needs.validate.result == 'success' && (needs.generators.result == 'success' || needs.generators.result == 'skipped') + outputs: + codegen_version: ${{ steps.expose.outputs.codegen_version }} + generators_version: ${{ steps.expose.outputs.generators_version }} + next_codegen_snapshot_version: ${{ steps.expose.outputs.next_codegen_snapshot_version }} + steps: + - name: Checkout swagger-codegen 3.0.0 + uses: actions/checkout@v6 + with: + ref: 3.0.0 + + - name: Set up Java and Maven + uses: actions/setup-java@v5 + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_PRIVATE_PASSPHRASE }} + with: + java-version: '17' + distribution: temurin + server-id: central + cache: maven + overwrite-settings: false + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.OSSRH_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + + - name: Add Central Portal snapshot repository + uses: s4u/maven-settings-action@v4.0.0 + with: + repositories: '[{"id":"central-portal-snapshots","name":"Sonatype Central Portal snapshots","url":"https://central.sonatype.com/repository/maven-snapshots/","releases":{"enabled":false},"snapshots":{"enabled":true}}]' + servers: '[{"id":"central","username":"${{ secrets.MAVEN_CENTRAL_USERNAME }}","password":"${{ secrets.MAVEN_CENTRAL_PASSWORD }}"}]' + + - name: Build swagger-codegen + run: | + mvn -B -U clean install -Pdocker \ + -Dswagger-codegen-generators-version="${{ needs.validate.outputs.codegen_build_generators_version }}" \ + -DJETTY_TEST_HTTP_PORT=8090 \ + -DJETTY_TEST_STOP_PORT=8089 + + - name: Deploy swagger-codegen + # Intended for final release run; disabled in dry_run/recovery skip mode. + if: inputs.skip_maven_deploy != 'true' && inputs.dry_run != 'true' + run: | + mvn --no-transfer-progress -B -Prelease deploy \ + -Dswagger-codegen-generators-version="${{ needs.validate.outputs.generators_version }}" + + - name: Skip swagger-codegen deploy + if: inputs.skip_maven_deploy == 'true' || inputs.dry_run == 'true' + run: | + echo "Codegen Maven deploy skipped." + echo "skip_maven_deploy=${{ inputs.skip_maven_deploy }}" + echo "dry_run=${{ inputs.dry_run }}" + + - name: Expose release outputs + id: expose + run: | + echo "codegen_version=${{ needs.validate.outputs.codegen_version }}" >> "${GITHUB_OUTPUT}" + echo "generators_version=${{ needs.validate.outputs.generators_version }}" >> "${GITHUB_OUTPUT}" + echo "next_codegen_snapshot_version=${{ needs.validate.outputs.next_codegen_snapshot_version }}" >> "${GITHUB_OUTPUT}" + + - name: Create draft release notes + # Creates draft GitHub release/tag metadata after successful codegen stage. + if: inputs.dry_run != 'true' + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ needs.validate.outputs.codegen_version }} + target_commitish: 3.0.0 + name: Swagger Codegen v${{ needs.validate.outputs.codegen_version }} + draft: true + generate_release_notes: true + + docker: + # Optional container publication stage; can be disabled independently. + runs-on: ubuntu-latest + needs: codegen + if: inputs.skip_docker_push != 'true' && inputs.dry_run != 'true' + steps: + - name: Checkout swagger-codegen 3.0.0 + uses: actions/checkout@v6 + with: + ref: 3.0.0 + + - name: Set up Java and Maven + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: temurin + cache: maven + + - name: Build Docker inputs + run: | + mvn -B -U clean install -Pdocker \ + -Dswagger-codegen-generators-version="${{ needs.codegen.outputs.generators_version }}" \ + -DJETTY_TEST_HTTP_PORT=8090 \ + -DJETTY_TEST_STOP_PORT=8089 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker login + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_SB_USERNAME }} + password: ${{ secrets.DOCKERHUB_SB_PASSWORD }} + + - name: Build and push swagger-generator-v3 + uses: docker/build-push-action@v5 + with: + context: ./modules/swagger-generator + file: ./modules/swagger-generator/Dockerfile + push: true + platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x + provenance: false + build-args: | + HIDDEN_OPTIONS_DEFAULT_PATH=hiddenOptions.yaml + JAVA_MEM=1024m + HTTP_PORT=8080 + tags: swaggerapi/swagger-generator-v3:${{ needs.codegen.outputs.codegen_version }},swaggerapi/swagger-generator-v3:latest + + - name: Build and push swagger-generator-v3-root + uses: docker/build-push-action@v5 + with: + context: ./modules/swagger-generator + file: ./modules/swagger-generator/Dockerfile_root + push: true + platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x + provenance: false + build-args: | + HIDDEN_OPTIONS_DEFAULT_PATH=hiddenOptions.yaml + JAVA_MEM=1024m + HTTP_PORT=8080 + tags: swaggerapi/swagger-generator-v3-root:${{ needs.codegen.outputs.codegen_version }},swaggerapi/swagger-generator-v3-root:latest + + - name: Build and push swagger-codegen-cli-v3 + uses: docker/build-push-action@v5 + with: + context: ./modules/swagger-codegen-cli + file: ./modules/swagger-codegen-cli/Dockerfile + push: true + platforms: linux/amd64,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x + provenance: false + tags: swaggerapi/swagger-codegen-cli-v3:${{ needs.codegen.outputs.codegen_version }},swaggerapi/swagger-codegen-cli-v3:latest + + - name: Build and push swagger-generator-v3-minimal + uses: docker/build-push-action@v5 + with: + context: ./modules/swagger-generator + file: ./modules/swagger-generator/Dockerfile_minimal + push: true + platforms: linux/amd64,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x + provenance: false + tags: swaggerapi/swagger-generator-v3-minimal:${{ needs.codegen.outputs.codegen_version }},swaggerapi/swagger-generator-v3-minimal:latest + + online_deploy: + # Optional Swagger online deployment with rollback on readiness failure. + runs-on: ubuntu-latest + needs: + - codegen + - docker + if: inputs.skip_rancher_deploy != 'true' && inputs.skip_docker_push != 'true' && inputs.dry_run != 'true' + steps: + - name: Deploy Swagger online generator with rollback + env: + SC_VERSION: ${{ needs.codegen.outputs.codegen_version }} + TOKEN: ${{ secrets.RANCHER2_BEARER_TOKEN }} + RANCHER_HOST: rancher.tools.swagger.io + CLUSTER_ID: c-n8zp2 + NAMESPACE_NAME: swagger-oss + K8S_OBJECT_TYPE: daemonsets + K8S_OBJECT_NAME: swagger-generator-v3 + run: | + set -euo pipefail + + DEPLOY_IMAGE="swaggerapi/swagger-generator-v3:${SC_VERSION}" + workloadStatus="" + + getStatus() { + echo "Getting ${K8S_OBJECT_NAME} status..." + workloadStatus="$(curl --fail --silent --show-error -X GET \ + -H "Authorization: Bearer ${TOKEN}" \ + -H 'Content-Type: application/json' \ + "https://${RANCHER_HOST}/k8s/clusters/${CLUSTER_ID}/apis/apps/v1/namespaces/${NAMESPACE_NAME}/${K8S_OBJECT_TYPE}/${K8S_OBJECT_NAME}/status")" + } + + updateObject() { + local image="${1}" + echo "Updating ${K8S_OBJECT_NAME} to ${image}" + curl --fail --silent --show-error -X PATCH \ + -H "Authorization: Bearer ${TOKEN}" \ + -H 'Content-Type: application/json-patch+json' \ + "https://${RANCHER_HOST}/k8s/clusters/${CLUSTER_ID}/apis/apps/v1/namespaces/${NAMESPACE_NAME}/${K8S_OBJECT_TYPE}/${K8S_OBJECT_NAME}" \ + -d "[{\"op\":\"replace\",\"path\":\"/spec/template/spec/containers/0/image\",\"value\":\"${image}\"}]" + } + + [[ "${SC_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { + echo "::error::Invalid release version ${SC_VERSION}" + exit 1 + } + + getStatus + ROLLBACK_IMAGE="$(echo "${workloadStatus}" | jq -r '.spec.template.spec.containers[0].image')" + echo "Current image: ${ROLLBACK_IMAGE}" + + updateObject "${DEPLOY_IMAGE}" + sleep 60s + + getStatus + status="$(echo "${workloadStatus}" | jq '.status')" + numberDesired="$(echo "${status}" | jq -r '.desiredNumberScheduled')" + numberReady="$(echo "${status}" | jq -r '.numberReady')" + + if (( numberReady == numberDesired )); then + echo "${K8S_OBJECT_NAME} upgraded to ${DEPLOY_IMAGE}" + exit 0 + fi + + echo "::error::Deployment did not become ready; rolling back to ${ROLLBACK_IMAGE}" + updateObject "${ROLLBACK_IMAGE}" + sleep 60s + + getStatus + status="$(echo "${workloadStatus}" | jq '.status')" + numberDesired="$(echo "${status}" | jq -r '.desiredNumberScheduled')" + numberReady="$(echo "${status}" | jq -r '.numberReady')" + + if (( numberReady == numberDesired )); then + echo "Rollback to ${ROLLBACK_IMAGE} completed." + else + echo "::error::Rollback to ${ROLLBACK_IMAGE} failed." + fi + + exit 1 + + post_release_pr: + # Prepares and opens next snapshot PR after successful release pipeline. + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + needs: + - codegen + - docker + - online_deploy + if: always() && needs.codegen.result == 'success' && (needs.docker.result == 'success' || needs.docker.result == 'skipped') && (needs.online_deploy.result == 'success' || needs.online_deploy.result == 'skipped') + env: + NEXT_CODEGEN_SNAPSHOT_VERSION: ${{ needs.codegen.outputs.next_codegen_snapshot_version }} + GENERATORS_VERSION: ${{ needs.codegen.outputs.generators_version }} + steps: + - name: Checkout swagger-codegen 3.0.0 + uses: actions/checkout@v6 + with: + ref: 3.0.0 + + - uses: actions/create-github-app-token@v3 + id: generate-token + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Set up Java and Maven + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: temurin + cache: maven + + - name: Prepare next snapshot changes + id: next-snapshot + # Moves repo from release version to next -SNAPSHOT and updates docs/poms. + run: bash CI/release/post-codegen-snapshot.sh + + - name: Create next snapshot pull request + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ steps.generate-token.outputs.token }} + commit-message: bump snapshot ${{ steps.next-snapshot.outputs.next_codegen_snapshot_version }} + title: bump snapshot ${{ steps.next-snapshot.outputs.next_codegen_snapshot_version }} + branch: bump-snap-${{ steps.next-snapshot.outputs.next_codegen_snapshot_version }} + base: 3.0.0 + body: | + Bump Swagger Codegen to ${{ steps.next-snapshot.outputs.next_codegen_snapshot_version }} after release. + + release_generators: ${{ inputs.release_generators }} + swagger-codegen-generators dependency retained as: ${{ needs.codegen.outputs.generators_version }} diff --git a/.github/workflows/release-full-3.yml b/.github/workflows/release-full-3.yml index 30def71c9e7..e69de29bb2d 100644 --- a/.github/workflows/release-full-3.yml +++ b/.github/workflows/release-full-3.yml @@ -1,317 +0,0 @@ -name: Release Codegen 3.0 and Generators - -on: - workflow_dispatch: - -jobs: - release: - runs-on: ubuntu-latest - steps: - - name: Checkout swagger-codegen (3.0.0) - uses: actions/checkout@v6 - with: - token: ${{ secrets.GITHUB_TOKEN }} - ref: 3.0.0 - - name: Checkout swagger-codegen-generators - uses: actions/checkout@v6 - with: - repository: swagger-api/swagger-codegen-generators - token: ${{ secrets.GITHUB_TOKEN }} - ref: master - path: generators-repo - - name: Set up Java and Maven - uses: actions/setup-java@v5 - with: - java-version: '17' - distribution: 'temurin' - server-id: central - cache: maven - overwrite-settings: false - server-username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - server-password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - gpg-private-key: ${{ secrets.OSSRH_GPG_PRIVATE_KEY }} - gpg-passphrase: MAVEN_GPG_PASSPHRASE - - name: Add Central-Portal snapshot repo to settings.xml - uses: s4u/maven-settings-action@v4.0.0 - with: - repositories: '[{"id":"central-portal-snapshots","name":"Sonatype Central Portal snapshots","url":"https://central.sonatype.com/repository/maven-snapshots/","releases":{"enabled":false},"snapshots":{"enabled":true}}]' - servers: '[{"id":"central","username":"${{ secrets.MAVEN_CENTRAL_USERNAME }}","password":"${{ secrets.MAVEN_CENTRAL_PASSWORD }}"}]' - - name: Cache Maven repository - uses: actions/cache@v5 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - - name: configure git user email - run: | - git config --global user.email "action@github.com" - git config --global user.name "GitHub Action" - git config --global hub.protocol https - git remote set-url origin https://\${{ secrets.GITHUB_TOKEN }}:x-oauth-basic@github.com/swagger-api/swagger-codegen.git - - name: Check Release Version codegen - id: preRelease - run: | - # export GPG_TTY=$(tty) - export MY_POM_VERSION=`mvn -q -Dexec.executable="echo" -Dexec.args='${projects.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec` - if [[ $MY_POM_VERSION =~ ^.*SNAPSHOT$ ]]; - then - echo "not releasing snapshot version: " ${MY_POM_VERSION} - echo "RELEASE_OK=no" >> $GITHUB_ENV - else - echo "RELEASE_OK=yes" >> $GITHUB_ENV - fi - echo "SC_VERSION=$MY_POM_VERSION" >> $GITHUB_ENV - - name: Check Release Version codegen-generators - id: preReleaseGenerators - if: env.RELEASE_OK == 'yes' - working-directory: generators-repo - run: | - # export GPG_TTY=$(tty) - SNAP_API="https://central.sonatype.com/repository/maven-snapshots" - ARTIFACT_PATH="io/swagger/codegen/v3/swagger-codegen" - ROOT_META="${SNAP_API}/${ARTIFACT_PATH}/maven-metadata.xml" - export LAST_SNAP=$(curl -s "$ROOT_META" | grep -oP '(?<=)3\.[^<]+' | sort -V | tail -n1) - echo "LAST_SNAP $LAST_SNAP" - export CODEGEN_VERSION_PROPERTY=-Dswagger-codegen-version=$LAST_SNAP - export MY_POM_VERSION_GENERATORS=`mvn -q -Dexec.executable="echo" ${CODEGEN_VERSION_PROPERTY} -Dexec.args='${projects.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec` - echo "generator version: " ${MY_POM_VERSION_GENERATORS} - if [[ $MY_POM_VERSION_GENERATORS =~ ^.*SNAPSHOT$ ]]; - then - echo "not releasing codegen-generators snapshot version: " ${MY_POM_VERSION_GENERATORS} - echo "GENERATORS_RELEASE_OK=no" >> $GITHUB_ENV - else - GROUP_ID="io.swagger.codegen.v3" - ARTIFACT_ID="swagger-codegen-generators" - if mvn -q dependency:get -DgroupId=${GROUP_ID} -DartifactId=${ARTIFACT_ID} -Dversion=${MY_POM_VERSION_GENERATORS} > /dev/null 2>&1; then - echo "Version ${MY_POM_VERSION_GENERATORS} already exists in Maven Central." - echo "GENERATORS_RELEASE_OK=no" >> $GITHUB_ENV - else - echo "Version ${MY_POM_VERSION_GENERATORS} does not exist in Maven Central. Will be deployed." - echo "GENERATORS_RELEASE_OK=yes" >> $GITHUB_ENV - fi - fi - echo "generators version: " ${MY_POM_VERSION_GENERATORS} - echo "SC_VERSION_GENERATORS=${MY_POM_VERSION_GENERATORS}" >> $GITHUB_ENV - - name: Run maven install for codegen-generators - if: env.RELEASE_OK == 'yes' - working-directory: generators-repo - run: | - SNAP_API="https://central.sonatype.com/repository/maven-snapshots" - ARTIFACT_PATH="io/swagger/codegen/v3/swagger-codegen" - ROOT_META="${SNAP_API}/${ARTIFACT_PATH}/maven-metadata.xml" - export LAST_SNAP=$(curl -s "$ROOT_META" | grep -oP '(?<=)3\.[^<]+' | sort -V | tail -n1) - echo "LAST_SNAP $LAST_SNAP" - export CODEGEN_VERSION_PROPERTY=-Dswagger-codegen-version=$LAST_SNAP - mvn clean install -U ${CODEGEN_VERSION_PROPERTY} - - name: Run maven install for codegen - if: env.RELEASE_OK == 'yes' - run: | - ARTIFACT_PATH="io/swagger/codegen/v3/swagger-codegen-generators" - LAST_VERSION="" - if [[ "${{ env.GENERATORS_RELEASE_OK }}" == 'yes' ]]; then - SNAP_API="https://central.sonatype.com/repository/maven-snapshots" - ROOT_META="${SNAP_API}/${ARTIFACT_PATH}/maven-metadata.xml" - export LAST_VERSION=$(curl -s "$ROOT_META" | awk -F'[<>]' '/1\./{print $3}' | sort -V | tail -n1) - echo "Using GENERATORS LAST_VERSION: $LAST_VERSION for codegen install" - else - RELEASED_API="https://repo1.maven.org/maven2" - ROOT_META="${RELEASED_API}/${ARTIFACT_PATH}/maven-metadata.xml" - export LAST_VERSION=$(curl -s "$ROOT_META" | awk -F'[<>]' '/1\./{print $3}' | sort -V | tail -n1) - echo "Using GENERATORS LAST_VERSION: $LAST_VERSION for codegen install" - fi - export GENERATORS_VERSION_PROPERTY=-Dswagger-codegen-generators-version=$LAST_VERSION - mvn clean install -U -Pdocker -DJETTY_TEST_HTTP_PORT=8090 -DJETTY_TEST_STOP_PORT=8089 ${GENERATORS_VERSION_PROPERTY} - - name: Run maven deploy/release for codegen-generators - if: env.RELEASE_OK == 'yes' && env.GENERATORS_RELEASE_OK == 'yes' - working-directory: generators-repo - run: | - echo "Releasing codegen-generators" - mvn --no-transfer-progress -B -Prelease deploy - - name: Run maven deploy/release for codegen - if: env.RELEASE_OK == 'yes' - run: | - echo "Releasing codegen" - mvn --no-transfer-progress -B -Prelease deploy - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: preliminary checks - run: | - docker login --username=${{ secrets.DOCKERHUB_SB_USERNAME }} --password=${{ secrets.DOCKERHUB_SB_PASSWORD }} - set -e - - name: Build with Maven - run: | - mvn clean install -U -Pdocker -DJETTY_TEST_HTTP_PORT=8090 -DJETTY_TEST_STOP_PORT=8089 - - name: docker generator build and push - if: env.RELEASE_OK == 'yes' - uses: docker/build-push-action@v5 - with: - context: ./modules/swagger-generator - file: ./modules/swagger-generator/Dockerfile - push: true - platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x - provenance: false - build-args: | - HIDDEN_OPTIONS_DEFAULT_PATH=hiddenOptions.yaml - JAVA_MEM=1024m - HTTP_PORT=8080 - tags: swaggerapi/swagger-generator-v3:${{ env.SC_VERSION }},swaggerapi/swagger-generator-v3:latest - - name: docker generator root build and push - if: env.RELEASE_OK == 'yes' - uses: docker/build-push-action@v5 - with: - context: ./modules/swagger-generator - file: ./modules/swagger-generator/Dockerfile_root - push: true - platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x - provenance: false - build-args: | - HIDDEN_OPTIONS_DEFAULT_PATH=hiddenOptions.yaml - JAVA_MEM=1024m - HTTP_PORT=8080 - tags: swaggerapi/swagger-generator-v3-root:${{ env.SC_VERSION }},swaggerapi/swagger-generator-v3-root:latest - - name: docker cli build and push - if: env.RELEASE_OK == 'yes' - uses: docker/build-push-action@v5 - with: - context: ./modules/swagger-codegen-cli - file: ./modules/swagger-codegen-cli/Dockerfile - push: true - platforms: linux/amd64,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x - provenance: false - tags: swaggerapi/swagger-codegen-cli-v3:latest,swaggerapi/swagger-codegen-cli-v3:${{ env.SC_VERSION }} - - name: docker minimal build and push - if: env.RELEASE_OK == 'yes' - uses: docker/build-push-action@v5 - with: - context: ./modules/swagger-generator - file: ./modules/swagger-generator/Dockerfile_minimal - push: true - platforms: linux/amd64,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x - provenance: false - tags: swaggerapi/swagger-generator-v3-minimal:${{ env.SC_VERSION }},swaggerapi/swagger-generator-v3-minimal:latest - - name: deploy - if: env.RELEASE_OK == 'yes' - run: | - echo "${{ env.SC_VERSION }}" - - TOKEN="${{ secrets.RANCHER2_BEARER_TOKEN }}" - RANCHER_HOST="rancher.tools.swagger.io" - CLUSTER_ID="c-n8zp2" - NAMESPACE_NAME="swagger-oss" - K8S_OBJECT_TYPE="daemonsets" - K8S_OBJECT_NAME="swagger-generator-v3" - DEPLOY_IMAGE="swaggerapi/swagger-generator-v3:${{ env.SC_VERSION }}" - - workloadStatus="" - getStatus() { - echo "Getting update status..." - if ! workloadStatus="$(curl -s -X GET \ - -H "Authorization: Bearer ${TOKEN}" \ - -H 'Content-Type: application/json' \ - "https://${RANCHER_HOST}/k8s/clusters/${CLUSTER_ID}/apis/apps/v1/namespaces/${NAMESPACE_NAME}/${K8S_OBJECT_TYPE}/${K8S_OBJECT_NAME}/status")" - then - echo 'ERROR - get status k8s API call failed!' - echo "Exiting build"... - exit 1 - fi - } - - # $1 = image to deploy - updateObject() { - local image="${1}" - echo "Updating image value..." - - if ! curl -s -X PATCH \ - -H "Authorization: Bearer ${TOKEN}" \ - -H 'Content-Type: application/json-patch+json' \ - "https://${RANCHER_HOST}/k8s/clusters/${CLUSTER_ID}/apis/apps/v1/namespaces/${NAMESPACE_NAME}/${K8S_OBJECT_TYPE}/${K8S_OBJECT_NAME}" \ - -d "[{\"op\": \"replace\", \"path\": \"/spec/template/spec/containers/0/image\", \"value\": \"${image}\"}]" - then - echo 'ERROR - image update k8s API call failed!' - echo "Exiting build..." - exit 1 - fi - } - - - # Check that the TAG is valid - if [[ ${{ env.SC_VERSION }} =~ ^[vV]?[0-9]*\.[0-9]*\.[0-9]*$ ]]; then - echo "" - echo "This is a Valid TAG..." - - # Get current image/tag in case we need to rollback - getStatus - ROLLBACK_IMAGE="$(echo "${workloadStatus}" | jq -r '.spec.template.spec.containers[0].image')" - echo "" - echo "Current image: ${ROLLBACK_IMAGE}" - - # Update image and validate response - echo "" - updateObject "${DEPLOY_IMAGE}" - echo "" - - echo "" - echo "Waiting for pods to start..." - echo "" - sleep 60s - - # Get state of the k8s object. If numberReady == desiredNumberScheduled, consider the upgrade successful. Else raise error - getStatus - status="$(echo "${workloadStatus}" | jq '.status')" - echo "" - echo "${status}" - echo "" - - numberDesired="$(echo "${status}" | jq -r '.desiredNumberScheduled')" - numberReady="$(echo "${status}" | jq -r '.numberReady')" - - if (( numberReady == numberDesired )); then - echo "${K8S_OBJECT_NAME} has been upgraded to ${DEPLOY_IMAGE}" - - # If pods are not starting, rollback the upgrade and exit the build with error - else - echo "state = error...rolling back upgrade" - updateObject "${ROLLBACK_IMAGE}" - echo "" - - echo "" - echo "Waiting for rollback pods to start..." - echo "" - sleep 60s - - getStatus - status="$(echo "${workloadStatus}" | jq '.status')" - echo "" - echo "${status}" - echo "" - - numberDesired="$(echo "${status}" | jq -r '.desiredNumberScheduled')" - numberReady="$(echo "${status}" | jq -r '.numberReady')" - - if (( numberReady == numberDesired )); then - echo "Rollback to ${ROLLBACK_IMAGE} completed." - else - echo "FATAL - rollback failed" - fi - echo "Exiting Build..." - exit 1 - fi - - else - echo "This TAG is not in a valid format..." - echo "Exiting Build..." - exit 0 - fi - echo "Exiting Build..." - exit 0 - env: - ACTIONS_ALLOW_UNSECURE_COMMANDS: true - MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SC_VERSION: - SC_VERSION_GENERATORS: - GPG_PRIVATE_KEY: ${{ secrets.OSSRH_GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_PRIVATE_PASSPHRASE }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_PRIVATE_PASSPHRASE }} diff --git a/CI/release/build-codegen-with-generators.sh b/CI/release/build-codegen-with-generators.sh new file mode 100644 index 00000000000..9cb430fa961 --- /dev/null +++ b/CI/release/build-codegen-with-generators.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +generators_version="${1:-}" +[[ -n "${generators_version}" ]] || fail "Usage: build-codegen-with-generators.sh " + +# Standardized codegen build command with explicit generators coordinate. +mvn -B -U clean install -Pdocker \ + -Dswagger-codegen-generators-version="${generators_version}" \ + -DJETTY_TEST_HTTP_PORT=8090 \ + -DJETTY_TEST_STOP_PORT=8089 diff --git a/CI/release/common.sh b/CI/release/common.sh new file mode 100644 index 00000000000..3f94da15e43 --- /dev/null +++ b/CI/release/common.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash + +set -euo pipefail + +RELEASED_MAVEN_BASE="${RELEASED_MAVEN_BASE:-https://repo1.maven.org/maven2}" +SNAPSHOT_MAVEN_BASE="${SNAPSHOT_MAVEN_BASE:-https://central.sonatype.com/repository/maven-snapshots}" +CODEGEN_GROUP_PATH="io/swagger/codegen/v3" +GENERATORS_ARTIFACT="swagger-codegen-generators" +CODEGEN_ARTIFACT="swagger-codegen" + +fail() { + echo "::error::$*" + exit 1 +} + +notice() { + echo "::notice::$*" +} + +require_release_version() { + local name="$1" + local version="$2" + + [[ -n "${version}" ]] || fail "${name} is required" + [[ ! "${version}" =~ SNAPSHOT$ ]] || fail "${name} must be a release version, got ${version}" + [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "${name} must match X.Y.Z, got ${version}" +} + +require_release_or_snapshot_version() { + local name="$1" + local version="$2" + + [[ -n "${version}" ]] || fail "${name} is required" + [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-SNAPSHOT)?$ ]] || fail "${name} must match X.Y.Z or X.Y.Z-SNAPSHOT, got ${version}" +} + +require_snapshot_version() { + local name="$1" + local version="$2" + + [[ -n "${version}" ]] || fail "${name} is required" + [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-SNAPSHOT$ ]] || fail "${name} must match X.Y.Z-SNAPSHOT, got ${version}" +} + +release_from_snapshot_version() { + local version="$1" + + [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-SNAPSHOT$ ]] || fail "Cannot derive release version from non-SNAPSHOT version ${version}" + printf '%s\n' "${version%-SNAPSHOT}" +} + +next_snapshot_from_release_version() { + local version="$1" + local major minor patch + + require_release_version "version" "${version}" + IFS=. read -r major minor patch <<< "${version}" + printf '%s.%s.%s-SNAPSHOT\n' "${major}" "${minor}" "$((patch + 1))" +} + +## Thin Maven helpers for reading project-level values from pom.xml. +maven_project_version() { + mvn -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec +} + +maven_property_value() { + local property_name="$1" + mvn -q -Dexec.executable="echo" -Dexec.args="\${${property_name}}" --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec +} + +## Shared curl wrapper for metadata fetch with retries and hard timeouts. +curl_metadata() { + local url="$1" + curl --fail --silent --show-error --location --retry 5 --retry-delay 2 --connect-timeout 20 --max-time 90 "${url}" +} + +## Parse entries from Maven metadata.xml. +versions_from_metadata() { + awk -F'[<>]' '//{print $3}' +} + +## Resolve the newest version matching a regex pattern from metadata. +latest_matching_version() { + local metadata_url="$1" + local pattern="$2" + local version + + version="$(curl_metadata "${metadata_url}" | versions_from_metadata | grep -E "${pattern}" | sort -V | tail -n 1 || true)" + [[ -n "${version}" ]] || fail "No version matching '${pattern}' found in ${metadata_url}" + printf '%s\n' "${version}" +} + +latest_released_generators_version() { + latest_matching_version "${RELEASED_MAVEN_BASE}/${CODEGEN_GROUP_PATH}/${GENERATORS_ARTIFACT}/maven-metadata.xml" '^1\.[0-9]+\.[0-9]+$' +} + +latest_snapshot_generators_version() { + latest_matching_version "${SNAPSHOT_MAVEN_BASE}/${CODEGEN_GROUP_PATH}/${GENERATORS_ARTIFACT}/maven-metadata.xml" '^1\.[0-9]+\.[0-9]+-SNAPSHOT$' +} + +latest_snapshot_codegen_version() { + latest_matching_version "${SNAPSHOT_MAVEN_BASE}/${CODEGEN_GROUP_PATH}/${CODEGEN_ARTIFACT}/maven-metadata.xml" '^3\.[0-9]+\.[0-9]+-SNAPSHOT$' +} + +## Build canonical artifact URLs and probe existence without downloading payloads. +release_artifact_url() { + local artifact="$1" + local version="$2" + + printf '%s/%s/%s/%s/%s-%s.pom\n' "${RELEASED_MAVEN_BASE}" "${CODEGEN_GROUP_PATH}" "${artifact}" "${version}" "${artifact}" "${version}" +} + +release_artifact_exists() { + local artifact="$1" + local version="$2" + local url + + url="$(release_artifact_url "${artifact}" "${version}")" + curl --fail --silent --show-error --head --location --retry 3 --connect-timeout 20 --max-time 60 "${url}" >/dev/null 2>&1 +} + +## SNAPSHOT coordinates resolve via maven-metadata.xml, not fixed file names. +snapshot_metadata_url() { + local artifact="$1" + local version="$2" + + printf '%s/%s/%s/%s/maven-metadata.xml\n' "${SNAPSHOT_MAVEN_BASE}" "${CODEGEN_GROUP_PATH}" "${artifact}" "${version}" +} + +assert_snapshot_metadata_exists() { + local artifact="$1" + local version="$2" + local metadata_url + + metadata_url="$(snapshot_metadata_url "${artifact}" "${version}")" + if ! curl_metadata "${metadata_url}" >/dev/null; then + fail "Required SNAPSHOT ${CODEGEN_GROUP_PATH}:${artifact}:${version} cannot be resolved from ${metadata_url}. Sonatype snapshots can expire. Recovery: publish that exact snapshot version, then rerun this workflow." + fi +} + +## Fast local Maven resolve check to fail early before long release stages. +assert_maven_resolves() { + local coordinates="$1" + + if ! mvn -B -q dependency:get -Dartifact="${coordinates}" >/tmp/maven-resolve.log 2>&1; then + cat /tmp/maven-resolve.log + fail "Maven could not resolve ${coordinates}. If this is a SNAPSHOT, publish the required snapshot before retrying." + fi +} + +## Write outputs for both step outputs and downstream environment reuse. +set_output() { + local name="$1" + local value="$2" + + echo "${name}=${value}" >> "${GITHUB_OUTPUT}" + echo "${name}=${value}" >> "${GITHUB_ENV}" +} diff --git a/CI/release/post-codegen-snapshot.sh b/CI/release/post-codegen-snapshot.sh new file mode 100644 index 00000000000..5f860cbd666 --- /dev/null +++ b/CI/release/post-codegen-snapshot.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +next_codegen_snapshot_version="${NEXT_CODEGEN_SNAPSHOT_VERSION:-}" +generators_version="${GENERATORS_VERSION:-}" + +# If not provided by workflow input, derive next snapshot from current release. +if [[ -z "${next_codegen_snapshot_version}" ]]; then + current_version="$(maven_project_version)" + require_release_version "current codegen POM version" "${current_version}" + next_codegen_snapshot_version="$(next_snapshot_from_release_version "${current_version}")" +fi +require_snapshot_version "NEXT_CODEGEN_SNAPSHOT_VERSION" "${next_codegen_snapshot_version}" +[[ -n "${generators_version}" ]] || fail "GENERATORS_VERSION is required" + +# Expose resolved snapshot version to subsequent workflow steps. +if [[ -n "${GITHUB_ENV:-}" ]]; then + echo "NEXT_CODEGEN_SNAPSHOT_VERSION=${next_codegen_snapshot_version}" >> "${GITHUB_ENV}" +fi + +if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "next_codegen_snapshot_version=${next_codegen_snapshot_version}" >> "${GITHUB_OUTPUT}" +fi + +## Return project to snapshot line after release completion. +mvn -B versions:set -DnewVersion="${next_codegen_snapshot_version}" +mvn -B versions:commit + +update_codegen_release_files_script="CI/release/update-codegen-release-files.py" +[[ -f "${update_codegen_release_files_script}" ]] || fail "Missing ${update_codegen_release_files_script}" + +## Synchronize docs/poms/openapi snapshot references. +python3 "${update_codegen_release_files_script}" post \ + "${next_codegen_snapshot_version}" \ + "${generators_version}" + +echo "Prepared next snapshot file updates for ${next_codegen_snapshot_version}" diff --git a/CI/release/prepare-codegen-release.sh b/CI/release/prepare-codegen-release.sh new file mode 100644 index 00000000000..b0578a69baf --- /dev/null +++ b/CI/release/prepare-codegen-release.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +codegen_version="${CODEGEN_VERSION:-}" +next_codegen_snapshot_version="${NEXT_CODEGEN_SNAPSHOT_VERSION:-}" +release_generators="${RELEASE_GENERATORS:-false}" +generators_version="${GENERATORS_VERSION:-}" +previous_generators_version="${PREVIOUS_GENERATORS_VERSION:-}" +build_generators_version="" + +# Prepare flow must start from a SNAPSHOT on branch 3.0.0. +current_version="$(maven_project_version)" +[[ "${current_version}" =~ SNAPSHOT$ ]] || fail "Prepare release must start from a SNAPSHOT codegen version, got ${current_version}" + +## Resolve target release + next snapshot versions when not provided explicitly. +if [[ -z "${codegen_version}" ]]; then + codegen_version="$(release_from_snapshot_version "${current_version}")" +fi +require_release_version "CODEGEN_VERSION" "${codegen_version}" + +if [[ -z "${next_codegen_snapshot_version}" ]]; then + next_codegen_snapshot_version="$(next_snapshot_from_release_version "${codegen_version}")" +fi +require_snapshot_version "NEXT_CODEGEN_SNAPSHOT_VERSION" "${next_codegen_snapshot_version}" + +if [[ "${release_generators}" == "true" ]]; then + # Releasing generators: release version is explicit, build can bootstrap from previous snapshot/release. + require_release_version "GENERATORS_VERSION" "${generators_version}" + if [[ -n "${previous_generators_version}" ]]; then + require_release_or_snapshot_version "PREVIOUS_GENERATORS_VERSION" "${previous_generators_version}" + build_generators_version="${previous_generators_version}" + else + build_generators_version="$(latest_snapshot_generators_version)" + fi +else + # Not releasing generators: pin codegen to an already released generators artifact. + if [[ -z "${generators_version}" ]]; then + generators_version="$(latest_released_generators_version)" + fi + require_release_version "resolved generators version" "${generators_version}" + build_generators_version="${generators_version}" +fi + +## Validate the exact generators coordinate used for the candidate build. +if [[ "${build_generators_version}" =~ SNAPSHOT$ ]]; then + assert_snapshot_metadata_exists "${GENERATORS_ARTIFACT}" "${build_generators_version}" +else + release_artifact_exists "${GENERATORS_ARTIFACT}" "${build_generators_version}" || fail "Generator release ${build_generators_version} does not exist in Maven Central" +fi + +echo "Preparing codegen ${codegen_version} from ${current_version}" +echo "Using swagger-codegen-generators ${generators_version}" +echo "Building release candidate with swagger-codegen-generators ${build_generators_version}" + +## Expose resolved values to later workflow steps and PR metadata. +if [[ -n "${GITHUB_ENV:-}" ]]; then + echo "GENERATORS_VERSION=${generators_version}" >> "${GITHUB_ENV}" + echo "BUILD_GENERATORS_VERSION=${build_generators_version}" >> "${GITHUB_ENV}" + echo "CODEGEN_VERSION=${codegen_version}" >> "${GITHUB_ENV}" + echo "NEXT_CODEGEN_SNAPSHOT_VERSION=${next_codegen_snapshot_version}" >> "${GITHUB_ENV}" +fi + +if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "generators_version=${generators_version}" >> "${GITHUB_OUTPUT}" + echo "build_generators_version=${build_generators_version}" >> "${GITHUB_OUTPUT}" + echo "codegen_version=${codegen_version}" >> "${GITHUB_OUTPUT}" + echo "next_codegen_snapshot_version=${next_codegen_snapshot_version}" >> "${GITHUB_OUTPUT}" +fi + +## Move project from snapshot to release version before file-level content updates. +mvn -B versions:set -DnewVersion="${codegen_version}" +mvn -B versions:commit + +# Generate a minimal release-notes draft aligned with current GitHub release style. +mkdir -p docs/release-notes +previous_tag="$(git tag --merged HEAD --list 'v3.*' | sort -V | tail -n 1 || true)" +release_notes_file="docs/release-notes/v${codegen_version}.md" +{ + echo "# Swagger Codegen v${codegen_version}" + echo + echo "## What's Changed" + echo + if [[ -n "${previous_tag}" ]]; then + git log --first-parent --pretty=format:'* %s' "${previous_tag}..HEAD" + echo + echo + echo "Full Changelog: ${previous_tag}...v${codegen_version}" + else + git log --first-parent --pretty=format:'* %s' HEAD + echo + echo + echo "Full Changelog: initial...v${codegen_version}" + echo + fi +} > "${release_notes_file}" + +update_codegen_release_files_script="CI/release/update-codegen-release-files.py" +[[ -f "${update_codegen_release_files_script}" ]] || fail "Missing ${update_codegen_release_files_script}" + +## Keep docs/poms/openapi in sync with the release state. +python3 "${update_codegen_release_files_script}" prepare \ + "${codegen_version}" \ + "${next_codegen_snapshot_version}" \ + "${generators_version}" + +echo "Prepared release file updates for ${codegen_version}" diff --git a/CI/release/resolve-release-versions.sh b/CI/release/resolve-release-versions.sh new file mode 100644 index 00000000000..5e6325eb2ed --- /dev/null +++ b/CI/release/resolve-release-versions.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +release_generators="${RELEASE_GENERATORS:-false}" +generators_version_override="${GENERATORS_VERSION_OVERRIDE:-}" +previous_generators_version="${PREVIOUS_GENERATORS_VERSION:-}" +bootstrap_codegen_version="${CODEGEN_GENERATORS_BOOTSTRAP_VERSION:-}" +generators_existing_behavior="${GENERATORS_EXISTING_BEHAVIOR:-skip}" +skip_maven_deploy="${SKIP_MAVEN_DEPLOY:-false}" +dry_run="${DRY_RUN:-false}" +next_codegen_snapshot_version="${NEXT_CODEGEN_SNAPSHOT_VERSION:-}" + +# Guard critical feature flags early to fail fast on bad inputs. +[[ "${release_generators}" == "true" || "${release_generators}" == "false" ]] || fail "RELEASE_GENERATORS must be true or false" +[[ "${generators_existing_behavior}" == "skip" || "${generators_existing_behavior}" == "fail" ]] || fail "GENERATORS_EXISTING_BEHAVIOR must be skip or fail" + +# Release workflow expects prepare PR already merged (non-SNAPSHOT project version). +codegen_version="$(maven_project_version)" +require_release_version "codegen POM version" "${codegen_version}" + +if [[ -z "${next_codegen_snapshot_version}" ]]; then + next_codegen_snapshot_version="$(next_snapshot_from_release_version "${codegen_version}")" +fi +require_snapshot_version "NEXT_CODEGEN_SNAPSHOT_VERSION" "${next_codegen_snapshot_version}" + +codegen_exists="false" +# Protect against accidental redeploy of existing releases. +if release_artifact_exists "${CODEGEN_ARTIFACT}" "${codegen_version}"; then + codegen_exists="true" + if [[ "${dry_run}" == "true" ]]; then + notice "Codegen artifact ${CODEGEN_ARTIFACT}:${codegen_version} already exists; continuing because DRY_RUN=true." + elif [[ "${skip_maven_deploy}" != "true" ]]; then + fail "Codegen artifact ${CODEGEN_ARTIFACT}:${codegen_version} already exists in Maven Central. Rerun with skip_maven_deploy=true only for recovery." + fi +fi + +generators_version="" +generators_deploy_needed="false" +codegen_build_generators_version="" +bootstrap_generators_version="" + +if [[ "${release_generators}" == "true" ]]; then + # Generators release path: determine if generators must be published this run. + [[ -d generators-repo ]] || fail "generators-repo checkout is required when release_generators=true" + if [[ -n "${bootstrap_codegen_version}" ]]; then + require_release_or_snapshot_version "codegen_generators_bootstrap_version" "${bootstrap_codegen_version}" + else + bootstrap_codegen_version="$(latest_snapshot_codegen_version)" + fi + generators_version="$( + cd generators-repo && \ + mvn -q -Dswagger-codegen-version="${bootstrap_codegen_version}" \ + -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive \ + org.codehaus.mojo:exec-maven-plugin:1.3.1:exec + )" + require_release_version "swagger-codegen-generators POM version" "${generators_version}" + + if release_artifact_exists "${GENERATORS_ARTIFACT}" "${generators_version}"; then + if [[ "${generators_existing_behavior}" == "fail" && "${skip_maven_deploy}" != "true" ]]; then + fail "Generator artifact ${GENERATORS_ARTIFACT}:${generators_version} already exists in Maven Central" + fi + generators_deploy_needed="false" + notice "Generator artifact ${generators_version} already exists; generator deploy will be skipped." + else + generators_deploy_needed="true" + notice "Generator artifact ${generators_version} does not exist; generator deploy is required." + fi + + if [[ "${generators_deploy_needed}" == "true" && "${skip_maven_deploy}" == "true" && "${dry_run}" != "true" ]]; then + fail "Generator artifact ${generators_version} does not exist, but skip_maven_deploy=true would skip publishing it. Publish generators first or rerun without skip_maven_deploy." + fi + + if [[ "${generators_deploy_needed}" == "true" ]]; then + # Circular dependency bootstrap checks: codegen <-> generators. + require_release_or_snapshot_version "codegen_generators_bootstrap_version" "${bootstrap_codegen_version}" + + if [[ "${bootstrap_codegen_version}" =~ SNAPSHOT$ ]]; then + assert_snapshot_metadata_exists "${CODEGEN_ARTIFACT}" "${bootstrap_codegen_version}" + elif ! release_artifact_exists "${CODEGEN_ARTIFACT}" "${bootstrap_codegen_version}"; then + fail "Bootstrap codegen release ${bootstrap_codegen_version} does not exist in Maven Central" + fi + + if [[ -n "${previous_generators_version}" ]]; then + bootstrap_generators_version="${previous_generators_version}" + else + bootstrap_generators_version="$(latest_snapshot_generators_version)" + fi + require_release_or_snapshot_version "previous_generators_version" "${bootstrap_generators_version}" + + if [[ "${bootstrap_generators_version}" =~ SNAPSHOT$ ]]; then + assert_snapshot_metadata_exists "${GENERATORS_ARTIFACT}" "${bootstrap_generators_version}" + elif ! release_artifact_exists "${GENERATORS_ARTIFACT}" "${bootstrap_generators_version}"; then + fail "Bootstrap generators release ${bootstrap_generators_version} does not exist in Maven Central" + fi + assert_maven_resolves "io.swagger.codegen.v3:${GENERATORS_ARTIFACT}:${bootstrap_generators_version}" + else + bootstrap_codegen_version="" + bootstrap_generators_version="${generators_version}" + fi + + if [[ "${generators_deploy_needed}" == "true" && "${dry_run}" == "true" ]]; then + # Dry run cannot use not-yet-published generators release. + codegen_build_generators_version="${bootstrap_generators_version}" + notice "Dry run will build codegen with bootstrap generators ${codegen_build_generators_version}; release deploy will use ${generators_version} after generators are published." + else + codegen_build_generators_version="${generators_version}" + fi +else + # Codegen-only path: generators must already be a resolvable release. + if [[ -n "${generators_version_override}" ]]; then + generators_version="${generators_version_override}" + require_release_version "generators_version_override" "${generators_version}" + else + generators_version="$(maven_property_value "swagger-codegen-generators-version")" + require_release_version "swagger-codegen-generators-version from pom.xml" "${generators_version}" + fi + bootstrap_generators_version="${generators_version}" + + if ! release_artifact_exists "${GENERATORS_ARTIFACT}" "${generators_version}"; then + fail "Resolved generator release ${generators_version} does not exist in Maven Central" + fi +fi + +if [[ "${generators_deploy_needed}" == "true" ]]; then + notice "Skipping Maven resolve check for ${GENERATORS_ARTIFACT}:${generators_version} because this workflow will publish it before codegen deploy." +else + assert_maven_resolves "io.swagger.codegen.v3:${GENERATORS_ARTIFACT}:${generators_version}" +fi + +# Normalize fallback to avoid empty output in edge branches. +if [[ -z "${codegen_build_generators_version}" ]]; then + codegen_build_generators_version="${generators_version}" +fi + +notice "Resolved codegen_version=${codegen_version}" +notice "Resolved generators_version=${generators_version}" +notice "Resolved codegen_build_generators_version=${codegen_build_generators_version}" +notice "Resolved bootstrap_generators_version=${bootstrap_generators_version}" +notice "Resolved bootstrap_codegen_version=${bootstrap_codegen_version:-none}" +notice "Resolved next_codegen_snapshot_version=${next_codegen_snapshot_version}" +notice "Resolved codegen_exists=${codegen_exists}" +notice "Resolved generators_deploy_needed=${generators_deploy_needed}" + +## Export one canonical set of resolved versions to all downstream jobs. +set_output codegen_version "${codegen_version}" +set_output generators_version "${generators_version}" +set_output codegen_build_generators_version "${codegen_build_generators_version}" +set_output bootstrap_generators_version "${bootstrap_generators_version}" +set_output bootstrap_codegen_version "${bootstrap_codegen_version}" +set_output next_codegen_snapshot_version "${next_codegen_snapshot_version}" +set_output codegen_exists "${codegen_exists}" +set_output generators_deploy_needed "${generators_deploy_needed}" diff --git a/CI/release/update-codegen-release-files.py b/CI/release/update-codegen-release-files.py new file mode 100644 index 00000000000..a4edc698cd6 --- /dev/null +++ b/CI/release/update-codegen-release-files.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 + +from pathlib import Path +import re +import sys + +COMPATIBILITY_DOCS = ["README.md", "docs/compatibility.md"] +RELEASE_DOCS = ["README.md", "docs/prerequisites.md", "docs/versioning.md"] + +# Regex patterns intentionally target release tables/examples used in public docs. +SNAPSHOT_ROW_PATTERN = ( + r"\| [0-9]+\.[0-9]+\.[0-9]+-SNAPSHOT \(current 3\.0\.0, upcoming minor release\).*?\| Minor release \|" +) +RELEASE_ROW_PATTERN = ( + r"\| \[[0-9]+\.[0-9]+\.[0-9]+\]\(https://github\.com/swagger-api/swagger-codegen/releases/tag/v[0-9]+\.[0-9]+\.[0-9]+\) " + r"(?:\(\*\*current stable\*\*\)\s*)?.*?\| \[tag v[0-9]+\.[0-9]+\.[0-9]+\]\(https://github\.com/swagger-api/swagger-codegen/tree/v[0-9]+\.[0-9]+\.[0-9]+\)\s*\|" +) +RELEASE_JAR_PATTERN = ( + r"io/swagger/codegen/v3/swagger-codegen-cli/[0-9]+\.[0-9]+\.[0-9]+/swagger-codegen-cli-[0-9]+\.[0-9]+\.[0-9]+\.jar" +) +SNAPSHOT_JAR_PATTERN = ( + r"io/swagger/codegen/v3/swagger-codegen-cli/[0-9]+\.[0-9]+\.[0-9]+-SNAPSHOT/swagger-codegen-cli-[0-9]+\.[0-9]+\.[0-9]+-SNAPSHOT\.jar" +) +RELEASE_VERSION_PATTERN = r"3\.0\.[0-9]+" +SNAPSHOT_VERSION_PATTERN = r"3\.0\.[0-9]+-SNAPSHOT" + + +def replace_text(path: str, replacements: list[tuple[str, str]]) -> None: + # Generic file replacement helper; no-op if target file is absent. + file_path = Path(path) + if not file_path.exists(): + return + text = file_path.read_text() + original = text + for pattern, value in replacements: + text = re.sub(pattern, value, text, flags=re.MULTILINE) + if text != original: + file_path.write_text(text) + + +def replace_text_in_docs( + docs: list[str], replacements: list[tuple[str, str]], *, count: int = 0, flags: int = re.MULTILINE +) -> None: + # Apply replacement sets across a known docs list. + for doc in docs: + file_path = Path(doc) + if not file_path.exists(): + continue + text = file_path.read_text() + original = text + for pattern, value in replacements: + text = re.sub(pattern, value, text, count=count, flags=flags) + if text != original: + file_path.write_text(text) + + +def update_generators_poms(generators_version: str) -> None: + # Keep generators dependency aligned in both root pom variants. + replacements = [ + ( + r"[^<]+", + f"{generators_version}", + ), + ] + replace_text("pom.xml", replacements) + replace_text("pom.docker.xml", replacements) + + +def update_openapi_version(version: str) -> None: + # Reflect current codegen version in online generator OpenAPI metadata. + replace_text( + "modules/swagger-generator/src/main/resources/openapi.yaml", + [(r"^ version: .*$", f" version: {version}")], + ) + + +def update_snapshot_rows(next_snapshot: str) -> None: + # Update "current upcoming snapshot" row in compatibility docs. + snapshot_row = ( + f"| {next_snapshot} (current 3.0.0, upcoming minor release) " + "[SNAPSHOT](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/" + f"io/swagger/codegen/v3/swagger-codegen-cli/{next_snapshot}/) | TBD | 1.0, 1.1, 1.2, 2.0, 3.0 | Minor release |" + ) + replace_text_in_docs(COMPATIBILITY_DOCS, [(SNAPSHOT_ROW_PATTERN, snapshot_row)]) + + +def update_release_rows(codegen_version: str) -> None: + # Mark latest stable release row at the top of compatibility tables. + release_row = ( + f"| [{codegen_version}](https://github.com/swagger-api/swagger-codegen/releases/tag/v{codegen_version}) " + "(**current stable**) | TBD | 1.0, 1.1, 1.2, 2.0, 3.0 | " + f"[tag v{codegen_version}](https://github.com/swagger-api/swagger-codegen/tree/v{codegen_version}) |" + ) + replace_text_in_docs(COMPATIBILITY_DOCS, [(RELEASE_ROW_PATTERN, release_row)], count=1) + + +def update_release_docs(codegen_version: str) -> None: + # Point release examples to concrete released CLI coordinates. + replace_text_in_docs( + RELEASE_DOCS, + [ + ( + RELEASE_JAR_PATTERN, + f"io/swagger/codegen/v3/swagger-codegen-cli/{codegen_version}/swagger-codegen-cli-{codegen_version}.jar", + ), + (RELEASE_VERSION_PATTERN, f"{codegen_version}"), + ], + ) + + +def update_snapshot_docs(next_snapshot: str) -> None: + # Point snapshot examples to next development CLI coordinates. + replace_text_in_docs( + RELEASE_DOCS, + [ + ( + SNAPSHOT_JAR_PATTERN, + f"io/swagger/codegen/v3/swagger-codegen-cli/{next_snapshot}/swagger-codegen-cli-{next_snapshot}.jar", + ), + (SNAPSHOT_VERSION_PATTERN, f"{next_snapshot}"), + ], + ) + + +def main() -> int: + if len(sys.argv) < 2: + print("usage: update-codegen-release-files.py [...]", file=sys.stderr) + return 2 + + mode = sys.argv[1] + if mode == "prepare": + # Prepare mode: move docs/content to released codegen version state. + if len(sys.argv) != 5: + print("usage: ... prepare ", file=sys.stderr) + return 2 + codegen_version, next_snapshot, generators_version = sys.argv[2:5] + update_generators_poms(generators_version) + update_openapi_version(codegen_version) + update_snapshot_rows(next_snapshot) + update_release_rows(codegen_version) + update_release_docs(codegen_version) + return 0 + + if mode == "post": + # Post mode: move docs/content back to next snapshot development state. + if len(sys.argv) != 4: + print("usage: ... post ", file=sys.stderr) + return 2 + next_snapshot, generators_version = sys.argv[2:4] + update_generators_poms(generators_version) + update_openapi_version(next_snapshot) + update_snapshot_rows(next_snapshot) + update_snapshot_docs(next_snapshot) + return 0 + + print(f"unknown mode: {mode}", file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main())