diff --git a/ci/dynamic-ci/configuration.mdx b/ci/dynamic-ci/configuration.mdx index 22190ec3..1dd4be8b 100644 --- a/ci/dynamic-ci/configuration.mdx +++ b/ci/dynamic-ci/configuration.mdx @@ -1,10 +1,10 @@ --- title: "Configure how Dynamic CI schedules each CI job" sidebarTitle: "Configuration" -description: "Tune Dynamic CI per job in the Trunk web app: set schedules for pull requests and the merge queue, and add path filters that feed the scoring engine." +description: "Tune Dynamic CI per job in the Trunk app" --- -Dynamic CI is configured job by job on the [Configuration tab](https://app.trunk.io/ci/dynamic-ci?tab=configuration) in the Trunk web app. Each job has a schedule that controls how it is evaluated on pull requests and in the merge queue, plus optional path filters that tell Dynamic CI which changes are relevant to it. +Dynamic CI is configured job by job on the [Configuration tab](https://app.trunk.io/ci/dynamic-ci/configuration) in the Trunk web app. Each job has a schedule that controls how it is evaluated on pull requests and in the merge queue, plus optional path filters that tell Dynamic CI which changes are relevant to it. ## Find a job diff --git a/ci/dynamic-ci/dynamic-ci-action-reference.mdx b/ci/dynamic-ci/dynamic-ci-action-reference.mdx new file mode 100644 index 00000000..eeb656cb --- /dev/null +++ b/ci/dynamic-ci/dynamic-ci-action-reference.mdx @@ -0,0 +1,66 @@ +--- +title: "Dynamic CI Action Reference" +sidebarTitle: "Action reference" +description: "Inputs, outputs, modes, and fail-open behavior of the trunk-io/dynamic-ci GitHub Action." +og:title: "trunk-io/dynamic-ci GitHub Action reference" +--- + +The [`trunk-io/dynamic-ci`](https://github.com/trunk-io/dynamic-ci) action connects a GitHub Actions workflow to Dynamic CI. It asks Trunk which jobs the current change needs and writes one output per job. Your jobs read those outputs in their `if:` conditions to decide whether to run. + +## Usage + +The action sets one output per job in the workflow, named by the job key. Outputs are set at runtime, so re-export each one from the filter job's `outputs:` under the same key: + +```yaml +jobs: + dynamic-ci: + runs-on: ubuntu-latest + outputs: + # 'false': Trunk recommends skipping the job. + # Any other value: Trunk recommends running the job. + # Empty: no verdict (Trunk unreachable, missing token, or no history + # yet). The job should run. + unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} + steps: + - uses: actions/checkout@v7 + with: + clean: false + - uses: trunk-io/dynamic-ci@v1 + with: + # Your organization API token. Store it as a repository or organization + # secret, such as TRUNK_API_TOKEN + # Required. + token: ${{ secrets.TRUNK_API_TOKEN }} + + # Token used to read the pull request's changed files when the checkout + # does not contain both the base and head commits + # Default: ${{ github.token }} + github-token: '' + + # Post the recommendation for each job to the run's annotations and job + # summary, so verdicts show on the run's summary page + # Default: false + enable-annotation: '' +``` + +## Fail-open behavior + +The action is built so a problem never causes a job to be skipped by mistake. It retries requests to Trunk, and when it still cannot get a verdict, the output is empty and the job will run. + +## Checkout + +The action compares the pull request's base and head commits to find the changed files.`actions/checkout` with `clean: false` is enough. When the checkout does not contain both commits, the action falls back to `github-token` to read the changed files. + +## Versioning + +Pin to the major version tag, `trunk-io/dynamic-ci@v1`, to receive compatible updates automatically. + +## GitHub token permissions + +Grant the workflow `pull-requests: read` so `github-token` can read the pull request's changed files: + +```yaml +permissions: + contents: read + pull-requests: read +``` diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index e03e530a..1c31a8fe 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -1,105 +1,99 @@ --- title: "GitHub Actions" -description: "Add the Dynamic CI filter job to a GitHub Actions workflow." +description: "Adding Dynamic CI to your existing GitHub Actions workflows" og:title: "Set up Trunk Dynamic CI for GitHub Actions" --- - - Before you start, [set up Trunk CI for GitHub Actions](/ci/get-started/github-actions) - and copy your organization API token from the **Organization API token (v1)** - section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). Store it - as a repository secret named `TRUNK_API_TOKEN`. - +## How it works -## Add the filter job to the top of your workflow +A filter job at the top of your workflow runs the `trunk-io/dynamic-ci` action. The action asks Trunk which jobs this change needs, and writes one output per job. The `outputs` of the Dynamic CI step will store the verdict for each job specified. By listing a job in the list you are asking Dynamic CI to weigh in and attempt to manage that job.

If Dynamic CI determines a job should be skipped it will set the output variable named after the job to `'false'.`At each job level you will need to add an `if: `clause to read that matching variable. -List every job you want to gate under `outputs`. Check out first, with full -history, so the action can read the changed files. +If Trunk is offline, the token is missing, or a job has no verdict yet, the output will be empty and the job will run. This allows Dynamic CI to "fail open" so you will not inadvertently skip jobs. + +## Workflow Example + +This workflow gates two jobs. Note the job ids are the critical matched. values in this flow ```yaml +name: CI + +on: + pull_request: {} + jobs: - dynamic-ci-filter: + dynamic-ci: name: Dynamic CI runs-on: ubuntu-latest timeout-minutes: 5 + if: github.event_name == 'pull_request' + # One output per gated job, named by its job key. outputs: - unit-tests: ${{ steps.ci-filter.outputs.unit-tests }} - integration-tests: ${{ steps.ci-filter.outputs.integration-tests }} + unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} + playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: - fetch-depth: 0 - filter: blob:none - - name: Dynamic CI - id: ci-filter - uses: trunk-io/dynamic-ci@v1 + clean: false + - uses: trunk-io/dynamic-ci@v1 with: token: ${{ secrets.TRUNK_API_TOKEN }} -``` -## Gate your jobs on it + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + needs: [dynamic-ci] + if: >- + !cancelled() && + needs.dynamic-ci.outputs.unit-tests != 'false' + steps: + - uses: actions/checkout@v7 + - run: make test -```yaml -unit-tests: - name: Unit Tests - runs-on: ubuntu-latest - needs: [dynamic-ci-filter] - if: >- - !cancelled() && - needs.dynamic-ci-filter.outputs.unit-tests != 'false' - steps: - - run: make test + playwright-tests: + name: Playwright Tests + runs-on: ubuntu-latest + needs: [dynamic-ci] + if: >- + !cancelled() && + needs.dynamic-ci.outputs.playwright-tests != 'false' + steps: + - uses: actions/checkout@v7 + - run: make test ``` - - Gate on `!= 'false'`, never on `== 'true'`. A job with no verdict has no - output, and `!= 'false'` makes it run. - +## Guidance for gating a job correctly -Use each job's key from `jobs:`, not its display `name:`. + + + Outputs are named by the key under `jobs:` In the example above playwright-tests ad unit-tests are the job ids. - - A job can ask about itself by passing its own key in `job-keys`, and skip its - own steps. It saves less, because the runner has already started. + Re-export each gated job's output from the filter job's `outputs:`, under the same key. The action sets its outputs at runtime, so GitHub cannot pass them on unless you list them there. + - ```yaml - unit-tests: - name: Unit Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - filter: blob:none - - name: Dynamic CI - id: ci-filter - uses: trunk-io/dynamic-ci@v1 - with: - token: ${{ secrets.TRUNK_API_TOKEN }} - job-keys: unit-tests - - if: steps.ci-filter.outputs.unit-tests != 'false' - run: make test - ``` - - - - If your build tool measures how much of the codebase a change affects, such - as a count of affected packages, pass it as a JSON list so Trunk runs broad - changes: - - ```yaml - with: - token: ${{ secrets.TRUNK_API_TOKEN }} - impacts: '[{"source":"tainted-packages","count":${{ steps.affected.outputs.count }}}]' - ``` - - Use `"all": true` in place of `count` when everything is affected, and leave - `impacts` unset when your tool computed nothing. A bad entry is dropped with - a warning. Ask us to turn the signal on and set each job's limit. - - -## Next - -[Turn on enforcement](/ci/dynamic-ci/getting-started) once the -recommendations look right. For how Dynamic CI behaves in a merge queue, see -[Merge queues](/ci/dynamic-ci/merge-queue). + + ```yaml + outputs: + unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} + playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests + ``` + + + + Each managed job has should specify `needs: [dynamic-ci-filter]`. This ensures the dynamic ci verdict will be available to assess before the job ever spins up. + + + + By default GitHub skips a job when a job it needs failed or was skipped. Without `!cancelled()`, a failed filter job (a bad token, an outage) skips every gated job, and nothing is tested. + + + + Join other conditions with && + + + +When your workflow files are updated and start calling into Dynamic CI a background linter will run to verify the format of your yaml file and call out problems. + +## Check your setup + +1. Open a pull request. The **Dynamic CI's** verdict will be reported on each pull request under the dynamic ci tab. Additionally you can see all decisions made by the system in the [Dynamic CI page](https://app.trunk.io/dynamic-ci). +2. Your repository starts in **Learning** mode, which records verdicts but will not actively skip anything. Once the recommendations look right you can [turn on enforcement](/ci/dynamic-ci/settings). diff --git a/ci/dynamic-ci/getting-started/index.mdx b/ci/dynamic-ci/getting-started/index.mdx index 1f0ea0a2..09ba4492 100644 --- a/ci/dynamic-ci/getting-started/index.mdx +++ b/ci/dynamic-ci/getting-started/index.mdx @@ -5,16 +5,15 @@ og:title: "Get started with Trunk Dynamic CI" --- - Dynamic CI is in **private beta**, and your organization has to be enabled for - it first. [Reach out to Trunk](https://slack.trunk.io) for access. + Dynamic CI is in **private beta**. [Reach out to Trunk](https://slack.trunk.io) for access. - - Dynamic CI decides from your CI history, so Trunk has to be collecting it. - Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your - organization API token from the **Organization API token (v1)** section of - [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). + + decides from your CI history, Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your organization API token from the **Organization API token (v1)** section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). + + + diff --git a/docs.json b/docs.json index b87fcc94..7daef4b2 100644 --- a/docs.json +++ b/docs.json @@ -418,6 +418,7 @@ "ci/dynamic-ci/settings", "ci/dynamic-ci/force-run", "ci/dynamic-ci/merge-queue", + "ci/dynamic-ci/dynamic-ci-action-reference", "ci/dynamic-ci/faq" ] }