-
Notifications
You must be signed in to change notification settings - Fork 37
137 lines (125 loc) · 5.81 KB
/
Copy pathdocker-beta.yml
File metadata and controls
137 lines (125 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
name: Docker (beta)
# Build the container images on every PR (validation only, no push) and publish
# the beta channel to GHCR on every push to master. Two images are built from one
# Dockerfile: the HTTP API (:beta) and the web UI (:beta-ui).
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
# Least privilege at the top level; the build job below opts into exactly the
# write scopes it needs (top-level write would fail OpenSSF Token-Permissions).
permissions:
contents: read
concurrency:
group: docker-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
IMAGE_NAME: ghcr.io/neverdecel/coderag
jobs:
# Compute ONE timestamp + short SHA shared by both matrix targets, so the server
# and UI images carry the *same* sortable base tag (`beta-<ts>-<sha>` /
# `beta-<ts>-<sha>-ui`). A GitOps image-updater (Flux ImagePolicy) needs a
# chronologically sortable tag to pick "the newest beta"; the rolling `:beta`
# tag is mutable and `:sha-<commit>` is not orderable. Computing the timestamp in
# one place keeps the two images' base tags byte-identical, so a single policy on
# the server tag also resolves the UI tag (UI = base tag + `-ui` suffix).
prepare:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
outputs:
version: ${{ steps.ver.outputs.version }}
steps:
- id: ver
run: echo "version=beta-$(date -u +%Y%m%d%H%M%S)-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
build:
needs: prepare
# `needs: prepare` is skipped-but-not-blocking on PRs (prepare is gated to
# non-PR events); always() keeps the build job running for PR validation.
if: always()
runs-on: ubuntu-latest
timeout-minutes: 30
# Write scopes are scoped to this job only; the prepare job needs none.
permissions:
contents: read # checkout
packages: write # push images to GHCR
id-token: write # OIDC for build provenance / keyless signing
attestations: write # SLSA provenance + SBOM attestations
strategy:
fail-fast: false
matrix:
include:
- target: server # HTTP/REST API
suffix: ""
- target: ui # Web UI
suffix: "-ui"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- name: Set up QEMU (arm64 emulation)
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Derive image tags & labels
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6
with:
images: ${{ env.IMAGE_NAME }}
flavor: |
latest=false
suffix=${{ matrix.suffix }}
tags: |
type=raw,value=beta
type=raw,value=${{ needs.prepare.outputs.version }},enable=${{ github.event_name != 'pull_request' }}
type=edge,branch=master
type=sha,prefix=sha-
- name: Build${{ github.event_name != 'pull_request' && ' and push' || ' (validate, no push)' }}
id: build
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
with:
context: .
target: ${{ matrix.target }}
# Multi-arch on master; amd64-only on PRs to keep validation fast.
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
push: ${{ github.event_name != 'pull_request' }}
# On PRs (no push) load the single-arch image into the local daemon so the
# Trivy step below can scan the exact artifact that was just built.
load: ${{ github.event_name == 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
cache-from: type=gha,scope=${{ matrix.target }}
# Cache in GitHub Actions storage (not GHCR, so no buildcache* tags). The
# transient export blip ("error writing layer blob: not_found") that used to
# fail the build is swallowed by ignore-error=true, now honored by the newer
# buildkit that ships with setup-buildx-action@v4 / build-push-action@v7.
cache-to: type=gha,mode=max,scope=${{ matrix.target }},ignore-error=true
provenance: ${{ github.event_name != 'pull_request' }}
sbom: ${{ github.event_name != 'pull_request' }}
# Vulnerability-scan the exact image we just built. On PRs the image is loaded
# into the local daemon (load: true above) and scanned by its concrete `beta`
# tag; on master the image is pushed and scanned by digest. Fails the build on
# any fixable HIGH/CRITICAL OS or library CVE.
- name: Scan image with Trivy
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: >-
${{ github.event_name == 'pull_request'
&& format('{0}:beta{1}', env.IMAGE_NAME, matrix.suffix)
|| format('{0}@{1}', env.IMAGE_NAME, steps.build.outputs.digest) }}
format: table
exit-code: "1"
ignore-unfixed: true
vuln-type: os,library
severity: HIGH,CRITICAL
env:
# Avoid GHCR rate limits when pulling the Trivy vulnerability DB.
TRIVY_USERNAME: ${{ github.actor }}
TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}