Integration #682
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Integration | |
on: | |
workflow_dispatch: | |
inputs: | |
reflector_access_token: | |
description: Access token for use in authenticating as the Reflector bot | |
type: string | |
reflector_user_id: | |
description: User id of the Reflector bot | |
type: string | |
permissions: | |
contents: write | |
pull-requests: write | |
# This is a workaround for the GitHub cli requiring excessive permissions when updating a pull | |
# request (https://github.com/cli/cli/discussions/5307) | |
repository-projects: read | |
jobs: | |
generate-update: | |
concurrency: | |
group: integration | |
cancel-in-progress: true | |
runs-on: ubuntu-22.04 | |
env: | |
INT_BRANCH: integration | |
TARGET_BRANCH: main | |
steps: | |
# Checkout both the target and integration branches | |
- uses: actions/[email protected] | |
with: | |
token: ${{ inputs.reflector_access_token }} | |
fetch-depth: 0 | |
# Attempt to merge target branch in to integration branch. Discarding any generated code on | |
# the integration branch | |
- name: Merge target | |
run: | | |
git config --local user.name "oxide-reflector-bot[bot]" | |
git config --local user.email "${{ inputs.reflector_user_id }}+oxide-reflector-bot[bot]@users.noreply.github.com" | |
MERGE_STATUS=0 | |
git checkout $INT_BRANCH 2>/dev/null || git checkout -b $INT_BRANCH | |
git merge $TARGET_BRANCH || MERGE_STATUS=$? | |
# If there was a merge conflict attempt to reset the generated files and commit them back | |
if [ $MERGE_STATUS -eq 1 ] | |
then | |
echo "Found conflicts. Attempt to use changes from $TARGET_BRANCH" | |
# Reset generated files | |
git checkout $TARGET_BRANCH -- cli/docs/cli.json | |
git checkout $TARGET_BRANCH -- cli/src/generated_cli.rs | |
git checkout $TARGET_BRANCH -- sdk/src/generated_sdk.rs | |
git checkout $TARGET_BRANCH -- sdk-httpmock/src/generated_httpmock.rs | |
# Always reset the Cargo.lock. It will be updated later | |
git checkout $TARGET_BRANCH -- Cargo.lock | |
# Commit the merge | |
git commit -m "Merge branch '$TARGET_BRANCH' into $INT_BRANCH and reset generated code" | |
fi | |
# Ensure there are no outstanding conflicts | |
STATUS=$(git status --porcelain=v1 2>/dev/null | wc -l) | |
if [ $STATUS -eq 0 ] | |
then | |
exit 0 | |
else | |
echo 'Found additional conflicts from merge attempt that need to be manually resolved' | |
git status | |
exit 1 | |
fi | |
# Configure Rust tools | |
- name: Install nightly rustfmt | |
uses: actions-rs/toolchain@v1 | |
with: | |
toolchain: nightly | |
components: rustfmt | |
default: false | |
- name: Report cargo version | |
run: cargo --version | |
- name: Report rustfmt version | |
run: cargo fmt -- --version | |
- name: Update progenitor | |
run: | | |
# Capture the progenitor commit from the target branch | |
git checkout $TARGET_BRANCH | |
FROM=$(cargo tree -i progenitor | grep -Eo '#([0-9a-z]+)') | |
# Perform the update and capture the new commit on the integration branch | |
git checkout $INT_BRANCH | |
cargo update -p https://github.com/oxidecomputer/progenitor | |
TO=$(cargo tree -i progenitor | grep -Eo '#([0-9a-z]+)') | |
echo "from=${FROM:1}" >> $GITHUB_OUTPUT | |
echo "to=${TO:1}" >> $GITHUB_OUTPUT | |
id: progenitor_versions | |
- name: Update schema | |
run: | | |
curl -s https://raw.githubusercontent.com/oxidecomputer/omicron/main/openapi/nexus.json --output oxide.json | |
SHA=$(curl -s "https://api.github.com/repos/oxidecomputer/omicron/commits?path=openapi/nexus.json&per_page=1" | jq -r '.[].sha') | |
echo "full=${SHA}" >> $GITHUB_OUTPUT | |
echo "short=${SHA:0:8}" >> $GITHUB_OUTPUT | |
id: schema_sha | |
- name: Rebuild client | |
run: | | |
cargo run -p xtask generate | |
# Tests are allowed to fail so that a partial PR can at least be generated. This is likely to | |
# occur when updating the oxide.json schema file and new types that are not yet handled by | |
# the CLI are introduced. The result of this should be a PR that has the updated schema and | |
# corresponding generated code that will fail due to compilation errors during test. | |
- name: Generate docs | |
continue-on-error: true | |
run: | | |
EXPECTORATE=overwrite cargo test | |
- name: Report changes | |
run: git status | |
- name: Commit changes | |
run: | | |
git config --local user.name "oxide-reflector-bot[bot]" | |
git config --local user.email "${{ inputs.reflector_user_id }}+oxide-reflector-bot[bot]@users.noreply.github.com" | |
git add . | |
git commit -m "Rebuilt with latest dependency updates" || echo "Nothing to commit" | |
git push origin $INT_BRANCH | |
# Detect changes to report back | |
# Check if the spec file has been updated | |
git diff $TARGET_BRANCH...$INT_BRANCH --quiet oxide.json || specUpdate=$? | |
echo "spec=${specUpdate}" >> $GITHUB_OUTPUT | |
# Check if the generated docs spec file has been updated | |
git diff $TARGET_BRANCH...$INT_BRANCH --quiet cli/docs/cli.json || docsUpdate=$? | |
echo "docs=${docsUpdate}" >> $GITHUB_OUTPUT | |
# Check if anything in the lock file has updated | |
git diff $TARGET_BRANCH...$INT_BRANCH --quiet Cargo.lock || depsUpdate=$? | |
echo "deps=${depsUpdate}" >> $GITHUB_OUTPUT | |
id: committed | |
- name: Update pull request | |
env: | |
GH_TOKEN: ${{ inputs.reflector_access_token }} | |
run: | | |
# Compare the integration branch with the target branch | |
TARGET_TO_INT="$(git rev-list --count $TARGET_BRANCH..$INT_BRANCH)" | |
INT_TO_TARGET="$(git rev-list --count $INT_BRANCH..$TARGET_BRANCH)" | |
CHANGED_FILES_LIST=$(git diff integration..main --name-only) | |
CHANGED_FILES=$(git diff integration..main --name-only | wc -l) | |
HAS_NON_LOCK_CHANGES=1 | |
# Check if there are more changes than just Cargo.lock | |
if [ "$CHANGED_FILES" -gt 1 ] || ([ "$CHANGED_FILES" -eq 1 ] && [ "$CHANGED_FILES_LIST" != "Cargo.lock" ]); then | |
HAS_NON_LOCK_CHANGES=0 | |
fi | |
# Check for an existing pull request from the integration branch to the target branch | |
eval $(gh pr view $INT_BRANCH --repo $GITHUB_REPOSITORY --json url,number,state | jq -r 'to_entries[] | "\(.key | ascii_upcase)=\(.value)"') | |
HASPR=0 | |
[ "$NUMBER" != "" ] && [ "$BASEREFNAME" == "$TARGET_BRANCH" ] || HASPR=$? | |
# When detecting if a PR is needed (or can be closed) we do not actually care about the | |
# distance between the branches. We care about if there are meaningful changes | |
if [ "$CHANGED_FILES" -eq 0 ] | |
then | |
echo "$TARGET_BRANCH is up to date with $INT_BRANCH. No pull request needed" | |
if [ "$HASPR" -eq 0 -a "$NUMBER" != "" ] | |
then | |
echo "Closing existing PR" | |
gh pr close $NUMBER | |
fi | |
elif [ "$TARGET_TO_INT" -gt 0 ] | |
then | |
echo "$TARGET_BRANCH is behind $INT_BRANCH ($TARGET_TO_INT)" | |
title="" | |
echo "" > body | |
if [ ${{ steps.committed.outputs.deps }} ] | |
then | |
title+=" dependencies" | |
echo "Lock file updated" >> body | |
echo "" >> body | |
fi | |
if [ "${{ steps.progenitor_versions.outputs.from }}" != "${{ steps.progenitor_versions.outputs.to }}" ] | |
then | |
if [ title != "" ]; then title+=","; fi | |
title+=" progenitor to ${{ steps.progenitor_versions.outputs.to }}" | |
progenitorDiffUrl="https://github.com/oxidecomputer/progenitor/compare/${{ steps.progenitor_versions.outputs.from }}...${{ steps.progenitor_versions.outputs.to }}" | |
echo "Bump [progenitor](https://github.com/oxidecomputer/progenitor) from \`${{ steps.progenitor_versions.outputs.from }}\` to \`${{ steps.progenitor_versions.outputs.to }}\`" >> body | |
echo "Changes: ${progenitorDiffUrl}" >> body | |
echo "" >> body | |
fi | |
if [ ${{ steps.committed.outputs.spec }} ] | |
then | |
if [ title != "" ]; then title+=","; fi | |
title+=" oxide.json to omicron:${{ steps.schema_sha.outputs.short }}" | |
schemaLabel="nexus.json \`${{ steps.schema_sha.outputs.short }}\`" | |
schemaUrl="https://github.com/oxidecomputer/omicron/blob/${{ steps.schema_sha.outputs.full }}/openapi/nexus.json" | |
echo "Generated code against [$schemaLabel]($schemaUrl)" >> body | |
echo "" >> body | |
fi | |
if [ ${{ steps.committed.outputs.docs }} ] | |
then | |
echo "CLI docs updated against the updated CLI" >> body | |
echo "" >> body | |
fi | |
title="Bump${title}" | |
if [ -z "$NUMBER" -o "$STATE" != "OPEN" ] | |
then | |
# Only create a new PR if the changes between the integration branch and target branch | |
# affect more than the Cargo.lock file | |
if [ "$HAS_NON_LOCK_CHANGES" -eq 0 ] | |
then | |
gh pr create -B $TARGET_BRANCH -H $INT_BRANCH --title "$title" --body-file body | |
else | |
echo "Only integration change is a modification to Cargo.lock. Skipping PR creation" | |
fi | |
else | |
echo "PR already exists: ($NUMBER) $URL . Updating..." | |
gh pr edit "$NUMBER" --title "$title" --body-file body | |
fi | |
else | |
echo "$INT_BRANCH is behind $TARGET_BRANCH ($INT_TO_TARGET). This is likely an error" | |
exit 1 | |
fi |