From 132c1aca42a2991c78af6ad7bc6c16b46dfe3ec7 Mon Sep 17 00:00:00 2001 From: Matthew Pagan Date: Mon, 11 May 2026 15:21:24 -0400 Subject: [PATCH 1/5] add validate and secrets workflows --- .github/workflows/secrets.yml | 38 +++++++++++++++++++++++ .github/workflows/validate.yml | 46 +++++++++++++++++++++++++++ .yamllint.yaml | 27 ++++++++++++++++ README.md | 6 ++++ ci/validate.sh | 57 ++++++++++++++++++++++++++++++++++ 5 files changed, 174 insertions(+) create mode 100644 .github/workflows/secrets.yml create mode 100644 .github/workflows/validate.yml create mode 100644 .yamllint.yaml create mode 100755 ci/validate.sh diff --git a/.github/workflows/secrets.yml b/.github/workflows/secrets.yml new file mode 100644 index 0000000..11590dc --- /dev/null +++ b/.github/workflows/secrets.yml @@ -0,0 +1,38 @@ +name: secrets + +# Server-side complement to the gitleaks pre-commit hook in .pre-commit-config.yaml. +# +# Why both: +# - Pre-commit runs locally and is bypassable (`git commit --no-verify`), +# skippable (if `pre-commit install` was never run), and doesn't fire for +# commits authored via the GitHub web UI, mobile app, or REST API. +# - This workflow runs in CI on every PR + push, can be enforced via branch +# protection ("required check"), and is the last line before merge. +# +# Both use the same gitleaks engine, so detection rules are consistent. + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + gitleaks: + name: gitleaks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # Fetch the full history so gitleaks can scan all commits in the PR, + # not just the tip. Catches secrets that were committed and then + # "deleted" (still in history) without being rotated. + fetch-depth: 0 + + - uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # GITLEAKS_LICENSE is required only for org-owned private repos. + # This repo is public, so the license check is skipped automatically. diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..fe057f2 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,46 @@ +name: validate + +# Pre-merge validation: yamllint, kustomize rendering, kubeconform, shellcheck. +# Calls the same script developers can run locally — see ci/validate.sh and +# the README "Optional, for running CI checks locally" line in Prerequisites. + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + validate: + name: validate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - run: pip install --quiet yamllint + + - name: Install kustomize + run: | + curl -sfL "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash + sudo mv kustomize /usr/local/bin/ + + # helm is required for kustomize's `--enable-helm` flag (renders the + # helmCharts: block in manifests/quine-enterprise/). + - uses: azure/setup-helm@v4 + with: + version: v3.16.0 + + - name: Install kubeconform + run: | + curl -sfL "https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz" \ + | sudo tar -xz -C /usr/local/bin kubeconform + + # shellcheck is pre-installed on ubuntu-latest runners. + + - name: Run validation + run: ./ci/validate.sh diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..0ddf384 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,27 @@ +# yamllint config — relaxed preset, with rules tuned for this repo. +# +# We disable line-length because Kustomize patches, Helm values, and operator +# CRs commonly carry long URLs or composite identifiers. We disable +# `truthy.check-keys` because Kubernetes manifests use `on:` (workflow trigger) +# and similar field names that yamllint would otherwise flag. +# +# What still fires (and should): +# - syntax errors +# - duplicate keys +# - inconsistent indentation +# - trailing whitespace +# - missing document start where required by surrounding context + +extends: relaxed + +rules: + line-length: disable + truthy: + check-keys: false + +ignore: | + charts/ + **/charts/ + tmp/ + temp/ + .pre-commit-cache/ diff --git a/README.md b/README.md index 407d6f3..a0f62a4 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,12 @@ export THATDOT_REGISTRY_USERNAME="..." export THATDOT_REGISTRY_PASSWORD="..." ``` +Optional — for reproducing the CI validation checks locally before pushing (`./ci/validate.sh` runs the same checks `.github/workflows/validate.yml` runs): + +```bash +brew install yamllint shellcheck kustomize helm kubeconform +``` + ## First-time setup ```bash diff --git a/ci/validate.sh b/ci/validate.sh new file mode 100755 index 0000000..033d770 --- /dev/null +++ b/ci/validate.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -uo pipefail + +# Validates the manifest tree, Kustomize rendering, and helper scripts. +# Same checks the .github/workflows/validate.yml workflow runs — install the +# tools locally and you can reproduce CI before pushing. +# +# Tools required: +# yamllint, shellcheck, kustomize, helm, kubeconform +# +# macOS install: +# brew install yamllint shellcheck kustomize helm kubeconform +# +# Usage: ./ci/validate.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$PROJECT_DIR" || exit 1 + +# ---- Tool check ---- +missing=() +for tool in yamllint shellcheck kustomize helm kubeconform; do + command -v "$tool" >/dev/null 2>&1 || missing+=("$tool") +done +if [[ ${#missing[@]} -gt 0 ]]; then + echo "ERROR: missing required tools: ${missing[*]}" + echo " macOS install: brew install ${missing[*]}" + exit 1 +fi + +# Run all checks, collecting failures rather than aborting on the first one — +# so the developer sees the full picture in a single run. +failed=() + +echo "==> yamllint" +yamllint . || failed+=("yamllint") + +echo "" +echo "==> shellcheck scripts/*.sh ci/*.sh" +shellcheck scripts/*.sh ci/*.sh || failed+=("shellcheck") + +echo "" +echo "==> kustomize + kubeconform per leaf" +for leaf in manifests/root manifests/platform manifests/product manifests/cassandra manifests/keycloak manifests/quine-enterprise; do + echo " --- $leaf ---" + if ! kustomize build --enable-helm "$leaf" \ + | kubeconform --strict --ignore-missing-schemas --summary; then + failed+=("$leaf") + fi +done + +echo "" +if [[ ${#failed[@]} -gt 0 ]]; then + echo "FAILED: ${failed[*]}" + exit 1 +fi +echo "All checks passed." From 68d9f5302c6720e137b9bd8486d060e434cb49bf Mon Sep 17 00:00:00 2001 From: Matthew Pagan Date: Mon, 11 May 2026 15:25:53 -0400 Subject: [PATCH 2/5] use free gitleaks cli, not paid for action --- .github/workflows/secrets.yml | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/.github/workflows/secrets.yml b/.github/workflows/secrets.yml index 11590dc..1c6b516 100644 --- a/.github/workflows/secrets.yml +++ b/.github/workflows/secrets.yml @@ -1,15 +1,18 @@ name: secrets # Server-side complement to the gitleaks pre-commit hook in .pre-commit-config.yaml. +# Uses the MIT-licensed gitleaks CLI directly rather than the gitleaks-action +# wrapper (which requires a paid license for org-owned repos as of v2). # -# Why both: +# Why both pre-commit AND this: # - Pre-commit runs locally and is bypassable (`git commit --no-verify`), # skippable (if `pre-commit install` was never run), and doesn't fire for # commits authored via the GitHub web UI, mobile app, or REST API. # - This workflow runs in CI on every PR + push, can be enforced via branch # protection ("required check"), and is the last line before merge. +# - Same engine, same rules — different enforcement surface. # -# Both use the same gitleaks engine, so detection rules are consistent. +# To bump gitleaks: edit GITLEAKS_VERSION below. on: push: @@ -19,6 +22,9 @@ on: permissions: contents: read +env: + GITLEAKS_VERSION: "8.30.1" + jobs: gitleaks: name: gitleaks @@ -26,13 +32,19 @@ jobs: steps: - uses: actions/checkout@v4 with: - # Fetch the full history so gitleaks can scan all commits in the PR, - # not just the tip. Catches secrets that were committed and then - # "deleted" (still in history) without being rotated. + # Fetch the full history so gitleaks scans every commit in the PR, + # not just the tip. Catches secrets that were committed and later + # "deleted" without being rotated. fetch-depth: 0 - - uses: gitleaks/gitleaks-action@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # GITLEAKS_LICENSE is required only for org-owned private repos. - # This repo is public, so the license check is skipped automatically. + - name: Install gitleaks + run: | + curl -sfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ + | sudo tar -xz -C /usr/local/bin gitleaks + gitleaks version + + - name: Scan repo history + # --redact : redact secret values in the output (don't leak them into CI logs) + # --verbose : per-commit progress (helpful when something fails) + # --no-banner : skip the ASCII art banner + run: gitleaks detect --redact --verbose --no-banner From 9a9054f3b75cece03b5f1ab7328d70e209fddf20 Mon Sep 17 00:00:00 2001 From: Matthew Pagan Date: Mon, 11 May 2026 15:28:38 -0400 Subject: [PATCH 3/5] pin kustomize version --- .github/workflows/validate.yml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index fe057f2..947a8ab 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -3,6 +3,8 @@ name: validate # Pre-merge validation: yamllint, kustomize rendering, kubeconform, shellcheck. # Calls the same script developers can run locally — see ci/validate.sh and # the README "Optional, for running CI checks locally" line in Prerequisites. +# +# Tool versions are pinned via env vars below — bump them as needed. on: push: @@ -12,6 +14,11 @@ on: permissions: contents: read +env: + KUSTOMIZE_VERSION: "5.8.1" + KUBECONFORM_VERSION: "0.7.0" + HELM_VERSION: "v3.16.0" + jobs: validate: name: validate @@ -25,20 +32,26 @@ jobs: - run: pip install --quiet yamllint - name: Install kustomize + # Direct download from a pinned release. (The kubernetes-sigs + # `install_kustomize.sh` script does its own discovery and is flaky + # under GitHub API rate limits — we pin instead.) The release tag is + # `kustomize/v`, so the slash is URL-encoded as `%2F`. run: | - curl -sfL "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash - sudo mv kustomize /usr/local/bin/ + curl -sfL "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_linux_amd64.tar.gz" \ + | sudo tar -xz -C /usr/local/bin kustomize + kustomize version # helm is required for kustomize's `--enable-helm` flag (renders the # helmCharts: block in manifests/quine-enterprise/). - uses: azure/setup-helm@v4 with: - version: v3.16.0 + version: ${{ env.HELM_VERSION }} - name: Install kubeconform run: | - curl -sfL "https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz" \ + curl -sfL "https://github.com/yannh/kubeconform/releases/download/v${KUBECONFORM_VERSION}/kubeconform-linux-amd64.tar.gz" \ | sudo tar -xz -C /usr/local/bin kubeconform + kubeconform -v # shellcheck is pre-installed on ubuntu-latest runners. From 170d5e6469ed6b53389c2988a9bd2180974e312c Mon Sep 17 00:00:00 2001 From: Matthew Pagan Date: Mon, 11 May 2026 15:38:26 -0400 Subject: [PATCH 4/5] validate bootstrap too --- ci/validate.sh | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ci/validate.sh b/ci/validate.sh index 033d770..d239c53 100755 --- a/ci/validate.sh +++ b/ci/validate.sh @@ -32,6 +32,17 @@ fi # so the developer sees the full picture in a single run. failed=() +# Note on `--ignore-missing-schemas`: +# kubeconform validates against built-in Kubernetes schemas only. Custom +# resources (ArgoCD Application, OLM Subscription/OperatorGroup, OpenShift +# Route, CassandraDatacenter, Keycloak, KeycloakRealmImport) are skipped +# rather than failed. To close the gap, pre-download the schemas we want +# from https://github.com/datreeio/CRDs-catalog into ci/schemas/ and add +# --schema-location flags pointing at the local paths. kubeconform v0.7.0 +# doesn't expose a `lower` template fn, so the catalog can't be referenced +# by URL directly (datreeio's filenames are lowercase; {{.ResourceKind}} +# renders PascalCase). Future work. + echo "==> yamllint" yamllint . || failed+=("yamllint") @@ -39,6 +50,20 @@ echo "" echo "==> shellcheck scripts/*.sh ci/*.sh" shellcheck scripts/*.sh ci/*.sh || failed+=("shellcheck") +echo "" +echo "==> kubeconform bootstrap/*.yaml" +# argocd-customizations.yaml is a patch payload (no apiVersion/kind/metadata), +# not a complete manifest — kubeconform would correctly reject it. Skip. +for f in bootstrap/*.yaml; do + case "$f" in + bootstrap/argocd-customizations.yaml) continue ;; + esac + echo " --- $f ---" + if ! kubeconform --strict --ignore-missing-schemas --summary "$f"; then + failed+=("$f") + fi +done + echo "" echo "==> kustomize + kubeconform per leaf" for leaf in manifests/root manifests/platform manifests/product manifests/cassandra manifests/keycloak manifests/quine-enterprise; do From 63ff739245f3a0c7cf274809c87b4d69b4eeb320 Mon Sep 17 00:00:00 2001 From: Matthew Pagan Date: Mon, 11 May 2026 15:51:32 -0400 Subject: [PATCH 5/5] update id of validate job --- .github/workflows/validate.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 947a8ab..a0e7277 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -20,8 +20,7 @@ env: HELM_VERSION: "v3.16.0" jobs: - validate: - name: validate + manifests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4