GitHub Actions Design Guidelines - Azure/azure-sdk-tools GitHub Wiki
Folder Structure
# Dev Dependencies for VS Code intellisense in github-script JS files
.github/package.json
.github/package-lock.json (required for repeatable builds of check unit tests)
# Composite Actions
.github/actions/do-something/action.yaml
.github/actions/do-something/src/action.js
.github/actions/do-something/test/action.test.js
.github/actions/src/util.js (code shared between actions)
.github/actions/test/util.test.js (code shared between actions)
# Workflows
.github/workflows/test-something.yaml
.github/workflows/src/test-something.js
.github/workflows/test/test-something.test.js
.github/workflows/src/util.js (code shared between workflows)
.github/workflows/test/util.test.js (code shared between workflows)
# Shared JS Libraries
.github/src/util.js (code shared between action and workflows)
.github/test/util.test.js (code shared between action and workflows)
VS Code
Extensions
Install dev dependencies
For VS Code intellisense and tools:
- cd
.github
npm i
YAML
Docs
- YAML syntax for Workflows - Files under
.github/workflows
- YAML syntax for Actions - Files under
.github/actions
Guidelines
- Workflows and Actions should use the
.yaml
file extension (not.yml
) - Use pinned runner images like
ubuntu-24.04
rather than floated images likeubuntu-latest
- Updating all pinned images is a single search/replace, while floating causes distracting warnings during updates
- Less is more, but make things nice
- Don't set a property (like
id
orshell
) unless it's useful - Do set a property (like
name
) if (and only if) it improves the log UI
- Don't set a property (like
- Sort properties approximately in the order listed in the documents above (and/or align with examples in GitHub docs)
- Log as much as you can, but within reason
- Do log anything you think might be useful to debug a problem in the future
- Don't log anything verbose, unless it's useful
- Consider grouping log chunks to improve UI
- Use
debug
when appropriate for verbose logs, but remember it's only useful for repros (not the original failure)
- Before merging, run "Format Document" (will be enforced in the future)
- Goal is 100% branch coverage of all actions, workflows, and shared code
Triggers
- For efficiency, trigger on the minimal number of events necessary, and aggressively filter based on the event payload to avoid allocating an agent unless necessary.
on:
issue_comment:
types:
- edited
pull_request:
types:
# Depends on labels, so must re-evaluate whenever a relevant label is manually added or removed.
- labeled
- unlabeled
# Depends on changed files and check_run status, so must re-evaluate whenever either could change
- opened
- reopened
- synchronize
jobs:
job:
# pull_request:labeled - filter to only the input and output labels
# issue_comment:edited - filter to only PR comments containing "next steps to merge",
# a signal that "Swagger LintDiff" status may have changed
if: |
(github.event_name == 'pull_request' &&
((github.event.action == 'opened' ||
github.event.action == 'reopened' ||
github.event.action == 'synchronize') ||
((github.event.action == 'labeled' ||
github.event.action == 'unlabeled') &&
(github.event.label.name == 'ARMReview' ||
github.event.label.name == 'NotReadyForARMReview' ||
github.event.label.name == 'SuppressionReviewRequired' ||
github.event.label.name == 'Suppression-Approved' ||
github.event.label.name == 'ARMAutoSignOffPreview')))) ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, 'next steps to merge'))
runs-on: ubuntu-24.04
Security
- Security hardening for GitHub Actions
- Keeping your GitHub Actions and workflows secure: Preventing pwn requests
- Trigger
pull_request
has a read-onlyGITHUB_TOKEN
- Should be used whenever possible, like to run a test and return pass/fail
- Triggers like
pull_request_target
andworkflow_completed
have read-writeGITHUB_TOKEN
- You must never do anything that could execute untrusted code from a fork PR
- Safest option is to never clone the PR source branch at all
- Perform most of the work in a safe
pull_request
trigger, then use aworkflow_completed
trigger to execute the privileged tasks (e.g. adding a label) based on artifacts published by the first workflow - If you must clone the PR source branch, it can only be treated as text, never executed
- Don't log anything that might leak secrets
- GitHub has some built-in protections (e.g. masking secrets), but treat them as defense-in-depth
Default Permissions
To follow the principle of least-privilege, all workflows should use the following top-level permissions by default:
permissions:
contents: read
This allows the workflow to clone the repo (and upload artifacts, which appears to be always allowed). Additional privileges should be granted as needed. For example:
permissions:
actions: read
contents: read
pull-requests: write
Most workflows either use a single job, or all jobs share the same permissions, and should only define permissions once at the top-level. If a workflow has multiple jobs with different permissions, apply the common least privileges at the top-level, and the necessary permissions in each job which needs more (job permissions do not inherit from the top-level).
Suggestions
- Use
workflow_dispatch
wherever you can- Manually triggering a pipeline can make testing/debugging easier
- Beware the different context objects available to the pipeline and don't couple to a specific one
- Naming jobs in a matrix
- Matrix UI improvements
- Composite Actions behavior is different from DevOps templates
JS
Guidelines
- Code is written in JavaScript instead of TypeScript
- Significantly improves startup time
- Recommended by
github-script
action - Encourages us to keep our code simple
- With type checking and intellisense, the editing experience is similar
- Before merging, run "Format Document" (will be enforced in the future)
Type Checking
- All JS files should start with header
// @ts-check
to enable type checking - Functions called from
github-script
should start with the following, to set the argument types:
/**
* @param {import('github-script').AsyncFunctionArguments} AsyncFunctionArguments
*/
module.exports = async ({ github, context, core }) => {
- Additional type declarations can be added via JSDoc comments
const payload =
/** @type {import("@octokit/webhooks-types").WorkflowRunCompletedEvent} */ (
context.payload
);
PowerShell
- Error logging in
pwsh
- To
$ErrorActionPreference
andWrite-Error
or not to$ErrorActionPreference
andWrite-Host
?
- To