Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/batch-merge-release-branch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:

- name: Get current release branch
id: get_current_release_branch
uses: PickNikRobotics/moveit_pro_ci/.github/actions/find_release_branch@d8676de7c5e49d274277cf536b6d41fbacf71642 # v0.2.0
uses: PickNikRobotics/moveit_pro_ci/.github/actions/find_release_branch@baf17129fd55f858b7965fb971fa333cf45463cb # v0.3.2

- name: Merge release branch into main
id: merge
Expand Down
228 changes: 225 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,151 @@ on:
description: 'The tag of the image to use for the container'
required: false
default: ''
repository_dispatch:
# Fired by the paired moveit_pro repo when a PR there finishes its image
# push. The payload carries `image_ref` (full GHCR reference with `{0}`
# placeholder for ros_distro), `image_tag`, `base_branch`, `moveit_pro_sha`,
# and `moveit_pro_pr` so this workflow can run the integration suite
# against the just-built image and post a commit status back to that PR.
types: [moveit_pro_pr]
Comment thread
shaur-k marked this conversation as resolved.
# Run every 6 hours Mon-Fri
schedule:
- cron: "0 */6 * * 1-5"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# Dispatch-triggered runs from different moveit_pro PRs all share the same
# `github.ref` (this repo's default branch), so a plain ref-keyed group
# would let one dispatch cancel another and lose its status post-back.
# Include the dispatch payload's `moveit_pro_sha` / `moveit_pro_pr` (and
# for non-dispatch events, the PR head SHA) so each PR gets its own slot.
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.client_payload.moveit_pro_sha || github.event.client_payload.moveit_pro_pr || github.event.pull_request.head.sha || github.ref }}
cancel-in-progress: true

jobs:
integration-test-in-studio-container:
# Compute the inputs we forward to the reusable integration workflow and to
# the rollup status post-back. The logic differs by trigger:
# - `pull_request`: image_tag = PR base ref; checkout uses the PR head
# by default (no explicit `git_ref`). If the PR body contains a
# `needs: moveit_pro/#N` token, also fetch that moveit_pro PR's head
# SHA so we can pull its private GHCR image as `image_ref` and post
# the rollup status back to the moveit_pro PR.
# - `repository_dispatch`: every value comes from the payload, including
# `git_ref` (the version-paired example_ws branch to check out) since
# the dispatch event itself has no PR context.
# - everything else (push, schedule, workflow_dispatch): image_tag = the
# triggering ref's branch name.
resolve:
name: Resolve dispatch context
runs-on: ubuntu-22.04
outputs:
image_ref: ${{ steps.resolve.outputs.image_ref }}
image_tag: ${{ steps.resolve.outputs.image_tag }}
git_ref: ${{ steps.resolve.outputs.git_ref }}
moveit_pro_sha: ${{ steps.resolve.outputs.moveit_pro_sha }}
moveit_pro_pr_number: ${{ steps.resolve.outputs.moveit_pro_pr_number }}
steps:
- name: Detect `needs:` token in PR body
id: detect_needs
if: github.event_name == 'pull_request'
env:
PR_BODY: ${{ github.event.pull_request.body }}
run: |
set -e
matched="$(printf '%s' "$PR_BODY" | grep -oiE 'needs:[[:space:]]*moveit_pro/#[0-9]+' || true)"
if [ -n "$matched" ]; then
pr_num="$(printf '%s' "$matched" | grep -oE '[0-9]+' | head -1)"
echo "moveit_pro_pr=$pr_num" >> "$GITHUB_OUTPUT"
echo "Detected paired moveit_pro PR: #$pr_num"
else
echo "No paired moveit_pro PR in body."
fi

- name: Generate cross-repo App token
id: app-token
if: steps.detect_needs.outputs.moveit_pro_pr != '' || github.event_name == 'repository_dispatch'
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ secrets.SISTER_REPOS_APP_CLIENT_ID }}
private-key: ${{ secrets.SISTER_REPOS_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: |
moveit_pro_example_ws
moveit_pro

- name: Resolve inputs
id: resolve
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
MOVEIT_PRO_PR_FROM_BODY: ${{ steps.detect_needs.outputs.moveit_pro_pr }}
GHCR_URL: ${{ vars.GHCR_URL }}
with:
# Falls back to the default GITHUB_TOKEN when the App token wasn't
# minted (no paired PR, not a dispatch). The default is sufficient
# for everything else this step does.
github-token: ${{ steps.app-token.outputs.token || github.token }}
script: |
const event = context.eventName;
const ghcrUrl = process.env.GHCR_URL || 'ghcr.io/picknikrobotics';
let image_ref = '';
let image_tag = '';
let git_ref = '';
let moveit_pro_sha = '';
let moveit_pro_pr_number = '';

const sanitizeBranch = (s) => s.replace(/[^a-zA-Z0-9._-]/g, '-');

if (event === 'repository_dispatch') {
const p = context.payload.client_payload || {};
image_ref = p.image_ref || '';
image_tag = p.image_tag || p.base_branch || 'main';
git_ref = p.base_branch || '';
moveit_pro_sha = p.moveit_pro_sha || '';
moveit_pro_pr_number = String(p.moveit_pro_pr || '');
} else if (event === 'pull_request') {
image_tag = context.payload.pull_request.base.ref;
const needsPr = process.env.MOVEIT_PRO_PR_FROM_BODY;
if (needsPr) {
const { data: pr } = await github.rest.pulls.get({
owner: 'PickNikRobotics',
repo: 'moveit_pro',
pull_number: parseInt(needsPr, 10),
});
moveit_pro_sha = pr.head.sha;
moveit_pro_pr_number = needsPr;
image_ref = `${ghcrUrl}/moveit-studio:${sanitizeBranch(pr.head.ref)}-{0}-amd64`;
}
Comment thread
shaur-k marked this conversation as resolved.
} else {
// push, schedule, workflow_dispatch
image_tag = (context.ref || 'refs/heads/main').replace(/^refs\/heads\//, '');
}

core.info(`event=${event}`);
core.info(`image_ref=${image_ref}`);
core.info(`image_tag=${image_tag}`);
core.info(`git_ref=${git_ref}`);
core.info(`moveit_pro_sha=${moveit_pro_sha}`);
core.info(`moveit_pro_pr_number=${moveit_pro_pr_number}`);

core.setOutput('image_ref', image_ref);
core.setOutput('image_tag', image_tag);
core.setOutput('git_ref', git_ref);
core.setOutput('moveit_pro_sha', moveit_pro_sha);
core.setOutput('moveit_pro_pr_number', moveit_pro_pr_number);

integration-test:
needs: resolve
strategy:
fail-fast: false
matrix:
# Stream 2 expands this list one PR at a time as each sim gains a
# pytest entry. Today only lab_sim has one.
config_package: [lab_sim]
uses: PickNikRobotics/moveit_pro_ci/.github/workflows/workspace_integration_test.yaml@baf17129fd55f858b7965fb971fa333cf45463cb # v0.3.2
with:
image_tag: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || github.ref_name }}
image_tag: ${{ needs.resolve.outputs.image_tag }}
image_ref: ${{ needs.resolve.outputs.image_ref }}
git_ref: ${{ needs.resolve.outputs.git_ref }}
config_package: ${{ matrix.config_package }}
colcon_test_args: "--executor sequential"
runner: "picknik-16-amd64"
# Coarsen MuJoCo timestep on CI (default 0.002s = 500Hz) so the heavier 3.6.0
Expand All @@ -32,6 +164,96 @@ jobs:
secrets:
moveit_license_key: ${{ secrets.STUDIO_CI_LICENSE_KEY }}

# Single byte-identical commit-status context (`example_ws / integration`)
# is posted from here only. Per the design doc: the matrix jobs must NOT
# post their own statuses, or branch protection / dedup semantics break.
integration-status:
name: Post integration status
needs: [resolve, integration-test]
if: always()
runs-on: ubuntu-22.04
steps:
- name: Generate cross-repo App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ secrets.SISTER_REPOS_APP_CLIENT_ID }}
private-key: ${{ secrets.SISTER_REPOS_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: |
moveit_pro_example_ws
moveit_pro

- name: Post commit status
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
INTEGRATION_RESULT: ${{ needs.integration-test.result }}
MOVEIT_PRO_SHA: ${{ needs.resolve.outputs.moveit_pro_sha }}
EVENT_NAME: ${{ github.event_name }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const result = process.env.INTEGRATION_RESULT;
// Map GitHub Actions job result → Commit Status API state.
// success/failure are obvious; cancelled / skipped map to error
// so reviewers see something other than a stale "pending".
const state = result === 'success' ? 'success'
: (result === 'cancelled' || result === 'skipped') ? 'error'
: 'failure';
const description = ({
success: 'Integration tests passed',
failure: 'Integration tests failed',
cancelled: 'Integration tests cancelled',
skipped: 'Integration tests skipped',
})[result] || `Integration tests result: ${result}`;
// Byte-identical across every post: GitHub deduplicates commit
// statuses by `(sha, context)`, so the dispatch-only run that
// fired against `:main` can be overwritten by the paired run.
// Drift here would break that dedup and leave stale checks.
const contextString = 'example_ws / integration';
const target_url = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;

const targets = [];
// example_ws PR head SHA (when triggered by pull_request).
if (process.env.EVENT_NAME === 'pull_request') {
targets.push({
owner: 'PickNikRobotics',
repo: 'moveit_pro_example_ws',
sha: context.payload.pull_request.head.sha,
label: 'example_ws PR',
});
}
// moveit_pro PR commit SHA (either resolved via `needs:` token in
// an example_ws PR body, or carried by a `repository_dispatch`
// payload from moveit_pro CI).
const moveitProSha = process.env.MOVEIT_PRO_SHA;
if (moveitProSha) {
targets.push({
owner: 'PickNikRobotics',
repo: 'moveit_pro',
sha: moveitProSha,
label: 'moveit_pro PR',
});
}

if (targets.length === 0) {
core.info('No commit-status targets resolved (likely a push / schedule run). Nothing to post.');
return;
}

for (const t of targets) {
core.info(`Posting state=${state} to ${t.label} (${t.owner}/${t.repo}@${t.sha}).`);
await github.rest.repos.createCommitStatus({
owner: t.owner,
repo: t.repo,
sha: t.sha,
state,
context: contextString,
description,
target_url,
});
}

ensure-no-ssh-in-gitmodules:
name: Ensure no SSH URLs in .gitmodules
runs-on: ubuntu-22.04
Expand Down
Loading