Skip to content

Commit

Permalink
Add named arguments to bot commands (#1463)
Browse files Browse the repository at this point in the history
  • Loading branch information
cartermckinnon authored Oct 12, 2023
1 parent 5b54e37 commit 915ce22
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 12 deletions.
11 changes: 10 additions & 1 deletion .github/actions/bot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@ This GitHub Action parses commands from pull request comments and executes them.

Only authorized users (members and owners of this repository) are able to execute commands.

Commands look like:
Commands look like `/COMMAND ARGS`, for example:
```
/echo hello world
```

Multiple commands can be included in a comment, one per line; but each command must be unique.

Some commands accept additional, named arguments specified on subsequent lines.
Named arguments look like `+NAME ARGS`, for example:
```
/ci launch
+build cache_container_images=true
```

Multiple named arguments can be specified.
74 changes: 65 additions & 9 deletions .github/actions/bot/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,30 @@ async function bot(core, github, context, uuid) {
}
console.log(`Comment author is authorized: ${author}`);

const commands = parseCommands(uuid, payload, payload.comment.body);
let commands;
try {
commands = parseCommands(uuid, payload, payload.comment.body);
} catch (error) {
console.log(error);
const reply = `@${author} I didn't understand [that](${payload.comment.html_url})! 🤔\n\nTake a look at my [logs](${getBotWorkflowURL(payload, context)}).`
replyToCommand(github, payload, reply);
return;
}
if (commands.length === 0) {
console.log("No commands found in comment body");
return;
}
const uniqueCommands = [...new Set(commands.map(command => typeof command))];
if (uniqueCommands.length != commands.length) {
console.log("Duplicate commands found in comment body");
replyToCommand(github, payload, `@${author} you can't use the same command more than once! 🙅`);
return;
}
console.log(commands.length + " command(s) found in comment body");

for (const command of commands) {
const reply = await command.run(author, github);
if (typeof reply === 'string') {
github.rest.issues.createComment({
owner: payload.repository.owner.login,
repo: payload.repository.name,
issue_number: payload.issue.number,
body: reply
});
replyToCommand(github, payload, reply);
} else if (reply) {
console.log(`Command returned: ${reply}`);
} else {
Expand All @@ -49,17 +52,48 @@ async function bot(core, github, context, uuid) {
}
}

// parseCommands splits the comment body into lines and parses each line as a command.
// replyToCommand creates a comment on the same PR that triggered this workflow
function replyToCommand(github, payload, reply) {
github.rest.issues.createComment({
owner: payload.repository.owner.login,
repo: payload.repository.name,
issue_number: payload.issue.number,
body: reply
});
}

// getBotWorkflowURL returns an HTML URL for this workflow execution of the bot
function getBotWorkflowURL(payload, context) {
return `https://github.com/${payload.repository.owner.login}/${payload.repository.name}/actions/runs/${context.runId}`;
}

// parseCommands splits the comment body into lines and parses each line as a command or named arguments to the previous command.
function parseCommands(uuid, payload, commentBody) {
const commands = [];
if (!commentBody) {
return commands;
}
const lines = commentBody.split(/\r?\n/);
for (const line of lines) {
console.log(`Parsing line: ${line}`);
const command = parseCommand(uuid, payload, line);
if (command) {
commands.push(command);
} else {
const namedArguments = parseNamedArguments(line);
if (namedArguments) {
const previousCommand = commands.at(-1);
if (previousCommand) {
if (typeof previousCommand.addNamedArguments === 'function') {
previousCommand.addNamedArguments(namedArguments.name, namedArguments.args);
} else {
throw new Error(`Parsed named arguments but previous command (${previousCommand.constructor.name}) does not support arguments: ${JSON.stringify(namedArguments)}`);
}
} else {
// don't treat this as an error, because the named argument syntax might just be someone '+1'-ing.
console.log(`Parsed named arguments with no previous command: ${JSON.stringify(namedArguments)}`);
}
}
}
}
return commands
Expand Down Expand Up @@ -89,6 +123,20 @@ function buildCommand(uuid, payload, name, args) {
}
}

// parseNamedArgument parses a line as named arguments.
// The format of a command is `+NAME ARGS...`.
// Leading and trailing spaces are ignored.
function parseNamedArguments(line) {
const parsed = line.trim().match(/^\+([a-z\-]+)(?:\s+(.+))?$/);
if (parsed) {
return {
name: parsed[1],
args: parsed[2]
}
}
return null;
}

class EchoCommand {
constructor(uuid, payload, args) {
this.phrase = args ? args : "echo";
Expand All @@ -111,6 +159,11 @@ class CICommand {
if (args != null && args != "") {
this.goal = args;
}
this.goal_args = {};
}

addNamedArguments(goal, args) {
this.goal_args[goal] = args;
}

async run(author, github) {
Expand All @@ -137,6 +190,9 @@ class CICommand {
requester: author,
comment_url: this.comment_url
};
for (const [goal, args] of Object.entries(this.goal_args)) {
inputs[`${goal}_arguments`] = args;
}
console.log(`Dispatching workflow with inputs: ${JSON.stringify(inputs)}`);
await github.rest.actions.createWorkflowDispatch({
owner: this.repository_owner,
Expand Down
5 changes: 4 additions & 1 deletion .github/actions/ci/build/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ inputs:
k8s_version:
required: true
type: string
additional_arguments:
required: false
type: string
outputs:
ami_id:
value: ${{ steps.build.outputs.ami_id }}
Expand All @@ -22,5 +25,5 @@ runs:
shell: bash
run: |
AMI_NAME="amazon-eks-node-${{ inputs.k8s_version }}-${{ inputs.build_id }}"
make ${{ inputs.k8s_version }} ami_name=${AMI_NAME}
make ${{ inputs.k8s_version }} ami_name=${AMI_NAME} ${{ inputs.additional_arguments }}
echo "ami_id=$(jq -r .builds[0].artifact_id "${AMI_NAME}-manifest.json" | cut -d ':' -f 2)" >> $GITHUB_OUTPUT
6 changes: 5 additions & 1 deletion .github/workflows/ci-manual.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ on:
- "build"
- "launch"
- "test"
build_arguments:
required: false
type: string
jobs:
setup:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -92,6 +95,7 @@ jobs:
git_sha: ${{ inputs.git_sha }}
k8s_version: ${{ matrix.k8s_version }}
build_id: ${{ needs.setup.outputs.build_id }}
additional_arguments: ${{ inputs.build_arguments }}
- if: ${{ inputs.goal == 'launch' || inputs.goal == 'test' }}
name: "${{ needs.setup.outputs.ci_step_name_prefix }} Launch"
id: launch
Expand Down Expand Up @@ -179,4 +183,4 @@ jobs:
repo: context.repo.repo,
issue_number: ${{ inputs.pr_number }},
body: commentBody
});
});

0 comments on commit 915ce22

Please sign in to comment.