Skip to content

Integration

Integration #713

Workflow file for this run

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/checkout@v3.5.0
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