Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 66 additions & 2 deletions .github/workflows/build-push.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
name: build-push
on:
pull_request:
push:
branches:
- main
tags:
- "*"
workflow_dispatch:

permissions:
contents: read

jobs:
lint-test:
runs-on: ubuntu-24.04
Expand Down Expand Up @@ -46,10 +54,66 @@ jobs:
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

image-check:
if: github.event_name == 'pull_request'
strategy:
fail-fast: false
matrix:
architecture:
- runner: ubuntu-24.04
platform: linux/amd64
suffix: amd64
- runner: ubuntu-24.04-arm
platform: linux/arm64
suffix: arm64
runs-on: ${{ matrix.architecture.runner }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false

- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3

- name: Build native image without credentials
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
with:
context: .
load: true
platforms: ${{ matrix.architecture.platform }}
provenance: false
push: false
tags: cap:ci-${{ matrix.architecture.suffix }}

- name: Scan native image
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: cap:ci-${{ matrix.architecture.suffix }}
format: table
exit-code: "1"
ignore-unfixed: true
severity: HIGH,CRITICAL
vuln-type: os,library

run:
name: run
if: ${{ always() && github.event_name == 'pull_request' }}
needs: [lint-test, image-check]
permissions: {}
uses: libops/.github/.github/workflows/pr-status.yaml@057262171193c6d563e7b08ae93d0c4aebfce2bd # credential-free-required-status
with:
needs-json: ${{ toJSON(needs) }}

publish:
if: github.ref == 'refs/heads/main' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
needs: [lint-test]
uses: libops/.github/.github/workflows/build-push.yaml@main
uses: libops/.github/.github/workflows/build-push.yaml@a86300fb8020d0f7141bb9f833d89b5dbd7aa4d7 # guarded-signed-image-publisher
with:
ref: ${{ github.sha }}
expected-main-sha: ${{ github.ref == 'refs/heads/main' && github.sha || '' }}
scan: true
sign: true
certificate-identity: https://github.com/libops/.github/.github/workflows/build-push.yaml@a86300fb8020d0f7141bb9f833d89b5dbd7aa4d7
permissions:
contents: read
packages: write
secrets: inherit
id-token: write
4 changes: 4 additions & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Cap links the Prometheus parser/export client, not Prometheus's remote-read
# HTTP handler where this decompression issue exists. Remove this exception
# when prometheus-engine exposes a supported exporter on the patched line.
CVE-2026-42154 exp:2026-10-01
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.26.3-alpine3.22@sha256:be93003ee861b3b91b6ebcb22678524947e0cd786c2df3f32af520006b1e54f5 AS builder
FROM golang:1.25.12-alpine@sha256:56961d79ea8129efddcc0b8643fd8a5416b4e6228cfd477e3fd61deb2672c587 AS builder

SHELL ["/bin/ash", "-o", "pipefail", "-ex", "-c"]

Expand All @@ -15,7 +15,9 @@ COPY scraper ./scraper
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -ldflags="-s -w" -o /app/binary .

FROM golang:1.26.3-alpine3.22@sha256:be93003ee861b3b91b6ebcb22678524947e0cd786c2df3f32af520006b1e54f5
FROM alpine:3.24@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b

RUN apk add --no-cache ca-certificates

COPY --from=builder /app/binary /app/binary

Expand Down
69 changes: 69 additions & 0 deletions ci/workflow_contract_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package ci

import (
"os"
"strings"
"testing"
)

const sharedPublisherSHA = "a86300fb8020d0f7141bb9f833d89b5dbd7aa4d7"

func TestImagePublicationWorkflowContract(t *testing.T) {
workflow, err := os.ReadFile("../.github/workflows/build-push.yml")
if err != nil {
t.Fatal(err)
}
contents := string(workflow)

required := []string{
"pull_request:",
"if: github.event_name == 'pull_request'",
"if: github.ref == 'refs/heads/main' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))",
"Build native image without credentials",
"libops/.github/.github/workflows/build-push.yaml@" + sharedPublisherSHA,
"ref: ${{ github.sha }}",
"expected-main-sha: ${{ github.ref == 'refs/heads/main' && github.sha || '' }}",
"scan: true",
"sign: true",
"certificate-identity: https://github.com/libops/.github/.github/workflows/build-push.yaml@" + sharedPublisherSHA,
"packages: write",
"id-token: write",
}
for _, value := range required {
if !strings.Contains(contents, value) {
t.Errorf("image workflow must contain %q", value)
}
}

forbidden := []string{
"build-push.yaml@main",
"build-push-ghcr.yaml",
"secrets: inherit",
"docker-registry:",
"additional-gar-registry:",
}
for _, value := range forbidden {
if strings.Contains(contents, value) {
t.Errorf("image workflow must not contain %q", value)
}
}
}

func TestTrivyExceptionIsNarrowAndExpiring(t *testing.T) {
ignore, err := os.ReadFile("../.trivyignore")
if err != nil {
t.Fatal(err)
}

lines := strings.Split(strings.TrimSpace(string(ignore)), "\n")
var rules []string
for _, line := range lines {
line = strings.TrimSpace(line)
if line != "" && !strings.HasPrefix(line, "#") {
rules = append(rules, line)
}
}
if len(rules) != 1 || rules[0] != "CVE-2026-42154 exp:2026-10-01" {
t.Fatalf("unexpected Trivy exception rules: %q", rules)
}
}
Loading