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
31 changes: 9 additions & 22 deletions .github/workflows/update-semconv-integration-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ jobs:
- name: Pick integration branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/gh/specs/pick-branch/cli.mjs --spec=semconv
run: node scripts/gh/specs/pick-branch/cli.mjs --spec semconv

- name: Checkout or create branch
run: |
if ! git ls-remote --exit-code --heads origin $BRANCH; then
git checkout -b $BRANCH origin/main
git push -u origin $BRANCH
if ! git ls-remote --exit-code --heads origin "$BRANCH"; then
git checkout -b "$BRANCH" origin/main
git push -u origin "$BRANCH"
else
git checkout $BRANCH
git checkout "$BRANCH"
fi

- name: Use CLA approved github bot
Expand All @@ -69,8 +69,8 @@ jobs:
git submodule update --init content-modules/${{ env.REPO }}
cd content-modules/${{ env.REPO }}

if git ls-remote --exit-code --tags origin $VERSION; then
git reset --hard $VERSION
if git ls-remote --exit-code --tags origin "$VERSION"; then
git reset --hard "$VERSION"
tag_exists=true
else
git reset --hard origin/main
Expand Down Expand Up @@ -113,24 +113,11 @@ jobs:
git push
fi

- name: Create pull request if needed
- name: Create or finalize pull request
env:
# not using secrets.GITHUB_TOKEN since pull requests from that token do not run workflows
GH_TOKEN: ${{ steps.otelbot-token.outputs.token }}
run: |
prs=$(gh pr list --state open --head $BRANCH)
if [ -z "$prs" ]; then
if [ -z "$(git rev-list origin/main..HEAD)" ]; then
# Bootstrap this long-lived integration branch with an empty
# commit so that PR creation succeeds (gh pr create fails when
# there are no commits between main and the branch).
git commit --allow-empty -m "Trigger PR creation for $BRANCH"
git push
fi
gh pr create --title "DRAFT Update ${{ env.REPO }} to unreleased $VERSION-dev" \
--body "This is a draft PR used for identifying issues integrating the latest (unreleased) [${{ env.REPO }}](https://github.com/open-telemetry/${{ env.REPO }})." \
--draft
fi
run: scripts/gh/specs/create-or-finalize-pr.mjs --spec semconv

report-failure:
needs: update-semconv-integration-branch
Expand Down
31 changes: 9 additions & 22 deletions .github/workflows/update-spec-integration-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ jobs:
- name: Pick integration branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/gh/specs/pick-branch/cli.mjs --spec=otel
run: node scripts/gh/specs/pick-branch/cli.mjs --spec otel

- name: Checkout or create branch
run: |
if ! git ls-remote --exit-code --heads origin $BRANCH; then
git checkout -b $BRANCH origin/main
git push -u origin $BRANCH
if ! git ls-remote --exit-code --heads origin "$BRANCH"; then
git checkout -b "$BRANCH" origin/main
git push -u origin "$BRANCH"
else
git checkout $BRANCH
git checkout "$BRANCH"
fi

- name: Use CLA approved github bot
Expand All @@ -68,8 +68,8 @@ jobs:
git submodule update --init content-modules/${{ env.REPO }}
cd content-modules/${{ env.REPO }}

if git ls-remote --exit-code --tags origin $VERSION; then
git reset --hard $VERSION
if git ls-remote --exit-code --tags origin "$VERSION"; then
git reset --hard "$VERSION"
tag_exists=true
else
git reset --hard origin/main
Expand Down Expand Up @@ -110,24 +110,11 @@ jobs:
git push
fi

- name: Create pull request if needed
- name: Create or finalize pull request
env:
# not using secrets.GITHUB_TOKEN since pull requests from that token do not run workflows
GH_TOKEN: ${{ steps.otelbot-token.outputs.token }}
run: |
prs=$(gh pr list --state open --head $BRANCH)
if [ -z "$prs" ]; then
if [ -z "$(git rev-list origin/main..HEAD)" ]; then
# Bootstrap this long-lived integration branch with an empty
# commit so that PR creation succeeds (gh pr create fails when
# there are no commits between main and the branch).
git commit --allow-empty -m "Trigger PR creation for $BRANCH"
git push
fi
gh pr create --title "DRAFT Update ${{ env.REPO }} to unreleased $VERSION-dev" \
--body "This is a draft PR used for identifying issues integrating the latest (unreleased) [${{ env.REPO }}](https://github.com/open-telemetry/${{ env.REPO }})." \
--draft
fi
run: scripts/gh/specs/create-or-finalize-pr.mjs --spec otel

report-failure:
needs: update-integration-branch
Expand Down
73 changes: 42 additions & 31 deletions content/en/site/build/ci-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,11 @@ usage lives in the [localization guide][localization-auto-merge].

## Spec integration branches {#spec-integration-branches}

Two scheduled workflows track unreleased changes from upstream spec repositories
and keep a draft PR ("integration branch") current with the next development
version:
Two scheduled workflows own the site's update cycle for the upstream spec
repositories (which `auto-update-versions.yml` therefore excludes): between
releases, each workflow tracks unreleased upstream changes through a draft PR
("integration branch"); once upstream releases, it finalizes that branch and PR
into the release PR.

| Workflow file | Upstream repository | Branch slug |
| ----------------------------------------- | ----------------------------- | ----------- |
Expand All @@ -397,42 +399,51 @@ version:
[update-semconv-integration-branch.yml]:
https://github.com/open-telemetry/opentelemetry.io/blob/main/.github/workflows/update-semconv-integration-branch.yml

Both workflows delegate the "pick the next version + branch" step to a shared
Both workflows delegate the "pick the mode, version and branch" step to a shared
Node helper, [scripts/gh/specs/pick-branch/cli.mjs][]. The helper:

- Reuses an existing `otelbot/<slug>-integration-vX.Y.Z-dev` branch when one
exists and the version has not yet been released; otherwise bumps the latest
release tag's minor version.
- Writes `VERSION` and `BRANCH` to `$GITHUB_ENV` for downstream steps.
- Selects the run's `MODE`: `dev` while the version pinned on main is the latest
upstream release, `release` once a newer release exists.
- Writes `MODE`, `VERSION` and `BRANCH` to `$GITHUB_ENV` for downstream steps.
- Opens a tracking issue (label `<slug>-integration-warning`, deduplicated) when
it detects problems such as multiple stale integration branches.

[scripts/gh/specs/pick-branch/cli.mjs]:
https://github.com/open-telemetry/opentelemetry.io/tree/main/scripts/gh/specs/pick-branch

The final step, [scripts/gh/specs/create-or-finalize-pr.mjs][], creates or
finalizes the PR as `MODE` calls for: in dev mode it opens the draft integration
PR if missing; in release mode it creates or finalizes the release PR. It only
rewrites PR text that the automation itself wrote: a maintainer-edited title or
body is left alone.

[scripts/gh/specs/create-or-finalize-pr.mjs]:
https://github.com/open-telemetry/opentelemetry.io/blob/main/scripts/gh/specs/create-or-finalize-pr.mjs

### Run modes

The helper auto-selects between dry-run and write mode and prints a `[mode]`
banner explaining its choice:
Both helpers auto-select between dry-run and write mode and print a `[mode]`
banner explaining their choice:

| Context | Default behavior | Override |
| --------------------- | ---------------- | ------------------- |
| GitHub Actions | write | pass `--dry-run` |
| Local (anywhere else) | dry-run | pass `--no-dry-run` |

Locally, dry-run still runs all read-only `git`/`gh` commands (so the issue
deduplication check executes), but skips writes. With `--no-dry-run` the helper
uses your local `gh` credentials; if `GITHUB_ENV` is unset, `VERSION`/`BRANCH`
are printed to stdout only. Try it:
deduplication check executes), but skips writes. With `--no-dry-run` the helpers
use your local `gh` credentials; if `GITHUB_ENV` is unset, pick-branch prints
`MODE`/`VERSION`/`BRANCH` to stdout only — export these to feed a local
create-or-finalize-pr run. Try it:

```sh
node scripts/gh/specs/pick-branch/cli.mjs --spec=otel
node scripts/gh/specs/pick-branch/cli.mjs --spec=semconv --no-dry-run
node scripts/gh/specs/pick-branch/cli.mjs --help
node scripts/gh/specs/pick-branch/cli.mjs --spec otel
node scripts/gh/specs/pick-branch/cli.mjs --spec semconv --no-dry-run
scripts/gh/specs/create-or-finalize-pr.mjs --help
```

Pure logic and CLI argument parsing live in `index.mjs` and are covered by
`*.test.mjs` files in the same folder (`npm run test:local-tools` to run them).
Pure logic lives in each helper's `index.mjs` and is covered by `*.test.mjs`
files in the same folder (`npm run test:local-tools` to run them).

## Workflow failure reporting {#workflow-failure-reporting}

Expand All @@ -451,19 +462,19 @@ logic lives in [scripts/gh/report-failure/][report-failure-script]

The repository includes several other workflows:

| Workflow | Purpose |
| -------------------------- | -------------------------------------------------------------------------------------- |
| `check-links.yml` | Sharded link checking using htmltest, plus a non-blocking [Lychee][lychee-pilot] pilot |
| `check-text.yml` | Textlint terminology checks |
| `check-i18n.yml` | Localization front matter validation |
| `check-spelling.yml` | Spell checking |
| `test.yml` | Test (excludes `test:base`) |
| `auto-update-registry.yml` | Auto-update registry package versions |
| `auto-update-versions.yml` | Auto-update OTel component versions |
| `build-dev.yml` | Development build and preview |
| `lint-scripts.yml` | ShellCheck linting for `.github/scripts/` |
| `label-manager.yml` | PR labels (component labels & approval flow) |
| `component-owners.yml` | Assign reviewers based on component ownership |
| Workflow | Purpose |
| -------------------------- | -------------------------------------------------------------------------------------------- |
| `check-links.yml` | Sharded link checking using htmltest, plus a non-blocking [Lychee][lychee-pilot] pilot |
| `check-text.yml` | Textlint terminology checks |
| `check-i18n.yml` | Localization front matter validation |
| `check-spelling.yml` | Spell checking |
| `test.yml` | Test (excludes `test:base`) |
| `auto-update-registry.yml` | Auto-update registry package versions |
| `auto-update-versions.yml` | Auto-update OTel component versions (except [spec repositories](#spec-integration-branches)) |
| `build-dev.yml` | Development build and preview |
| `lint-scripts.yml` | ShellCheck linting for `.github/scripts/` |
| `label-manager.yml` | PR labels (component labels & approval flow) |
| `component-owners.yml` | Assign reviewers based on component ownership |

<!-- prettier-ignore-start -->
[lychee-pilot]: ../npm-scripts/#notes
Expand Down
6 changes: 0 additions & 6 deletions scripts/auto-update/all-versions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,9 @@ function auto_update_versions() {
contrib content/en/docs/languages/java/_index.md"
"opentelemetry-android
ot-android content/en/docs/platforms/client-apps/android.md"
"opentelemetry-specification
spec scripts/content-modules/adjust-pages.pl
spec .gitmodules"
"opentelemetry-proto
otlp scripts/content-modules/adjust-pages.pl
otlp .gitmodules"
"semantic-conventions
semconv scripts/content-modules/adjust-pages.pl
semconv .gitmodules"
"semantic-conventions-java
semconv content/en/docs/languages/java/_index.md"
"opentelemetry-configuration
Expand Down
105 changes: 105 additions & 0 deletions scripts/gh/specs/create-or-finalize-pr.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/usr/bin/env node
// Create or finalize the integration-branch PR of a spec workflow, according
// to the MODE picked by ./pick-branch. Run with --help for usage.

import { spawnSync } from 'node:child_process';

import { parseCliArgs, SPECS } from './pick-branch/index.mjs';
import {
createOrFinalizePullRequest,
readEnvInputs,
} from './create-or-finalize-pr/index.mjs';

main();

function main() {
let parsed;
try {
parsed = parseCliArgs(process.argv.slice(2));
} catch (err) {
fatal(`${err.message}\n\n${cliUsage()}`);
}

if (parsed.help) {
process.stdout.write(`${cliUsage()}\n`);
process.exit(0);
}

const { spec, dryRun, dryRunReason } = parsed;
const { repo, abbr } = SPECS[spec];

let inputs;
try {
inputs = readEnvInputs(process.env, { abbr });
} catch (err) {
fatal(`${err.message}\n\n${cliUsage()}`);
}
const { mode, version, branch } = inputs;

console.log(
`[mode] ${dryRun ? 'DRY-RUN' : 'WRITE'} (reason: ${dryRunReason}; spec=${spec})`,
);
console.log(`[input] MODE: ${mode}; VERSION: ${version}; BRANCH: ${branch}`);

const outcome = createOrFinalizePullRequest({
mode,
repo,
version,
branch,
dryRun,
runGh: (args) => run('gh', args),
runGit: (args) => run('git', args),
});

console.log(`[done] outcome: ${outcome}${dryRun ? ' (dry-run)' : ''}`);
}

/**
* Run a command synchronously, returning `{ stdout, status }` so the pure
* helper can branch on exit code without throwing. Stderr passes through to
* the workflow log.
*
* @param {string} cmd
* @param {string[]} args
* @returns {{ stdout: string, status: number }}
*/
function run(cmd, args) {
const result = spawnSync(cmd, args, {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'inherit'],
});
return { stdout: result.stdout ?? '', status: result.status ?? 1 };
}

function fatal(msg) {
process.stderr.write(`${msg}\n`);
process.exit(1);
}

/** Help text for `--help`. */
function cliUsage() {
return [
'Create or finalize the pull request of an integration-branch workflow',
'run. In dev mode, open the draft integration PR if none exists; in',
'release mode, create the release PR, promote the existing draft, or',
're-sync the title of an already-final PR.',
'',
'Usage: scripts/gh/specs/create-or-finalize-pr.mjs \\',
' [--spec <otel|semconv>] [--[no-]dry-run]',
'',
'Options:',
' -s, --spec <otel|semconv> Selects the upstream spec (default: otel).',
' --dry-run Skip writes (default when run locally).',
' --no-dry-run Perform writes (default under GitHub Actions).',
' -h, --help Show this help.',
'',
'Environment:',
' MODE, VERSION, BRANCH As written to $GITHUB_ENV by pick-branch;',
' strictly validated (set manually for local runs).',
' GH_TOKEN Used by `gh`; needs PR write access when writes',
' are enabled.',
'',
'Expects the integration branch to be checked out, with origin/main',
'fetched (for the branch-has-commits check).',
].join('\n');
}
Loading