Add GitOps validation promotion flow
This commit is contained in:
parent
329a3d1abd
commit
0777bed10c
|
|
@ -4,10 +4,35 @@ name: Homelab Main
|
|||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: homelab-debian
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
find . -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
|
||||
git init
|
||||
git remote add origin ssh://git@192.168.100.73:32222/jv/my-homelab-configs.git
|
||||
git fetch --prune origin +refs/heads/main:refs/remotes/origin/main +refs/heads/dev:refs/remotes/origin/dev +refs/tags/*:refs/tags/*
|
||||
git checkout --force "${{ gitea.sha }}"
|
||||
git config --global --add safe.directory "$PWD"
|
||||
|
||||
- name: Validate repository
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
./jeannie validate
|
||||
|
||||
deploy:
|
||||
runs-on: homelab-debian
|
||||
needs:
|
||||
- validate
|
||||
if: ${{ gitea.ref == 'refs/heads/main' }}
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
|
|
@ -21,12 +46,6 @@ jobs:
|
|||
git checkout --force "${{ gitea.sha }}"
|
||||
git config --global --add safe.directory "$PWD"
|
||||
|
||||
- name: Check generated docs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
scripts/render-docs --check
|
||||
|
||||
- name: Block automatic deploy for external Gitea changes
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
|
|
|||
17
README.md
17
README.md
|
|
@ -760,8 +760,21 @@ ls -lh /home/jv/backups/gitea-restore-drills
|
|||
## Gitea Actions
|
||||
|
||||
This repo includes a Gitea Actions workflow at
|
||||
`.gitea/workflows/homelab-main.yml`. It runs only on pushes to `main` and targets
|
||||
a repository-scoped Debian host runner with the label `homelab-debian`.
|
||||
`.gitea/workflows/homelab-main.yml`. It runs validation on pushes to `dev` and
|
||||
`main`, and deploys only from `main`. That keeps the promotion path simple:
|
||||
|
||||
```text
|
||||
dev -> ./jeannie validate in Gitea Actions -> main -> Argo CD sync
|
||||
```
|
||||
|
||||
The workflow targets a repository-scoped Debian host runner with the label
|
||||
`homelab-debian`.
|
||||
|
||||
Run the same validation locally before promoting:
|
||||
|
||||
```bash
|
||||
./jeannie validate
|
||||
```
|
||||
|
||||
The workflow only blocks automatic deploy for external Gitea service
|
||||
changes: files under `infra/gitea/`, or edits inside the `deploy_gitea`,
|
||||
|
|
|
|||
|
|
@ -274,7 +274,9 @@ Run a read-only health snapshot from the Debian server with:
|
|||
|
||||
It reports host memory/disk, systemd services, Docker Compose stacks,
|
||||
Kubernetes health when the API is reachable, Pimox worker VM status, RPi
|
||||
services, Tailscale, and key local/public HTTP endpoints.
|
||||
services, Tailscale, key local/public HTTP endpoints, and "what broke" signals
|
||||
such as deployment readiness, pod restarts, node pressure, disk pressure,
|
||||
Traefik 5xx/404 log evidence, and recent Gitea errors.
|
||||
|
||||
Focused doctor commands run narrower read-only checks and print the most likely
|
||||
next step:
|
||||
|
|
@ -758,8 +760,21 @@ ls -lh /home/jv/backups/gitea-restore-drills
|
|||
## Gitea Actions
|
||||
|
||||
This repo includes a Gitea Actions workflow at
|
||||
`.gitea/workflows/homelab-main.yml`. It runs only on pushes to `main` and targets
|
||||
a repository-scoped Debian host runner with the label `homelab-debian`.
|
||||
`.gitea/workflows/homelab-main.yml`. It runs validation on pushes to `dev` and
|
||||
`main`, and deploys only from `main`. That keeps the promotion path simple:
|
||||
|
||||
```text
|
||||
dev -> ./{{ main_script }} validate in Gitea Actions -> main -> Argo CD sync
|
||||
```
|
||||
|
||||
The workflow targets a repository-scoped Debian host runner with the label
|
||||
`homelab-debian`.
|
||||
|
||||
Run the same validation locally before promoting:
|
||||
|
||||
```bash
|
||||
./{{ main_script }} validate
|
||||
```
|
||||
|
||||
The workflow only blocks automatic deploy for external Gitea service
|
||||
changes: files under `infra/gitea/`, or edits inside the `deploy_gitea`,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,11 @@ the older detailed tables. The cascade includes "what broke" signals for recent
|
|||
deployment readiness, pod restarts, node pressure, disk pressure, Traefik
|
||||
5xx/404 log evidence, and recent Gitea errors.
|
||||
|
||||
`validate`
|
||||
: Run non-mutating repository checks used by the GitOps promotion flow:
|
||||
inventory, generated docs, Bash syntax, shellcheck, YAML parsing, OpenTofu
|
||||
formatting, tailnet policy validation, and optional static security checks.
|
||||
|
||||
`nuke`
|
||||
: Destroy Kubernetes state and Pimox worker VMs. Requires
|
||||
`LAB_CONFIRM_NUKE=homelab`.
|
||||
|
|
|
|||
9
jeannie
9
jeannie
|
|
@ -5939,6 +5939,10 @@ security_attack_path() {
|
|||
"${REPO_ROOT}/scripts/security-attack-path" "${@:2}"
|
||||
}
|
||||
|
||||
validate_homelab() {
|
||||
"${REPO_ROOT}/scripts/validate-homelab"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
up)
|
||||
up
|
||||
|
|
@ -5958,6 +5962,9 @@ case "${1:-}" in
|
|||
status)
|
||||
status_report
|
||||
;;
|
||||
validate)
|
||||
validate_homelab
|
||||
;;
|
||||
apps)
|
||||
require_debian_server "apps"
|
||||
jeannie_log_start "apps"
|
||||
|
|
@ -6087,7 +6094,7 @@ case "${1:-}" in
|
|||
echo "Log: ${JEANNIE_LOG_FILE}"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {up|plan [all|provisioning|cluster|platform|apps|edge]|rebuild-cluster|stop-cluster|start-cluster|status|apps|website-translation-model|website-ollama-listen|ollama-setup|deploy-gitea|rpi-services|bootstrap-gitea-repo|backup-gitea|drill-gitea-restore|install-gitea-runner|move-prometheus-stack-workers|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|state-backup|fix-debian-docker-root|secrets-init|secrets-check|tailnet-policy-check|ai-index|ai-check|security-scan|security-prepare|security-zap|security-k8s|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-runtime|security-attack-path|openwrt|nuke}"
|
||||
echo "Usage: $0 {up|plan [all|provisioning|cluster|platform|apps|edge]|rebuild-cluster|stop-cluster|start-cluster|status|validate|apps|website-translation-model|website-ollama-listen|ollama-setup|deploy-gitea|rpi-services|bootstrap-gitea-repo|backup-gitea|drill-gitea-restore|install-gitea-runner|move-prometheus-stack-workers|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|state-backup|fix-debian-docker-root|secrets-init|secrets-check|tailnet-policy-check|ai-index|ai-check|security-scan|security-prepare|security-zap|security-k8s|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-runtime|security-attack-path|openwrt|nuke}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
run_step() {
|
||||
local label="$1"
|
||||
shift
|
||||
|
||||
printf '\n== %s ==\n' "${label}"
|
||||
"$@"
|
||||
}
|
||||
|
||||
shell_files() {
|
||||
local path
|
||||
|
||||
git -C "${REPO_ROOT}" ls-files | while IFS= read -r path; do
|
||||
case "${path}" in
|
||||
jeannie | *.sh)
|
||||
printf '%s\n' "${path}"
|
||||
;;
|
||||
scripts/*)
|
||||
if head -n 1 "${REPO_ROOT}/${path}" | grep -Eq 'sh|bash'; then
|
||||
printf '%s\n' "${path}"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
validate_bash_syntax() {
|
||||
local files=()
|
||||
local file
|
||||
|
||||
while IFS= read -r file; do
|
||||
files+=("${REPO_ROOT}/${file}")
|
||||
done < <(shell_files)
|
||||
|
||||
if ((${#files[@]} == 0)); then
|
||||
echo "no shell files found"
|
||||
return 0
|
||||
fi
|
||||
bash -n "${files[@]}"
|
||||
}
|
||||
|
||||
validate_shellcheck() {
|
||||
local files=()
|
||||
local file
|
||||
|
||||
while IFS= read -r file; do
|
||||
files+=("${REPO_ROOT}/${file}")
|
||||
done < <(shell_files)
|
||||
|
||||
if ! command -v shellcheck >/dev/null 2>&1; then
|
||||
echo "shellcheck not installed; skipping"
|
||||
return 0
|
||||
fi
|
||||
if ((${#files[@]} == 0)); then
|
||||
echo "no shell files found"
|
||||
return 0
|
||||
fi
|
||||
shellcheck "${files[@]}"
|
||||
}
|
||||
|
||||
validate_yaml() {
|
||||
if command -v ruby >/dev/null 2>&1; then
|
||||
ruby -e 'require "yaml"; (Dir["**/*.yml"] + Dir["**/*.yaml"]).each { |path| next if path.start_with?(".git/"); YAML.load_stream(File.read(path)) }; puts "yaml parse ok"'
|
||||
return 0
|
||||
fi
|
||||
echo "ruby not installed; skipping generic YAML parse"
|
||||
}
|
||||
|
||||
validate_tofu_fmt() {
|
||||
if ! command -v tofu >/dev/null 2>&1; then
|
||||
echo "tofu not installed; skipping fmt check"
|
||||
return 0
|
||||
fi
|
||||
tofu fmt -check -recursive "${REPO_ROOT}/bootstrap"
|
||||
}
|
||||
|
||||
validate_security_static() {
|
||||
"${REPO_ROOT}/scripts/validate-tailnet-policy"
|
||||
if command -v trivy >/dev/null 2>&1; then
|
||||
trivy config --quiet "${REPO_ROOT}"
|
||||
else
|
||||
echo "trivy not installed; skipping IaC security static scan"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "${REPO_ROOT}"
|
||||
run_step "Inventory" "${REPO_ROOT}/scripts/validate-homelab-inventory" "${HOMELAB_INVENTORY_FILE:-${REPO_ROOT}/homelab.yml}"
|
||||
run_step "Generated docs" "${REPO_ROOT}/scripts/render-docs" --check
|
||||
run_step "Bash syntax" validate_bash_syntax
|
||||
run_step "Shellcheck" validate_shellcheck
|
||||
run_step "YAML parse" validate_yaml
|
||||
run_step "OpenTofu formatting" validate_tofu_fmt
|
||||
run_step "Security static checks" validate_security_static
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in New Issue