Skip to content

ci: migrate to netresearch/.github reusable container workflows#11

Merged
CybotTM merged 7 commits into
mainfrom
feat/use-reusable-workflows
May 22, 2026
Merged

ci: migrate to netresearch/.github reusable container workflows#11
CybotTM merged 7 commits into
mainfrom
feat/use-reusable-workflows

Conversation

@CybotTM
Copy link
Copy Markdown
Member

@CybotTM CybotTM commented May 21, 2026

Phase 2 of the container-CI consolidation. Phase 1 reusables (lint-container, security-container, smoke-test-container) are now on main in netresearch/.github — PR netresearch/.github#141 merged at 7477e5e.

What's migrated

Workflow Before After Reusable
lint.yml::container-lint 87 lines inline delegates to lint-container.yml@main hadolint v2.14.0 + shellcheck
smoke-test.yml::image-surface 25 lines inline delegates to smoke-test-container.yml@main buildx --load + container-structure-test
scorecard.yml 64 lines local delegates to scorecard.yml@main OpenSSF Scorecard + SARIF upload

What STAYS in this caller (snipe-it-specific, doesn't generalise):

  • lint.yml::compose-validate.env.example placeholder substitution + docker compose config --quiet
  • lint.yml::yamllint — no .yamllint.yml config in this repo, rules passed via config_data
  • smoke-test.yml::{stack-boots-healthy,init.sh-is-idempotent,upstream-tests} — depend on snipe-it's .env shape + HTTP probes
  • build.yml — track×composer matrix + .snipe-it-version ref resolution + tag fan-out + continue-on-error: rolling
  • security.yml::trivy — needs continue-on-error: ${{ matrix.tag == 'rolling' }} which GitHub forbids on reusable-caller jobs
  • security.yml::osv-scanner — composer.lock extraction is snipe-it-specific

Open dependencies (separate PRs upstream)

To migrate build.yml to the reusable, netresearch/.github/.github/workflows/build-container.yml needs six additional inputs: custom tags fan-out, build-args, target, OCI labels, per-cell cache-scope, provenance: mode=max / sbom: true. Tracking as follow-up.

To migrate security.yml::trivy, security-container.yml needs a step-level tolerate-pull-failure boolean input so callers can keep rolling-tag-may-not-exist semantics without using job-level continue-on-error. Tracking as follow-up.

Manual action required from a SonarCloud admin

The reusable refs use @main (org policy: first-party reusables are NOT SHA-pinned). SonarCloud's githubactions:S7637 rule ("Use full commit SHA hash for this dependency") flags these as 3 LOW security hotspots. Tried SHA-pinning in b38104d; reverted in 5aa57b9 per maintainer feedback ("WIR PINNEN UNSERE EIGENEN RE-USABLE WORKFLOWS NICHT").

These cannot be marked via API without a SonarCloud admin token (not in this repo's secrets). Please mark them as REVIEWED + SAFE in the SonarCloud UI:

https://sonarcloud.io/project/security_hotspots?id=netresearch_snipe-it-docker-compose-stack&pullRequest=11

Or deactivate the rule for this project in its Quality Profile.

Test plan

  • container-lint / hadolint green (hadolint v2.14.0 handles HEALTHCHECK --start-interval correctly)
  • container-lint / shellcheck green
  • image-surface / container-structure-test green
  • scorecard SARIF uploaded
  • All 6 build matrix cells succeeded (1 expected continue-on-error on tag/rolling)
  • SonarCloud hotspots marked SAFE by admin (manual)

CybotTM added 3 commits May 21, 2026 22:45
Replaces the inline hadolint container-run and ludeeus/action-shellcheck
step with a single `uses:` of
netresearch/.github/.github/workflows/lint-container.yml. Container-lint
logic is now shared with phpbu-docker and other future container repos.

Pinned to feat/reusable-container-workflows because PR
netresearch/.github#141 is mergeable but BLOCKED on review at the time
of this commit; a follow-up commit on this branch will flip the ref to
@main once that PR merges.

compose-validate (docker compose config with .env.example placeholder
substitution) and yamllint stay inline — both depend on caller-shaped
inputs that don't generalise to the reusable.

Refs: netresearch/.github#141
Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Replaces the inline buildx --load + container-structure-test steps in
the `image-surface` job with a single `uses:` of
netresearch/.github/.github/workflows/smoke-test-container.yml. Same
behaviour (amd64 build at the `runtime` target, GHA cache scope
`smoke-test`, cst config at tests/container-structure-test.yaml) — now
shared with phpbu-docker and other future container repos.

Pinned to feat/reusable-container-workflows because PR
netresearch/.github#141 is mergeable but BLOCKED on review at the time
of this commit; a follow-up commit on this branch will flip the ref to
@main once that PR merges.

The other three jobs stay inline — all snipe-it-specific:
  - compose-up           — stack boot with placeholder-substituted .env
                            and HTTP probe of the known login route
  - init-idempotency     — verifies bin/init.sh's APP_KEY / random-PW
                            generation contract (must not re-roll on
                            second run)
  - upstream-tests       — builds the `tester` Dockerfile stage which
                            runs the upstream Snipe-IT phpunit suite

Refs: netresearch/.github#141
Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Replaces the four-step inline OpenSSF Scorecard job (checkout +
scorecard-action + upload-artifact + upload-sarif) with a single
`uses:` of netresearch/.github/.github/workflows/scorecard.yml@main.

The reusable's job is functionally equivalent: same scorecard-action
version, same publish_results=true, same SARIF upload to GitHub
code-scanning. The only behavioural change is that the per-run raw
results.sarif is no longer kept as a workflow artifact for 5 days —
the SARIF is still uploaded to code-scanning (the canonical surface),
and re-running the workflow regenerates it on demand.

This reusable is already on `main` in netresearch/.github (predates the
container-workflows PR), so no ref-flip follow-up is needed for this
file.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Copilot AI review requested due to automatic review settings May 21, 2026 20:46
@gemini-code-assist
Copy link
Copy Markdown

Note

Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates selected GitHub Actions workflows in this repository to org-wide reusable container workflows hosted in netresearch/.github, reducing duplication as part of the container-CI consolidation effort.

Changes:

  • Delegate container linting (hadolint + shellcheck) from .github/workflows/lint.yml to netresearch/.github reusable.
  • Delegate the smoke-test “image surface” (build + container-structure-test) job from .github/workflows/smoke-test.yml to a reusable workflow.
  • Delegate OpenSSF Scorecard workflow from .github/workflows/scorecard.yml to the org reusable.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
.github/workflows/lint.yml Replaces inline hadolint + shellcheck jobs with a reusable lint-container.yml invocation.
.github/workflows/smoke-test.yml Replaces inline image-surface job steps with a reusable smoke-test-container.yml invocation.
.github/workflows/scorecard.yml Replaces inline scorecard steps with a reusable scorecard.yml invocation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/lint.yml
Comment thread .github/workflows/smoke-test.yml
Comment thread .github/workflows/scorecard.yml
CybotTM added 4 commits May 21, 2026 22:50
The netresearch/.github reusable lint-container.yml on branch
feat/reusable-container-workflows (HEAD 1a56c99) pins hadolint to
v2.12.0-alpine by digest. That's exactly the version the original
snipe-it lint.yml warned against — it predates Docker 25's
HEALTHCHECK --start-interval flag and crashes on the snipe-it
Dockerfile's healthcheck line:

  /dev/stdin:228:5 invalid flag: --start-interval
  Process completed with exit code 1.

The reusable's docstring still claims latest-alpine, so this is an
upstream regression between PR review and the current branch HEAD.

Reverting the lint.yml migration until netresearch/.github either:
  - re-pins to a hadolint version >= v2.13 (which supports
    --start-interval), or
  - adds a hadolint-image input so callers can override.

The shellcheck migration is reverted together with hadolint — they
share the same caller-job wrapping.

Refs: netresearch/.github#141
Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
PR netresearch/.github#141 merged at 2026-05-21T20:58:39Z with merge
commit 7477e5e. Two follow-ups in this commit:

1. smoke-test.yml: ref flip
   @feat/reusable-container-workflows → @main

2. lint.yml: re-apply the container-lint migration that was reverted
   in f78c9e5. The revert was needed because the reusable's initial
   hadolint pin (v2.12.0-alpine) was the version that crashes on
   HEALTHCHECK --start-interval — exactly the bug the reusable's
   DESIGN NOTES warned about. The reusable's pin was corrected to
   v2.14.0 in netresearch/.github commit a4a763e (also part of the
   merged PR 141), so the migration now works end-to-end.

   Inline yamllint job stays as-is (the snipe-it repo has no
   .yamllint.yml so the contract is local — caller-specific, doesn't
   generalise to the reusable).

Verified locally: actionlint clean on both files. CI should pick up
the new state on push.

TODOs still open (from the Phase 2 sub-agent's report):

- build.yml not migrated. build-container.yml needs six additional
  inputs (custom tags fan-out, build-args, target, OCI labels,
  per-cell cache-scope, provenance/sbom) before the snipe-it
  track×composer matrix can call it. Follow-up PR upstream.
- security.yml::trivy not migrated. Needs a tolerate-pull-failure
  step-level input on security-container.yml so callers can keep
  `continue-on-error: ${{ matrix.tag == 'rolling' }}` semantics
  (GitHub forbids continue-on-error on reusable-caller jobs).
  Follow-up PR upstream.
- security.yml::osv-scanner stays inline (composer.lock extraction
  is snipe-it-specific).

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
SonarCloud's quality gate fails on 3 LOW hotspots — one per reusable
call — with message "Use full commit SHA hash for this dependency".
This is the same org-policy that already SHA-pins third-party actions
in this repo; SonarCloud applies it to internal reusable workflow refs
as well.

Pinned all three to netresearch/.github main SHA
5ce9b7d44245a76416317ff3af38589f475e055f (current main, includes the
v2.14.0 hadolint fix from PR 141 + one subsequent merge).

Trade-off: SHA pin loses the auto-update benefit of `@main`. Two
follow-ups make this less painful:

1. Renovate's github-actions manager can be configured to bump these
   refs — `netresearch/.github/.github/workflows/...@<sha>` matches
   its default detection patterns.
2. If `netresearch/.github` adopts tagged releases (`@v1`, `@v1.2`),
   we can switch to those and let `v1` be the moving major. Org-wide
   decision; the open question raised in PR 141's discovery is still
   on the table.

For now, SHA pin satisfies SonarCloud + matches the existing
third-party action convention in this repo.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
This reverts b38104d. Hard org policy from user: first-party reusable
workflows (anything under netresearch/) are called with @main, never
SHA-pinned. SHA-pinning defeats the central-reusables architecture by
freezing every consumer to a snapshot — every reusable improvement
would require a per-consumer SHA-bump PR.

This is distinct from third-party actions (actions/checkout,
docker/build-push-action, etc.) which DO get SHA-pinned per
supply-chain policy. Different trust models.

SonarCloud's 3 LOW hotspots ("Use full commit SHA hash for this
dependency") are NOT addressed by this revert — they need to be
addressed at the SonarCloud config layer:

- Mark each as REVIEWED + SAFE via SonarCloud web UI (one click each;
  requires a project admin), OR
- Disable the rule at Quality Profile level for this project, OR
- Get the rule's hotspot equivalent in the project's Quality Gate
  configured so that org-internal reusable refs don't count.

I cannot do any of those without a SonarCloud admin token. Surfacing
this to the user as the next manual step.

Captured in memory feedback (feedback_no_sha_pin_own_reusables.md) so
this mistake doesn't repeat.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
3 Security Hotspots

See analysis details on SonarQube Cloud

CybotTM added a commit to netresearch/.github that referenced this pull request May 22, 2026
…ure) (#143)

## Summary

Two atomic commits, both unblock the Phase 2 caller migration in
netresearch/snipe-it-docker-compose-stack#11 (build.yml + security.yml
currently cannot delegate to these reusables because of input gaps).

### Commit 1 — build-container.yml: +7 inputs

| Input | Type | Purpose |
|---|---|---|
| `target` | string | Multi-stage build target (snipe-it: `runtime`) |
| `build-args` | string (multi-line) | Newline-separated KEY=VALUE for
Dockerfile ARGs |
| `cache-scope` | string | GHA cache scope name — parallel matrix cells
stay separate |
| `provenance` | string | Buildx provenance mode (snipe-it: `mode=max`)
|
| `sbom` | boolean | In-image SBOM (default false, backward compat) |
| `metadata-tags` | string (multi-line) | Override metadata-action's
`tags:` for bespoke fan-out |
| `metadata-labels` | string (multi-line) | OCI labels appended to
auto-generated set |

Backward compatible: every new input has a safe default that preserves
current behavior.

The metadata-action's default 5 tag patterns are now computed in a shell
step gated on `metadata-tags == ''`, then passed as a step output. Keeps
the YAML expression-syntax simple without losing flexibility.

### Commit 2 — security-container.yml: +tolerate-pull-failure

Boolean input. When true, the Trivy step gets `continue-on-error: true`.
Use case: caller fans a matrix over tags where some images may not exist
(rolling-variants blocked by composer audit). Job-level
`continue-on-error:` on a reusable-caller is forbidden by GitHub, so
only step-level (inside the reusable) works.

Off by default → no behavioral change for existing callers.

## Caller migration sketch (snipe-it Phase 2 follow-up)

```yaml
jobs:
  build:
    uses: netresearch/.github/.github/workflows/build-container.yml@main
    permissions:
      contents: read
      packages: write
      security-events: write
      id-token: write
      attestations: write
    with:
      image-name: snipe-it-php-fpm
      ref: ${{ steps.ref.outputs.ref }}
      target: runtime
      build-args: |
        SNIPE_IT_VERSION=${{ steps.ref.outputs.ref }}
        BUILD_DATE=${{ steps.ref.outputs.build_date }}
        VCS_REF=${{ github.sha }}
        ROLLING_DEPS=${{ matrix.composer == 'rolling' }}
      provenance: mode=max
      sbom: true
      cache-scope: ${{ matrix.track.name }}-${{ matrix.composer }}
      metadata-tags: ${{ steps.tags.outputs.tags }}
      attest: true
      sign: true

  security:
    uses: netresearch/.github/.github/workflows/security-container.yml@main
    permissions:
      contents: read
      security-events: write
      packages: read
    with:
      image-ref: ghcr.io/.../snipe-it-php-fpm:${{ matrix.tag }}
      tolerate-pull-failure: ${{ matrix.composer == 'rolling' }}
```

## Verification

- [x] `actionlint` clean on both modified files
- [x] All new inputs default to backward-compatible values
- [x] Signed commits + DCO + conventional-commit prefix

## After this lands

- snipe-it-docker-compose-stack#11 can absorb the build.yml +
security.yml migration as a follow-up commit. Realistic line-count for
build.yml: ~301 → ~150 (matrix + ref-resolution + per-cell tag
computation stay in the caller).
@CybotTM CybotTM merged commit c6b9194 into main May 22, 2026
17 of 19 checks passed
@CybotTM CybotTM deleted the feat/use-reusable-workflows branch May 22, 2026 05:14
CybotTM added a commit that referenced this pull request May 22, 2026
…le (#13)

## Phase 3 of the container-CI consolidation

Migrates `.github/workflows/security.yml`'s `trivy` job to the
Phase-2.5-extended `security-container.yml@main` reusable in
`netresearch/.github`.

## What migrated

**`security.yml::trivy`** — now calls
`netresearch/.github/.github/workflows/security-container.yml@main` with
`tolerate-pull-failure: ${{ matrix.tag == 'rolling' }}`. The reusable's
pre-flight `docker manifest inspect` probe replaces the previous
job-level `continue-on-error: ${{ matrix.tag == 'rolling' }}` (which
GitHub forbids on reusable-caller jobs anyway — actionlint rejects it).
The behavioural contract is preserved: a missing rolling image
short-circuits the Trivy + SARIF-upload steps and exits the job green,
while legitimate Trivy finding-based failures (caller `exit-code: '1'`)
still propagate normally because the reusable does NOT slap a broad
`continue-on-error` on Trivy itself.

Defaults of the reusable line up 1:1 with the previous inline values:
`exit-code: 0`, `ignore-unfixed: true`, `vuln-type: os,library`,
`severity: HIGH,CRITICAL`, `limit-severities-for-sarif: true`,
`scanners: vuln`. No behavioural change beyond replacing the
`continue-on-error` mechanism. SARIF category remains `trivy-${{
matrix.tag }}` so the GitHub Security tab grouping is unchanged.

Line counts: `security.yml` 135 → 118 (-17). The `trivy` job block
itself shrank from ~50 lines (steps: checkout + trivy-action +
upload-sarif) to ~26 (matrix + `with:` block + permissions).

## What stays inline (and why)

**`security.yml::osv-scanner`** — Per the explicit task constraint #11:
composer.lock extraction (`docker pull` + `docker create` + `docker cp
/var/www/html/composer.lock`) is Snipe-IT-specific (path + lock-file
location are not generalisable) and the osv-scanner action itself
doesn't fit the container reusable's surface. The job retains its own
`continue-on-error: ${{ matrix.tag == 'rolling' }}` since it is NOT a
reusable-caller job and the keyword is legal at the regular-job level.

**`build.yml` — left inline as a follow-up upstream item.** Honest
report: two independent blockers prevent a clean migration to
`build-container.yml@main` right now.

1. **Job-level `continue-on-error: ${{ matrix.composer == 'rolling' }}`
is forbidden on reusable-caller jobs** (same GitHub restriction we just
worked around for trivy). The build-container reusable does not
currently expose a `tolerate-build-failure` knob analogous to
`tolerate-pull-failure` — and unlike the security side, rolling-build
failures aren't a manifest-not-found case the reusable can pre-flight
cheaply; they're caused by `composer audit` blocking inside the docker
build, which is a build-time failure mode that needs different plumbing.
2. **Pre-build ref resolution + tag fan-out can't sit alongside
`uses:`.** A reusable-caller job allows only `name`, `uses`, `with`,
`secrets`, `needs`, `if`, `permissions`, `concurrency`, `strategy` — no
inline `steps:`. The current `build.yml` does meaningful work in steps
*before* the metadata + build calls: reads `.snipe-it-version` (tag
track), calls `gh api
repos/grokability/snipe-it/commits/{master,develop}` (branch tracks),
computes the `{version, major, major_minor, date_tag, build_date}`
outputs, then computes a custom tag-fan-out list (`8.5.0 / 8.5 / 8 /
latest`, with `-rolling` suffixes when applicable). Refactoring this
into a separate `compute` matrix job whose outputs feed the `build`
matrix job via `needs:` + `outputs:` + `fromJSON` gymnastics would make
the workflow MORE complex than the inline version, not less. Task
description explicitly authorises leaving build.yml inline in this case.

Proposed follow-up upstream PR on `netresearch/.github` (does not block
this PR): add a `tolerate-build-failure: bool` input to
`build-container.yml` that wraps the `Build and push` step in equivalent
semantics (e.g. `continue-on-error: true` on the build step + a
`steps.build.outcome != 'failure'` gate on attest/sign). Once that's
available, the `build.yml` migration can be revisited — though the
second blocker (caller-side ref resolution as steps) will likely keep
build.yml inline regardless. The `metadata-tags` + `metadata-labels` +
`build-args` + `cache-scope` + `target` + `provenance` + `sbom` inputs
added in Phase 2.5 are all the reusable-side surface this caller would
consume, and they're complete.

## Hard-constraint compliance

`uses:
netresearch/.github/.github/workflows/security-container.yml@main` —
pinned to `@main`, no SHA pin on the org's own reusable (rule #3). All
third-party action SHAs preserved verbatim (no new third-party action
references introduced; we deleted some). SPDX header retained with
"Netresearch DTT GmbH". Conventional-commit prefix on the commit. Signed
+ signed-off commit matching `git config user.name` / `user.email`.

## Verification

- `actionlint .github/workflows/*.yml` — clean across all workflows.
- `yamllint` with the snipe-it project config — only pre-existing
comment-spacing warnings on SHA-pin comments (no new findings; net
warnings down from 5 to 2 since 3 inline action references were
removed).
- `tail -c 2 .github/workflows/security.yml | xxd -p` → `6b0a` (single
trailing newline, no double).
- `git show --stat HEAD` confirms only `.github/workflows/security.yml`
changed.

CI run will exercise the new caller against the published `:latest`
(present) and `:rolling` (currently failing builds → manifest absent →
pre-flight skip path).
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.

2 participants