Add GitOps validation promotion flow
This commit is contained in:
parent
329a3d1abd
commit
0777bed10c
|
|
@ -4,10 +4,35 @@ name: Homelab Main
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- dev
|
||||||
|
|
||||||
jobs:
|
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:
|
deploy:
|
||||||
runs-on: homelab-debian
|
runs-on: homelab-debian
|
||||||
|
needs:
|
||||||
|
- validate
|
||||||
|
if: ${{ gitea.ref == 'refs/heads/main' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
|
|
@ -21,12 +46,6 @@ jobs:
|
||||||
git checkout --force "${{ gitea.sha }}"
|
git checkout --force "${{ gitea.sha }}"
|
||||||
git config --global --add safe.directory "$PWD"
|
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
|
- name: Block automatic deploy for external Gitea changes
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
|
||||||
17
README.md
17
README.md
|
|
@ -760,8 +760,21 @@ ls -lh /home/jv/backups/gitea-restore-drills
|
||||||
## Gitea Actions
|
## Gitea Actions
|
||||||
|
|
||||||
This repo includes a Gitea Actions workflow at
|
This repo includes a Gitea Actions workflow at
|
||||||
`.gitea/workflows/homelab-main.yml`. It runs only on pushes to `main` and targets
|
`.gitea/workflows/homelab-main.yml`. It runs validation on pushes to `dev` and
|
||||||
a repository-scoped Debian host runner with the label `homelab-debian`.
|
`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
|
The workflow only blocks automatic deploy for external Gitea service
|
||||||
changes: files under `infra/gitea/`, or edits inside the `deploy_gitea`,
|
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,
|
It reports host memory/disk, systemd services, Docker Compose stacks,
|
||||||
Kubernetes health when the API is reachable, Pimox worker VM status, RPi
|
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
|
Focused doctor commands run narrower read-only checks and print the most likely
|
||||||
next step:
|
next step:
|
||||||
|
|
@ -758,8 +760,21 @@ ls -lh /home/jv/backups/gitea-restore-drills
|
||||||
## Gitea Actions
|
## Gitea Actions
|
||||||
|
|
||||||
This repo includes a Gitea Actions workflow at
|
This repo includes a Gitea Actions workflow at
|
||||||
`.gitea/workflows/homelab-main.yml`. It runs only on pushes to `main` and targets
|
`.gitea/workflows/homelab-main.yml`. It runs validation on pushes to `dev` and
|
||||||
a repository-scoped Debian host runner with the label `homelab-debian`.
|
`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
|
The workflow only blocks automatic deploy for external Gitea service
|
||||||
changes: files under `infra/gitea/`, or edits inside the `deploy_gitea`,
|
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
|
deployment readiness, pod restarts, node pressure, disk pressure, Traefik
|
||||||
5xx/404 log evidence, and recent Gitea errors.
|
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`
|
`nuke`
|
||||||
: Destroy Kubernetes state and Pimox worker VMs. Requires
|
: Destroy Kubernetes state and Pimox worker VMs. Requires
|
||||||
`LAB_CONFIRM_NUKE=homelab`.
|
`LAB_CONFIRM_NUKE=homelab`.
|
||||||
|
|
|
||||||
9
jeannie
9
jeannie
|
|
@ -5939,6 +5939,10 @@ security_attack_path() {
|
||||||
"${REPO_ROOT}/scripts/security-attack-path" "${@:2}"
|
"${REPO_ROOT}/scripts/security-attack-path" "${@:2}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validate_homelab() {
|
||||||
|
"${REPO_ROOT}/scripts/validate-homelab"
|
||||||
|
}
|
||||||
|
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
up)
|
up)
|
||||||
up
|
up
|
||||||
|
|
@ -5958,6 +5962,9 @@ case "${1:-}" in
|
||||||
status)
|
status)
|
||||||
status_report
|
status_report
|
||||||
;;
|
;;
|
||||||
|
validate)
|
||||||
|
validate_homelab
|
||||||
|
;;
|
||||||
apps)
|
apps)
|
||||||
require_debian_server "apps"
|
require_debian_server "apps"
|
||||||
jeannie_log_start "apps"
|
jeannie_log_start "apps"
|
||||||
|
|
@ -6087,7 +6094,7 @@ case "${1:-}" in
|
||||||
echo "Log: ${JEANNIE_LOG_FILE}"
|
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
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
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