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:

  1. cd .github
  2. npm i

YAML

Docs

Guidelines

  • Workflows and Actions should use the .yaml file extension (not .yml)
  • Use pinned runner images like ubuntu-24.04 rather than floated images like ubuntu-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 or shell) unless it's useful
    • Do set a property (like name) if (and only if) it improves the log UI
  • 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

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 and Write-Error or not to $ErrorActionPreference and Write-Host?

Disabling Actions in Forks

Disable all Actions

image

Disable individual workflows

image