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
82 changes: 76 additions & 6 deletions .github/workflows/lint-test-build.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
name: build-push
on:
pull_request:
push:
branches:
- main
tags:
- "*"
workflow_dispatch:

permissions:
contents: read

jobs:
lint-test:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
Expand All @@ -19,8 +29,14 @@ jobs:
with:
version: latest

- name: Install dependencies
run: go get .
- name: Validate workflow syntax
run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12

- name: Download dependencies
run: go mod download

- name: Require tidy module metadata
run: go mod tidy -diff

- run: go build

Expand All @@ -35,13 +51,67 @@ jobs:
with:
files: ./coverage.out
fail_ci_if_error: false
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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: lightsout:ci-${{ matrix.architecture.suffix }}

- name: Scan native image
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: lightsout: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-ghcr.yaml@d7f0ba06b4c8a1f8559892ea65a0b1af4d024dd3 # main
uses: libops/.github/.github/workflows/build-push.yaml@8e27d95846671a9e319f1900e86a488a1d4f39b3 # resilient-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@8e27d95846671a9e319f1900e86a488a1d4f39b3
permissions:
contents: read
packages: write
secrets: inherit
id-token: write
10 changes: 8 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ghcr.io/libops/go1.25:main@sha256:f43c9b34f888d2ac53e87c8e061554f826b8eb580863d7b21fd787b6f0378f8f AS builder
FROM golang:1.25.12-alpine@sha256:56961d79ea8129efddcc0b8643fd8a5416b4e6228cfd477e3fd61deb2672c587 AS builder

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

Expand All @@ -13,10 +13,14 @@ COPY *.go ./
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -ldflags="-s -w" -o /app/binary .

FROM ghcr.io/libops/go1.25:main@sha256:f43c9b34f888d2ac53e87c8e061554f826b8eb580863d7b21fd787b6f0378f8f
FROM alpine:3.24@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b

RUN apk add --no-cache ca-certificates

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

USER 65532:65532

ENV \
PORT=8808 \
INACTIVITY_TIMEOUT=90 \
Expand All @@ -25,3 +29,5 @@ ENV \
GCP_ZONE= \
GCP_INSTANCE_NAME= \
LIBOPS_KEEP_ONLINE=

CMD ["/app/binary"]
76 changes: 76 additions & 0 deletions ci/workflow_contract_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package ci

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

const sharedPublisherSHA = "8e27d95846671a9e319f1900e86a488a1d4f39b3"

func TestImagePublicationWorkflowContract(t *testing.T) {
workflow, err := os.ReadFile("../.github/workflows/lint-test-build.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",
"persist-credentials: false",
"go mod tidy -diff",
"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",
"secrets.",
"docker-registry:",
"additional-gar-registry:",
}
for _, value := range forbidden {
if strings.Contains(contents, value) {
t.Errorf("image workflow must not contain %q", value)
}
}
}

func TestRuntimeImageContract(t *testing.T) {
dockerfile, err := os.ReadFile("../Dockerfile")
if err != nil {
t.Fatal(err)
}
contents := string(dockerfile)

for _, value := range []string{
"FROM golang:1.25.12-alpine@sha256:",
"FROM alpine:3.24@sha256:",
"USER 65532:65532",
`CMD ["/app/binary"]`,
} {
if !strings.Contains(contents, value) {
t.Errorf("Dockerfile must contain %q", value)
}
}

if strings.Contains(contents, "ghcr.io/libops/go") {
t.Error("Lightsout must not depend on a moving LibOps Go utility image")
}
}
Loading