From d2b664a752e8a4ed3821e85f43f756d7de937e8d Mon Sep 17 00:00:00 2001 From: Ben Apprederisse Date: Thu, 9 Apr 2026 13:27:01 -0700 Subject: [PATCH 1/7] Add Dockerfile, Helm chart, and Docker & Helm GHA workflow - deploy/Dockerfile: multi-stage Go build (1.24.2-alpine), non-root user, port 8080 - charts/version-guard/: Helm chart (Deployment, Service, ServiceAccount) with env-var config - .github/workflows/docker.yml: PR builds (single-arch + chart lint), main push (multi-arch to GHCR), v* tag (publish chart to OCI registry) - Makefile: updated docker-build to use deploy/Dockerfile Amp-Thread-ID: https://ampcode.com/threads/T-019d73dd-3cfe-773a-898f-7f7efbb7eb68 Co-authored-by: Amp --- .github/workflows/docker.yml | 131 ++++++++++++++++++ Makefile | 2 +- charts/version-guard/.helmignore | 3 + charts/version-guard/Chart.yaml | 9 ++ charts/version-guard/templates/_helpers.tpl | 58 ++++++++ .../version-guard/templates/deployment.yaml | 74 ++++++++++ charts/version-guard/templates/service.yaml | 16 +++ .../templates/serviceaccount.yaml | 14 ++ charts/version-guard/values.yaml | 62 +++++++++ deploy/Dockerfile | 48 +++++++ 10 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docker.yml create mode 100644 charts/version-guard/.helmignore create mode 100644 charts/version-guard/Chart.yaml create mode 100644 charts/version-guard/templates/_helpers.tpl create mode 100644 charts/version-guard/templates/deployment.yaml create mode 100644 charts/version-guard/templates/service.yaml create mode 100644 charts/version-guard/templates/serviceaccount.yaml create mode 100644 charts/version-guard/values.yaml create mode 100644 deploy/Dockerfile diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..b89329f --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,131 @@ +# .github/workflows/docker.yml — Docker image + Helm chart publishing +# +# Triggers: +# - Pull request: build Docker image (single platform) + lint Helm chart +# - Push to main: build + push Docker image (multi-arch, latest + sha) +# - Tag v*: build + push Docker image (multi-arch) + package + push Helm chart +# +# Published to: +# - Docker: ghcr.io/block/version-guard: +# - Helm: oci://ghcr.io/block/charts/version-guard: + +name: Docker & Helm + +on: + push: + tags: ["v*"] + branches: [main] + pull_request: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + lint-chart: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Install Helm + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 + + - name: Lint Helm chart + uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b # v2.7.0 + + - name: Run chart-testing lint + run: ct lint --target-branch ${{ github.event.pull_request.base.ref }} --charts charts/version-guard + + - name: Check chart version bump + run: | + # If any chart files changed, Chart.yaml version must be bumped. + CHANGED=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }}...HEAD -- 'charts/version-guard/') + if [ -z "$CHANGED" ]; then + echo "No chart changes detected, skipping version check." + exit 0 + fi + + BASE_VERSION=$(git show origin/${{ github.event.pull_request.base.ref }}:charts/version-guard/Chart.yaml 2>/dev/null | grep '^version:' | awk '{print $2}') + HEAD_VERSION=$(grep '^version:' charts/version-guard/Chart.yaml | awk '{print $2}') + + if [ "$BASE_VERSION" = "$HEAD_VERSION" ]; then + echo "::error::Chart files changed but charts/version-guard/Chart.yaml version was not bumped (still ${HEAD_VERSION}). Bump the version before merging." + exit 1 + fi + echo "Chart version bumped: ${BASE_VERSION} → ${HEAD_VERSION}" + + build-and-push: + runs-on: ubuntu-latest + if: github.repository == 'block/Version-Guard' + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Set up QEMU + if: github.event_name != 'pull_request' + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + + - name: Login to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 + id: meta + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=sha,prefix= + type=semver,pattern=v{{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + with: + context: . + file: deploy/Dockerfile + # Multi-arch on push/tag, single platform on PR for speed + platforms: ${{ github.event_name != 'pull_request' && 'linux/amd64,linux/arm64' || 'linux/amd64' }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VERSION=${{ github.ref_name }} + COMMIT=${{ github.sha }} + BUILD_DATE=${{ github.event.head_commit.timestamp || github.event.pull_request.updated_at }} + + # Helm chart publishing (only on tags) + - name: Install Helm + if: startsWith(github.ref, 'refs/tags/v') + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 + + - name: Package and push Helm chart + if: startsWith(github.ref, 'refs/tags/v') + run: | + # Chart.yaml is the source of truth for version and appVersion. + # Validate that the tag matches what's committed. + TAG_VERSION="${GITHUB_REF_NAME#v}" + CHART_VERSION=$(grep '^version:' charts/version-guard/Chart.yaml | awk '{print $2}') + if [ "$TAG_VERSION" != "$CHART_VERSION" ]; then + echo "::error::Tag ${GITHUB_REF_NAME} does not match Chart.yaml version ${CHART_VERSION}. Bump Chart.yaml before tagging." + exit 1 + fi + + # Package and push + helm package charts/version-guard --destination ./build + echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin + helm push ./build/version-guard-${CHART_VERSION}.tgz oci://ghcr.io/block/charts diff --git a/Makefile b/Makefile index ef15cba..be11b07 100644 --- a/Makefile +++ b/Makefile @@ -182,7 +182,7 @@ run-server: build ## Run gRPC server locally .PHONY: docker-build docker-build: ## Build Docker image @echo "🐳 Building Docker image..." - @docker build -t block/version-guard:latest . + @docker build -t block/version-guard:latest -f deploy/Dockerfile . @echo "✅ Docker image built: block/version-guard:latest" .PHONY: docker-run diff --git a/charts/version-guard/.helmignore b/charts/version-guard/.helmignore new file mode 100644 index 0000000..59b1bea --- /dev/null +++ b/charts/version-guard/.helmignore @@ -0,0 +1,3 @@ +.git +.gitignore +*.md diff --git a/charts/version-guard/Chart.yaml b/charts/version-guard/Chart.yaml new file mode 100644 index 0000000..ad54c91 --- /dev/null +++ b/charts/version-guard/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v2 +name: version-guard +description: Cloud infrastructure version drift and EOL detection +type: application +version: 0.1.0 +appVersion: "0.1.0" +maintainers: + - name: bakayolo + url: https://github.com/bakayolo diff --git a/charts/version-guard/templates/_helpers.tpl b/charts/version-guard/templates/_helpers.tpl new file mode 100644 index 0000000..b5d8a23 --- /dev/null +++ b/charts/version-guard/templates/_helpers.tpl @@ -0,0 +1,58 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "version-guard.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "version-guard.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Common labels. +*/}} +{{- define "version-guard.labels" -}} +helm.sh/chart: {{ include "version-guard.chart" . }} +{{ include "version-guard.selectorLabels" . }} +app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels. +*/}} +{{- define "version-guard.selectorLabels" -}} +app.kubernetes.io/name: {{ include "version-guard.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Chart label. +*/}} +{{- define "version-guard.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Service account name. +*/}} +{{- define "version-guard.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "version-guard.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/version-guard/templates/deployment.yaml b/charts/version-guard/templates/deployment.yaml new file mode 100644 index 0000000..800ae5e --- /dev/null +++ b/charts/version-guard/templates/deployment.yaml @@ -0,0 +1,74 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "version-guard.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "version-guard.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "version-guard.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "version-guard.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "version-guard.serviceAccountName" . }} + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + containers: + - name: version-guard + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: grpc + containerPort: 8080 + protocol: TCP + env: + - name: GRPC_PORT + value: "8080" + {{- with .Values.env }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: tmp + mountPath: /tmp + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + volumes: + - name: tmp + emptyDir: {} + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/version-guard/templates/service.yaml b/charts/version-guard/templates/service.yaml new file mode 100644 index 0000000..49d4bbf --- /dev/null +++ b/charts/version-guard/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "version-guard.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "version-guard.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: grpc + protocol: TCP + name: grpc + selector: + {{- include "version-guard.selectorLabels" . | nindent 4 }} diff --git a/charts/version-guard/templates/serviceaccount.yaml b/charts/version-guard/templates/serviceaccount.yaml new file mode 100644 index 0000000..6b879c6 --- /dev/null +++ b/charts/version-guard/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +automountServiceAccountToken: false +metadata: + name: {{ include "version-guard.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "version-guard.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/version-guard/values.yaml b/charts/version-guard/values.yaml new file mode 100644 index 0000000..584d349 --- /dev/null +++ b/charts/version-guard/values.yaml @@ -0,0 +1,62 @@ +# Version Guard Helm Chart — Default Values + +replicaCount: 1 + +image: + repository: ghcr.io/block/version-guard + tag: "" # defaults to appVersion + pullPolicy: IfNotPresent + +serviceAccount: + create: true + name: "" + annotations: {} + # eks.amazonaws.com/role-arn: arn:aws:iam::role/version-guard + +service: + type: ClusterIP + port: 8080 + +# Environment variables for the Version Guard container. +# All runtime config is via env vars — see .env.example for the full list. +env: [] + # - name: TEMPORAL_ENDPOINT + # value: "temporal.default.svc.cluster.local:7233" + # - name: TEMPORAL_NAMESPACE + # value: "version-guard" + # - name: WIZ_CLIENT_ID_SECRET + # valueFrom: + # secretKeyRef: + # name: version-guard-wiz + # key: client-id + # - name: WIZ_CLIENT_SECRET_SECRET + # valueFrom: + # secretKeyRef: + # name: version-guard-wiz + # key: client-secret + # - name: S3_BUCKET + # value: "version-guard-snapshots" + # - name: AWS_REGION + # value: "us-west-2" + +# Additional volume mounts. +extraVolumeMounts: [] + +# Additional volumes. +extraVolumes: [] + +resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: 1000m + memory: 512Mi + +# Pod annotations (e.g., IAM role, Datadog). +podAnnotations: {} + +# Node selector, tolerations, affinity. +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/deploy/Dockerfile b/deploy/Dockerfile new file mode 100644 index 0000000..07d32f7 --- /dev/null +++ b/deploy/Dockerfile @@ -0,0 +1,48 @@ +# Version Guard Docker Image +# +# Build: +# docker build -t version-guard -f deploy/Dockerfile . +# +# Run: +# docker run -p 8080:8080 --env-file .env version-guard + +# Build stage +FROM golang:1.24.2-alpine AS builder + +RUN apk add --no-cache git ca-certificates + +WORKDIR /src + +# Copy go mod files first for better caching +COPY go.mod go.sum ./ +RUN go mod download + +# Copy source code +COPY . . + +# Build the server binary +ARG VERSION=dev +ARG COMMIT=unknown +ARG BUILD_DATE=unknown +RUN CGO_ENABLED=0 GOOS=linux go build \ + -ldflags="-s -w -X main.version=${VERSION}" \ + -o /version-guard ./cmd/server + +# Runtime stage +FROM alpine:3.21 + +RUN apk add --no-cache ca-certificates tzdata + +# Create non-root user +RUN adduser -D -u 1000 version-guard +USER version-guard + +WORKDIR /app + +# Copy binary from builder +COPY --from=builder /version-guard /app/version-guard + +# gRPC port +EXPOSE 8080 + +ENTRYPOINT ["/app/version-guard"] From fbc42fbcfbc907eef7817801ddb096d628d4e9d0 Mon Sep 17 00:00:00 2001 From: Ben Apprederisse Date: Thu, 9 Apr 2026 13:40:03 -0700 Subject: [PATCH 2/7] =?UTF-8?q?Fix=20golangci-lint=20version:=20v1.62.2=20?= =?UTF-8?q?=E2=86=92=20v1.64.8=20(Go=201.24=20compat)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Amp-Thread-ID: https://ampcode.com/threads/T-019d73dd-3cfe-773a-898f-7f7efbb7eb68 Co-authored-by: Amp --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4482c89..0c4d1e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: - name: Run linter uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 with: - version: v1.62.2 + version: v1.64.8 - name: Check formatting run: | From 8c1fd5a91c1b380201382b158373ee2c43f19ba3 Mon Sep 17 00:00:00 2001 From: Ben Apprederisse Date: Thu, 9 Apr 2026 14:47:24 -0700 Subject: [PATCH 3/7] Lint only new issues on PRs (pre-existing lint errors in codebase) Amp-Thread-ID: https://ampcode.com/threads/T-019d73dd-3cfe-773a-898f-7f7efbb7eb68 Co-authored-by: Amp --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0c4d1e5..0983b5a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,9 +28,10 @@ jobs: run: make test-coverage - name: Run linter - uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 - with: - version: v1.64.8 + uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 + with: + version: v1.64.8 + only-new-issues: true - name: Check formatting run: | From ce535e0dfe27538bdb09d8bdf685364b50b1a2f2 Mon Sep 17 00:00:00 2001 From: Ben Apprederisse Date: Thu, 9 Apr 2026 14:58:10 -0700 Subject: [PATCH 4/7] Minimize: remove comments, unused args, verbose examples (-192 lines) Amp-Thread-ID: https://ampcode.com/threads/T-019d73dd-3cfe-773a-898f-7f7efbb7eb68 Co-authored-by: Amp --- .github/workflows/docker.yml | 66 +++---------------- charts/version-guard/templates/_helpers.tpl | 26 ++------ .../version-guard/templates/deployment.yaml | 37 ----------- .../templates/serviceaccount.yaml | 1 - charts/version-guard/values.yaml | 39 +---------- deploy/Dockerfile | 39 +---------- 6 files changed, 16 insertions(+), 192 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b89329f..1f2e9b7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,14 +1,3 @@ -# .github/workflows/docker.yml — Docker image + Helm chart publishing -# -# Triggers: -# - Pull request: build Docker image (single platform) + lint Helm chart -# - Push to main: build + push Docker image (multi-arch, latest + sha) -# - Tag v*: build + push Docker image (multi-arch) + package + push Helm chart -# -# Published to: -# - Docker: ghcr.io/block/version-guard: -# - Helm: oci://ghcr.io/block/charts/version-guard: - name: Docker & Helm on: @@ -29,33 +18,9 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - - - name: Install Helm - uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 - - - name: Lint Helm chart - uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b # v2.7.0 - - - name: Run chart-testing lint - run: ct lint --target-branch ${{ github.event.pull_request.base.ref }} --charts charts/version-guard - - - name: Check chart version bump - run: | - # If any chart files changed, Chart.yaml version must be bumped. - CHANGED=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }}...HEAD -- 'charts/version-guard/') - if [ -z "$CHANGED" ]; then - echo "No chart changes detected, skipping version check." - exit 0 - fi - - BASE_VERSION=$(git show origin/${{ github.event.pull_request.base.ref }}:charts/version-guard/Chart.yaml 2>/dev/null | grep '^version:' | awk '{print $2}') - HEAD_VERSION=$(grep '^version:' charts/version-guard/Chart.yaml | awk '{print $2}') - - if [ "$BASE_VERSION" = "$HEAD_VERSION" ]; then - echo "::error::Chart files changed but charts/version-guard/Chart.yaml version was not bumped (still ${HEAD_VERSION}). Bump the version before merging." - exit 1 - fi - echo "Chart version bumped: ${BASE_VERSION} → ${HEAD_VERSION}" + - uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 + - uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b # v2.7.0 + - run: ct lint --target-branch ${{ github.event.pull_request.base.ref }} --charts charts/version-guard build-and-push: runs-on: ubuntu-latest @@ -63,7 +28,6 @@ jobs: permissions: contents: read packages: write - steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -71,10 +35,9 @@ jobs: if: github.event_name != 'pull_request' uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - name: Login to GitHub Container Registry + - name: Login to GHCR if: github.event_name != 'pull_request' uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: @@ -93,39 +56,26 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=raw,value=latest,enable={{is_default_branch}} - - name: Build and push Docker image + - name: Build and push uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . file: deploy/Dockerfile - # Multi-arch on push/tag, single platform on PR for speed platforms: ${{ github.event_name != 'pull_request' && 'linux/amd64,linux/arm64' || 'linux/amd64' }} push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - build-args: | - VERSION=${{ github.ref_name }} - COMMIT=${{ github.sha }} - BUILD_DATE=${{ github.event.head_commit.timestamp || github.event.pull_request.updated_at }} - - # Helm chart publishing (only on tags) - - name: Install Helm - if: startsWith(github.ref, 'refs/tags/v') - uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 + build-args: VERSION=${{ github.ref_name }} - name: Package and push Helm chart if: startsWith(github.ref, 'refs/tags/v') run: | - # Chart.yaml is the source of truth for version and appVersion. - # Validate that the tag matches what's committed. TAG_VERSION="${GITHUB_REF_NAME#v}" CHART_VERSION=$(grep '^version:' charts/version-guard/Chart.yaml | awk '{print $2}') if [ "$TAG_VERSION" != "$CHART_VERSION" ]; then - echo "::error::Tag ${GITHUB_REF_NAME} does not match Chart.yaml version ${CHART_VERSION}. Bump Chart.yaml before tagging." + echo "::error::Tag ${GITHUB_REF_NAME} does not match Chart.yaml version ${CHART_VERSION}" exit 1 fi - - # Package and push helm package charts/version-guard --destination ./build echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin helm push ./build/version-guard-${CHART_VERSION}.tgz oci://ghcr.io/block/charts diff --git a/charts/version-guard/templates/_helpers.tpl b/charts/version-guard/templates/_helpers.tpl index b5d8a23..54e2102 100644 --- a/charts/version-guard/templates/_helpers.tpl +++ b/charts/version-guard/templates/_helpers.tpl @@ -1,13 +1,7 @@ -{{/* -Expand the name of the chart. -*/}} {{- define "version-guard.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} -{{/* -Create a default fully qualified app name. -*/}} {{- define "version-guard.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} @@ -21,9 +15,10 @@ Create a default fully qualified app name. {{- end }} {{- end }} -{{/* -Common labels. -*/}} +{{- define "version-guard.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + {{- define "version-guard.labels" -}} helm.sh/chart: {{ include "version-guard.chart" . }} {{ include "version-guard.selectorLabels" . }} @@ -31,24 +26,11 @@ app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | qu app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} -{{/* -Selector labels. -*/}} {{- define "version-guard.selectorLabels" -}} app.kubernetes.io/name: {{ include "version-guard.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} -{{/* -Chart label. -*/}} -{{- define "version-guard.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Service account name. -*/}} {{- define "version-guard.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} {{- default (include "version-guard.fullname" .) .Values.serviceAccount.name }} diff --git a/charts/version-guard/templates/deployment.yaml b/charts/version-guard/templates/deployment.yaml index 800ae5e..5c07ec5 100644 --- a/charts/version-guard/templates/deployment.yaml +++ b/charts/version-guard/templates/deployment.yaml @@ -12,10 +12,6 @@ spec: {{- include "version-guard.selectorLabels" . | nindent 6 }} template: metadata: - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} labels: {{- include "version-guard.selectorLabels" . | nindent 8 }} spec: @@ -23,17 +19,8 @@ spec: securityContext: runAsNonRoot: true runAsUser: 1000 - fsGroup: 1000 - seccompProfile: - type: RuntimeDefault containers: - name: version-guard - securityContext: - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - allowPrivilegeEscalation: false image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: @@ -48,27 +35,3 @@ spec: {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} - volumeMounts: - - name: tmp - mountPath: /tmp - {{- with .Values.extraVolumeMounts }} - {{- toYaml . | nindent 12 }} - {{- end }} - volumes: - - name: tmp - emptyDir: {} - {{- with .Values.extraVolumes }} - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} diff --git a/charts/version-guard/templates/serviceaccount.yaml b/charts/version-guard/templates/serviceaccount.yaml index 6b879c6..73de219 100644 --- a/charts/version-guard/templates/serviceaccount.yaml +++ b/charts/version-guard/templates/serviceaccount.yaml @@ -1,7 +1,6 @@ {{- if .Values.serviceAccount.create }} apiVersion: v1 kind: ServiceAccount -automountServiceAccountToken: false metadata: name: {{ include "version-guard.serviceAccountName" . }} namespace: {{ .Release.Namespace }} diff --git a/charts/version-guard/values.yaml b/charts/version-guard/values.yaml index 584d349..2a9f5ef 100644 --- a/charts/version-guard/values.yaml +++ b/charts/version-guard/values.yaml @@ -1,49 +1,20 @@ -# Version Guard Helm Chart — Default Values - replicaCount: 1 image: repository: ghcr.io/block/version-guard - tag: "" # defaults to appVersion + tag: "" pullPolicy: IfNotPresent serviceAccount: create: true name: "" annotations: {} - # eks.amazonaws.com/role-arn: arn:aws:iam::role/version-guard service: type: ClusterIP port: 8080 -# Environment variables for the Version Guard container. -# All runtime config is via env vars — see .env.example for the full list. env: [] - # - name: TEMPORAL_ENDPOINT - # value: "temporal.default.svc.cluster.local:7233" - # - name: TEMPORAL_NAMESPACE - # value: "version-guard" - # - name: WIZ_CLIENT_ID_SECRET - # valueFrom: - # secretKeyRef: - # name: version-guard-wiz - # key: client-id - # - name: WIZ_CLIENT_SECRET_SECRET - # valueFrom: - # secretKeyRef: - # name: version-guard-wiz - # key: client-secret - # - name: S3_BUCKET - # value: "version-guard-snapshots" - # - name: AWS_REGION - # value: "us-west-2" - -# Additional volume mounts. -extraVolumeMounts: [] - -# Additional volumes. -extraVolumes: [] resources: requests: @@ -52,11 +23,3 @@ resources: limits: cpu: 1000m memory: 512Mi - -# Pod annotations (e.g., IAM role, Datadog). -podAnnotations: {} - -# Node selector, tolerations, affinity. -nodeSelector: {} -tolerations: [] -affinity: {} diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 07d32f7..186b5f7 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -1,48 +1,15 @@ -# Version Guard Docker Image -# -# Build: -# docker build -t version-guard -f deploy/Dockerfile . -# -# Run: -# docker run -p 8080:8080 --env-file .env version-guard - -# Build stage FROM golang:1.24.2-alpine AS builder - -RUN apk add --no-cache git ca-certificates - +RUN apk add --no-cache git WORKDIR /src - -# Copy go mod files first for better caching COPY go.mod go.sum ./ RUN go mod download - -# Copy source code COPY . . - -# Build the server binary ARG VERSION=dev -ARG COMMIT=unknown -ARG BUILD_DATE=unknown -RUN CGO_ENABLED=0 GOOS=linux go build \ - -ldflags="-s -w -X main.version=${VERSION}" \ - -o /version-guard ./cmd/server +RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=${VERSION}" -o /version-guard ./cmd/server -# Runtime stage FROM alpine:3.21 - -RUN apk add --no-cache ca-certificates tzdata - -# Create non-root user -RUN adduser -D -u 1000 version-guard +RUN apk add --no-cache ca-certificates tzdata && adduser -D -u 1000 version-guard USER version-guard - -WORKDIR /app - -# Copy binary from builder COPY --from=builder /version-guard /app/version-guard - -# gRPC port EXPOSE 8080 - ENTRYPOINT ["/app/version-guard"] From 54333cfbf6d73c84b664db27c2a26708f01e3dc5 Mon Sep 17 00:00:00 2001 From: Ben Apprederisse Date: Fri, 10 Apr 2026 14:37:02 -0700 Subject: [PATCH 5/7] Fix CI, Helm chart, and Dockerfile issues - Fix YAML indentation in test.yml (broken linter step) - Add explicit permissions to lint-chart job - Add semver tag without v-prefix to match Chart.yaml appVersion - Use yq instead of grep/awk for Chart.yaml parsing - Add container-level securityContext (readOnlyRootFilesystem, drop ALL caps) - Extract containerPort to values.yaml for consistency - Add gRPC liveness and readiness probes - Use BuildKit cache mounts in Dockerfile for faster builds Amp-Thread-ID: https://ampcode.com/threads/T-019d794b-a1d1-7425-b8a6-f519f6c68b53 Co-authored-by: Amp --- .github/workflows/docker.yml | 5 ++++- .github/workflows/test.yml | 8 ++++---- .../version-guard/templates/deployment.yaml | 20 +++++++++++++++++-- charts/version-guard/values.yaml | 2 ++ deploy/Dockerfile | 5 +++-- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1f2e9b7..480ae70 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -14,6 +14,8 @@ jobs: lint-chart: runs-on: ubuntu-latest if: github.event_name == 'pull_request' + permissions: + contents: read steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: @@ -53,6 +55,7 @@ jobs: tags: | type=sha,prefix= type=semver,pattern=v{{version}} + type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=raw,value=latest,enable={{is_default_branch}} @@ -71,7 +74,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') run: | TAG_VERSION="${GITHUB_REF_NAME#v}" - CHART_VERSION=$(grep '^version:' charts/version-guard/Chart.yaml | awk '{print $2}') + CHART_VERSION=$(yq '.version' charts/version-guard/Chart.yaml) if [ "$TAG_VERSION" != "$CHART_VERSION" ]; then echo "::error::Tag ${GITHUB_REF_NAME} does not match Chart.yaml version ${CHART_VERSION}" exit 1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0983b5a..21b4fbf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,10 +28,10 @@ jobs: run: make test-coverage - name: Run linter - uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 - with: - version: v1.64.8 - only-new-issues: true + uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 + with: + version: v1.64.8 + only-new-issues: true - name: Check formatting run: | diff --git a/charts/version-guard/templates/deployment.yaml b/charts/version-guard/templates/deployment.yaml index 5c07ec5..59e2d66 100644 --- a/charts/version-guard/templates/deployment.yaml +++ b/charts/version-guard/templates/deployment.yaml @@ -23,15 +23,31 @@ spec: - name: version-guard image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL ports: - name: grpc - containerPort: 8080 + containerPort: {{ .Values.containerPort }} protocol: TCP env: - name: GRPC_PORT - value: "8080" + value: {{ .Values.containerPort | quote }} {{- with .Values.env }} {{- toYaml . | nindent 12 }} {{- end }} + livenessProbe: + grpc: + port: {{ .Values.containerPort }} + initialDelaySeconds: 10 + periodSeconds: 15 + readinessProbe: + grpc: + port: {{ .Values.containerPort }} + initialDelaySeconds: 5 + periodSeconds: 10 resources: {{- toYaml .Values.resources | nindent 12 }} diff --git a/charts/version-guard/values.yaml b/charts/version-guard/values.yaml index 2a9f5ef..9f53f48 100644 --- a/charts/version-guard/values.yaml +++ b/charts/version-guard/values.yaml @@ -10,6 +10,8 @@ serviceAccount: name: "" annotations: {} +containerPort: 8080 + service: type: ClusterIP port: 8080 diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 186b5f7..730da3b 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -2,10 +2,11 @@ FROM golang:1.24.2-alpine AS builder RUN apk add --no-cache git WORKDIR /src COPY go.mod go.sum ./ -RUN go mod download +RUN --mount=type=cache,target=/go/pkg/mod go mod download COPY . . ARG VERSION=dev -RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=${VERSION}" -o /version-guard ./cmd/server +RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \ + CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=${VERSION}" -o /version-guard ./cmd/server FROM alpine:3.21 RUN apk add --no-cache ca-certificates tzdata && adduser -D -u 1000 version-guard From 21f84858b3a6ec6fbe44467e3fba7beab5b97741 Mon Sep 17 00:00:00 2001 From: Ben Apprederisse Date: Fri, 10 Apr 2026 14:40:14 -0700 Subject: [PATCH 6/7] Fix remaining medium-severity review items - Add top-level permissions: {} for least-privilege default - Pass secrets/context vars via env: blocks instead of inline interpolation - Switch gRPC probes to tcpSocket (server lacks grpc.health.v1.Health) - Add seccompProfile: RuntimeDefault for restricted PSS compliance Amp-Thread-ID: https://ampcode.com/threads/T-019d794b-a1d1-7425-b8a6-f519f6c68b53 Co-authored-by: Amp --- .github/workflows/docker.yml | 11 +++++++++-- charts/version-guard/templates/deployment.yaml | 10 ++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 480ae70..1bfa3ba 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,5 +1,7 @@ name: Docker & Helm +permissions: {} + on: push: tags: ["v*"] @@ -22,7 +24,9 @@ jobs: fetch-depth: 0 - uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 - uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b # v2.7.0 - - run: ct lint --target-branch ${{ github.event.pull_request.base.ref }} --charts charts/version-guard + - run: ct lint --target-branch "$BASE_REF" --charts charts/version-guard + env: + BASE_REF: ${{ github.event.pull_request.base.ref }} build-and-push: runs-on: ubuntu-latest @@ -80,5 +84,8 @@ jobs: exit 1 fi helm package charts/version-guard --destination ./build - echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin + echo "$HELM_TOKEN" | helm registry login ghcr.io -u "$HELM_USER" --password-stdin helm push ./build/version-guard-${CHART_VERSION}.tgz oci://ghcr.io/block/charts + env: + HELM_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HELM_USER: ${{ github.actor }} diff --git a/charts/version-guard/templates/deployment.yaml b/charts/version-guard/templates/deployment.yaml index 59e2d66..97ad4f2 100644 --- a/charts/version-guard/templates/deployment.yaml +++ b/charts/version-guard/templates/deployment.yaml @@ -19,6 +19,8 @@ spec: securityContext: runAsNonRoot: true runAsUser: 1000 + seccompProfile: + type: RuntimeDefault containers: - name: version-guard image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" @@ -40,13 +42,13 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} livenessProbe: - grpc: - port: {{ .Values.containerPort }} + tcpSocket: + port: grpc initialDelaySeconds: 10 periodSeconds: 15 readinessProbe: - grpc: - port: {{ .Values.containerPort }} + tcpSocket: + port: grpc initialDelaySeconds: 5 periodSeconds: 10 resources: From 45c3787215215025e52778deaa33f992b6c20a1f Mon Sep 17 00:00:00 2001 From: Ben Apprederisse Date: Fri, 10 Apr 2026 14:48:13 -0700 Subject: [PATCH 7/7] Fix gofmt formatting drift Amp-Thread-ID: https://ampcode.com/threads/T-019d794b-a1d1-7425-b8a6-f519f6c68b53 Co-authored-by: Amp --- pkg/eol/aws/eks_endoflife_test.go | 6 +++--- pkg/eol/endoflife/client.go | 14 +++++++------- pkg/eol/endoflife/integration_test.go | 1 + pkg/eol/endoflife/provider.go | 24 ++++++++++++------------ pkg/inventory/wiz/client.go | 1 - 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pkg/eol/aws/eks_endoflife_test.go b/pkg/eol/aws/eks_endoflife_test.go index f5d24ca..36bfeb2 100644 --- a/pkg/eol/aws/eks_endoflife_test.go +++ b/pkg/eol/aws/eks_endoflife_test.go @@ -166,9 +166,9 @@ func TestEKSEOLProvider_WithEndOfLifeClient(t *testing.T) { return []*endoflife.ProductCycle{ { Cycle: "1.35", - ReleaseDate: "2025-11-20", // Different from static - EOL: "2027-12-20", // End of standard support - ExtendedSupport: "2029-05-20", // End of extended support + ReleaseDate: "2025-11-20", // Different from static + EOL: "2027-12-20", // End of standard support + ExtendedSupport: "2029-05-20", // End of extended support }, }, nil }, diff --git a/pkg/eol/endoflife/client.go b/pkg/eol/endoflife/client.go index 1a74f26..91d412d 100644 --- a/pkg/eol/endoflife/client.go +++ b/pkg/eol/endoflife/client.go @@ -29,13 +29,13 @@ type Client interface { // ProductCycle represents a single version/cycle from endoflife.date API // API docs: https://endoflife.date/docs/api/ type ProductCycle struct { - Cycle string `json:"cycle"` // Version identifier (e.g., "1.31") - ReleaseDate string `json:"releaseDate"` // Release date (YYYY-MM-DD) - Support string `json:"support"` // End of standard support (YYYY-MM-DD or boolean) - EOL string `json:"eol"` // End of life date (YYYY-MM-DD or boolean) - ExtendedSupport any `json:"extendedSupport"` // Extended support availability (boolean or date) - LTS bool `json:"lts"` // Long-term support flag - Latest string `json:"latest"` // Latest patch version + Cycle string `json:"cycle"` // Version identifier (e.g., "1.31") + ReleaseDate string `json:"releaseDate"` // Release date (YYYY-MM-DD) + Support string `json:"support"` // End of standard support (YYYY-MM-DD or boolean) + EOL string `json:"eol"` // End of life date (YYYY-MM-DD or boolean) + ExtendedSupport any `json:"extendedSupport"` // Extended support availability (boolean or date) + LTS bool `json:"lts"` // Long-term support flag + Latest string `json:"latest"` // Latest patch version LatestReleaseDate string `json:"latestReleaseDate"` // Latest patch release date } diff --git a/pkg/eol/endoflife/integration_test.go b/pkg/eol/endoflife/integration_test.go index 98764d4..b261b3d 100644 --- a/pkg/eol/endoflife/integration_test.go +++ b/pkg/eol/endoflife/integration_test.go @@ -1,3 +1,4 @@ +//go:build integration // +build integration package endoflife diff --git a/pkg/eol/endoflife/provider.go b/pkg/eol/endoflife/provider.go index 36dce78..770580b 100644 --- a/pkg/eol/endoflife/provider.go +++ b/pkg/eol/endoflife/provider.go @@ -15,18 +15,18 @@ import ( // ProductMapping maps internal engine names to endoflife.date product identifiers var ProductMapping = map[string]string{ - "kubernetes": "amazon-eks", - "k8s": "amazon-eks", - "eks": "amazon-eks", - "postgres": "amazon-rds-postgresql", - "postgresql": "amazon-rds-postgresql", - "mysql": "amazon-rds-mysql", - "aurora-mysql": "amazon-rds-mysql", - "aurora-postgresql": "amazon-rds-postgresql", - "redis": "amazon-elasticache-redis", - "elasticache-redis": "amazon-elasticache-redis", - "valkey": "valkey", - "elasticache-valkey": "valkey", + "kubernetes": "amazon-eks", + "k8s": "amazon-eks", + "eks": "amazon-eks", + "postgres": "amazon-rds-postgresql", + "postgresql": "amazon-rds-postgresql", + "mysql": "amazon-rds-mysql", + "aurora-mysql": "amazon-rds-mysql", + "aurora-postgresql": "amazon-rds-postgresql", + "redis": "amazon-elasticache-redis", + "elasticache-redis": "amazon-elasticache-redis", + "valkey": "valkey", + "elasticache-valkey": "valkey", } // Provider fetches EOL data from endoflife.date API diff --git a/pkg/inventory/wiz/client.go b/pkg/inventory/wiz/client.go index 9a1179d..b438178 100644 --- a/pkg/inventory/wiz/client.go +++ b/pkg/inventory/wiz/client.go @@ -222,4 +222,3 @@ func parseTagObject(tagObj string) (key, value string) { return key, value } -