[payslip-ingest] ActualBudget payroll sync CronJob + Panel 14 (Phase C)
Wires the daily ActualBudget deposit sync from the payslip-ingest app into K8s as a CronJob, and adds dashboard Panel 14 to overlay bank deposits against payslip net_pay. CronJob: actualbudget-payroll-sync in payslip-ingest namespace, runs 02:00 UTC. Calls `python -m payslip_ingest sync-meta-deposits`, which hits budget-http-api-viktor in the actualbudget namespace and upserts matching Meta payroll deposits into payslip_ingest.external_meta_deposits. ExternalSecret extended with three new Vault keys: - ACTUALBUDGET_API_KEY (same as actualbudget-http-api-viktor's env API_KEY) - ACTUALBUDGET_ENCRYPTION_PASSWORD (Viktor's budget password) - ACTUALBUDGET_BUDGET_SYNC_ID (Viktor's sync_id) These must be seeded at secret/payslip-ingest in Vault before the CronJob will run — it'll CrashLoop on missing env vars otherwise. First run can be triggered on demand via `kubectl -n payslip-ingest create job --from=cronjob/actualbudget-payroll-sync initial-sync`. Panel 14 plots monthly SUM(external_meta_deposits.amount) vs SUM(payslip.net_pay), plus a delta bar series — |delta| > £50 flags likely parser drift on net_pay. Part of: code-860
This commit is contained in:
parent
ef53053ae6
commit
1c0e1bcdde
2 changed files with 239 additions and 1 deletions
|
|
@ -32,7 +32,20 @@ resource "kubernetes_namespace" "payslip_ingest" {
|
|||
# Seed these manually in Vault before applying:
|
||||
# secret/paperless-ngx -> property `api_token`
|
||||
# secret/claude-agent-service -> property `api_bearer_token`
|
||||
# secret/payslip-ingest -> property `webhook_bearer_token`
|
||||
# secret/payslip-ingest -> properties:
|
||||
# - `webhook_bearer_token`
|
||||
# - `actualbudget_api_key` (same value as
|
||||
# actualbudget-http-api-viktor random
|
||||
# api-key — fetch via `kubectl get pods
|
||||
# -n actualbudget -l
|
||||
# app=actualbudget-http-api-viktor -o
|
||||
# jsonpath={.items[0].spec.containers[0].env}`
|
||||
# and grep API_KEY)
|
||||
# - `actualbudget_encryption_password`
|
||||
# (same as Viktor's budget password in
|
||||
# secret/actualbudget/credentials[viktor])
|
||||
# - `actualbudget_budget_sync_id`
|
||||
# (same as Viktor's sync_id)
|
||||
resource "kubernetes_manifest" "external_secret" {
|
||||
manifest = {
|
||||
apiVersion = "external-secrets.io/v1beta1"
|
||||
|
|
@ -79,6 +92,27 @@ resource "kubernetes_manifest" "external_secret" {
|
|||
property = "webhook_bearer_token"
|
||||
}
|
||||
},
|
||||
{
|
||||
secretKey = "ACTUALBUDGET_API_KEY"
|
||||
remoteRef = {
|
||||
key = "payslip-ingest"
|
||||
property = "actualbudget_api_key"
|
||||
}
|
||||
},
|
||||
{
|
||||
secretKey = "ACTUALBUDGET_ENCRYPTION_PASSWORD"
|
||||
remoteRef = {
|
||||
key = "payslip-ingest"
|
||||
property = "actualbudget_encryption_password"
|
||||
}
|
||||
},
|
||||
{
|
||||
secretKey = "ACTUALBUDGET_BUDGET_SYNC_ID"
|
||||
remoteRef = {
|
||||
key = "payslip-ingest"
|
||||
property = "actualbudget_budget_sync_id"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -288,6 +322,85 @@ resource "kubernetes_service" "payslip_ingest" {
|
|||
}
|
||||
}
|
||||
|
||||
# Daily sync of Meta payroll deposits from ActualBudget's http-api sidecar.
|
||||
# Populates payslip_ingest.external_meta_deposits so Panel 14 can overlay bank
|
||||
# deposits against payslip.net_pay — catches parser drift on net_pay.
|
||||
resource "kubernetes_cron_job_v1" "actualbudget_payroll_sync" {
|
||||
metadata {
|
||||
name = "actualbudget-payroll-sync"
|
||||
namespace = kubernetes_namespace.payslip_ingest.metadata[0].name
|
||||
}
|
||||
spec {
|
||||
schedule = "0 2 * * *"
|
||||
concurrency_policy = "Forbid"
|
||||
successful_jobs_history_limit = 3
|
||||
failed_jobs_history_limit = 5
|
||||
starting_deadline_seconds = 300
|
||||
|
||||
job_template {
|
||||
metadata {
|
||||
labels = local.labels
|
||||
}
|
||||
spec {
|
||||
backoff_limit = 1
|
||||
ttl_seconds_after_finished = 86400
|
||||
template {
|
||||
metadata {
|
||||
labels = local.labels
|
||||
}
|
||||
spec {
|
||||
restart_policy = "OnFailure"
|
||||
image_pull_secrets {
|
||||
name = "registry-credentials"
|
||||
}
|
||||
container {
|
||||
name = "sync"
|
||||
image = local.image
|
||||
command = ["python", "-m", "payslip_ingest", "sync-meta-deposits"]
|
||||
|
||||
env_from {
|
||||
secret_ref {
|
||||
name = "payslip-ingest-secrets"
|
||||
}
|
||||
}
|
||||
env_from {
|
||||
secret_ref {
|
||||
name = "payslip-ingest-db-creds"
|
||||
}
|
||||
}
|
||||
|
||||
env {
|
||||
name = "ACTUALBUDGET_HTTP_API_URL"
|
||||
value = "http://budget-http-api-viktor.actualbudget.svc.cluster.local"
|
||||
}
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
cpu = "50m"
|
||||
memory = "128Mi"
|
||||
}
|
||||
limits = {
|
||||
memory = "256Mi"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
# KYVERNO_LIFECYCLE_V1
|
||||
ignore_changes = [spec[0].job_template[0].spec[0].template[0].spec[0].dns_config]
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
kubernetes_manifest.external_secret,
|
||||
kubernetes_manifest.db_external_secret,
|
||||
]
|
||||
}
|
||||
|
||||
# Plan-time read of the ESO-created K8s Secret for Grafana datasource password.
|
||||
# First apply: -target=kubernetes_manifest.db_external_secret first so the Secret exists.
|
||||
data "kubernetes_secret" "payslip_ingest_db_creds" {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue