Finalizing Patching End 2 End (#8906)

Co-authored-by: gemini-cli-robot <gemini-cli-robot@google.com>
This commit is contained in:
matt korwel
2025-09-19 05:39:38 -07:00
committed by GitHub
parent f371ecb948
commit 23467cdbdb
3 changed files with 165 additions and 42 deletions

View File

@@ -27,6 +27,11 @@ on:
required: false
type: 'boolean'
default: false
force_skip_tests:
description: 'Select to skip the "Run Tests" step in testing. Prod releases should run tests'
required: false
type: 'boolean'
default: false
jobs:
trigger-patch-release:
@@ -34,7 +39,7 @@ jobs:
runs-on: 'ubuntu-latest'
permissions:
actions: 'write'
contents: 'read'
contents: 'write'
pull-requests: 'write'
steps:
- name: 'Checkout'
@@ -62,5 +67,6 @@ jobs:
GITHUB_REPOSITORY_NAME: '${{ github.event.repository.name }}'
GITHUB_EVENT_NAME: '${{ github.event_name }}'
GITHUB_EVENT_PAYLOAD: '${{ toJSON(github.event) }}'
FORCE_SKIP_TESTS: '${{ github.event.inputs.force_skip_tests }}'
run: |
node scripts/releasing/patch-trigger.js

View File

@@ -191,6 +191,65 @@ Upon merging the pull request, the `Release: Patch (2) Trigger` workflow is auto
This fully automated process ensures that patches are created and released consistently and reliably.
#### Troubleshooting: Older Branch Workflows
**Issue**: If the patch trigger workflow fails with errors like "Resource not accessible by integration" or references to non-existent workflow files (e.g., `patch-release.yml`), this indicates the hotfix branch contains an outdated version of the workflow files.
**Root Cause**: When a PR is merged, GitHub Actions runs the workflow definition from the **source branch** (the hotfix branch), not from the target branch (the release branch). If the hotfix branch was created from an older release branch that predates workflow improvements, it will use the old workflow logic.
**Solutions**:
**Option 1: Manual Trigger (Quick Fix)**
Manually trigger the updated workflow from the branch with the latest workflow code:
```bash
# For a preview channel patch with tests skipped
gh workflow run release-patch-2-trigger.yml --ref <branch-with-updated-workflow> \
--field ref="hotfix/v0.6.0-preview.2/preview/cherry-pick-abc1234" \
--field workflow_ref=<branch-with-updated-workflow> \
--field dry_run=false \
--field force_skip_tests=true
# For a stable channel patch
gh workflow run release-patch-2-trigger.yml --ref <branch-with-updated-workflow> \
--field ref="hotfix/v0.5.1/stable/cherry-pick-abc1234" \
--field workflow_ref=<branch-with-updated-workflow> \
--field dry_run=false \
--field force_skip_tests=false
# Example using main branch (most common case)
gh workflow run release-patch-2-trigger.yml --ref main \
--field ref="hotfix/v0.6.0-preview.2/preview/cherry-pick-abc1234" \
--field workflow_ref=main \
--field dry_run=false \
--field force_skip_tests=true
```
**Note**: Replace `<branch-with-updated-workflow>` with the branch containing the latest workflow improvements (usually `main`, but could be a feature branch if testing updates).
**Option 2: Update the Hotfix Branch**
Merge the latest main branch into your hotfix branch to get the updated workflows:
```bash
git checkout hotfix/v0.6.0-preview.2/preview/cherry-pick-abc1234
git merge main
git push
```
Then close and reopen the PR to retrigger the workflow with the updated version.
**Option 3: Direct Release Trigger**
Skip the trigger workflow entirely and directly run the release workflow:
```bash
# Replace channel and release_ref with appropriate values
gh workflow run release-patch-3-release.yml --ref main \
--field type="preview" \
--field dry_run=false \
--field force_skip_tests=true \
--field release_ref="release/v0.6.0-preview.2"
```
### Docker
We also run a Google cloud build called [release-docker.yml](../.gcp/release-docker.yml). Which publishes the sandbox docker to match your release. This will also be moved to GH and combined with the main release file once service account permissions are sorted out.

View File

@@ -37,6 +37,11 @@ async function main() {
type: 'boolean',
default: false,
})
.option('force-skip-tests', {
description: 'Skip the "Run Tests" step in testing',
type: 'boolean',
default: false,
})
.example(
'$0 --head-ref "hotfix/v0.5.3/preview/cherry-pick-abc1234" --test',
'Test channel detection logic',
@@ -50,15 +55,6 @@ async function main() {
const testMode = argv.test || process.env.TEST_MODE === 'true';
// Initialize GitHub API client only if not in test mode
let github;
if (!testMode) {
const { Octokit } = await import('@octokit/rest');
github = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
}
const context = {
eventName: process.env.GITHUB_EVENT_NAME || 'pull_request',
repo: {
@@ -72,6 +68,8 @@ async function main() {
const headRef = argv.headRef || process.env.HEAD_REF;
const body = argv.prBody || process.env.PR_BODY || '';
const isDryRun = argv.dryRun || body.includes('[DRY RUN]');
const forceSkipTests =
argv.forceSkipTests || process.env.FORCE_SKIP_TESTS === 'true';
if (!headRef) {
throw new Error(
@@ -131,21 +129,26 @@ async function main() {
// Try to find the original PR that requested this patch
let originalPr = null;
if (!testMode && github) {
if (!testMode) {
try {
console.log('Looking for original PR using search...');
// Use GitHub search to find the PR with a comment referencing the hotfix branch.
// This is much more efficient than listing PRs and their comments.
const query = `repo:${context.repo.owner}/${context.repo.repo} is:pr is:all in:comments "Patch PR Created" "${headRef}"`;
const searchResults = await github.rest.search.issuesAndPullRequests({
q: query,
sort: 'updated',
order: 'desc',
per_page: 1,
});
const { execFileSync } = await import('node:child_process');
if (searchResults.data.items.length > 0) {
originalPr = searchResults.data.items[0].number;
// Use gh CLI to search for PRs with comments referencing the hotfix branch
const query = `repo:${context.repo.owner}/${context.repo.repo} is:pr is:all in:comments "Patch PR Created" "${headRef}"`;
const result = execFileSync(
'gh',
['search', 'prs', '--json', 'number,title', '--limit', '1', query],
{
encoding: 'utf8',
env: { ...process.env, GH_TOKEN: process.env.GITHUB_TOKEN },
},
);
const searchResults = JSON.parse(result);
if (searchResults && searchResults.length > 0) {
originalPr = searchResults[0].number;
console.log(`Found original PR: #${originalPr}`);
} else {
console.log('Could not find a matching original PR via search.');
@@ -160,23 +163,45 @@ async function main() {
// Trigger the release workflow
console.log(`Triggering release workflow: ${workflowId}`);
if (!testMode && github) {
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: workflowId,
ref: 'main',
inputs: {
type: channel,
dry_run: isDryRun.toString(),
release_ref: releaseRef,
original_pr: originalPr ? originalPr.toString() : '',
},
});
if (!testMode) {
try {
const { execFileSync } = await import('node:child_process');
const args = [
'workflow',
'run',
workflowId,
'--ref',
'main',
'--field',
`type=${channel}`,
'--field',
`dry_run=${isDryRun.toString()}`,
'--field',
`force_skip_tests=${forceSkipTests.toString()}`,
'--field',
`release_ref=${releaseRef}`,
'--field',
originalPr ? `original_pr=${originalPr.toString()}` : 'original_pr=',
];
console.log(`Running command: gh ${args.join(' ')}`);
execFileSync('gh', args, {
stdio: 'inherit',
env: { ...process.env, GH_TOKEN: process.env.GITHUB_TOKEN },
});
console.log('✅ Workflow dispatch completed successfully');
} catch (e) {
console.error('❌ Failed to dispatch workflow:', e.message);
throw e;
}
} else {
console.log('✅ Would trigger workflow with inputs:', {
type: channel,
dry_run: isDryRun.toString(),
force_skip_tests: forceSkipTests.toString(),
release_ref: releaseRef,
original_pr: originalPr ? originalPr.toString() : '',
});
@@ -200,13 +225,46 @@ async function main() {
**🔗 Track Progress:**
- [View release workflow](https://github.com/${context.repo.owner}/${context.repo.repo}/actions)`;
if (!testMode && github) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: originalPr,
body: commentBody,
});
if (!testMode) {
let tempDir;
try {
const { execFileSync } = await import('node:child_process');
const { writeFileSync, mkdtempSync } = await import('node:fs');
const { join } = await import('node:path');
const { tmpdir } = await import('node:os');
// Create secure temporary directory and file
tempDir = mkdtempSync(join(tmpdir(), 'patch-trigger-'));
const tempFile = join(tempDir, 'comment.md');
writeFileSync(tempFile, commentBody);
execFileSync(
'gh',
['pr', 'comment', originalPr.toString(), '--body-file', tempFile],
{
stdio: 'inherit',
env: { ...process.env, GH_TOKEN: process.env.GITHUB_TOKEN },
},
);
console.log('✅ Comment posted successfully');
} catch (e) {
console.error('❌ Failed to post comment:', e.message);
// Don't throw here since the main workflow dispatch succeeded
} finally {
// Clean up temp directory and all its contents
if (tempDir) {
try {
const { rmSync } = await import('node:fs');
rmSync(tempDir, { recursive: true, force: true });
} catch (cleanupError) {
console.warn(
'⚠️ Failed to clean up temp directory:',
cleanupError.message,
);
}
}
}
} else {
console.log('✅ Would post comment:', commentBody);
}