From d2681bda1ed998c2a9ad43629435aeeeac162444 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Sat, 6 Jun 2026 23:47:53 +1000 Subject: [PATCH] chore: bump almalinux9 image tags to 20260606 --- .claude/commands/solve-issue.md | 273 ++++++++++++++++++ .woodpecker/kubeconform.yaml | 2 +- .woodpecker/pre-commit.yaml | 2 +- AGENTS.md | 261 +++++++++++++++++ apps/base/puppet/cronjob_g10k-code.yaml | 2 +- apps/base/puppet/deployment_puppetboard.yaml | 2 +- .../deployment_puppetserver-compiler.yaml | 2 +- migration.md | 145 ++++++++++ 8 files changed, 684 insertions(+), 5 deletions(-) create mode 100644 .claude/commands/solve-issue.md create mode 100644 AGENTS.md create mode 100644 migration.md diff --git a/.claude/commands/solve-issue.md b/.claude/commands/solve-issue.md new file mode 100644 index 0000000..2759e76 --- /dev/null +++ b/.claude/commands/solve-issue.md @@ -0,0 +1,273 @@ +--- +description: Pull master, read open issues, pick one, branch, implement, test, commit, PR, and comment. +--- + +# Solve a Gitea Issue + +## Current repo state + +```! +git status --short +echo "Current branch: $(git branch --show-current)" +echo "Remote: $(git remote get-url origin 2>/dev/null || echo 'none')" +``` + +## Open issues (with full body) + +```! +echo "Fetching open issues..." +issue_ids=$(tea issues list --output simple 2>/dev/null | awk 'NF && $1 ~ /^[0-9]+$/ {print $1}') +if [ -z "$issue_ids" ]; then + echo "No open issues found (or tea is not logged in)." +else + for id in $issue_ids; do + echo "" + echo "══════════════════════════════════════" + tea issues view "$id" --fields index,title,body 2>/dev/null \ + || tea issue "$id" 2>/dev/null \ + || echo " (could not read issue #$id)" + echo "══════════════════════════════════════" + done +fi +``` + +--- + +## Your task + +Follow these steps **in order**. Do not skip steps. + +### 1 — Choose an issue + +Present the issues above to the user as a numbered list (index, one-line title). Ask which one to work on. Wait for the answer before continuing. + +### 2 — Sync master + +```bash +git checkout master +git pull +``` + +Confirm you are on master and up to date. + +### 3 — Create a branch + +Name the branch `benvin/issue--` where `` is 2–4 kebab-case words from the issue title. + +```bash +git checkout -b benvin/issue-- +``` + +### 4 — Read the issue in full + +Re-read the full issue body shown above. If any part is ambiguous, state your interpretation before coding. + +**If you discover other problems while working:** do NOT solve them inline. Create a new Gitea issue with `tea issues create --title "..." --description "..."` and stay focused on the assigned issue. + +### 5 — Implement the solution + +Make the code changes needed to resolve the issue. Follow the conventions already in the repo: +- `main.py` route handlers each contain a single function call; logic lives in submodules. +- No comments unless the WHY is non-obvious. +- No new files unless the issue or architecture requires it. +- Security: no command injection, XSS, SQL injection, or secrets in code. +- **For performance improvements:** implement at the most generic call site possible so the fix applies to all current and future implementations, not just the one being tested. + +### 6 — Update tests + +Add or update tests that cover the new behaviour. Tests live in `tests/`. Check existing test structure before writing new ones — mirror the style and fixture patterns already in use. + +### 7 — Update README + +If the feature introduces new config keys, endpoints, or user-facing behaviour, document it in `README.md`. Keep additions concise — follow the existing section style. + +### 8 — Run the full test suite + +```bash +make test +``` + +All tests must pass. If any fail, fix them before proceeding. Do not skip or suppress failing tests. + +### 9 — Live Docker test (new package type only) + +**Skip this step if the issue does not add a new remote package type.** + +If the issue adds a new package type (e.g. `deb`, `conda`, `cargo`, `rubygems`, or any type not already in `remotes.yaml`), do the following before committing. + +#### 9a — Add a real test remote to remotes.yaml + +Append a valid, publicly accessible remote of the new type to `remotes.yaml`. Use a real upstream URL and patterns that cover both an immutable file (versioned artifact) and a mutable file (index/metadata). Add a comment explaining which URLs to use for manual testing. + +#### 9b — Start the stack + +```bash +make docker-up +``` + +Wait until `curl -s http://localhost:8000/health` returns `{"status":"healthy"}`. + +#### 9c — Test a mutable file (first fetch — cache miss) + +Download the index or metadata file for the new remote. Confirm: +- HTTP 200 +- `X-Artifact-Source: remote` header (or equivalent log line confirming a cache miss) +- Content looks correct (not empty, not an error page) + +```bash +curl -sv "http://localhost:8000/api/v1/remote//" 2>&1 | grep -E "< HTTP|X-Artifact" +``` + +#### 9d — Test a mutable file (second fetch — cache hit) + +Repeat the exact same request. Confirm: +- HTTP 200 +- `X-Artifact-Source: cache` + +```bash +curl -sv "http://localhost:8000/api/v1/remote//" 2>&1 | grep -E "< HTTP|X-Artifact" +``` + +#### 9e — Test an immutable file (first fetch — cache miss) + +Download a versioned/immutable artifact. Confirm HTTP 200 and a cache-miss log line. + +```bash +curl -sv "http://localhost:8000/api/v1/remote//" 2>&1 | grep -E "< HTTP|X-Artifact" +``` + +#### 9f — Test an immutable file (second fetch — cache hit) + +Repeat. Confirm `X-Artifact-Source: cache`. + +#### 9g — Check container logs + +```bash +make docker-logs +``` + +Scan for: +- `Cache MISS` on first fetches, `Cache HIT` on second fetches +- `Cache ADD SUCCESS` with correct sizes +- No unhandled exceptions or ERROR lines + +#### 9h — Exercise package-type tooling against the proxy + +Use the native tooling for this package type to verify end-to-end behaviour. Examples: + +| Package type | Command | +|---|---| +| `pypi` | `uv run --index-url http://localhost:8000/api/v1/remote//simple ` | +| `npm` | `npm install --registry http://localhost:8000/api/v1/remote// ` | +| `helm` | `helm repo add test http://localhost:8000/api/v1/remote/ && helm search repo test && helm template test/` | +| `alpine` | `apk fetch --repository http://localhost:8000/api/v1/remote/// ` | +| `rpm` | `dnf install --repofrompath ... ` or `repoquery` | +| `generic` | `curl` / `wget` as appropriate | + +Confirm the tool resolves and downloads correctly through the proxy. + +#### 9i — Tear down + +```bash +make docker-down +``` + +Fix any failures found during 9b–9h before moving on. + +### 9.5 — Performance issues: measure before/after and gate the PR + +**Skip this step if the issue is not a performance improvement.** + +For performance issues, a PR is only warranted if there is a measurable gain. Use the Docker stack to compare before and after. + +#### 9.5a — Baseline measurement (before) + +Start the stack with the **unmodified** code (temporarily revert your change): + +```bash +make docker-up +``` + +Warm or clear the cache as appropriate, then measure the relevant metric — e.g. concurrent request latency during a slow operation, response time for a specific endpoint, or throughput. Record the numbers. + +#### 9.5b — Apply your change and rebuild + +```bash +make docker-up # rebuilds the image +``` + +Repeat exactly the same measurement. Record the numbers. + +#### 9.5c — Decide + +If the improvement is not clearly measurable, **do not open a PR**. Instead: +1. Update the issue with your findings. +2. Note any conditions under which the improvement would be observable. +3. Skip steps 11–14. + +If the improvement is clear, proceed with the commit and PR. Include the before/after numbers in the PR description and the issue comment. + +#### 9.5d — Tear down + +```bash +make docker-down +``` + +### 10 — Build the wheel (smoke check) + +```bash +uv build --wheel +``` + +Confirm the build succeeds. + +### 11 — Stage and commit + +Stage only the files you changed. Do not use `git add -A` or `git add .` — list files explicitly. Run: + +```bash +git add ... +git commit +``` + +The commit message must: +- Start with a conventional-commit prefix (`feat:`, `fix:`, `refactor:`, `chore:`, etc.) +- Summarise the change in ≤ 72 characters on the first line +- Optionally include a short body explaining *why* (not *what*) + +If the pre-commit hook auto-fixes files, re-stage the fixed files and commit again. + +### 12 — Push the branch + +```bash +git push origin +``` + +### 13 — Open a pull request + +```bash +tea pulls create \ + --base master \ + --head \ + --title "" \ + --description "Closes #\n\n## Summary\n\n\n## Test plan\n" +``` + +### 14 — Comment on the issue + +```bash +tea comment "" +``` + +The comment must cover: +- **How it was resolved** — what changed and why +- **Issues encountered** — any non-obvious problems hit during implementation +- **Potential future improvements** — what could be done next + +### 15 — Return to master + +```bash +git checkout master +``` + +Report the PR URL and a one-sentence summary to the user. diff --git a/.woodpecker/kubeconform.yaml b/.woodpecker/kubeconform.yaml index 6177e2f..483390a 100644 --- a/.woodpecker/kubeconform.yaml +++ b/.woodpecker/kubeconform.yaml @@ -3,7 +3,7 @@ when: steps: - name: kubeconform - image: git.unkin.net/unkin/almalinux9-kubetest:20260319 + image: git.unkin.net/unkin/almalinux9-kubetest:20260606 commands: - make kubeconform backend_options: diff --git a/.woodpecker/pre-commit.yaml b/.woodpecker/pre-commit.yaml index 260e0a8..1c96165 100644 --- a/.woodpecker/pre-commit.yaml +++ b/.woodpecker/pre-commit.yaml @@ -3,7 +3,7 @@ when: steps: - name: pre-commit - image: git.unkin.net/unkin/almalinux9-base:20260308 + image: git.unkin.net/unkin/almalinux9-base:20260606 commands: - uvx pre-commit run --all-files backend_options: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fc49a75 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,261 @@ +# AGENTS.md + +## Project Overview + +This is an **ArgoCD GitOps repository** that manages Kubernetes applications for the `au-syd1` cluster using a Kustomize + Helm pattern. Applications are deployed via ArgoCD ApplicationSets that watch directory patterns in this repo. + +The migration pattern for this repo is: **Terragrunt/Terraform → ArgoCD** (see `migration.md` for full guide). + +--- + +## Essential Commands + +```bash +# Build and render manifests for a path (outputs to manifests//) +make build apps/overlays/au-syd1/ +make build clusters/au-syd1/bootstrap + +# Validate all apps and clusters with kubeconform +make kubeconform + +# Clean generated manifests +make clean + +# Quick build + inspect without persisting output +kustomize build --enable-helm apps/overlays/au-syd1/ + +# Check all resource kinds produced by an overlay +kustomize build --enable-helm apps/overlays/au-syd1/ | grep "^kind:" | sort | uniq -c + +# Run pre-commit checks against all files +uvx pre-commit run --all-files +``` + +--- + +## Directory Structure + +``` +argocd-apps/ +├── argocd/ +│ ├── applicationsets/ # ArgoCD ApplicationSet definitions (platform.yaml, storage.yaml) +│ └── projects/ # ArgoCD AppProject definitions (platform.yaml, storage.yaml) +├── apps/ +│ ├── base/ # Base Kustomize resources per app (no cluster-specific config) +│ │ └── / +│ │ ├── kustomization.yaml +│ │ ├── namespace.yaml +│ │ ├── vaultauth.yaml # (if Vault-managed secrets) +│ │ └── vaultstaticsecret.yaml +│ └── overlays/ +│ └── au-syd1/ # Cluster-specific overlays +│ └── / +│ ├── kustomization.yaml # references base + helmCharts +│ └── values.yaml # Helm values for this cluster +├── clusters/ +│ └── au-syd1/ +│ ├── apps/ # Entry point: references apps/base (ArgoCD app-of-apps) +│ └── bootstrap/ # ArgoCD install + initial Application manifest +├── ci/ +│ ├── validate-apps.sh # kubeconform over apps/overlays/*/kustomization.yaml +│ ├── validate-clusters.sh # kubeconform over clusters/*/kustomization.yaml +│ └── validate-no-secrets.sh # pre-commit hook: blocks plain Kubernetes Secrets +└── sources/ # Reference sources (Terraform configs, upstream charts, etc.) + └── terraform-k8s/ # Original Terraform configs — reference when migrating +``` + +--- + +## Adding a New Application + +Follow these 10 steps (detailed in `migration.md`): + +### 1. Create base resources +``` +apps/base// +├── kustomization.yaml +├── namespace.yaml +├── vaultauth.yaml # if needed +└── vaultstaticsecret.yaml # if needed +``` + +### 2. Create cluster overlay +``` +apps/overlays/au-syd1// +├── kustomization.yaml +└── values.yaml +``` + +**Overlay kustomization.yaml pattern:** +```yaml +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../base/ + +helmCharts: + - name: + repo: + version: "" + releaseName: + namespace: + valuesFile: values.yaml +``` + +### 3. Register in ApplicationSet +Add a directory entry to `argocd/applicationsets/platform.yaml` (or `storage.yaml` for `csi-*` apps): +```yaml +- path: apps/overlays/*/ +``` + +### 4. Update AppProject +In `argocd/projects/platform.yaml` (or `storage.yaml`): +- Add the Helm repo URL to `sourceRepos` +- Add the namespace to `destinations` +- Add any required cluster-scoped resource types to `clusterResourceWhitelist` + +### 5. Validate +```bash +kustomize build --enable-helm apps/overlays/au-syd1/ +make kubeconform +``` + +--- + +## Secret Management + +**Plain Kubernetes `Secret` objects are blocked** by the pre-commit hook. Use Vault Operator CRDs instead: + +### VaultAuth template +```yaml +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultAuth +metadata: + name: default + namespace: +spec: + method: kubernetes + mount: k8s/au/syd1 + vaultConnectionRef: vso-system/default + allowedNamespaces: + - + kubernetes: + role: + serviceAccount: + audiences: + - vault + tokenExpirationSeconds: 600 +``` + +### VaultStaticSecret template +```yaml +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultStaticSecret +metadata: + name: + namespace: +spec: + vaultAuthRef: default + mount: kv + type: kv-v2 + path: kubernetes/namespace//default/ + refreshAfter: 5m + destination: + name: + create: true + overwrite: true + hmacSecretData: true +``` + +--- + +## YAML Conventions + +- **2-space indentation** (enforced by yamllint) +- All files must end with a newline (`end-of-file-fixer`) +- No trailing whitespace +- YAML linting uses relaxed rules with `line-length: disable` (long base64/URLs are fine) +- yamllint ignores `chart` directories (vendored Helm charts) +- `---` document separator at top of every YAML file +- Multiple documents in one file are allowed (e.g., `vaultstaticsecret.yaml` often contains multiple secrets) + +--- + +## Kubernetes Labels Pattern + +Use standard `app.kubernetes.io/*` labels consistently: +```yaml +labels: + app.kubernetes.io/component: + app.kubernetes.io/instance: + app.kubernetes.io/name: + app.kubernetes.io/version: +``` + +--- + +## Resource Naming Conventions + +Files in `apps/base//` follow the pattern: +``` +_.yaml +``` +Examples: +- `deployment_puppetserver-master.yaml` +- `cronjob_g10k-code.yaml` +- `configmap_puppetboard-config.yaml` +- `horizontalpodautoscaler_puppetserver-compilers-autoscaler.yaml` +- `service_puppet-headless.yaml` + +--- + +## Helm Chart Vendoring + +Some overlays vendor Helm charts locally under `apps/overlays/au-syd1//charts//`. When a chart is vendored, the overlay's `kustomization.yaml` references the local path. When not vendored, it references the OCI or HTTP repo directly. + +Current Kubernetes target version: **1.33.7** (used by kubeconform in CI). + +--- + +## Project Boundaries + +| Project | ApplicationSet | App pattern | +|------------|---------------------------|--------------------------| +| `platform` | `argocd/applicationsets/platform.yaml` | Named apps (cert-manager, puppet, woodpecker, etc.) | +| `storage` | `argocd/applicationsets/storage.yaml` | `csi-*` apps | + +The `clusters/au-syd1/apps/` entry-point is deployed as a standalone ArgoCD `Application` (not an ApplicationSet) called `au-syd1-apps`. + +--- + +## CI / Pre-commit Hooks + +Runs on every PR via Woodpecker CI (`.woodpecker/`): + +| Check | Tool | Trigger | +|---|---|---| +| YAML lint + general file checks | `pre-commit` (yamllint + pre-commit-hooks) | PR | +| No plain Secrets | `ci/validate-no-secrets.sh` | PR (staged files) | +| Kubernetes manifest validation | `kubeconform` via `make kubeconform` | PR | + +kubeconform skips: `CustomResourceDefinition`, `GpuDevicePlugin` (for apps validation). + +--- + +## Git Workflow + +- Branch naming: `benvin/` (user prefix) +- **Never `git add .`** — add only relevant files explicitly +- If pre-commit modifies files, `git add -u` then `git commit --amend --no-edit` +- Use `git push --force-with-lease` after amending + +--- + +## Security Policies + +- `reloader.stakater.com/auto: "true"` annotation triggers rolling restarts on ConfigMap/Secret changes +- Security contexts follow least-privilege: `drop: [all]` then add only required capabilities +- `fsGroup: 999` on pod security context for Puppet workloads +- `runAsUser: 0` is used only for init containers that need to set file permissions, then regular containers run as non-root diff --git a/apps/base/puppet/cronjob_g10k-code.yaml b/apps/base/puppet/cronjob_g10k-code.yaml index 74f55e5..2b1343c 100644 --- a/apps/base/puppet/cronjob_g10k-code.yaml +++ b/apps/base/puppet/cronjob_g10k-code.yaml @@ -28,7 +28,7 @@ spec: imagePullSecrets: null containers: - name: g10k-code - image: git.unkin.net/unkin/almalinux9-g10k:20260308 + image: git.unkin.net/unkin/almalinux9-g10k:20260606 imagePullPolicy: IfNotPresent resources: requests: diff --git a/apps/base/puppet/deployment_puppetboard.yaml b/apps/base/puppet/deployment_puppetboard.yaml index 1d8bb50..b3256ec 100644 --- a/apps/base/puppet/deployment_puppetboard.yaml +++ b/apps/base/puppet/deployment_puppetboard.yaml @@ -50,7 +50,7 @@ spec: cpu: 20m memory: 32Mi - name: cert-generator - image: git.unkin.net/unkin/almalinux9-base:20260308 + image: git.unkin.net/unkin/almalinux9-base:20260606 imagePullPolicy: IfNotPresent command: - sh diff --git a/apps/base/puppet/deployment_puppetserver-compiler.yaml b/apps/base/puppet/deployment_puppetserver-compiler.yaml index 4a0a08c..04ebeca 100644 --- a/apps/base/puppet/deployment_puppetserver-compiler.yaml +++ b/apps/base/puppet/deployment_puppetserver-compiler.yaml @@ -181,7 +181,7 @@ spec: name: puppet-puppet-volume - name: setup-shared-bins - image: git.unkin.net/unkin/almalinux9-base:20260308 + image: git.unkin.net/unkin/almalinux9-base:20260606 command: - sh - -c diff --git a/migration.md b/migration.md new file mode 100644 index 0000000..2238279 --- /dev/null +++ b/migration.md @@ -0,0 +1,145 @@ +# Migration Guide: Terragrunt to ArgoCD + +## Prerequisites +- Examine existing Terraform configuration in `sources/terraform-k8s/config//` +- Identify Helm charts, values, storage classes, secrets, and other resources + +## Migration Steps + +### 1. Branch Creation +- Change back to main branch: `git checkout main` +- Create new branch: `git checkout -b benvin/` + +### 2. Create Base Application Structure +- Create directory: `apps/base//` +- Create `apps/base//kustomization.yaml` with resources: + - `namespace.yaml` + - `vaultauth.yaml` (if needed) + - `vaultstaticsecret.yaml` (if needed) + - Additional resources as required (storageclass.yaml, etc.) + +### 3. Create Namespace +- Create `apps/base//namespace.yaml` with app namespace + +### 4. Create Vault Integration (if needed) +- Create `apps/base//vaultauth.yaml` from Terraform `vault_auth.yaml` + - Convert snake_case to camelCase for Kubernetes + - Map Terraform fields to VaultAuth spec +- Create `apps/base//vaultstaticsecret.yaml` from `vault_static_secret.yaml` + - Convert to VaultStaticSecret spec format + +### 5. Create Additional Resources +- Create any additional resources (StorageClass, etc.) from Terraform config +- Maintain exact parameter parity with Terraform + +### 6. Create Overlay Structure +- Create directory: `apps/overlays/au-syd1//` +- Create `apps/overlays/au-syd1//kustomization.yaml`: + - Reference base: `../../../base/` + - Add helmCharts section with repo, version, valuesFile +- Create `apps/overlays/au-syd1//values.yaml` from Terraform helm_release values + +### 7. Update Project Configuration +- Add Helm repository to appropriate project in `argocd/projects/` +- Add namespace to project destinations (if needed) +- Add required cluster resource permissions + +### 8. Update ApplicationSet +- Add directory pattern to appropriate ApplicationSet in `argocd/applicationsets/` +- Use existing patterns like `apps/overlays/*/csi-*` or `apps/overlays/*/` + +### 9. Validation +- Run `kustomize build --enable-helm apps/overlays/au-syd1/` to generate all resources +- Check resource types: `kustomize build --enable-helm apps/overlays/au-syd1/ | grep "^kind:" | sort | uniq -c` +- Verify all resource types are permitted in the target project's `clusterResourceWhitelist` and `namespaceResourceWhitelist` +- Run `make kubeconform` to validate all resources +- Fix any validation errors + +### 10. Git Workflow +- Add only created/modified files: `git add apps/base// apps/overlays/au-syd1// argocd/projects/.yaml argocd/applicationsets/.yaml` +- **Never use `git add .`** +- Create commit with descriptive message following existing patterns +- Push branch: `git push -u origin benvin/` + +## Project Organization + +### Platform Project +- Core infrastructure and system components +- Examples: cert-manager, external-dns, cnpg-system, reflector-system, etc. + +### Storage Project +- Storage-related components +- Pattern: `csi-*` applications +- Examples: csi-cephfs, csi-cephrbd + +### Application-Specific Projects +- Create new projects for logical groupings as needed + +## Common Patterns + +### Helm Chart Integration +```yaml +helmCharts: + - name: + repo: + version: "" + releaseName: + namespace: + valuesFile: values.yaml +``` + +### VaultAuth Template +```yaml +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultAuth +metadata: + name: + namespace: +spec: + method: kubernetes + mount: k8s/au/syd1 + vaultConnectionRef: vso-system/default + allowedNamespaces: + - + kubernetes: + role: + serviceAccount: + audiences: + - vault + tokenExpirationSeconds: 600 +``` + +### VaultStaticSecret Template +```yaml +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultStaticSecret +metadata: + name: + namespace: +spec: + vaultAuthRef: + mount: kv + type: kv-v2 + path: + refreshAfter: 5m + destination: + name: + create: true +``` + +## Troubleshooting + +### Pre-commit Hook Issues +- If hooks modify files, add changes: `git add -u` +- Amend commit if safe: `git commit --amend --no-edit` +- Use `git push --force-with-lease` for amended commits + +### Validation Failures +- Check Helm chart compatibility with Kubernetes version +- Verify all required fields are present in resources +- Ensure proper YAML formatting + +### Missing Permissions +- Add required cluster resources to project clusterResourceWhitelist +- Add namespaces to project destinations +- Verify Helm repository is in project sourceRepos