mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 12:03:59 +00:00
fix(cnpg): stabilize CNPG/Barman Cloud init on GKE (knoe-dev-0 / knoe-db-0)
- cloudnative_pg.py: add _wait_for_webhook_ca_bundle() to poll ValidatingWebhookConfiguration
until cainjector has injected the CA bundle; fall back to _try_inject_ca_bundle_manually()
on timeout (reads cert-manager-webhook-ca secret, patches webhook directly)
- cloudnative_pg.py: add _wait_for_barman_tls_secrets() to wait for barman-cloud-{client,server}-tls
before declaring Barman Cloud ready; surfaces warning instead of silent FailedMount
- deploy/gcp/gke/namespace.yaml: ecosystem-0 -> knoe-db-0 (ekosystem = k8s cluster, not namespace)
- deploy/gcp/gke/knoe-db.yaml: fix namespace ecosystem-0 -> knoe-db-0; set
podAntiAffinityType: required (one CNPG pod per node); add Spot node toleration so
GKE autoscaler simulation succeeds and scale-up is unblocked; add resource
requests/limits (500m/512Mi -> 2/2Gi); set googleCredentials.gkeEnvironment: true
(use Workload Identity - no explicit credentials secret needed)
- deploy/gcp/gke/knoe-db-backup-gcs.yaml: fix namespaces ecosystem-0 -> knoe-db-0
- deploy/gcp/gke/workload-identity.yaml: update comments ecosystem-0 -> knoe-db-0
- tests/installer/test_cnpg_barman_webhook.py: new - 13 tests covering webhook CA bundle
polling, manual CA injection, Barman TLS secret wait, and install_barman_plugin retry logic
Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
parent
3312c39b1f
commit
c165ee1a87
@ -1,23 +1,23 @@
|
||||
{
|
||||
"assignments": {
|
||||
"0": "gk3-knoe-dev-0-pool-1-11c87d92-8qr5",
|
||||
"1": "gk3-knoe-dev-0-pool-1-88b7392b-62c4",
|
||||
"2": "gk3-knoe-dev-0-pool-1-88b7392b-d6lb"
|
||||
"0": "k3d-knoe-dev-cluster-agent-0",
|
||||
"1": "k3d-knoe-dev-cluster-agent-1",
|
||||
"2": "k3d-knoe-dev-cluster-server-0"
|
||||
},
|
||||
"cluster_name": "knoe-db",
|
||||
"desired_instances": 3,
|
||||
"eligible_nodes": [
|
||||
"gk3-knoe-dev-0-pool-1-11c87d92-8qr5",
|
||||
"gk3-knoe-dev-0-pool-1-88b7392b-62c4",
|
||||
"gk3-knoe-dev-0-pool-1-88b7392b-d6lb"
|
||||
"k3d-knoe-dev-cluster-agent-0",
|
||||
"k3d-knoe-dev-cluster-agent-1",
|
||||
"k3d-knoe-dev-cluster-server-0"
|
||||
],
|
||||
"metadata": {
|
||||
"prior_plan_present": true,
|
||||
"reason": "reused",
|
||||
"regenerated": false,
|
||||
"reused": true
|
||||
"reason": "assigned_node_no_longer_eligible",
|
||||
"regenerated": true,
|
||||
"reused": false
|
||||
},
|
||||
"plan_hash": "334860873593911a",
|
||||
"plan_id": "cnpg-placement-334860873593911a",
|
||||
"plan_hash": "adaed574a7910245",
|
||||
"plan_id": "cnpg-placement-adaed574a7910245",
|
||||
"schema_version": "v1"
|
||||
}
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
{
|
||||
"assignments": {
|
||||
"0": "gk3-knoe-dev-0-pool-1-11c87d92-8qr5",
|
||||
"1": "gk3-knoe-dev-0-pool-1-88b7392b-62c4",
|
||||
"2": "gk3-knoe-dev-0-pool-1-88b7392b-d6lb"
|
||||
"0": "k3d-knoe-dev-cluster-agent-0",
|
||||
"1": "k3d-knoe-dev-cluster-agent-1",
|
||||
"2": "k3d-knoe-dev-cluster-server-0"
|
||||
},
|
||||
"cluster_name": "knoe-db",
|
||||
"desired_instances": 3,
|
||||
"eligible_nodes": [
|
||||
"gk3-knoe-dev-0-pool-1-11c87d92-8qr5",
|
||||
"gk3-knoe-dev-0-pool-1-88b7392b-62c4",
|
||||
"gk3-knoe-dev-0-pool-1-88b7392b-d6lb"
|
||||
"k3d-knoe-dev-cluster-agent-0",
|
||||
"k3d-knoe-dev-cluster-agent-1",
|
||||
"k3d-knoe-dev-cluster-server-0"
|
||||
],
|
||||
"metadata": {
|
||||
"prior_plan_present": true,
|
||||
@ -17,7 +17,7 @@
|
||||
"regenerated": false,
|
||||
"reused": true
|
||||
},
|
||||
"plan_hash": "334860873593911a",
|
||||
"plan_id": "cnpg-placement-334860873593911a",
|
||||
"plan_hash": "adaed574a7910245",
|
||||
"plan_id": "cnpg-placement-adaed574a7910245",
|
||||
"schema_version": "v1"
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
; Prole Master Configuration File
|
||||
; Generated by install.py on 2026-04-04 12:25:21
|
||||
; Generated by install.py on 2026-04-05 01:23:00
|
||||
; This file is used as input for Ansible deployment and k8s cluster creation.
|
||||
|
||||
[User]
|
||||
@ -56,7 +56,7 @@ init_cluster.cluster_env = dev
|
||||
init_cluster.deployment_target = prole-dev-cluster
|
||||
init_cluster.gitops_enabled = false
|
||||
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:ZAG_hQCgEUXMSqra:6ipuCNnUu2hC4n0jmsdt_Z4ILvTuPeRs5GqLNhCnBiRKSSlJmwp9PW4f36hYLr_06iHi63laqwCGXLGFW5KCmqUcNerl_ntmZNS10k6xkj--BEqAf6O2JnHotZoCu8EkNDh-xnyBTflXE3oLTTafUHFjhqZQ_YXYA-resA==}
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:hlXkN11mhpvAfmgS:R1-wv-Sa29MQp6izxht7qJxqUkZz1zMeWWPWSeIWfms26XkApuWGFzYwZt0Ii2pyJDkYqBd_Yx5JH4VbyK6YCnVUXOa8eqAmhUObK-qr3hek7o0e3zOnLIyEj3nEZa8HMcplf8h1hN1M2CqsQtE-Mjh5zUyRAEEZ18VMag==}
|
||||
init_cluster.kerberos_enabled = true
|
||||
init_cluster.mode = k3d
|
||||
init_cluster.start_cluster = true
|
||||
@ -80,7 +80,7 @@ kerberos_config.test_connection = false
|
||||
kerberos_config.user = administrator
|
||||
network_scan.run = true
|
||||
ollama_config.model =
|
||||
ollama_config.server_host = fairyland.prole.org
|
||||
ollama_config.server_host =
|
||||
ollama_config.server_port = 11434
|
||||
supabase_config.pv_base_dir = /synology/d005
|
||||
supabase_config.pv_node = gandalf.prole.org
|
||||
@ -89,6 +89,9 @@ supabase_config.pv_node = gandalf.prole.org
|
||||
; Variables used by name in more than one place or assumed global scope
|
||||
CLUSTER_ENV = dev
|
||||
CLUSTER_NAME = knoe-db
|
||||
CNPG_PLACEMENT_PLAN_FILE = /Users/chrisfu/dev/prole/conf/cnpg-placement/knoe-system-knoe-db.json
|
||||
CNPG_PLACEMENT_PLAN_HASH = 965d2fde4035059f
|
||||
CNPG_PLACEMENT_PLAN_ID = cnpg-placement-965d2fde4035059f
|
||||
DATABASE_NAMESPACE = knoe-db-18-008
|
||||
DB_HOST_PORT = 5432
|
||||
DEPLOYMENT_MODE = k3d
|
||||
@ -97,9 +100,9 @@ DOCKER_PRELOAD = false
|
||||
KNOE_DB_USER = root
|
||||
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
|
||||
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
|
||||
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:NMTTkd1v51pfcCXs:elEzbvXUWk_e8Xw_JUwhG2WI9vnyooM03hU3r2nUOPl39gymW2C72TwOSqbsNXWzpyC-pTuEXrDIHVdVQhHpfUQR08kt-qb5Bl_OElRmudqm8jB3JJg8ChWpXAZGd9btbTe9_8LuE4PW9N7z1Ep8u2yNlMz1tL-hcGk0_Q==}
|
||||
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:XME8dFpJqpSvNh67:DuFmvxAOsTuoto2bacV28Swp0ohml3GgB954rEofX2zsP_d0RTFYIcReRzAdBTDUS4F8oXcCex7CGR8L-AmaSQkftzVq1VA5r6ljb7Cid6xZw56aQwH6fr4t2ba32uuUEBpBxiXfmoqYZACddSvb1-HR7VXC259YpSW-LA==}
|
||||
PROLE_OPENTOFU_URL = http://127.0.0.1:8080
|
||||
SERVICE_NAMESPACE = default
|
||||
SERVICE_NAMESPACE = knoe-system
|
||||
SUPABASE_PV_BASE = /synology/d005
|
||||
SUPABASE_PV_BASE_DIR = /synology/d005
|
||||
SUPABASE_PV_NODE = gandalf.prole.org
|
||||
@ -145,7 +148,10 @@ OLLAMA_SERVER_HOST = fairyland.prole.org
|
||||
OLLAMA_SERVER_PORT = 11434
|
||||
|
||||
[Optional Features]
|
||||
; No configuration values captured yet for this section.
|
||||
AT_REST_ENCRYPTION_ENABLED = True
|
||||
GITOPS_ENABLED = False
|
||||
KERBEROS_ENABLED = True
|
||||
SUPABASE_ENABLED = False
|
||||
|
||||
[GitOps]
|
||||
; No configuration values captured yet for this section.
|
||||
@ -154,7 +160,9 @@ OLLAMA_SERVER_PORT = 11434
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
[Initialize Cluster]
|
||||
; No configuration values captured yet for this section.
|
||||
ENVIRONMENT = dev
|
||||
K3S_SERVER_URL = https://myrddin.prole.org:6443
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:Puz4fzkm528-XoYM:YlPecZrTA2orKSD6NeuquMD3J39L4RGXqOzGzgNzHqmrhLPPlpLaczU3CwoM3omEuF6rjd0NVlf7Ief5HSKPm49S-X70x-vH_9QaiY8bPx3xtfypGC--p-05NQIw_rDWz5mdonzzj41U-zxA9N3r5mVILiBAkI1trkPRLg==}
|
||||
|
||||
[Dev Cluster (k3d)]
|
||||
CLUSTER_ENV = dev
|
||||
@ -165,12 +173,15 @@ MODE = k3d
|
||||
CLUSTER_ENV = prole-service-cluster
|
||||
DISPLAY_NAME = prole-service-cluster
|
||||
K3S_SERVER_URL = https://myrddin.prole.org:6443
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:THNElHPMqx8EPHxg:2BOEHH50KsNSMXLupemftQOZ8X8NBjnOaWsi50qdZZ-M68KanY-5K1hxOsRJ2P90FWqKGf2H4VqjQWog6Z9aaLkOnT_AW9s3XER1KjUR9rDvNHwO4PKYbesmzYcBI_qwkEPwbxCaWl_ralcKxaQBKP4XoHH6eYm6dDhZYw==}
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:IjPqAfeqHKD4cmtX:su6LTjq1oMB6FwCDw53JhUPPgmoLTjiEtobR20AakbOFuIfQLUqz_nrfJTMV17Ip9BMUB6hUc9leZo1qSWnDFY_rlWUOLxaGtNOUBxVNUPzRJtLEmXDJ5T3hJDQZvukPcTvN8m_3gY72MGMgC5zhTvQI3eEli0ZZ17q3tg==}
|
||||
MODE = k3s
|
||||
PIPELINE_URL = http://127.0.0.1:8080
|
||||
|
||||
[GCP]
|
||||
; No configuration values captured yet for this section.
|
||||
BILLING_ACCOUNT = 01193C-25783B-3211AD
|
||||
BILLING_PROJECT = plenary-truck-485623-p7
|
||||
ORG_ID = 584001916389
|
||||
PROJECT_ID = plenary-truck-485623-p7
|
||||
|
||||
[Prod Cluster (k8s)]
|
||||
ARTIFACTS_DIR = /Users/chrisfu/dev/prole/data/staging
|
||||
@ -180,7 +191,8 @@ MODE = k8s
|
||||
PIPELINE_URL = http://127.0.0.1:8080
|
||||
|
||||
[Docker Build]
|
||||
; No configuration values captured yet for this section.
|
||||
LOCAL_REGISTRY = localhost:5000
|
||||
LOCAL_REGISTRY_INTERNAL = k3d-prole-registry.localhost:5000
|
||||
|
||||
[Initialization Scripts]
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
@ -1,11 +1,3 @@
|
||||
# Port mappings for Prole Tools (generated).
|
||||
# Format: key: local=... remote=... ns=... svc=... address=...
|
||||
|
||||
argocd: local=8081 remote=80 ns=argocd svc=argocd-server address=0.0.0.0
|
||||
garage: local=3900 remote=3900 ns=knoe-system svc=garage address=0.0.0.0
|
||||
openbao: local=8200 remote=8200 ns=knoe-system svc=openbao address=0.0.0.0
|
||||
opentofu: local=8080 remote=8080 ns=knoe-system svc=opentofu address=0.0.0.0
|
||||
dashboard: local=8443 remote=443 ns=kubernetes-dashboard svc=kubernetes-dashboard-kong-proxy address=127.0.0.1
|
||||
postgres: local=5432 remote=5432 ns=knoe-db-0 svc=knoe-db-rw address=0.0.0.0
|
||||
prometheus: local=9090 remote=9090 ns=monitoring svc=kps-kube-prometheus-stack-prometheus address=127.0.0.1
|
||||
grafana: local=3000 remote=80 ns=monitoring svc=kps-grafana address=0.0.0.0
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
; Prole Master Configuration File
|
||||
; Generated by install.py on 2026-04-04 12:31:19
|
||||
; Generated by install.py on 2026-04-05 01:48:40
|
||||
; This file is used as input for Ansible deployment and k8s cluster creation.
|
||||
|
||||
[User]
|
||||
@ -39,15 +39,15 @@ dependencies.opentofu.install = true
|
||||
dependencies.python.install = true
|
||||
dependencies.verify_all = false
|
||||
disk_selection.disk_type = local
|
||||
disk_selection.local_path = ${HOME}/dev/prole/prole-tools-app/dist
|
||||
disk_selection.local_path = /Users/chrisfu/dev/prole/prole-tools-app/dist
|
||||
disk_selection.removable_mount =
|
||||
env_setup.CLUSTER_NAME = ${CLUSTER_NAME}
|
||||
env_setup.DATABASE_NAMESPACE = ${DATABASE_NAMESPACE}
|
||||
env_setup.PROLE_CONF = ${HOME}/dev/prole/conf
|
||||
env_setup.PROLE_DATA = ${HOME}/dev/prole/data
|
||||
env_setup.PROLE_HOME = ${HOME}/dev/prole
|
||||
env_setup.PROLE_LOGS = ${HOME}/dev/prole/logs
|
||||
env_setup.PROLE_SERVICE = ${HOME}/dev/prole/etc
|
||||
env_setup.PROLE_CONF = /Users/chrisfu/dev/prole/conf
|
||||
env_setup.PROLE_DATA = /Users/chrisfu/dev/prole/data
|
||||
env_setup.PROLE_HOME = /Users/chrisfu/dev/prole
|
||||
env_setup.PROLE_LOGS = /Users/chrisfu/dev/prole/logs
|
||||
env_setup.PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
|
||||
gitops.git_provider = Gitea
|
||||
gitops.node_selector =
|
||||
init_cluster.argocd_enabled = false
|
||||
@ -56,7 +56,7 @@ init_cluster.cluster_env = prod
|
||||
init_cluster.deployment_target = prole-prod-cluster
|
||||
init_cluster.gitops_enabled = false
|
||||
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:lkXqS7JRjESKcQr9:yZTvAEY_BiN_nhPgyhmV9i8bRHXW3Twbi3ZP_gXjrTgBYx8aN0I0wtiZpqW-c21kKFMo8TmQ5EJ5Sd8LZIh7ARuGztax3_9_DIxjHbEN4Ja0EPy5EThncJ6cLXm-ycF6Pwq2sOr62rL7ZSxmvDbPBXm3B22WGRJOh_nF3A==}
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:00IKKRpdvdWHIF6m:9J5xviuXy_oiUsP4S6nm80PAwLKwqgw5ziEuE3FTrt0Sf6KcWOseXy7SXQt8UMgLP11Ph7Otts3nah6euSNbRKTBIqVDNL24vcHaSSjuTUvU1tYbQffF4AOVXNJ13_5G3jgPW5Uba_X-2fgsG5bghBF-0v_x9K_NaTxcfg==}
|
||||
init_cluster.kerberos_enabled = true
|
||||
init_cluster.mode = k8s
|
||||
init_cluster.start_cluster = true
|
||||
@ -67,8 +67,8 @@ init_db_build.run_build = true
|
||||
init_password.cluster_name = ${CLUSTER_NAME}
|
||||
init_password.db_host_port = 5432
|
||||
init_password.db_namespace = ${DATABASE_NAMESPACE}
|
||||
init_password.db_password = ${PROLE_SECRET:v1:1TLhOe9B8E1WJsYH:hFxPKUiWLMJmdbw8yGtyqrPU_I8RyFpOI18aMcCcog9rlSkt}
|
||||
init_password.db_password_confirm = ${PROLE_SECRET:v1:1TLhOe9B8E1WJsYH:hFxPKUiWLMJmdbw8yGtyqrPU_I8RyFpOI18aMcCcog9rlSkt}
|
||||
init_password.db_password = ${PROLE_SECRET:v1:C1gpQ0W8x66jxqop:F5xTd8kM8BTAqNiGkrHlZ6XQ3Qr503oo7V5Rs2hHURH-YrbY}
|
||||
init_password.db_password_confirm = ${PROLE_SECRET:v1:C1gpQ0W8x66jxqop:F5xTd8kM8BTAqNiGkrHlZ6XQ3Qr503oo7V5Rs2hHURH-YrbY}
|
||||
init_password.db_username = root
|
||||
init_password.generate_ssh_key = true
|
||||
init_scripts.run_scripts = true
|
||||
@ -80,32 +80,33 @@ kerberos_config.test_connection = false
|
||||
kerberos_config.user = administrator
|
||||
network_scan.run = true
|
||||
ollama_config.model =
|
||||
ollama_config.server_host = fairyland.prole.org
|
||||
ollama_config.server_host =
|
||||
ollama_config.server_port = 11434
|
||||
supabase_config.pv_base_dir = /synology/d005
|
||||
supabase_config.pv_node = gandalf.prole.org
|
||||
|
||||
[Global]
|
||||
; Variables used by name in more than one place or assumed global scope
|
||||
ARTIFACT_REGISTRY_AVAILABLE = true
|
||||
CLUSTER_ENV = prod
|
||||
CLUSTER_NAME = knoe-db
|
||||
CNPG_ELIGIBLE_NODES = gk3-knoe-dev-0-pool-1-11c87d92-8qr5,gk3-knoe-dev-0-pool-1-88b7392b-62c4,gk3-knoe-dev-0-pool-1-88b7392b-d6lb
|
||||
CNPG_PLACEMENT_PLAN_FILE = ${HOME}/dev/prole/conf/cnpg-placement/knoe-system-knoe-db.json
|
||||
CNPG_PLACEMENT_PLAN_HASH = 334860873593911a
|
||||
CNPG_PLACEMENT_PLAN_ID = cnpg-placement-334860873593911a
|
||||
CNPG_STAGE1_NODE = gk3-knoe-dev-0-pool-1-11c87d92-8qr5
|
||||
CNPG_ELIGIBLE_NODES = k3d-knoe-dev-cluster-agent-0,k3d-knoe-dev-cluster-agent-1,k3d-knoe-dev-cluster-server-0
|
||||
CNPG_PLACEMENT_PLAN_FILE = /Users/chrisfu/dev/prole/conf/cnpg-placement/knoe-system-knoe-db.json
|
||||
CNPG_PLACEMENT_PLAN_HASH = adaed574a7910245
|
||||
CNPG_PLACEMENT_PLAN_ID = cnpg-placement-adaed574a7910245
|
||||
CNPG_STAGE1_NODE = k3d-knoe-dev-cluster-agent-0
|
||||
DATABASE_NAMESPACE = knoe-db-0
|
||||
DB_HOST_PORT = 5432
|
||||
DB_PASSWORD = ${PROLE_SECRET:v1:bGkdlAlb8osFLRrZ:aR9z9sOfxvVXsafDHDLNopioqwfNlV9lkALaxM-AF8CrzPe5}
|
||||
DB_PASSWORD = ${PROLE_SECRET:v1:RrW-X0wEk60ytw2L:EgNqfY2Ox__oRWttWsPVJjYy2zo7jNEKugj8u36vvfuecJxJ}
|
||||
DEPLOYMENT_MODE = k8s
|
||||
DEPLOYMENT_TARGET = prole-prod-cluster
|
||||
DOCKER_PRELOAD = false
|
||||
KNOE_DB_USER = root
|
||||
KUBECONFIG = ${HOME}/.kube/config
|
||||
KUBECONTEXT = gke_plenary-truck-485623-p7_us-west3_knoe-dev-0
|
||||
KUBECONFIG = /Users/chrisfu/.kube/config
|
||||
KUBECONTEXT = k3d-knoe-dev-cluster
|
||||
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
|
||||
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
|
||||
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:AIY1H6BntV97_h-F:lEi5q_GtOlxpZLHah0aYVObAAeWknsz94-SaXOHD9TaVF_zXaHoHo9L4gwvU67XKnnU6DjGsvIumtvW98ChoBfmHfj3n4OHve_MYokZKSSlr0HfGjUmnaXVJ1YuJAJsG-7XZGa-Itv9zYciQ2-4TM6UtBRPdAzar6kkrwA==}
|
||||
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:OnDkamBldOCeAKF8:FN47uqDWbzSfqXzKxEaKEGEx14G8rCintVIBsaN60LNNLR5-4X0-KlFyKG4ZtrsDAy_x7c1CDvJePBoMDmhWmpriAPNEsDz6_K7UakxfSfuJvQzSnKulcBec9YhNewBTd9lauf6JD6vx8Fm7dX8PuS3EaXNJQyGzY1_07w==}
|
||||
SERVICE_NAMESPACE = knoe-system
|
||||
SUPABASE_PV_BASE = /synology/d005
|
||||
SUPABASE_PV_BASE_DIR = /synology/d005
|
||||
@ -121,8 +122,8 @@ SUPABASE_PV_NODE = gandalf.prole.org
|
||||
AD_DC_HOST = myrddin.prole.org
|
||||
AD_DC_IP = 10.0.0.3
|
||||
ANSIBLE_DOMAIN = prole.org
|
||||
ANSIBLE_INFRASTRUCTURE = ${HOME}/dev/prole/infrastructure
|
||||
ANSIBLE_INVENTORY = ${HOME}/dev/prole/infrastructure/inventory
|
||||
ANSIBLE_INFRASTRUCTURE = /Users/chrisfu/dev/prole/infrastructure
|
||||
ANSIBLE_INVENTORY = /Users/chrisfu/dev/prole/infrastructure/inventory
|
||||
ANSIBLE_REALM = PROLE.ORG
|
||||
ANSIBLE_TOPOLOGY = {"domain":"prole.org","realm":"PROLE.ORG","internal_records":{"aventage.prole.org":"10.0.0.206","fairyland.prole.org":"10.0.0.208","loghost.prole.org":"10.0.0.3","merlin.prole.org":"10.0.0.6","morana.prole.org":"10.0.0.66","morgoth.prole.org":"10.0.0.204","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207","synology.prole.org":"10.0.0.203","zinfandel.prole.org":"10.0.0.205"},"ad_dc":{"host":"myrddin.prole.org","ip":"10.0.0.3"},"k3s":{"server_url":"https://myrddin.prole.org:6443","server_host":"myrddin.prole.org","token_present":true},"groups":{"iscsi":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org","merlin.prole.org","gandalf.prole.org"],"pihole":["pi.prole.org","raspberry.prole.org"],"ad_dc":["myrddin.prole.org"],"k3s_servers":["myrddin.prole.org"],"k3s_agents":["merlin.prole.org","gandalf.prole.org"],"k3s_hosts:children":["k3s_servers","k3s_agents"],"linux_hosts":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org","merlin.prole.org","gandalf.prole.org"],"ssl_hosts":["myrddin.prole.org"],"mariadb_primary":["merlin.prole.org"],"mariadb_replica":["raspberry.prole.org"],"mariadb:children":["mariadb_primary","mariadb_replica"],"merlin_bootstrap":["merlin"],"k3s_hosts":["gandalf.prole.org"]},"hosts":{"merlin":"10.0.0.6","merlin.prole.org":"10.0.0.6","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207"},"unmapped_hosts":["gandalf.prole.org","k3s_agents","k3s_servers","mariadb_primary","mariadb_replica"]}
|
||||
KDC_ANSIBLE_DETECTED = 10.0.0.3
|
||||
@ -133,11 +134,11 @@ KERBEROS_AUTO_ENABLED = True
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
[System Environment]
|
||||
PROLE_CONF = ${HOME}/dev/prole/conf
|
||||
PROLE_DATA = ${HOME}/dev/prole/data
|
||||
PROLE_HOME = ${HOME}/dev/prole
|
||||
PROLE_LOGS = ${HOME}/dev/prole/logs
|
||||
PROLE_SERVICE = ${HOME}/dev/prole/etc
|
||||
PROLE_CONF = /Users/chrisfu/dev/prole/conf
|
||||
PROLE_DATA = /Users/chrisfu/dev/prole/data
|
||||
PROLE_HOME = /Users/chrisfu/dev/prole
|
||||
PROLE_LOGS = /Users/chrisfu/dev/prole/logs
|
||||
PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
|
||||
|
||||
[Monitoring]
|
||||
; No configuration values captured yet for this section.
|
||||
@ -165,19 +166,19 @@ SUPABASE_ENABLED = False
|
||||
[Initialize Cluster]
|
||||
ENVIRONMENT = prod
|
||||
K3S_SERVER_URL = https://myrddin.prole.org:6443
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:5xChfOZ00d7VjXyT:F56d9li8Q_-DkWo7a8Nx14gKx_8NHgfF3vD0MRUJfgdodI1HunR1zCPq1XZFpfJJxVRMuwsvahnCRo-0uvlfjANA6RTvrcyeRxNKQr-QsRRJ1BdnQ0dWuJyuy1lbOAz6SHl079OxQJTibjz1A8rOIDeY-L9wboBabPf0Kg==}
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:NFdcfCobgzp-u-Lj:YdTVsoCjyS76VzIoUqUzBHDF2tXPTUTeYZk0RGPK7EF6UNOXG1smsfyzWq-xhEnqGYoRLfV4Ni7AOuGeSwUuwSq4xpVRoF7MSMMF5-pwc-aLPv8U3bmPVcfNyoZ7BJvlwEvir3SQKpQbUggL5B4hWxMSl33k6yW0BfGJyA==}
|
||||
|
||||
[Dev Cluster (k3d)]
|
||||
CLUSTER_ENV = dev
|
||||
DISPLAY_NAME = knoe-dev-cluster
|
||||
KUBECTL_CONTEXT = gke_plenary-truck-485623-p7_us-west3_knoe-dev-0
|
||||
KUBECTL_CONTEXT = k3d-knoe-dev-cluster
|
||||
MODE = k3d
|
||||
|
||||
[Service Cluster (k3s)]
|
||||
CLUSTER_ENV = prole-service-cluster
|
||||
DISPLAY_NAME = prole-service-cluster
|
||||
K3S_SERVER_URL = https://myrddin.prole.org:6443
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:3w94MClqbC2ygx3c:gzXxHXm5u_Au6o2aN_SS5jhcY9OULo_3QadNpDwURnYSt5TJ0FaaJMAOxQ5A0qP6XxRgboWCvEI4HN90AJnzjMJvplV-9Okljpx8UMoYPaiMqP5omLgkpKf1CsyWX7bvyFM9wuU9wqvfb4qIARTYFPABsRha4N_BvRVFiQ==}
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:rUWR4v1suD9K4a1i:44pggdzZCCkkLJ85cpnCOyWf4KGB888i5mrPGp6nKxPT9EDjyC_GXxDmZMFxxkXg9DodYCnMbfk_MyrO6j1tWGgakGBHqeQxoHgKvZKlVXkmvbbst-KWLBSl0ADAKrfYgxsBgybYLpt1S04OmqAdbB7QLt-HDQLQ0G0g2w==}
|
||||
MODE = k3s
|
||||
|
||||
[GCP]
|
||||
@ -191,7 +192,7 @@ org_id = 584001916389
|
||||
project_id = plenary-truck-485623-p7
|
||||
|
||||
[Prod Cluster (k8s)]
|
||||
ARTIFACTS_DIR = ${HOME}/dev/prole/data/staging
|
||||
ARTIFACTS_DIR = /Users/chrisfu/dev/prole/data/staging
|
||||
CLUSTER_ENV = prole-prod-cluster
|
||||
DISPLAY_NAME = prole-prod-cluster
|
||||
INSTALL_PLAN = Validate production config and required secrets
|
||||
@ -204,7 +205,7 @@ Configure routing, TLS, and frontdoor domain mappings
|
||||
Prepare migration artifacts from source environment
|
||||
Generate OpenTofu plan and apply production pipeline
|
||||
MODE = k8s
|
||||
OPENTOFU_VARS = {"admin_user":"knoe_admin","app_database":"knoey","app_user":"knoey_app","backup_bucket":"knoe-0-backups","backup_provider":"gcs","backup_retention_days":14,"bootstrap_admin_email":"admin@knoey.com","cluster_name":"knoe-dev-0","cnpg_cluster_name":"knoe-db","db_instances":3,"db_storage_class":"premium-rwo","db_storage_size_gi":100,"ecosystem_id":0,"environment":"production","frontdoor_host":"knoey.com","gcp_project_id":"plenary-truck-485623-p7","gcp_region":"\u2713 us-west3","google_hosted_domain":"","meta_database":"knoe_meta","migration_continuous_until_cutover":false,"migration_mode":"snapshot-restore","migration_source_database":"knoey","migration_source_environment":"prole.org","migration_source_host":"knoe-local-db.prole.org","migration_source_password_ref":"secretref://local-source-db-password","migration_source_port":5432,"migration_source_user":"replication_user","namespace":"ecosystem-0","oidc_client_id_ref":"secretref://google-oidc-client-id","oidc_client_secret_ref":"secretref://google-oidc-client-secret","oidc_issuer":"https://accounts.google.com","platform_domain":"knoe.dev","postgres_version":"18","project_id":"plenary-truck-485623-p7","region":"\u2713 us-west3","tls_mode":"managed","wal_bucket":"knoe-0-wal","workload_identity_sa":"cnpg-backup"}
|
||||
OPENTOFU_VARS = {"admin_user":"knoe_admin","app_database":"knoey","app_user":"knoey_app","backup_bucket":"knoe-0-backups","backup_provider":"gcs","backup_retention_days":14,"bootstrap_admin_email":"admin@knoey.com","cluster_name":"knoe-dev-0","cnpg_cluster_name":"knoe-db","db_instances":3,"db_storage_class":"premium-rwo","db_storage_size_gi":100,"ecosystem_id":0,"environment":"production","frontdoor_host":"knoey.com","gcp_project_id":"plenary-truck-485623-p7","gcp_region":"\u2713 us-west3","google_hosted_domain":"","meta_database":"knoe_meta","migration_continuous_until_cutover":false,"migration_mode":"snapshot-restore","migration_source_database":"knoey","migration_source_environment":"prole.org","migration_source_host":"knoe-local-db.prole.org","migration_source_password_ref":"secretref://local-source-db-password","migration_source_port":5432,"migration_source_user":"replication_user","namespace":"ecosystem-0","oidc_client_id_ref":"secretref://google-oidc-client-id","oidc_client_secret_ref":"secretref://google-oidc-client-secret","oidc_issuer":"https://accounts.google.com","platform_domain":"knoe.dev","postgres_version":"16","project_id":"plenary-truck-485623-p7","region":"\u2713 us-west3","tls_mode":"managed","wal_bucket":"knoe-0-wal","workload_identity_sa":"cnpg-backup"}
|
||||
PRODUCTION_CONFIG_YAML = kind: KnoeProductionConfig
|
||||
metadata:
|
||||
ecosystemId: 0
|
||||
@ -225,7 +226,7 @@ spec:
|
||||
namespace: ecosystem-0
|
||||
database:
|
||||
clusterName: knoe-db
|
||||
postgresVersion: '18'
|
||||
postgresVersion: '16'
|
||||
instances: 3
|
||||
storageClass: premium-rwo
|
||||
storageSizeGi: 100
|
||||
@ -269,8 +270,8 @@ spec:
|
||||
STATUS = Attempted
|
||||
|
||||
[Deployment]
|
||||
MODE = k8s
|
||||
TARGET = prole-prod-cluster
|
||||
MODE = k3d
|
||||
TARGET = prole-dev-cluster
|
||||
|
||||
[Install]
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
@ -5,7 +5,7 @@ apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: cnpg-backup-sa
|
||||
namespace: ecosystem-0
|
||||
namespace: knoe-db-0
|
||||
annotations:
|
||||
# Set by etc/init_cnpg_gke.sh — substituted at deploy time
|
||||
iam.gke.io/gcp-service-account: "cnpg-backup@${GCP_PROJECT_ID}.iam.gserviceaccount.com"
|
||||
@ -17,7 +17,7 @@ apiVersion: postgresql.cnpg.io/v1
|
||||
kind: ScheduledBackup
|
||||
metadata:
|
||||
name: knoe-db-daily
|
||||
namespace: ecosystem-0
|
||||
namespace: knoe-db-0
|
||||
spec:
|
||||
schedule: "0 2 * * *"
|
||||
backupOwnerReference: self
|
||||
|
||||
@ -2,7 +2,7 @@ apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: knoe-db
|
||||
namespace: ecosystem-0
|
||||
namespace: knoe-db-0
|
||||
spec:
|
||||
instances: 3
|
||||
# Image pulled from GCP Artifact Registry — set ARTIFACT_REGISTRY in conf/prod/gcp.cfg
|
||||
@ -14,12 +14,20 @@ spec:
|
||||
|
||||
affinity:
|
||||
enablePodAntiAffinity: true
|
||||
podAntiAffinityType: required # hard: refuse to co-locate pods on the same node
|
||||
podAntiAffinityType: required # hard: one CNPG pod (rw/ro/r) per node; Pending pods trigger GKE cluster autoscaler
|
||||
topologyKey: kubernetes.io/hostname # physical node boundary (not zone)
|
||||
tolerations:
|
||||
# Allow scheduling on GKE Spot nodes (common in knoe-dev-0 to reduce cost).
|
||||
# Without this toleration the cluster-autoscaler predicate simulation fails
|
||||
# for any MIG whose nodes carry the spot taint, blocking scale-up entirely.
|
||||
- key: "cloud.google.com/gke-spot"
|
||||
operator: "Equal"
|
||||
value: "true"
|
||||
effect: "NoSchedule"
|
||||
|
||||
postgresql:
|
||||
parameters:
|
||||
shared_buffers: 256MB
|
||||
shared_buffers: 128MB
|
||||
pg_stat_statements.max: '10000'
|
||||
pg_stat_statements.track: all
|
||||
shared_preload_libraries:
|
||||
@ -63,6 +71,7 @@ spec:
|
||||
- GRANT anon TO authenticator;
|
||||
# demo schema for guest read-only access (evolves over time)
|
||||
- CREATE SCHEMA IF NOT EXISTS demo;
|
||||
- CREATE ROLE IF NOT EXISTS guest NOLOGIN;
|
||||
- GRANT USAGE ON SCHEMA demo TO guest;
|
||||
- ALTER DEFAULT PRIVILEGES IN SCHEMA demo GRANT SELECT ON TABLES TO guest;
|
||||
|
||||
@ -88,6 +97,14 @@ spec:
|
||||
serverTLSSecret: knoe-db-tls
|
||||
serverCASecret: knoe-db-ca
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: "500m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "2Gi"
|
||||
|
||||
enableSuperuserAccess: true
|
||||
|
||||
# GCS backup via Workload Identity — see knoe-db-backup-gcs.yaml
|
||||
@ -95,8 +112,7 @@ spec:
|
||||
barmanObjectStore:
|
||||
destinationPath: "gs://knoe-0-backups/"
|
||||
googleCredentials:
|
||||
applicationCredentials:
|
||||
name: "" # empty = Workload Identity; no static key needed
|
||||
gkeEnvironment: true # use Workload Identity; no applicationCredentials secret needed
|
||||
wal:
|
||||
compression: gzip
|
||||
data:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ecosystem-0
|
||||
name: knoe-db-0
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: prole-installer
|
||||
|
||||
@ -18,9 +18,9 @@
|
||||
# gcloud iam service-accounts add-iam-policy-binding \
|
||||
# cnpg-backup@$GCP_PROJECT_ID.iam.gserviceaccount.com \
|
||||
# --role=roles/iam.workloadIdentityUser \
|
||||
# --member="serviceAccount:$GCP_PROJECT_ID.svc.id.goog[ecosystem-0/cnpg-backup-sa]"
|
||||
# --member="serviceAccount:$GCP_PROJECT_ID.svc.id.goog[knoe-db-0/cnpg-backup-sa]"
|
||||
#
|
||||
# kubectl annotate serviceaccount cnpg-backup-sa \
|
||||
# -n ecosystem-0 \
|
||||
# -n knoe-db-0 \
|
||||
# iam.gke.io/gcp-service-account=cnpg-backup@$GCP_PROJECT_ID.iam.gserviceaccount.com \
|
||||
# --overwrite
|
||||
|
||||
@ -743,10 +743,12 @@ def install_barman_plugin(
|
||||
if is_cert_issue:
|
||||
if not _cert_manager_recovered:
|
||||
_log(log, "WARN: cert-manager webhook error; ensuring cert-manager and retrying...")
|
||||
_ensure_cert_manager_for_barman(env=env, log=log)
|
||||
ca_ready = _ensure_cert_manager_for_barman(env=env, log=log)
|
||||
_cert_manager_recovered = True
|
||||
_log(log, "cert-manager recovered; waiting 30 s for webhook CA bundle to propagate...")
|
||||
time.sleep(30)
|
||||
if ca_ready:
|
||||
_log(log, "cert-manager recovered; webhook CA bundle ready.")
|
||||
else:
|
||||
_log(log, "WARN: cert-manager restarted but CA bundle not confirmed; proceeding with retries...")
|
||||
else:
|
||||
wait_s = min(20 * _attempt, 60)
|
||||
_log(log, f"Webhook CA not yet trusted (attempt {_attempt}); retrying in {wait_s} s...")
|
||||
@ -769,6 +771,11 @@ def install_barman_plugin(
|
||||
env=env, timeout=timeout_s + 10,
|
||||
)
|
||||
|
||||
# Wait for cert-manager to issue the Barman TLS secrets that the pod mounts
|
||||
tls_timeout = int((env or {}).get("BARMAN_TLS_TIMEOUT", "300") or "300")
|
||||
if not _wait_for_barman_tls_secrets(env=env, log=log, timeout=tls_timeout):
|
||||
_log(log, "WARN: Barman Cloud TLS secrets not ready after install; pods may fail to start.")
|
||||
|
||||
|
||||
def _ensure_cert_manager_for_barman(
|
||||
env: dict | None = None,
|
||||
@ -851,6 +858,130 @@ def _ensure_cert_manager_for_barman(
|
||||
timeout=250,
|
||||
)
|
||||
|
||||
_log(log, "Waiting for cert-manager webhook CA bundle to be injected...")
|
||||
if _wait_for_webhook_ca_bundle(env=env, log=log, timeout=300):
|
||||
return True
|
||||
_log(log, "WARN: cert-manager webhook CA bundle not detected within 300 s; attempting manual CA injection...")
|
||||
if _try_inject_ca_bundle_manually(env=env, log=log):
|
||||
_log(log, "Manual CA bundle injection succeeded.")
|
||||
return True
|
||||
_log(log, "WARN: Manual CA bundle injection also failed; cert-manager webhook may not be fully trusted.")
|
||||
return False
|
||||
|
||||
|
||||
def _try_inject_ca_bundle_manually(
|
||||
env: dict | None = None,
|
||||
log: _LogFn | None = None,
|
||||
) -> bool:
|
||||
"""Manually patch the caBundle into the cert-manager ValidatingWebhookConfiguration.
|
||||
|
||||
Reads the CA certificate from the ``cert-manager-webhook-ca`` secret (created
|
||||
by cert-manager itself) and patches it directly into the webhook configuration
|
||||
when the cainjector is too slow or has not yet reconciled.
|
||||
|
||||
Returns True if the patch succeeded and the caBundle is now non-empty.
|
||||
"""
|
||||
for secret_name in ("cert-manager-webhook-ca", "cert-manager-cainjector-leader-election"):
|
||||
r = subprocess.run(
|
||||
[
|
||||
"kubectl", "-n", "cert-manager", "get", "secret", secret_name,
|
||||
"-o", "jsonpath={.data.tls\\.crt}",
|
||||
],
|
||||
env=env, capture_output=True, text=True, timeout=15,
|
||||
)
|
||||
ca_bundle = (r.stdout or "").strip()
|
||||
if r.returncode == 0 and ca_bundle:
|
||||
break
|
||||
else:
|
||||
_log(log, "WARN: cert-manager CA secret not found; skipping manual caBundle patch.")
|
||||
return False
|
||||
|
||||
_log(log, "Patching cert-manager-webhook ValidatingWebhookConfiguration with CA bundle...")
|
||||
patch_json = json.dumps(
|
||||
[{"op": "replace", "path": "/webhooks/0/clientConfig/caBundle", "value": ca_bundle}]
|
||||
)
|
||||
r2 = subprocess.run(
|
||||
[
|
||||
"kubectl", "patch", "validatingwebhookconfiguration", "cert-manager-webhook",
|
||||
"--type=json", f"-p={patch_json}",
|
||||
],
|
||||
env=env, capture_output=True, text=True, timeout=20,
|
||||
)
|
||||
if r2.returncode != 0:
|
||||
_log(log, f"WARN: Failed to patch caBundle: {(r2.stderr or r2.stdout or '').strip()}")
|
||||
return False
|
||||
# Brief wait for API server to propagate the patch
|
||||
time.sleep(3)
|
||||
return _wait_for_webhook_ca_bundle(env=env, log=log, timeout=30)
|
||||
|
||||
|
||||
def _wait_for_webhook_ca_bundle(
|
||||
env: dict | None = None,
|
||||
log: _LogFn | None = None,
|
||||
timeout: int = 300,
|
||||
) -> bool:
|
||||
"""Poll until the cert-manager ValidatingWebhookConfiguration has a CA bundle injected.
|
||||
|
||||
Returns True if the caBundle field becomes non-empty within *timeout* seconds,
|
||||
False otherwise.
|
||||
"""
|
||||
deadline = time.time() + timeout
|
||||
interval = 5
|
||||
while time.time() < deadline:
|
||||
r = subprocess.run(
|
||||
[
|
||||
"kubectl", "get", "validatingwebhookconfiguration",
|
||||
"cert-manager-webhook",
|
||||
"-o", "jsonpath={.webhooks[0].clientConfig.caBundle}",
|
||||
],
|
||||
env=env, capture_output=True, text=True, timeout=15,
|
||||
)
|
||||
if r.returncode == 0 and r.stdout.strip():
|
||||
return True
|
||||
time.sleep(interval)
|
||||
return False
|
||||
|
||||
|
||||
def _wait_for_barman_tls_secrets(
|
||||
env: dict | None = None,
|
||||
log: _LogFn | None = None,
|
||||
timeout: int = 180,
|
||||
) -> bool:
|
||||
"""Poll until cert-manager has issued both Barman Cloud TLS secrets in cnpg-system.
|
||||
|
||||
The Barman Cloud plugin manifest declares two Certificate resources
|
||||
(``barman-cloud-client`` and ``barman-cloud-server``). cert-manager
|
||||
populates the corresponding secrets asynchronously; the pod cannot start
|
||||
until both secrets exist and have non-empty ``tls.crt`` / ``tls.key`` data.
|
||||
|
||||
Returns True when all four fields are present, False on timeout.
|
||||
"""
|
||||
_log(log, f"Waiting for Barman Cloud TLS secrets in cnpg-system (timeout: {timeout}s)...")
|
||||
deadline = time.time() + timeout
|
||||
interval = 5
|
||||
while time.time() < deadline:
|
||||
all_ready = True
|
||||
for secret in ("barman-cloud-client-tls", "barman-cloud-server-tls"):
|
||||
for field in ("tls.crt", "tls.key"):
|
||||
r = subprocess.run(
|
||||
[
|
||||
"kubectl", "-n", "cnpg-system", "get", "secret", secret,
|
||||
"-o", f"jsonpath={{.data.{field.replace('.', '\\.')}}}",
|
||||
],
|
||||
env=env, capture_output=True, text=True, timeout=15,
|
||||
)
|
||||
if r.returncode != 0 or not (r.stdout or "").strip():
|
||||
all_ready = False
|
||||
break
|
||||
if not all_ready:
|
||||
break
|
||||
if all_ready:
|
||||
_log(log, "Barman Cloud TLS secrets are available.")
|
||||
return True
|
||||
time.sleep(interval)
|
||||
_log(log, f"WARN: Barman Cloud TLS secrets not ready after {timeout}s.")
|
||||
return False
|
||||
|
||||
|
||||
def _resolve_barman_plugin_tag(env: dict | None, log: _LogFn | None) -> str:
|
||||
"""Fetch the latest Barman Cloud plugin release tag from GitHub, with fallback."""
|
||||
@ -887,7 +1018,9 @@ def _bootstrap_db_user_secrets(
|
||||
_log(log, "WARN: DB_PASSWORD not resolved in env; skipping db-user secret bootstrap.")
|
||||
return
|
||||
|
||||
db_user = ((env or {}).get("KNOE_DB_USER") or "postgres").strip()
|
||||
# CNPG requires the secret username to match the cluster owner field (owner: prole).
|
||||
# KNOE_DB_USER is the admin/superuser login name, not the cluster owner.
|
||||
db_owner = ((env or {}).get("CNPG_DB_OWNER") or "prole").strip()
|
||||
|
||||
def _apply_if_missing(secret_name: str, username: str, password: str) -> None:
|
||||
if _kubectl_ok(["-n", namespace, "get", "secret", secret_name], env=env):
|
||||
@ -913,7 +1046,7 @@ def _bootstrap_db_user_secrets(
|
||||
raise RuntimeError(f"Failed to apply secret '{secret_name}':\n{apply_res.stderr}")
|
||||
_log(log, f"Created secret '{secret_name}' in '{namespace}'.")
|
||||
|
||||
_apply_if_missing("knoe-db-user", db_user, pw)
|
||||
_apply_if_missing("knoe-db-user", db_owner, pw)
|
||||
_apply_if_missing("knoe-db-superuser", "postgres", pw)
|
||||
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
8
|
||||
9
|
||||
@ -1 +1 @@
|
||||
13
|
||||
22
|
||||
@ -1,56 +1,52 @@
|
||||
Network Discovery Summary:
|
||||
Primary Router: 10.0.0.1 (eero_5d:50:f2)
|
||||
DNS Servers: 10.0.0.5, 100.100.100.100, 10.0.0.4
|
||||
DNS Servers: 10.0.0.4, 10.0.0.5, 100.100.100.100
|
||||
Detected Devices:
|
||||
- 10.0.0.35 [7e:19:9a:fe:9f:ed] (7e:19:9a:fe:9f:ed): Ports [22, 445, 5900, 11434, 88], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Ollama', 'Active Directory Related']
|
||||
- 10.0.0.205 [a8:20:66:28:12:e7] (Apple_28:12:e7): Ports [22, 445, 5900, 88], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
|
||||
- 10.0.0.203 [00:11:32:3b:2f:08] (Synology_3b:2f:08): Ports [22, 80, 443, 2049, 445], Services: ['SSH', 'NFS', 'Web Server', 'SMB/CIFS (Possible Windows/AD)']
|
||||
- 10.0.0.1 [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.4 [b8:27:eb:b3:6f:2b] (RaspberryPiF_b3:6f:2b): Ports [22, 53, 443, 2049], Services: ['DNS', 'SSH', 'NFS', 'Web Server']
|
||||
- 10.0.0.3 [2c:cf:67:8c:2b:47] (RaspberryPi_8c:2b:47): Ports [22, 53, 80, 443, 2049, 445, 5900, 88, 389, 636], Services: ['DNS', 'SSH', 'NFS', 'VNC', 'Web Server', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
|
||||
- 10.0.0.7 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.5 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 53, 443, 2049, 5900], Services: ['DNS', 'SSH', 'NFS', 'VNC', 'Web Server']
|
||||
- 10.0.0.7 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.6 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 80, 443], Services: ['SSH', 'Web Server']
|
||||
- 10.0.0.203 [00:11:32:3b:2f:08] (Synology_3b:2f:08): Ports [22, 80, 443, 2049, 445], Services: ['SSH', 'NFS', 'Web Server', 'SMB/CIFS (Possible Windows/AD)']
|
||||
- 10.0.0.32 [48:a6:b8:a4:8e:cc] (Sonos_a4:8e:cc): Ports [], Services: []
|
||||
- 10.0.0.2 [dc:a4:ca:ea:1a:2f] (Apple_ea:1a:2f): Ports [], Services: []
|
||||
- 10.0.0.6 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 80, 443], Services: ['SSH', 'Web Server']
|
||||
- 10.0.0.22 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 80, 443], Services: ['SSH', 'Web Server']
|
||||
- 10.0.0.26 [c8:db:26:08:52:f8] (Logitech_08:52:f8): Ports [], Services: []
|
||||
- 10.0.0.205 [a8:20:66:28:12:e7] (Apple_28:12:e7): Ports [22, 445, 5900, 88], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
|
||||
- 10.0.0.33 [ec:b5:fa:b0:76:e4] (PhilipsLight_b0:76:e4): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.189 [00:17:88:a3:2f:cc] (PhilipsLight_a3:2f:cc): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.37 [54:07:7d:22:c0:b2] (Netgear_22:c0:b2): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.44 [24:fc:e5:51:cf:74] (SamsungElect_51:cf:74): Ports [], Services: []
|
||||
- 10.0.0.45 [f8:b4:6a:30:a4:1f] (HewlettPacka_30:a4:1f): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.48 [0c:fe:45:53:f4:3c] (SonyInteract_53:f4:3c): Ports [], Services: []
|
||||
- 10.0.0.37 [54:07:7d:22:c0:b2] (Netgear_22:c0:b2): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.52 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.46 [d4:f7:d5:40:ab:17] (SonyInteract_40:ab:17): Ports [], Services: []
|
||||
- 10.0.0.41 [b8:27:eb:57:10:d9] (RaspberryPiF_57:10:d9): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.107 [b8:06:0d:b7:7c:56] (TuyaSmart_b7:7c:56): Ports [], Services: []
|
||||
- 10.0.0.188 [00:16:6c:c5:1f:54] (SamsungElect_c5:1f:54): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.73 [4c:a9:19:b3:12:f8] (TuyaSmart_b3:12:f8): Ports [], Services: []
|
||||
- 10.0.0.206 [00:a0:de:a2:0b:ef] (Yamaha_a2:0b:ef): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.35\ [7e:19:9a:fe:9f:ed] (7e:19:9a:fe:9f:ed): Ports [], Services: []
|
||||
- 10.0.0.95 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 53, 443, 2049, 5900], Services: ['DNS', 'SSH', 'NFS', 'VNC', 'Web Server']
|
||||
- 10.0.0.99 [20:28:bc:f1:dc:ba] (Visionscape_f1:dc:ba): Ports [], Services: []
|
||||
- 10.0.0.106 [48:a6:b8:a7:50:60] (Sonos_a7:50:60): Ports [], Services: []
|
||||
- 10.0.0.113 [60:81:10:92:02:4e] (Apple_92:02:4e): Ports [], Services: []
|
||||
- 10.0.0.111 [ca:0b:6c:5d:0a:e5] (ca:0b:6c:5d:0a:e5): Ports [], Services: []
|
||||
- 10.0.0.170 [b0:ee:7b:ca:73:99] (Roku_ca:73:99): Ports [], Services: []
|
||||
- 10.0.0.206 [00:a0:de:a2:0b:ef] (Yamaha_a2:0b:ef): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.73 [4c:a9:19:b3:12:f8] (TuyaSmart_b3:12:f8): Ports [], Services: []
|
||||
- 10.0.0.188 [00:16:6c:c5:1f:54] (SamsungElect_c5:1f:54): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.107 [b8:06:0d:b7:7c:56] (TuyaSmart_b7:7c:56): Ports [], Services: []
|
||||
- 10.0.0.99 [20:28:bc:f1:dc:ba] (Visionscape_f1:dc:ba): Ports [], Services: []
|
||||
- 10.0.0.123 [3c:ef:8c:96:e3:3c] (ZhejiangDahu_96:e3:3c): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.124 [9c:8e:cd:02:73:c4] (AmcrestTechn_02:73:c4): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.117 [40:f5:20:1e:5e:91] (Espressif_1e:5e:91): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.125 [a0:60:32:04:73:ba] (AmcrestTechn_04:73:ba): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.127 [58:55:ca:4a:60:6e] (Apple_4a:60:6e): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.130 [f8:bb:bf:a3:84:eb] (eero_a3:84:eb): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.128 [20:c9:d0:94:30:dd] (Apple_94:30:dd): Ports [5900], Services: ['VNC']
|
||||
- 10.0.0.143 [60:5f:8d:7a:aa:32] (eero_7a:aa:32): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.207 [e4:5f:01:90:35:0b] (RaspberryPiT_90:35:0b): Ports [22, 5900], Services: ['SSH', 'VNC']
|
||||
- 10.0.0.127 [58:55:ca:4a:60:6e] (Apple_4a:60:6e): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.125 [a0:60:32:04:73:ba] (AmcrestTechn_04:73:ba): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.130 [f8:bb:bf:a3:84:eb] (eero_a3:84:eb): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.112 [f8:33:31:40:58:c0] (TexasInstrum_40:58:c0): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.143 [60:5f:8d:7a:aa:32] (eero_7a:aa:32): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.145 [7c:a6:b0:04:db:1c] (7c:a6:b0:04:db:1c): Ports [], Services: []
|
||||
- 10.0.0.155 [62:17:97:b3:06:a1] (62:17:97:b3:06:a1): Ports [], Services: []
|
||||
- 10.0.0.128 [20:c9:d0:94:30:dd] (Apple_94:30:dd): Ports [], Services: []
|
||||
- 10.0.0.208 [a0:ad:9f:30:84:5f] (ASUSTekCOMPU_30:84:5f): Ports [], Services: []
|
||||
- 10.0.0.170 [b0:ee:7b:ca:73:99] (Roku_ca:73:99): Ports [], Services: []
|
||||
- 10.0.0.155 [62:17:97:b3:06:a1] (62:17:97:b3:06:a1): Ports [], Services: []
|
||||
- 10.0.0.189 [00:17:88:a3:2f:cc] (PhilipsLight_a3:2f:cc): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.175 [28:80:88:e4:10:1a] (Netgear_e4:10:1a): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.196 [60:5f:8d:88:08:f2] (eero_88:08:f2): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.194 [64:16:66:ac:04:a3] (NestLabs_ac:04:a3): Ports [], Services: []
|
||||
- 10.0.0.199 [7c:a6:b0:01:a7:33] (7c:a6:b0:01:a7:33): Ports [], Services: []
|
||||
- 10.0.0.204 [50:eb:f6:56:f3:26] (ASUSTekCOMPU_56:f3:26): Ports [22, 3389, 445, 5900, 11434], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'RDP (Windows)', 'Ollama']
|
||||
- 10.0.0.1\ [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [], Services: []
|
||||
Ollama Instances found at: 10.0.0.35, 10.0.0.204
|
||||
|
||||
Ansible Inventory Summary:
|
||||
|
||||
302
tests/installer/test_cnpg_barman_webhook.py
Normal file
302
tests/installer/test_cnpg_barman_webhook.py
Normal file
@ -0,0 +1,302 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch, call
|
||||
|
||||
import pytest
|
||||
|
||||
from knoe.core.ops import cloudnative_pg
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _wait_for_webhook_ca_bundle
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_wait_for_webhook_ca_bundle_returns_true_when_ca_bundle_present(monkeypatch):
|
||||
"""Returns True immediately when caBundle is non-empty on the first poll."""
|
||||
calls = []
|
||||
|
||||
def _fake_run(args, **kwargs):
|
||||
calls.append(args)
|
||||
return SimpleNamespace(returncode=0, stdout="dGVzdA==", stderr="")
|
||||
|
||||
monkeypatch.setattr(cloudnative_pg.subprocess, "run", _fake_run)
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
|
||||
result = cloudnative_pg._wait_for_webhook_ca_bundle(env=None, log=None, timeout=30)
|
||||
|
||||
assert result is True
|
||||
assert len(calls) == 1
|
||||
assert "cert-manager-webhook" in calls[0]
|
||||
assert "jsonpath={.webhooks[0].clientConfig.caBundle}" in calls[0]
|
||||
|
||||
|
||||
def test_wait_for_webhook_ca_bundle_returns_false_on_timeout(monkeypatch):
|
||||
"""Returns False when caBundle never appears within the timeout."""
|
||||
monkeypatch.setattr(
|
||||
cloudnative_pg.subprocess, "run",
|
||||
lambda *_a, **_kw: SimpleNamespace(returncode=0, stdout="", stderr=""),
|
||||
)
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
|
||||
# Patch time.time to advance past the deadline quickly
|
||||
_times = iter([0.0, 1.0, 2.0, 200.0]) # deadline=5; last value exceeds it
|
||||
monkeypatch.setattr(cloudnative_pg.time, "time", lambda: next(_times))
|
||||
|
||||
result = cloudnative_pg._wait_for_webhook_ca_bundle(env=None, log=None, timeout=5)
|
||||
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_wait_for_webhook_ca_bundle_returns_true_after_retries(monkeypatch):
|
||||
"""Returns True once caBundle becomes non-empty after initial empty responses."""
|
||||
responses = [
|
||||
SimpleNamespace(returncode=0, stdout="", stderr=""),
|
||||
SimpleNamespace(returncode=1, stdout="", stderr="not found"),
|
||||
SimpleNamespace(returncode=0, stdout="dGVzdA==", stderr=""),
|
||||
]
|
||||
_iter = iter(responses)
|
||||
monkeypatch.setattr(cloudnative_pg.subprocess, "run", lambda *_a, **_kw: next(_iter))
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
|
||||
result = cloudnative_pg._wait_for_webhook_ca_bundle(env=None, log=None, timeout=60)
|
||||
|
||||
assert result is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _try_inject_ca_bundle_manually
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_try_inject_ca_bundle_manually_succeeds(monkeypatch):
|
||||
"""Patches the webhook caBundle from the cert-manager-webhook-ca secret."""
|
||||
logs: list[str] = []
|
||||
run_calls: list[list] = []
|
||||
|
||||
def _fake_run(args, **kwargs):
|
||||
run_calls.append(list(args))
|
||||
# Secret fetch returns a valid CA bundle
|
||||
if "get" in args and "secret" in args:
|
||||
return SimpleNamespace(returncode=0, stdout="dGVzdA==", stderr="")
|
||||
# Patch command succeeds
|
||||
if "patch" in args and "validatingwebhookconfiguration" in args:
|
||||
return SimpleNamespace(returncode=0, stdout="patched", stderr="")
|
||||
# Final poll: caBundle is now present
|
||||
if "jsonpath={.webhooks[0].clientConfig.caBundle}" in args:
|
||||
return SimpleNamespace(returncode=0, stdout="dGVzdA==", stderr="")
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
|
||||
monkeypatch.setattr(cloudnative_pg.subprocess, "run", _fake_run)
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
|
||||
result = cloudnative_pg._try_inject_ca_bundle_manually(env=None, log=logs.append)
|
||||
|
||||
assert result is True
|
||||
assert any("Patching cert-manager-webhook" in m for m in logs)
|
||||
|
||||
|
||||
def test_try_inject_ca_bundle_manually_returns_false_when_secret_missing(monkeypatch):
|
||||
"""Returns False and logs a warning when neither CA secret is found."""
|
||||
logs: list[str] = []
|
||||
|
||||
monkeypatch.setattr(
|
||||
cloudnative_pg.subprocess, "run",
|
||||
lambda *_a, **_kw: SimpleNamespace(returncode=1, stdout="", stderr="not found"),
|
||||
)
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
|
||||
result = cloudnative_pg._try_inject_ca_bundle_manually(env=None, log=logs.append)
|
||||
|
||||
assert result is False
|
||||
assert any("CA secret not found" in m for m in logs)
|
||||
|
||||
|
||||
def test_try_inject_ca_bundle_manually_returns_false_when_patch_fails(monkeypatch):
|
||||
"""Returns False when the kubectl patch command fails."""
|
||||
logs: list[str] = []
|
||||
|
||||
def _fake_run(args, **kwargs):
|
||||
if "get" in args and "secret" in args:
|
||||
return SimpleNamespace(returncode=0, stdout="dGVzdA==", stderr="")
|
||||
if "patch" in args:
|
||||
return SimpleNamespace(returncode=1, stdout="", stderr="patch failed")
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
|
||||
monkeypatch.setattr(cloudnative_pg.subprocess, "run", _fake_run)
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
|
||||
result = cloudnative_pg._try_inject_ca_bundle_manually(env=None, log=logs.append)
|
||||
|
||||
assert result is False
|
||||
assert any("Failed to patch caBundle" in m for m in logs)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# install_barman_plugin — cert-manager recovery path
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_install_barman_plugin_succeeds_after_cert_manager_recovery(monkeypatch):
|
||||
"""Plugin apply succeeds on the second attempt after cert-manager recovery."""
|
||||
logs: list[str] = []
|
||||
|
||||
_apply_calls = [0]
|
||||
_cert_mgr_called = [False]
|
||||
|
||||
def _fake_run(args, **kwargs):
|
||||
if args[:2] == ["kubectl", "apply"]:
|
||||
_apply_calls[0] += 1
|
||||
if _apply_calls[0] == 1:
|
||||
return SimpleNamespace(
|
||||
returncode=1,
|
||||
stdout="",
|
||||
stderr="webhook.cert-manager.io: failed to call webhook: tls: failed to verify certificate: x509: certificate signed by unknown authority",
|
||||
)
|
||||
return SimpleNamespace(returncode=0, stdout="configured", stderr="")
|
||||
# CA bundle poll
|
||||
if "jsonpath={.webhooks[0].clientConfig.caBundle}" in args:
|
||||
return SimpleNamespace(returncode=0, stdout="dGVzdA==", stderr="")
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
|
||||
monkeypatch.setattr(cloudnative_pg.subprocess, "run", _fake_run)
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
|
||||
def _fake_ensure(**_kw):
|
||||
_cert_mgr_called[0] = True
|
||||
return True # CA bundle confirmed ready
|
||||
|
||||
monkeypatch.setattr(cloudnative_pg, "_ensure_cert_manager_for_barman", _fake_ensure)
|
||||
monkeypatch.setattr(cloudnative_pg, "_kubectl", lambda *_a, **_kw: SimpleNamespace(returncode=0, stdout="", stderr=""))
|
||||
monkeypatch.setattr(cloudnative_pg, "_wait_for_barman_tls_secrets", lambda **_kw: True)
|
||||
|
||||
env = {"BARMAN_PLUGIN_MANIFEST_URL": "https://example.com/barman.yaml"}
|
||||
cloudnative_pg.install_barman_plugin(env=env, log=logs.append)
|
||||
|
||||
assert _cert_mgr_called[0], "cert-manager recovery must be triggered"
|
||||
assert any("cert-manager recovered; webhook CA bundle ready" in m for m in logs)
|
||||
assert _apply_calls[0] == 2
|
||||
|
||||
|
||||
def test_install_barman_plugin_logs_warn_when_ca_not_confirmed(monkeypatch):
|
||||
"""Logs a warning (not 'ready') when _ensure_cert_manager_for_barman returns False."""
|
||||
logs: list[str] = []
|
||||
_apply_calls = [0]
|
||||
|
||||
def _fake_run(args, **kwargs):
|
||||
if args[:2] == ["kubectl", "apply"]:
|
||||
_apply_calls[0] += 1
|
||||
if _apply_calls[0] == 1:
|
||||
return SimpleNamespace(
|
||||
returncode=1, stdout="", stderr="x509: certificate signed by unknown authority",
|
||||
)
|
||||
return SimpleNamespace(returncode=0, stdout="ok", stderr="")
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
|
||||
monkeypatch.setattr(cloudnative_pg.subprocess, "run", _fake_run)
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
monkeypatch.setattr(cloudnative_pg, "_ensure_cert_manager_for_barman", lambda **_kw: False)
|
||||
monkeypatch.setattr(cloudnative_pg, "_kubectl", lambda *_a, **_kw: SimpleNamespace(returncode=0, stdout="", stderr=""))
|
||||
monkeypatch.setattr(cloudnative_pg, "_wait_for_barman_tls_secrets", lambda **_kw: True)
|
||||
|
||||
env = {"BARMAN_PLUGIN_MANIFEST_URL": "https://example.com/barman.yaml"}
|
||||
cloudnative_pg.install_barman_plugin(env=env, log=logs.append)
|
||||
|
||||
assert any("CA bundle not confirmed" in m for m in logs)
|
||||
assert not any("webhook CA bundle ready" in m for m in logs)
|
||||
|
||||
|
||||
def test_install_barman_plugin_raises_after_all_retries_exhausted(monkeypatch):
|
||||
"""RuntimeError is raised if all 6 attempts fail with webhook CA errors."""
|
||||
monkeypatch.setattr(
|
||||
cloudnative_pg.subprocess, "run",
|
||||
lambda *_a, **_kw: SimpleNamespace(
|
||||
returncode=1,
|
||||
stdout="",
|
||||
stderr="x509: certificate signed by unknown authority",
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
monkeypatch.setattr(
|
||||
cloudnative_pg, "_ensure_cert_manager_for_barman",
|
||||
lambda **_kw: False,
|
||||
)
|
||||
monkeypatch.setattr(cloudnative_pg, "_kubectl", lambda *_a, **_kw: SimpleNamespace(returncode=0, stdout="", stderr=""))
|
||||
|
||||
env = {"BARMAN_PLUGIN_MANIFEST_URL": "https://example.com/barman.yaml"}
|
||||
with pytest.raises(RuntimeError, match="Failed to apply Barman Cloud plugin after cert-manager restart"):
|
||||
cloudnative_pg.install_barman_plugin(env=env, log=None)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _wait_for_barman_tls_secrets
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_wait_for_barman_tls_secrets_returns_true_when_secrets_present(monkeypatch):
|
||||
"""Returns True immediately when both TLS secrets have all fields populated."""
|
||||
logs: list[str] = []
|
||||
|
||||
monkeypatch.setattr(
|
||||
cloudnative_pg.subprocess, "run",
|
||||
lambda *_a, **_kw: SimpleNamespace(returncode=0, stdout="dGVzdA==", stderr=""),
|
||||
)
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
|
||||
result = cloudnative_pg._wait_for_barman_tls_secrets(env=None, log=logs.append, timeout=30)
|
||||
|
||||
assert result is True
|
||||
assert any("available" in m for m in logs)
|
||||
|
||||
|
||||
def test_wait_for_barman_tls_secrets_returns_false_on_timeout(monkeypatch):
|
||||
"""Returns False when secrets never appear within the timeout."""
|
||||
logs: list[str] = []
|
||||
|
||||
monkeypatch.setattr(
|
||||
cloudnative_pg.subprocess, "run",
|
||||
lambda *_a, **_kw: SimpleNamespace(returncode=1, stdout="", stderr="not found"),
|
||||
)
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
_times = iter([0.0, 1.0, 2.0, 200.0])
|
||||
monkeypatch.setattr(cloudnative_pg.time, "time", lambda: next(_times))
|
||||
|
||||
result = cloudnative_pg._wait_for_barman_tls_secrets(env=None, log=logs.append, timeout=5)
|
||||
|
||||
assert result is False
|
||||
assert any("not ready" in m for m in logs)
|
||||
|
||||
|
||||
def test_wait_for_barman_tls_secrets_returns_true_after_retry(monkeypatch):
|
||||
"""Returns True once secrets become available after an initial miss."""
|
||||
logs: list[str] = []
|
||||
_call_count = [0]
|
||||
|
||||
def _fake_run(args, **kwargs):
|
||||
_call_count[0] += 1
|
||||
# First 4 calls (one full secret loop) return empty; after that return populated
|
||||
if _call_count[0] <= 4:
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
return SimpleNamespace(returncode=0, stdout="dGVzdA==", stderr="")
|
||||
|
||||
monkeypatch.setattr(cloudnative_pg.subprocess, "run", _fake_run)
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
|
||||
result = cloudnative_pg._wait_for_barman_tls_secrets(env=None, log=logs.append, timeout=60)
|
||||
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_install_barman_plugin_warns_when_tls_secrets_not_ready(monkeypatch):
|
||||
"""Logs a warning when _wait_for_barman_tls_secrets returns False after successful apply."""
|
||||
logs: list[str] = []
|
||||
|
||||
monkeypatch.setattr(
|
||||
cloudnative_pg.subprocess, "run",
|
||||
lambda *_a, **_kw: SimpleNamespace(returncode=0, stdout="configured", stderr=""),
|
||||
)
|
||||
monkeypatch.setattr(cloudnative_pg.time, "sleep", lambda _: None)
|
||||
monkeypatch.setattr(cloudnative_pg, "_kubectl", lambda *_a, **_kw: SimpleNamespace(returncode=0, stdout="", stderr=""))
|
||||
monkeypatch.setattr(cloudnative_pg, "_wait_for_barman_tls_secrets", lambda **_kw: False)
|
||||
|
||||
env = {"BARMAN_PLUGIN_MANIFEST_URL": "https://example.com/barman.yaml"}
|
||||
cloudnative_pg.install_barman_plugin(env=env, log=logs.append)
|
||||
|
||||
assert any("pods may fail to start" in m for m in logs)
|
||||
Loading…
Reference in New Issue
Block a user