Skip to content

refactor(govulncheck): convert reusable workflow to composite action#109

Merged
sydorovdmytro merged 1 commit intomainfrom
devops-772/govulncheck-composite-action
Apr 15, 2026
Merged

refactor(govulncheck): convert reusable workflow to composite action#109
sydorovdmytro merged 1 commit intomainfrom
devops-772/govulncheck-composite-action

Conversation

@sydorovdmytro
Copy link
Copy Markdown
Collaborator

Why

The reusable workflow couldn't resolve its own ref at call time. Both github.workflow_ref and github.workflow_sha leak the caller's context under workflow_call, so the sparse-checkout of loft-sh/github-actions (to get run.sh) failed with:

fatal: couldn't find remote ref refs/pull/6676/merge

on any PR-triggered call — observed in loft-enterprise#6676. github.job_workflow_sha is documented but only populated as an OIDC claim, not in the expression context (actions/runner#2417).

Converting to a composite action sidesteps the problem entirely: composite actions are checked out to github.action_path at the correct ref automatically, no ref resolution needed. This is already the pattern used by ci-test-notify, release-notification, linear-release-sync, and semver-validation.

What changed

  • .github/scripts/govulncheck/.github/actions/govulncheck/ (code + bats tests moved, unchanged)
  • .github/actions/govulncheck/action.yml — new composite action
  • .github/workflows/govulncheck.yamldeleted (the broken reusable workflow)
  • .github/workflows/test-govulncheck.yaml — path filter + test target updated
  • Makefile, README.md, renovate.json — updated to reflect the new location

Caller contract change (breaking, but no callers yet)

Before (reusable workflow) After (composite action)
uses: at job level uses: at step level
runs-on input caller sets on job
timeout-minutes input caller sets on job (composite steps can't timeout)
secrets: gh-access-token, slack-webhook-url regular inputs (actions have no secrets: block)
Fork guard baked in caller adds if: github.repository_owner == 'loft-sh'
No checkout from caller caller does actions/checkout first

Since no caller currently uses govulncheck/v1, the tag can be moved forward to this commit after merge without migration fallout. loft-enterprise#6676 will be updated to call the composite action.

Test plan

  • make test-govulncheck — 11/11 bats tests pass
  • make lint — actionlint + zizmor clean
  • test-govulncheck.yaml CI job green on this PR
  • After merge: move govulncheck/v1 tag; update loft-enterprise#6676 caller

Refs DEVOPS-772

The reusable workflow couldn't resolve its own ref at call time:
`github.workflow_ref` and `github.workflow_sha` both leak the caller's
context under `workflow_call`, so the sparse-checkout of
`loft-sh/github-actions` for the scripts failed with `fatal: couldn't
find remote ref refs/pull/N/merge` on any PR-triggered call (observed
in loft-enterprise#6676). `github.job_workflow_sha` is documented but
only populated as an OIDC claim, not in the expression context
(actions/runner#2417).

Converting to a composite action sidesteps the problem entirely:
composite actions are checked out to `github.action_path` at the
correct ref automatically, no ref resolution needed. Same pattern
already used by `ci-test-notify`, `release-notification`,
`linear-release-sync`, `semver-validation`.

Caller contract change (v1, breaking):
- `runs-on` input removed; caller sets it on their job
- `timeout-minutes` input removed; composite steps can't timeout, caller
  sets job-level `timeout-minutes`
- `secrets:` (`gh-access-token`, `slack-webhook-url`) → inputs; actions
  can't declare `secrets:` — caller interpolates `${{ secrets.* }}`
- Caller supplies their own `actions/checkout` step + fork guard

Since no caller currently uses `govulncheck/v1`, the tag can be moved
forward to this commit without migration fallout.

- Move `.github/scripts/govulncheck/` → `.github/actions/govulncheck/`
- Delete `.github/workflows/govulncheck.yaml`
- Update `test-govulncheck.yaml` path filter + test target
- Extend renovate customManager to scan `action.yml` inputs
- Rewrite README section for the composite-action contract

Refs DEVOPS-772
@sydorovdmytro sydorovdmytro force-pushed the devops-772/govulncheck-composite-action branch from f8890e0 to 8318e89 Compare April 15, 2026 12:49
@sydorovdmytro sydorovdmytro merged commit 035ae72 into main Apr 15, 2026
6 checks passed
@sydorovdmytro sydorovdmytro deleted the devops-772/govulncheck-composite-action branch April 15, 2026 12:52
sydorovdmytro added a commit that referenced this pull request Apr 15, 2026
Reusable workflows can't resolve their own ref from the `github`
context: both `github.workflow_ref` and `github.workflow_sha` leak the
caller under `workflow_call`, so the sparse-checkout of
`loft-sh/github-actions` for the scripts fails on any PR-triggered
call (observed in loft-enterprise#6676 for the sibling govulncheck
workflow). `github.job_workflow_sha` is documented but only exposed
as an OIDC claim (actions/runner#2417).

Ship this as a composite action from the start — the same pattern
already used by ci-test-notify, release-notification,
linear-release-sync, semver-validation, and (post-#109) govulncheck:
composite actions get `github.action_path` and the repo is
auto-checked-out at the correct ref, so no ref resolution needed.

One action with a `mode: check|report` input instead of two: both
flows share the same setup (setup-go, install go-licenses, private-
repo git auth) — only the trailing steps differ (`go-licenses check`
vs `go-licenses report` + peter-evans/create-pull-request). The same
`run.sh` with check/report subcommands ports unchanged, keeping all
17 bats tests valid.

Caller contract (each caller provides their own checkout + fork
guard + timeout-minutes at job level):

  jobs:
    check:
      runs-on: ubuntu-latest
      if: github.repository_owner == 'loft-sh'
      permissions:
        contents: read
      timeout-minutes: 15
      steps:
        - uses: actions/checkout@...
          with: {persist-credentials: false}
        - uses: loft-sh/github-actions/.github/actions/go-licenses@go-licenses/v1
          with:
            mode: check   # or: report
            ...

- `.github/actions/go-licenses/action.yml` — composite action
- `.github/actions/go-licenses/run.sh` — moved from `.github/scripts/`
- `.github/actions/go-licenses/test/run.bats` — moved, 17/17 pass
- `.github/actions/go-licenses/README.md` — usage + inputs
- `.github/workflows/go-licenses-check.yaml` — deleted
- `.github/workflows/go-licenses-report.yaml` — deleted
- `test-go-licenses.yaml` + `Makefile` — retargeted at the new path

Refs DEVOPS-772
sydorovdmytro added a commit that referenced this pull request Apr 15, 2026
Reusable workflows can't resolve their own ref from the `github`
context: both `github.workflow_ref` and `github.workflow_sha` leak the
caller under `workflow_call`, so the sparse-checkout of
`loft-sh/github-actions` for the scripts fails on any PR-triggered
call (observed in loft-enterprise#6676 for the sibling govulncheck
workflow). `github.job_workflow_sha` is documented but only exposed
as an OIDC claim (actions/runner#2417).

Ship this as a composite action from the start — the same pattern
already used by ci-test-notify, release-notification,
linear-release-sync, semver-validation, and (post-#109) govulncheck:
composite actions get `github.action_path` and the repo is
auto-checked-out at the correct ref, so no ref resolution needed.

One action with a `mode: check|report` input instead of two: both
flows share the same setup (setup-go, install go-licenses, private-
repo git auth) — only the trailing steps differ (`go-licenses check`
vs `go-licenses report` + peter-evans/create-pull-request). The same
`run.sh` with check/report subcommands ports unchanged, keeping all
17 bats tests valid.

Caller contract (each caller provides their own checkout + fork
guard + timeout-minutes at job level):

  jobs:
    check:
      runs-on: ubuntu-latest
      if: github.repository_owner == 'loft-sh'
      permissions:
        contents: read
      timeout-minutes: 15
      steps:
        - uses: actions/checkout@...
          with: {persist-credentials: false}
        - uses: loft-sh/github-actions/.github/actions/go-licenses@go-licenses/v1
          with:
            mode: check   # or: report
            ...

- `.github/actions/go-licenses/action.yml` — composite action
- `.github/actions/go-licenses/run.sh` — moved from `.github/scripts/`
- `.github/actions/go-licenses/test/run.bats` — moved, 17/17 pass
- `.github/actions/go-licenses/README.md` — usage + inputs
- `.github/workflows/go-licenses-check.yaml` — deleted
- `.github/workflows/go-licenses-report.yaml` — deleted
- `test-go-licenses.yaml` + `Makefile` — retargeted at the new path

Refs DEVOPS-772
sydorovdmytro added a commit that referenced this pull request Apr 15, 2026
…action

The reusable workflow couldn't resolve its own ref at call time.
`github.workflow_ref` and `github.workflow_sha` both leak the caller's
context under `workflow_call` — the sparse-checkout of
`loft-sh/github-actions` for the scripts used the parsed workflow_ref
(`refs/heads/main` for push events, `refs/tags/vX` for releases),
which:

- On push-to-main: happened to resolve because main/main lined up,
  but silently pulled scripts from `main` rather than the pinned
  `@publish-helm-chart/v1` — tag pinning was a lie.
- On release events: the caller's `refs/tags/v4.6.0` doesn't exist in
  `loft-sh/github-actions`, so the first release-triggered caller
  (pending loft-enterprise release-chart migration) would fail with
  `fatal: couldn't find remote ref`.

Converting to a composite action sidesteps both — `github.action_path`
is correct automatically, tag pinning is real, no release-event
breakage. Same pattern already used by ci-test-notify,
release-notification, linear-release-sync, semver-validation,
govulncheck (#109), and go-licenses (#100).

Caller contract change (v2, breaking):
- `ref` input removed — caller checks out the desired ref themselves
- `runs-on` / `timeout-minutes` / `permissions` set by caller
- `secrets: chart-museum-*` → regular inputs (actions can't declare
  secrets; caller interpolates `${{ secrets.* }}`)
- Caller supplies `actions/checkout` before the action

Only caller today is loft-enterprise push-head-images.yaml (merged via
#6673 yesterday) — one migration PR follows.

- `.github/scripts/publish-helm-chart/` → `.github/actions/publish-helm-chart/`
- Delete `.github/workflows/publish-helm-chart.yaml`
- Retarget `test-publish-helm-chart.yaml` path filter + test target
- Rewrite README section for the composite-action contract

Refs DEVOPS-772
sydorovdmytro added a commit that referenced this pull request Apr 15, 2026
…action (#115)

The reusable workflow couldn't resolve its own ref at call time.
`github.workflow_ref` and `github.workflow_sha` both leak the caller's
context under `workflow_call` — the sparse-checkout of
`loft-sh/github-actions` for the scripts used the parsed workflow_ref
(`refs/heads/main` for push events, `refs/tags/vX` for releases),
which:

- On push-to-main: happened to resolve because main/main lined up,
  but silently pulled scripts from `main` rather than the pinned
  `@publish-helm-chart/v1` — tag pinning was a lie.
- On release events: the caller's `refs/tags/v4.6.0` doesn't exist in
  `loft-sh/github-actions`, so the first release-triggered caller
  (pending loft-enterprise release-chart migration) would fail with
  `fatal: couldn't find remote ref`.

Converting to a composite action sidesteps both — `github.action_path`
is correct automatically, tag pinning is real, no release-event
breakage. Same pattern already used by ci-test-notify,
release-notification, linear-release-sync, semver-validation,
govulncheck (#109), and go-licenses (#100).

Caller contract change (v2, breaking):
- `ref` input removed — caller checks out the desired ref themselves
- `runs-on` / `timeout-minutes` / `permissions` set by caller
- `secrets: chart-museum-*` → regular inputs (actions can't declare
  secrets; caller interpolates `${{ secrets.* }}`)
- Caller supplies `actions/checkout` before the action

Only caller today is loft-enterprise push-head-images.yaml (merged via
#6673 yesterday) — one migration PR follows.

- `.github/scripts/publish-helm-chart/` → `.github/actions/publish-helm-chart/`
- Delete `.github/workflows/publish-helm-chart.yaml`
- Retarget `test-publish-helm-chart.yaml` path filter + test target
- Rewrite README section for the composite-action contract

Refs DEVOPS-772
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant