Skip to content

Latest commit

 

History

History
238 lines (190 loc) · 8.63 KB

File metadata and controls

238 lines (190 loc) · 8.63 KB

AGENTS.md

This file provides guidance to AI coding assistants (Claude Code, Cursor, etc.) when working with code in this repository.

Overview

This repository contains reusable GitHub Actions workflows and actions for building, testing, and deploying Grafana plugins. It's primarily for Grafana Labs internal teams.

Commands

make actionlint      # Lint all workflows (run before completing workflow changes)
make act-lint        # Lint Go code in tests/act/
make act-test        # Run Go tests (timeout: 1h)
make genreadme       # Regenerate examples/base/README.md (auto-generated, don't edit directly)
make mockdata        # Regenerate test mock data after modifying test plugins
make clean           # Clean all temporary files, node_modules, and dist folders

Project Structure

.github/workflows/

  • Reusable workflows: ci.yml, cd.yml (main entry points for plugin users)
  • Internal to ci/cd: playwright.yml, playwright-docker.yml, check-release-channel.yml
  • This repo only: pr-checks-*, release-please-*

actions/

  • actions/internal/: Actions used internally by ci.yml/cd.yml. NOT for direct user use.
  • actions/plugins/: Standalone reusable actions users can use directly. Each has its own release tag.

tests/

  • tests/act/: Go testing framework using nektos/act in Docker
  • tests/simple-*: Dummy Grafana plugins used by tests
  • tests/act/mockdata/: Pre-built plugin artifacts for fast test execution

examples/

  • examples/base/: Core examples. README is auto-generated by genreadme.go
  • examples/extra/: Additional helper examples

GitHub Actions Rules

Pin External Actions to Commit SHAs

# Correct
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.2.2

# Wrong
uses: actions/checkout@v4

Use @main for Same-Repo References

uses: grafana/plugin-ci-workflows/.github/workflows/ci.yml@main
uses: grafana/plugin-ci-workflows/actions/internal/plugins/setup@main

The release process automatically switches references to tagged versions.

Never Interpolate GitHub Expressions in Shell Scripts

# Dangerous - shell injection
- run: echo "${{ inputs.value }}"

# Safe - use env vars
- run: echo "${VALUE}"
  env:
    VALUE: ${{ inputs.value }}

Exception: Safe contexts (like github.action, github.event_name) may be used in if: conditions, but always use env vars in run: blocks.

Runner Labels

Never use ubuntu-latest. Use self-hosted runners:

  • ARM (preferred): ubuntu-arm64-small, ubuntu-arm64, ubuntu-arm64-large, ubuntu-arm64-xlarge, ubuntu-arm64-2xlarge
  • x64 (when needed): ubuntu-x64-small, ubuntu-x64, ubuntu-x64-large
  • Add -io suffix for I/O intensive workloads

Complex Logic

Use actions/github-script instead of complex bash:

- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
  with:
    script: |
      const value = process.env.INPUT_VALUE;
      core.setOutput('result', computedValue);
  env:
    INPUT_VALUE: ${{ inputs.value }}

Common Patterns

Safe Input Handling:

- run: |
    case "${DEPLOY_ENV}" in
      dev|ops|prod-canary|prod)
        echo "Deploying to ${DEPLOY_ENV}"
        ;;
      *)
        echo "Invalid environment: ${DEPLOY_ENV}"
        exit 1
        ;;
    esac
  env:
    DEPLOY_ENV: ${{ inputs.environment }}

Conditional Steps (safe in if: context):

- name: Deploy to production
  if: inputs.environment == 'prod'
  run: ./deploy.sh
  env:
    TARGET_ENV: ${{ inputs.environment }}

Matrix Strategy:

strategy:
  matrix:
    os: [linux, darwin, windows]
    arch: [amd64, arm64]
steps:
  - run: echo "Building for ${TARGET_OS}/${TARGET_ARCH}"
    env:
      TARGET_OS: ${{ matrix.os }}
      TARGET_ARCH: ${{ matrix.arch }}

Release Process

Uses release-please with separate versioning:

  • Main workflows: ci-cd-workflows/vX.Y.Z
  • Each action in actions/plugins/: its own version tag

When adding a new shared workflow (internal to ci/cd), add its path to the switch-references step in:

  • .github/workflows/pr-checks-workflow-references.yml
  • .github/workflows/release-please-pr-update-tagged-references.yml
  • .github/workflows/release-please-restore-rolling-release.yml

When adding a new user-facing action in actions/plugins/, update release-please-config.json:

  1. Add the new package entry under packages (with its package-name).
  2. Add the package path to the . package's exclude-paths list. This prevents commits in the new plugin from double-counting toward the ci-cd-workflows release.

Testing Framework

The Go testing framework in tests/act/ is work in progress. Do not write new tests unless explicitly requested.

Uses: testify for assertions, nektos/act in Docker, pre-built mockdata for speed.

When modifying test plugins in tests/simple-*, run make mockdata to regenerate mock data.

Go Testing Rules

Use testify, not stdlib testing:

import "github.com/stretchr/testify/require"

func TestSomething(t *testing.T) {
    result, err := DoSomething()
    require.NoError(t, err)
    require.Equal(t, expected, result)
}

Use table-driven tests:

for _, tc := range []testCase{
    {name: "case1", input: "a", expected: "A"},
    {name: "case2", input: "b", expected: "B"},
} {
    t.Run(tc.name, func(t *testing.T) {
        t.Parallel()
        require.Equal(t, tc.expected, Transform(tc.input))
    })
}

Run independent tests in parallel with t.Parallel().

Workflow testing patterns: See existing tests for current API usage:

Asserting that a specific workflow step, configuration or edge case:

Use the ::act-debug:: custom GHA command to emit a debug annotation from within a workflow shell step or a mage target. The test framework intercepts these and stores them as AnnotationLevelDebug annotations, which you can then assert on with require.Contains.

Emit from a workflow step (logfmt format):

- run: printf '::act-debug::msg="%s" key=%s\n' "my step ran" "${SOME_VAR}"
  env:
    SOME_VAR: ${{ inputs.something }}

Emit from a mage target (Go):

fmt.Printf("::act-debug::msg=%q\n", "my target was invoked")

Assert in the test:

expMsg, err := logfmt.MarshalKeyvals("msg", "my target was invoked")
require.NoError(t, err)
require.Contains(t, r.Annotations, act.Annotation{
    Level:   act.AnnotationLevelDebug,
    Message: string(expMsg),
})

Keeping act tests fast — skip irrelevant jobs and steps:

Act tests run real workflows in Docker, so they are slow by default. Always scope tests to the minimum necessary work using MutateCIWorkflow().With(...):

ci.MutateCIWorkflow().With(
    // Keep only the job under test; strip all other jobs and clear its `needs`
    workflow.WithOnlyOneJob(t, "test-and-build", true),
    // No-op steps that are irrelevant to what you're testing (e.g. frontend build)
    workflow.WithNoOpStep(t, "test-and-build", "frontend"),
    // Drop all steps after the one you care about (e.g. packaging, signing, GCS upload)
    workflow.WithRemoveAllStepsAfter(t, "test-and-build", "backend"),
),
  • WithOnlyOneJob(t, jobID, removeDependencies) — removes all jobs except jobID. Pass true to also clear its needs so it can run standalone.
  • WithNoOpStep(t, jobID, stepID) — replaces a step with a shell no-op (:), leaving the step ID intact so subsequent steps that depend on its outputs don't break wiring.
  • WithRemoveAllStepsAfter(t, jobID, stepID) — drops every step that comes after stepID in the job, cutting packaging, signing, upload, etc.

When you only need to test backend behaviour, combine all three: skip the frontend step, stop after the backend step, and run only the test-and-build job.

Adding a new CI workflow input:

  1. Add the input to WorkflowInputs in internal/workflow/ci/ci.go and wire it in SetCIInputs.
  2. Add the input to .github/workflows/ci.yml and pass it to the relevant action.
  3. Add the input to .github/workflows/cd.yml and pass it through to ci.yml.
  4. Add the input to the target composite action in actions/internal/plugins/.