Skip to content

Command

Command #202

Workflow file for this run

name: Command
on:
issue_comment: # listen for comments on issues
types: [created]
permissions: # allow the action to comment on the PR
contents: write
issues: write
pull-requests: write
actions: read
jobs:
is-org-member:
if: startsWith(github.event.comment.body, '/cmd')
runs-on: ubuntu-latest
outputs:
member: ${{ steps.is-member.outputs.result }}
steps:
- name: Generate token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CMD_BOT_APP_ID }}
private-key: ${{ secrets.CMD_BOT_APP_KEY }}
- name: Check if user is a member of the organization
id: is-member
uses: actions/github-script@v7
with:
github-token: ${{ steps.generate_token.outputs.token }}
result-encoding: string
script: |
const fs = require("fs");
try {
const org = '${{ github.event.repository.owner.login }}';
const username = '${{ github.event.comment.user.login }}';
const membership = await github.rest.orgs.checkMembershipForUser({
org: org,
username: username
});
console.log(membership, membership.status, membership.status === 204);
if (membership.status === 204) {
return 'true';
} else {
console.log(membership);
fs.appendFileSync(process.env["GITHUB_STEP_SUMMARY"], `${membership.data && membership.data.message || 'Unknown error happened, please check logs'}`);
}
} catch (error) {
console.log(error)
}
return 'false';
reject-non-members:
needs: is-org-member
if: ${{ startsWith(github.event.comment.body, '/cmd') && needs.is-org-member.outputs.member != 'true' }}
runs-on: ubuntu-latest
steps:
- name: Add reaction to rejected comment
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.reactions.createForIssueComment({
comment_id: ${{ github.event.comment.id }},
owner: context.repo.owner,
repo: context.repo.repo,
content: 'confused'
})
- name: Comment PR (Rejected)
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Sorry, only members of the organization ${{ github.event.repository.owner.login }} members can run commands.`
})
acknowledge:
needs: is-org-member
if: ${{ startsWith(github.event.comment.body, '/cmd') && needs.is-org-member.outputs.member == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Add reaction to triggered comment
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.reactions.createForIssueComment({
comment_id: ${{ github.event.comment.id }},
owner: context.repo.owner,
repo: context.repo.repo,
content: 'eyes'
})
clean:
needs: is-org-member
runs-on: ubuntu-latest
steps:
- name: Clean previous comments
if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--clean') && needs.is-org-member.outputs.member == 'true' }}
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo
}).then(comments => {
for (let comment of comments.data) {
console.log(comment)
if (
${{ github.event.comment.id }} !== comment.id &&
(
(
(
comment.body.startsWith('Command') ||
comment.body.startsWith('<details><summary>Command') ||
comment.body.startsWith('Sorry, only ')
) && comment.user.type === 'Bot'
) ||
(comment.body.startsWith('/cmd') && comment.user.login === context.actor)
)
) {
github.rest.issues.deleteComment({
comment_id: comment.id,
owner: context.repo.owner,
repo: context.repo.repo
})
}
}
})
help:
needs: [clean, is-org-member]
if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--help') && needs.is-org-member.outputs.member == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get command
uses: actions-ecosystem/action-regex-match@v2
id: get-pr-comment
with:
text: ${{ github.event.comment.body }}
regex: "^(\\/cmd )([-\\/\\s\\w.=:]+)$" # see explanation in docs/contributor/commands-readme.md#examples
- name: Save output of help
id: help
env:
CMD: ${{ steps.get-pr-comment.outputs.group2 }} # to avoid "" around the command
run: |
python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt
echo 'help<<EOF' >> $GITHUB_OUTPUT
python3 .github/scripts/cmd/cmd.py $CMD >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
- name: Comment PR (Help)
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `<details><summary>Command help:</summary>
\`\`\`
${{ steps.help.outputs.help }}
\`\`\`
</details>`
})
- name: Add confused reaction on failure
uses: actions/github-script@v7
if: ${{ failure() }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.reactions.createForIssueComment({
comment_id: ${{ github.event.comment.id }},
owner: context.repo.owner,
repo: context.repo.repo,
content: 'confused'
})
- name: Add πŸ‘ reaction on success
uses: actions/github-script@v7
if: ${{ !failure() }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.reactions.createForIssueComment({
comment_id: ${{ github.event.comment.id }},
owner: context.repo.owner,
repo: context.repo.repo,
content: '+1'
})
set-image:
needs: [clean, is-org-member]
if: ${{ startsWith(github.event.comment.body, '/cmd') && !contains(github.event.comment.body, '--help') && needs.is-org-member.outputs.member == 'true' }}
runs-on: ubuntu-latest
outputs:
IMAGE: ${{ steps.set-image.outputs.IMAGE }}
RUNNER: ${{ steps.set-image.outputs.RUNNER }}
steps:
- name: Checkout
uses: actions/checkout@v4
- id: set-image
run: |
BODY=$(echo "${{ github.event.comment.body }}" | xargs)
IMAGE_OVERRIDE=$(echo $BODY | grep -oe 'docker.io/paritytech/ci-unified:.*\s' | xargs)
cat .github/env >> $GITHUB_OUTPUT
if [ -n "$IMAGE_OVERRIDE" ]; then
IMAGE=$IMAGE_OVERRIDE
echo "IMAGE=$IMAGE" >> $GITHUB_OUTPUT
fi
if [[ $BODY == "/cmd bench"* ]]; then
echo "RUNNER=parity-weights" >> $GITHUB_OUTPUT
elif [[ $BODY == "/cmd update-ui"* ]]; then
echo "RUNNER=parity-large" >> $GITHUB_OUTPUT
else
echo "RUNNER=ubuntu-latest" >> $GITHUB_OUTPUT
fi
- name: Print outputs
run: |
echo "RUNNER=${{ steps.set-image.outputs.RUNNER }}"
echo "IMAGE=${{ steps.set-image.outputs.IMAGE }}"
# Get PR branch name, because the issue_comment event does not contain the PR branch name
get-pr-branch:
needs: [set-image]
runs-on: ubuntu-latest
outputs:
pr-branch: ${{ steps.get-pr.outputs.pr_branch }}
repo: ${{ steps.get-pr.outputs.repo }}
steps:
- name: Check if the issue is a PR
id: check-pr
run: |
if [ -n "${{ github.event.issue.pull_request.url }}" ]; then
echo "This is a pull request comment"
else
echo "This is not a pull request comment"
exit 1
fi
- name: Get PR Branch Name and Repo
if: steps.check-pr.outcome == 'success'
id: get-pr
uses: actions/github-script@v7
with:
script: |
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
const prBranch = pr.data.head.ref;
const repo = pr.data.head.repo.full_name;
console.log(prBranch, repo)
core.setOutput('pr_branch', prBranch);
core.setOutput('repo', repo);
- name: Use PR Branch Name and Repo
run: |
echo "The PR branch is ${{ steps.get-pr.outputs.pr_branch }}"
echo "The repository is ${{ steps.get-pr.outputs.repo }}"
cmd:
needs: [set-image, get-pr-branch]
env:
JOB_NAME: "cmd"
runs-on: ${{ needs.set-image.outputs.RUNNER }}
container:
image: ${{ needs.set-image.outputs.IMAGE }}
timeout-minutes: 1440 # 24 hours per runtime
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: ${{ needs.get-pr-branch.outputs.repo }}
ref: ${{ needs.get-pr-branch.outputs.pr-branch }}
- name: Get command
uses: actions-ecosystem/action-regex-match@v2
id: get-pr-comment
with:
text: ${{ github.event.comment.body }}
regex: "^(\\/cmd )([-\\/\\s\\w.=:]+)$" # see explanation in docs/contributor/commands-readme.md#examples
# In order to run prdoc without specifying the PR number, we need to add the PR number as an argument automatically
- name: Prepare PR Number argument
id: pr-arg
run: |
CMD="${{ steps.get-pr-comment.outputs.group2 }}"
if echo "$CMD" | grep -q "prdoc" && ! echo "$CMD" | grep -qE "\-\-pr[[:space:]=][0-9]+"; then
echo "arg=--pr ${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
else
echo "arg=" >> $GITHUB_OUTPUT
fi
- name: Build workflow link
if: ${{ !contains(github.event.comment.body, '--quiet') }}
id: build-link
run: |
# Get exactly the CMD job link, filtering out the other jobs
jobLink=$(curl -s \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs | jq '.jobs[] | select(.name | contains("${{ env.JOB_NAME }}")) | .html_url')
runLink=$(curl -s \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }} | jq '.html_url')
echo "job_url=${jobLink}"
echo "run_url=${runLink}"
echo "job_url=$jobLink" >> $GITHUB_OUTPUT
echo "run_url=$runLink" >> $GITHUB_OUTPUT
- name: Comment PR (Start)
# No need to comment on prdoc start or if --quiet
if: ${{ !contains(github.event.comment.body, '--quiet') && !contains(github.event.comment.body, 'prdoc') }}
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
let job_url = ${{ steps.build-link.outputs.job_url }}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has started πŸš€ [See logs here](${job_url})`
})
- name: Install dependencies for bench
if: startsWith(steps.get-pr-comment.outputs.group2, 'bench')
run: |
cargo install subweight --locked
cargo install --path substrate/utils/frame/omni-bencher --locked
- name: Run cmd
id: cmd
env:
CMD: ${{ steps.get-pr-comment.outputs.group2 }} # to avoid "" around the command
PR_ARG: ${{ steps.pr-arg.outputs.arg }}
run: |
echo "Running command: '$CMD $PR_ARG' on '${{ needs.set-image.outputs.RUNNER }}' runner, container: '${{ needs.set-image.outputs.IMAGE }}'"
echo "RUST_NIGHTLY_VERSION: $RUST_NIGHTLY_VERSION"
# Fixes "detected dubious ownership" error in the ci
git config --global --add safe.directory '*'
git remote -v
cat /proc/cpuinfo
python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt
python3 .github/scripts/cmd/cmd.py $CMD $PR_ARG
git status
git diff
if [ -f /tmp/cmd/command_output.log ]; then
CMD_OUTPUT=$(cat /tmp/cmd/command_output.log)
# export to summary to display in the PR
echo "$CMD_OUTPUT" >> $GITHUB_STEP_SUMMARY
# should be multiline, otherwise it captures the first line only
echo 'cmd_output<<EOF' >> $GITHUB_OUTPUT
echo "$CMD_OUTPUT" >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
fi
- name: Upload command output
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: command-output
path: /tmp/cmd/command_output.log
# Generate token for commit, as the earlier token expires after 1 hour, while cmd can take longer
- name: Generate token for commit
uses: actions/create-github-app-token@v1
id: generate_token_commit
with:
app-id: ${{ secrets.CMD_BOT_APP_ID }}
private-key: ${{ secrets.CMD_BOT_APP_KEY }}
- name: Get GitHub App User ID
id: get-user-id
run: echo "user-id=$(gh api "/users/${{ steps.generate_token_commit.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ steps.generate_token_commit.outputs.token }}
- name: Commit changes
run: |
# Allow some time for token propagation
sleep 5
if [ -n "$(git status --porcelain)" ]; then
git config --global user.name '${{ steps.generate_token_commit.outputs.app-slug }}[bot]'
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate_token_commit.outputs.app-slug }}[bot]@users.noreply.github.com>'
git config --global pull.rebase false
TOKEN="${{ steps.generate_token_commit.outputs.token }}"
REPO="${{ needs.get-pr-branch.outputs.repo }}"
BRANCH="${{ needs.get-pr-branch.outputs.pr-branch }}"
# Remove existing remote if any
git remote remove github 2>/dev/null || true
# Add remote with explicit token
git remote add github "https://x-access-token:${TOKEN}@github.com/${REPO}.git"
# Stage changes
git add .
git restore --staged Cargo.lock # ignore changes in Cargo.lock
# Commit changes
git commit -m "Update from ${{ github.actor }} running command '${{ steps.get-pr-comment.outputs.group2 }}'" || {
echo "No changes to commit"
exit 0
}
# Function to push with retries
push_with_retries() {
max_attempts=3
attempt=1
while [ $attempt -le $max_attempts ]; do
echo "Push attempt $attempt of $max_attempts"
if git push github "HEAD:${BRANCH}"; then
echo "Push successful"
return 0
else
echo "Push failed on attempt $attempt"
if [ $attempt -lt $max_attempts ]; then
echo "Trying to rebase..."
if ! git pull --rebase github "${BRANCH}"; then
echo "Rebase failed, retrying from scratch"
git rebase --abort || true
fi
sleep 5
fi
fi
attempt=$((attempt + 1))
done
echo "Failed to push after $max_attempts attempts"
return 1
}
# Attempt the push with retries
if ! push_with_retries; then
echo "All push attempts failed"
exit 1
fi
else
echo "No changes to commit"
fi
- name: Run Subweight
id: subweight
if: startsWith(steps.get-pr-comment.outputs.group2, 'bench')
shell: bash
run: |
git fetch
result=$(subweight compare commits \
--path-pattern "./**/weights/**/*.rs,./**/weights.rs" \
--method asymptotic \
--format markdown \
--no-color \
--change added changed \
--ignore-errors \
refs/remotes/origin/master refs/heads/${{ needs.get-pr-branch.outputs.pr-branch }})
# Save the multiline result to the output
{
echo "result<<EOF"
echo "$result"
echo "EOF"
} >> $GITHUB_OUTPUT
- name: Comment PR (End)
# No need to comment on prdoc success or --quiet
if: ${{ !failure() && !contains(github.event.comment.body, '--quiet') && !contains(github.event.comment.body, 'prdoc') }}
uses: actions/github-script@v7
env:
SUBWEIGHT: "${{ steps.subweight.outputs.result }}"
CMD_OUTPUT: "${{ steps.cmd.outputs.cmd_output }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
let runUrl = ${{ steps.build-link.outputs.run_url }}
let subweight = process.env.SUBWEIGHT;
let cmdOutput = process.env.CMD_OUTPUT;
console.log(cmdOutput);
let subweightCollapsed = subweight.trim() !== ''
? `<details>\n\n<summary>Subweight results:</summary>\n\n${subweight}\n\n</details>`
: '';
let cmdOutputCollapsed = cmdOutput.trim() !== ''
? `<details>\n\n<summary>Command output:</summary>\n\n${cmdOutput}\n\n</details>`
: '';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has finished βœ… [See logs here](${runUrl})${subweightCollapsed}${cmdOutputCollapsed}`
})
- name: Comment PR (Failure)
if: ${{ failure() && !contains(github.event.comment.body, '--quiet') }}
uses: actions/github-script@v7
env:
CMD_OUTPUT: "${{ steps.cmd.outputs.cmd_output }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
let jobUrl = ${{ steps.build-link.outputs.job_url }}
let cmdOutput = process.env.CMD_OUTPUT;
let cmdOutputCollapsed = cmdOutput.trim() !== ''
? `<details>\n\n<summary>Command output:</summary>\n\n${cmdOutput}\n\n</details>`
: '';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has failed ❌! [See logs here](${jobUrl})${cmdOutputCollapsed}`
})
- name: Add πŸ˜• reaction on failure
uses: actions/github-script@v7
if: ${{ failure() }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.reactions.createForIssueComment({
comment_id: ${{ github.event.comment.id }},
owner: context.repo.owner,
repo: context.repo.repo,
content: 'confused'
})
- name: Add πŸ‘ reaction on success
uses: actions/github-script@v7
if: ${{ !failure() }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.reactions.createForIssueComment({
comment_id: ${{ github.event.comment.id }},
owner: context.repo.owner,
repo: context.repo.repo,
content: '+1'
})