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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/resolve-release-branch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ same project is refused rather than silently rewriting `main`.
| Input | Description | Required | Default |
|-------|-------------|----------|---------|
| `repo` | Full repository path (e.g. `spring-cloud/spring-cloud-config`) | Yes | |
| `internal` | Resolve `<major>.<minor>.x-internal` instead of `<major>.<minor>.x`. No `main` fallback; a missing branch is `branch-not-found`. | No | `false` |
| `version` | The version whose line is wanted. Any qualifier is stripped first, so `5.0.4-SNAPSHOT`, `5.1.0-INTERNAL-SNAPSHOT`, `5.1.0-M1` and `5.0.3` all resolve the same way. | Yes | |
| `commercial` | Repository is a commercial one, so there is no `main` to fall back to | No | `false` |
| `token` | Token with read access to the repository | Yes | |
Expand Down
21 changes: 19 additions & 2 deletions .github/actions/resolve-release-branch/action.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: 'Resolve Release Branch'
description: >
Resolves the maintenance branch a given project version belongs to: <major>.<minor>.x
when it exists, otherwise main for OSS repositories whose main is on that same line.
when it exists, otherwise main for OSS repositories whose main is on that same line. With
internal=true it resolves <major>.<minor>.x-internal instead, with no fallback.

inputs:
repo:
Expand All @@ -20,6 +21,13 @@ inputs:
reported rather than guessed at.
required: false
default: 'false'
internal:
description: >
When true, resolve the long-lived <major>.<minor>.x-internal branch of a commercial
repository rather than <major>.<minor>.x. A missing branch is reported, never replaced
by main.
required: false
default: 'false'
token:
description: 'GitHub token with read access to the repository'
required: true
Expand Down Expand Up @@ -48,6 +56,7 @@ runs:
REPO: ${{ inputs.repo }}
VERSION: ${{ inputs.version }}
COMMERCIAL: ${{ inputs.commercial }}
INTERNAL: ${{ inputs.internal }}
run: |
node - << 'JSEOF'
const fs = require('fs');
Expand All @@ -56,6 +65,7 @@ runs:
const repo = process.env.REPO;
const version = process.env.VERSION;
const commercial = process.env.COMMERCIAL === 'true';
const internal = process.env.INTERNAL === 'true';

const out = process.env.GITHUB_OUTPUT;
const emit = (k, v) => fs.appendFileSync(out, `${k}=${v}\n`);
Expand All @@ -78,7 +88,8 @@ runs:
stop('version-mismatch',
`ERROR: '${version}' is not a numeric version, so no branch can be derived from it.`);
}
const target = parts.slice(0, -1).join('.') + '.x';
const line = parts.slice(0, -1).join('.') + '.x';
const target = internal ? `${line}-internal` : line;

const branchExists = branch => {
try {
Expand All @@ -96,6 +107,12 @@ runs:
process.exit(0);
}

// An -internal branch is a copy of the OSS branch kept for the commercial repos; main is
// never a stand-in for it.
if (internal) {
stop('branch-not-found', `ERROR: ${repo} has no ${target} branch. Skipping this project.`);
}

// Commercial repos have no main branch at all - spring-cloud-config-commercial's
// default is 4.3.x - so there is no sane fallback to make.
if (commercial) {
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/README-post-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,7 @@ Notes on this:
## Notes

- **Milestone lookups are paginated, and that is not optional.** There is no "get a milestone by title" endpoint, so every milestone check here lists them — and several of these repositories are well past a hundred milestones (`spring-cloud-release` has 167, `spring-cloud-config` 154, `spring-cloud-commons` 151). The list API returns them **sorted by due date ascending**, so an unpaginated `per_page=100` read drops exactly the newest ones: the release just made and the one after it. A run against `2025.1.3` reported `not-found` for five repositories whose milestones were plainly there. Every lookup now uses `--paginate` — `--slurp` in the Node steps, since gh refuses `--slurp` together with `--jq`, and `jq -s add` in the shell ones.
- **`spring-boot` is in every properties file but is not a Spring Cloud repository**, so it is excluded from tag checks, milestones, releases and branch updates. It *is* bumped in the snapshot properties file, matching the existing files.
- **`spring-boot` and `spring-vault` are in every properties file but are not Spring Cloud repositories**, so they are excluded from tag checks, milestones, releases and branch updates. They *are* bumped in the snapshot properties file, matching the existing files.
- Pre-release qualifiers (`-M1`, `-RC1`) are rejected rather than half-handled — bumping the patch of `2025.1.0-RC1` produces a version nobody wants, and post-release chores are not run for milestones or release candidates.
- `update-project-versions` is called with `release-train-version` rather than an explicit versions map, because only that path applies `project-version-substitutions` (which maps `spring-cloud-dependencies-parent` → `spring-cloud-build`, `verifierVersion` → `spring-cloud-contract`, and so on). That path resolves over `raw.githubusercontent.com`, which is CDN-cached, so after committing the snapshot file the workflow waits for the raw URL to serve it before any project is updated. If the CDN never catches up, version updates are skipped rather than applied from a stale file, and the run can simply be repeated.
- Tag existence is checked with `git/matching-refs` and an exact comparison, not a plain `git/refs/tags/<tag>` lookup, which would also prefix-match `v5.0.20` when asked for `v5.0.2`.
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/README-update-versions.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ For the normal end-of-release path, run [post-release](README-post-release.md)
| `dry_run` | Clone, update, and diff without pushing | No | boolean (default: `true`) |
| `token` | Token with write access to all target repos. Falls back to `GH_ACTIONS_REPO_TOKEN`. | No | string |

`spring-boot` is in the properties file so that every project picks up its version, but it is not a Spring Cloud repository — it is never pushed to, and naming it in `projects` is an error.
`spring-boot` and `spring-vault` are in the properties file so that every project picks up their versions, but they are not Spring Cloud repositories — they are never pushed to, and naming either in `projects` is an error.

## OSS or commercial, never both

Expand All @@ -54,7 +54,11 @@ If that branch does not exist:
- **commercial** — the project is skipped. Commercial repos have no `main` to fall back to (`spring-cloud-config-commercial`'s default branch is `4.3.x`).
- **OSS** — falls back to `main`, but only after reading `pom.xml` on `main` and confirming it is on the same `major.minor` line. Otherwise the project is skipped with `version-mismatch` rather than bumping an unrelated major to these versions.

**There is no input to override this.** Between those two rules every branch the workflow targets is covered: the only names in [`projects.json`](../../config/projects.json) that are not `<major>.<minor>.x` are `main`, which the fallback handles, and the `-internal` branches, which this never targets — those are rebased from the OSS branches it does update. An override input would therefore have no correct use, and would be the one way to push a train's versions onto a branch they do not belong on, since a branch given by hand cannot be checked against the version the way a derived one can.
### `-INTERNAL-SNAPSHOT` trains

A `release_train_version` such as `2025.1.4-INTERNAL-SNAPSHOT` targets the long-lived `<major>.<minor>.x-internal` branches of the **commercial** repositories instead (`5.1.0-INTERNAL-SNAPSHOT` → `5.1.x-internal`). There is no fallback: a project without that branch is skipped as `branch-not-found`. The run fails early if `commercial` is not set (or the project names lack `-commercial`), since OSS repos have no `-internal` branches. The properties file read is `2025_1_4-internal-snapshot.properties` on `jenkins-releaser-config`, which must already exist.

**There is no input to override this.** Between those two rules every branch the workflow targets is covered: the only names in [`projects.json`](../../config/projects.json) that are not `<major>.<minor>.x` are `main`, which the fallback handles, and the `-internal` branches, which are reached only through an `-INTERNAL` train (above). An override input would therefore have no correct use, and would be the one way to push a train's versions onto a branch they do not belong on, since a branch given by hand cannot be checked against the version the way a derived one can.

## The raw wait

Expand Down
11 changes: 6 additions & 5 deletions .github/workflows/post-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,10 @@ jobs:
}
console.log(`Found ${entries.length} version entries.`);

// spring-boot lives in the properties file but is not a Spring Cloud repository, so it
// has no tag, milestone or release of ours. It is still bumped in the snapshot file.
const NON_REPO_KEYS = new Set(['spring-boot']);
// spring-boot and spring-vault live in the properties file but are not Spring Cloud
// repositories, so they have no tag, milestone or release of ours. They are still bumped
// in the snapshot file.
const NON_REPO_KEYS = new Set(['spring-boot', 'spring-vault']);

let wanted = null;
if (names.length) {
Expand Down Expand Up @@ -1630,7 +1631,7 @@ jobs:
// ones without access - a row of links that 404 for the audience is worse than
// no links at all. The module and its version are the useful part either way.
const included = entries
.filter(e => e.key !== 'spring-boot' && e.key !== 'spring-cloud-release')
.filter(e => !['spring-boot', 'spring-vault', 'spring-cloud-release'].includes(e.key))
.map(e => commercial
? ` - ${titleCase(e.key)} \`${e.version}\``
: ` - ${titleCase(e.key)} \`${e.version}\` ` +
Expand Down Expand Up @@ -1848,7 +1849,7 @@ jobs:
// Every project in the train, for the OSS site: it documents the versions currently
// being documented, and even a project that did not move needs its snapshot entry
// following the new snapshot train.
const projects = entries.filter(e => e.key !== 'spring-boot');
const projects = entries.filter(e => !['spring-boot', 'spring-vault'].includes(e.key));

// For the commercial site, only the projects with a tag of their own in the
// commercial repo. An oss-fallback entry carries the plain OSS version because that
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/setup-next-release-train.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ jobs:
}
console.log(`Found ${entries.length} version entries.`);

// spring-boot is in the properties file so that every project picks up its version, but
// it is not a Spring Cloud repository and nothing is pushed to it.
const NON_REPO_KEYS = new Set(['spring-boot']);
// spring-boot and spring-vault are in the properties file so that every project picks up
// their versions, but they are not Spring Cloud repositories and nothing is pushed to them.
const NON_REPO_KEYS = new Set(['spring-boot', 'spring-vault']);

let wanted = null;
if (names.length) {
Expand Down
37 changes: 26 additions & 11 deletions .github/workflows/update-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ on:
workflow_dispatch:
inputs:
release_train_version:
description: 'Release train version whose versions should be applied (e.g. 2025.1.3-SNAPSHOT, or 2025.1.2). Must have a matching properties file on the jenkins-releaser-config branch.'
description: 'Release train version whose versions should be applied (e.g. 2025.1.3-SNAPSHOT, 2025.1.2, or 2025.1.4-INTERNAL-SNAPSHOT for the -internal branches of the commercial repos). Must have a matching properties file on the jenkins-releaser-config branch.'
required: true
type: string
commercial:
Expand Down Expand Up @@ -69,6 +69,7 @@ jobs:
commercial: ${{ steps.parse.outputs.commercial }}
release-repo: ${{ steps.parse.outputs.release-repo }}
props-file: ${{ steps.parse.outputs.props-file }}
internal: ${{ steps.parse.outputs.internal }}
steps:
# Only so the inline scripts below can require .github/scripts/.
- uses: actions/checkout@v4
Expand Down Expand Up @@ -103,13 +104,19 @@ jobs:
const fail = (...msg) => { for (const m of msg) console.error(m); process.exit(1); };

// 3 or 4 numeric segments, optionally with a qualifier: 2025.1.3, 2025.1.3-SNAPSHOT,
// 2025.1.0-RC1, 2025.1.2.1. Anything else would resolve to a properties file name
// that cannot exist, so it is rejected here rather than as a confusing 404.
if (!/^\d+(\.\d+){2,3}(-[A-Za-z][\w.]*)?$/.test(trainVersion)) {
// 2025.1.0-RC1, 2025.1.2.1, 2025.1.4-INTERNAL-SNAPSHOT (the qualifier may itself
// contain hyphens). Anything else would resolve to a properties file name that cannot
// exist, so it is rejected here rather than as a confusing 404.
if (!/^\d+(\.\d+){2,3}(-[A-Za-z][\w.]*)*$/.test(trainVersion)) {
fail(`ERROR: release_train_version must be 3 or 4 numeric segments with an optional ` +
`qualifier (e.g. 2025.1.3-SNAPSHOT or 2025.1.2); got '${trainVersion}'.`);
`qualifier (e.g. 2025.1.3-SNAPSHOT, 2025.1.2 or 2025.1.4-INTERNAL-SNAPSHOT); ` +
`got '${trainVersion}'.`);
}

// An -INTERNAL train addresses the <major>.<minor>.x-internal branches, which exist
// only in the commercial repositories.
const internal = /-INTERNAL(-|$)/i.test(trainVersion);

// This job must read exactly the file update-project-versions will read, or it
// would validate one file and apply another.
const propsFile = releaserConfigFileName(trainVersion);
Expand Down Expand Up @@ -140,6 +147,11 @@ jobs:
}
}

if (internal && !commercial) {
fail(`ERROR: ${trainVersion} is an -INTERNAL train, and -internal branches exist only ` +
'in the -commercial repositories. Set commercial, or use -commercial project names.');
}

// Where the config lives, from the shared module - it owns that choice for every
// caller. `commercial` decides only which project repositories are updated.
const releaseRepo = RELEASER_CONFIG_REPO;
Expand All @@ -161,9 +173,9 @@ jobs:
}
console.log(`Found ${entries.length} version entries.`);

// spring-boot is in the properties file so that every project picks up its version, but
// it is not a Spring Cloud repository and nothing is pushed to it.
const NON_REPO_KEYS = new Set(['spring-boot']);
// spring-boot and spring-vault are in the properties file so that every project picks up
// their versions, but they are not Spring Cloud repositories and nothing is pushed to them.
const NON_REPO_KEYS = new Set(['spring-boot', 'spring-vault']);

let wanted = null;
if (names.length) {
Expand Down Expand Up @@ -231,6 +243,7 @@ jobs:

console.log('');
console.log(`commercial: ${commercial}`);
console.log(`internal: ${internal}`);
console.log(`release repo: ${releaseRepo}`);
console.log(`properties file: ${propsFile}`);
console.log('');
Expand All @@ -243,6 +256,7 @@ jobs:
fs.appendFileSync(out, `commercial=${commercial}\n`);
fs.appendFileSync(out, `release-repo=${releaseRepo}\n`);
fs.appendFileSync(out, `props-file=${propsFile}\n`);
fs.appendFileSync(out, `internal=${internal}\n`);
JSEOF

update:
Expand All @@ -265,16 +279,17 @@ jobs:
# ── resolve the branch to work on ────────────────────────────────────────────────
# The branch is always derived from the version in the properties file - there is no
# input to override it. Between the .x derivation and the main fallback the action
# makes, every branch this workflow targets is covered: the only names in projects.json
# that are not <major>.<minor>.x are main, and the -internal branches, which are rebased
# from the OSS branches this updates and so are never targeted here.
# makes, every OSS and commercial release-line branch this workflow targets is covered.
# An -INTERNAL train is the exception: it resolves to <major>.<minor>.x-internal, with
# no fallback, and only in the commercial repos.
- name: Resolve target branch
id: branch
uses: ./.github/actions/resolve-release-branch
with:
repo: ${{ matrix.repo }}
version: ${{ matrix.version }}
commercial: ${{ needs.setup.outputs.commercial }}
internal: ${{ needs.setup.outputs.internal }}
token: ${{ inputs.token || secrets.GH_ACTIONS_REPO_TOKEN }}

- name: Clone project
Expand Down
Loading