mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
Refine installer orchestration and Supabase rendering paths
- improve installer/action/controller flow and shell-variable expansion handling across screens\n- adjust Supabase Helm rendering and storage deployment templates\n- align monitoring, cloudnative-pg and repair pipeline behavior with updated config paths\n- refresh and expand installer/core regression tests around milestones, navigation and repair logic Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
parent
c65d336d1f
commit
c0a7d0c5dc
@ -1,5 +1,5 @@
|
||||
; Prole Master Configuration File
|
||||
; Generated by install.py on 2026-03-27 21:57:14
|
||||
; Generated by install.py on 2026-03-31 00:08:32
|
||||
; This file is used as input for Ansible deployment and k8s cluster creation.
|
||||
|
||||
[User]
|
||||
@ -13,10 +13,12 @@ build.run_build = false
|
||||
database_options.distribution = percona
|
||||
database_options.ext.dblink = true
|
||||
database_options.ext.pg_buffercache = true
|
||||
database_options.ext.pg_cron = true
|
||||
database_options.ext.pg_freespacemap = true
|
||||
database_options.ext.pg_repack = true
|
||||
database_options.ext.pg_stat_monitor = true
|
||||
database_options.ext.pg_stat_statements = true
|
||||
database_options.ext.pg_tde = true
|
||||
database_options.ext.pgaudit = true
|
||||
database_options.ext.pgbadger = true
|
||||
database_options.ext.pgcrypto = true
|
||||
@ -51,7 +53,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:WmcrTDHDhUyzeCC4:DvGQhp_kiVfcbpTCDncA_t-xH4ktjvCka7jst-7LjWLb-k0NicaNogLRwFT8b5_pJsPnBFnNhcrz-2iD4Zb0-L3i0gILy8LIwwJajRaic9qDNd01Wm_9kRjv_z2XRhFWiwGEm7m3sd__6BYkeKhBXE9lzii8WpGJgo9bAA==}
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:h4uThQOAj6ASFIFf:_cS5t3-Z3q90-OWiMsX5BnLQnjC8tbomoyuv_UfnNM6XmI2H6yofzJWSyhgNfgFaWxL8ImIwYWVKf9OjvwEhxr54LVSQ9EEm3KQsRW1cADHWnkjl7AGifsi2Wa9ve6E0PueVPAyHC5cIUXJ3Njt_bdegibhjZv6_-jFkGg==}
|
||||
init_cluster.kerberos_enabled = true
|
||||
init_cluster.mode = k8s
|
||||
init_cluster.start_cluster = true
|
||||
@ -62,14 +64,14 @@ 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:eN8L5Bo1KE2xGU-Z:y4ybETpw1POYjX2tP96lWTajMLFsiTXn5DNj7s_l6gBZInnGB5MjuxQTZ-G_FBnK}
|
||||
init_password.db_password_confirm = ${PROLE_SECRET:v1:eN8L5Bo1KE2xGU-Z:y4ybETpw1POYjX2tP96lWTajMLFsiTXn5DNj7s_l6gBZInnGB5MjuxQTZ-G_FBnK}
|
||||
init_password.db_password = ${PROLE_SECRET:v1:uLCCn7fAxP1AHcv7:zahrhbz50ngH59bVmJUvbCm5OTM1Id_ksd5D61I-GixJETQVMewsr1oC7Avu8m6E}
|
||||
init_password.db_password_confirm = ${PROLE_SECRET:v1:uLCCn7fAxP1AHcv7:zahrhbz50ngH59bVmJUvbCm5OTM1Id_ksd5D61I-GixJETQVMewsr1oC7Avu8m6E}
|
||||
init_password.db_username = root
|
||||
init_password.generate_ssh_key = true
|
||||
init_scripts.run_scripts = true
|
||||
kerberos_config.enabled = true
|
||||
kerberos_config.kdc = 10.0.0.3
|
||||
kerberos_config.password = ${PROLE_SECRET:v1:1UPXXGF4GW7MIfhR:_hFj8SAVHRd60qnOw-q5MaM9W8CaxNHgPkFnyXmNXXNzCj5x}
|
||||
kerberos_config.password = ${PROLE_SECRET:v1:O-RQRUsyZ-C-oV7k:-9LlOE5KxpT8qGApgzzH2IneU76fYbCk-GwTsAII1tzxK_P3}
|
||||
kerberos_config.realm = PROLE.ORG
|
||||
kerberos_config.test_connection = false
|
||||
kerberos_config.user = administrator
|
||||
@ -77,25 +79,28 @@ network_scan.run = true
|
||||
ollama_config.model =
|
||||
ollama_config.server_host = fairyland.prole.org
|
||||
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
|
||||
ARGOCD_NAMESPACE = argocd
|
||||
CLUSTER_ENV = prod
|
||||
CLUSTER_NAME = knoe-db
|
||||
DATABASE_NAMESPACE = knoe-db-17-7-043-18-140-17-7-043-18-140
|
||||
DATABASE_NAMESPACE = knoe-db-17-7-043-18-140-17-7-043-18-140-17-7-043-18-140
|
||||
DB_HOST_PORT = 5432
|
||||
DB_PASSWORD = ${PROLE_SECRET:v1:hk7ryF9uC0HMNmE0:ZFtwyX85sqA61qZDuSLEL1gWwuC20r6qCKnTihsGvJ7PODH5XcIRSQ5Ms3Il8lMt}
|
||||
DB_PASSWORD = ${PROLE_SECRET:v1:7FWWyYsJ27QWE7Hf:E99YJFxLGoz_QyHPH7odXmCk6WBuYL3tWU0iBdpcHmb1LBph_VBAa5GyHmH6LVYP}
|
||||
DEPLOYMENT_MODE = k8s
|
||||
DEPLOYMENT_TARGET = prole-prod-cluster
|
||||
DOCKER_IMPORT_DIR =
|
||||
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:eRF7V3BH7FObAF2t:omapQMsXQQA765nO3w8Y7LY5Hn4C1Mrglwk9xyNHEpQoKPBgrbd8aGZwtPE3UgmT_i5eeW110q0aWJtR4kwuK5q6LR9Ec2iHknIJYXA-v6isvhsPK8az1A8rE9kIVcahABT9mSEFmdNPk-UNvISsxqtOZ98fCXoFbfWegg==}
|
||||
PROLE_OPENTOFU_URL = http://127.0.0.1:8080
|
||||
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:dcAmcZGfYyHgaY_0:wtuc48pEhK9Ha7BC3XG57MCf_4Wqgs09g3Jam2VQIc_3jNMIEIxORsDxGJsqZtyN0zmdSxs1ApZos-h6h3R5AkEZsmqdw6TTcFpixP0RwuPWCe5ObWIMyPR3ztWcwDiM69fm1QiFUNGPvARdyw6dBgyRKRqDpVSfXMpM5w==}
|
||||
SERVICE_NAMESPACE = knoe-system
|
||||
SUPABASE_PV_BASE = /synology/d005
|
||||
SUPABASE_PV_BASE_DIR = /synology/d005
|
||||
SUPABASE_PV_NODE = gandalf.prole.org
|
||||
|
||||
[Welcome]
|
||||
; No configuration values captured yet for this section.
|
||||
@ -110,7 +115,7 @@ ANSIBLE_DOMAIN = prole.org
|
||||
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"],"pihole":["pi.prole.org","raspberry.prole.org"],"ad_dc":["myrddin.prole.org"],"k3s_servers":["myrddin.prole.org"],"k3s_agents":["pi.prole.org","merlin.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"],"ssl_hosts":["myrddin.prole.org"],"mariadb_primary":["merlin.prole.org"],"mariadb_replica":["raspberry.prole.org"],"mariadb:children":["mariadb_primary","mariadb_replica"],"merlin_bootstrap":["merlin"]},"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":["k3s_agents","k3s_servers","mariadb_primary","mariadb_replica"]}
|
||||
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":["pi.prole.org","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
|
||||
KDC_AUTO_DETECTED = 10.0.0.3
|
||||
KERBEROS_AUTO_ENABLED = True
|
||||
@ -133,7 +138,6 @@ PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
|
||||
|
||||
[Ollama]
|
||||
OLLAMA_HOST = http://fairyland.prole.org:11434
|
||||
OLLAMA_SERVERS = fairyland.prole.org:11434,k3d.localhost:11434,morgoth.prole.org:11434
|
||||
OLLAMA_SERVER_HOST = fairyland.prole.org
|
||||
OLLAMA_SERVER_PORT = 11434
|
||||
|
||||
@ -152,23 +156,20 @@ GITOPS_ENABLED = False
|
||||
[Dev Cluster (k3d)]
|
||||
CLUSTER_ENV = dev
|
||||
DISPLAY_NAME = knoe-dev-cluster
|
||||
KUBECTL_CONTEXT =
|
||||
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:U7foZs_vBN4-2fLv:wFtVuuFMcJ-R9zXZwdOqxfPsTmRFZSkyvKc_Kqo_kL1-ie8MxWU2M25t78O7EjW9dFxEAYRsPanjxYKDHDd8arRW17kGIqtzlcRM9sXQJimTpa-mMgNCFew28b99c-LuCZbeG3nZU72joGZN6TZ5ZjZZbvJLAOA1Hq6b0A==}
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:ZBrRQReMetk0k4X5:yl6S40jqATR45Ivg5DxoGlo_32X7Q6Zz0BpKIzWO0iKpLe5xK4H-lYTxzucn6AsivxNrZTRjLsTiQ1CrtIcmtDJzOwPjobkt5t2XGKmcBrPoeoUjqSFJolp2-0ClawERDXTnqLHOQJhNLFpqxrRc_AiU0iHiT6DuuZZ9Kg==}
|
||||
MODE = k3s
|
||||
PIPELINE_URL = http://127.0.0.1:8080
|
||||
|
||||
[Prod Cluster (k8s)]
|
||||
ARTIFACTS_DIR = /Users/chrisfu/dev/prole/data/staging
|
||||
CLUSTER_ENV = prole-prod-cluster
|
||||
DISPLAY_NAME = prole-prod-cluster
|
||||
MODE = k8s
|
||||
PIPELINE_URL = http://127.0.0.1:8080
|
||||
|
||||
[Docker Build]
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
; Prole Master Configuration File
|
||||
; Generated by install.py on 2026-03-29 22:54:52
|
||||
; Generated by install.py on 2026-03-31 00:16:16
|
||||
; This file is used as input for Ansible deployment and k8s cluster creation.
|
||||
|
||||
[User]
|
||||
@ -53,7 +53,7 @@ init_cluster.cluster_env = service
|
||||
init_cluster.deployment_target = prole-service-cluster
|
||||
init_cluster.gitops_enabled = false
|
||||
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:ncw40j3021elkcqU:OO4QXoXGTi4ptxeNvqFV9WXNsh_f2QEqSYnJxIR-MW-0J50oItM9NLQeQT3rY2Hju2C2VlSyeuKi3-MBGsm3wzQl5m1yjuVQ9MKrT20u2_pjyom3GuIhfCEx6agfadRHH_9lAio3wTVdvZqBgrTRyMQ67Um07eZRsXBTUA==}
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:AL731QFqjjFGqfFT:Xt95lYCIk-6iCHerFSJQpKwWGJMdr04v-4DawlGbM3OBLJ7qbHOSZ_oJm1WtHkvqpoM7BSJNwlEqHMV7R3gl7IFqVUtqJ_AoaRcLSPKE2Z6JGWkIatR6Vzw3J1cm1iZiM2M9wikI5Wqp3t4cdWTOTf7PUaQHkYbBG1PV3w==}
|
||||
init_cluster.kerberos_enabled = true
|
||||
init_cluster.mode = k3s
|
||||
init_cluster.start_cluster = true
|
||||
@ -64,14 +64,14 @@ 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:4QVLv6L_Zb6w1Zja:4PHGYWVndekQb3ZG7hjNAoIQ_T2yEZlS}
|
||||
init_password.db_password_confirm = ${PROLE_SECRET:v1:4QVLv6L_Zb6w1Zja:4PHGYWVndekQb3ZG7hjNAoIQ_T2yEZlS}
|
||||
init_password.db_password = ${PROLE_SECRET:v1:OcWBTOiijqf-bfTT:6r-ioWdDRdkPNxmbEAQP2TYEPaycWasP}
|
||||
init_password.db_password_confirm = ${PROLE_SECRET:v1:OcWBTOiijqf-bfTT:6r-ioWdDRdkPNxmbEAQP2TYEPaycWasP}
|
||||
init_password.db_username = root
|
||||
init_password.generate_ssh_key = true
|
||||
init_scripts.run_scripts = true
|
||||
kerberos_config.enabled = true
|
||||
kerberos_config.kdc = 10.0.0.3
|
||||
kerberos_config.password = ${PROLE_SECRET:v1:lDmjQ4Cj45LYBD9_:9Aes8WNTZ97oYpi1aFvS8ODHDCQJJJCUBFgNrJBZ3a-Mgwp2}
|
||||
kerberos_config.password = ${PROLE_SECRET:v1:h9mzEWvGpnpU2FRz:5W8czWfjnRxzA8tJPk3eFRtrVLD5DxREkfqmQsnKLsbuAaec}
|
||||
kerberos_config.realm = PROLE.ORG
|
||||
kerberos_config.test_connection = false
|
||||
kerberos_config.user = administrator
|
||||
@ -79,6 +79,8 @@ network_scan.run = true
|
||||
ollama_config.model =
|
||||
ollama_config.server_host = fairyland.prole.org
|
||||
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
|
||||
@ -92,7 +94,7 @@ CNPG_PLACEMENT_PLAN_ID = cnpg-placement-7036c5f65b6ef0db
|
||||
CNPG_STAGE1_NODE = gandalf.prole.org
|
||||
DATABASE_NAMESPACE = knoe-db
|
||||
DB_HOST_PORT = 5432
|
||||
DB_PASSWORD = ${PROLE_SECRET:v1:FgbkzVICzbA4WX7l:t5eAnQUYrTfAiXuUUPgZ8rBJVqAp-WFA}
|
||||
DB_PASSWORD = ${PROLE_SECRET:v1:M7Rt2Xp273X8ixb5:Rjys8A6SqmBv19DvPAvgihwJ0eJ8Xvda}
|
||||
DEPLOYMENT_MODE = k3s
|
||||
DEPLOYMENT_TARGET = prole-service-cluster
|
||||
DOCKER_PRELOAD = false
|
||||
@ -100,8 +102,11 @@ KNOE_DB_USER = root
|
||||
KUBECONTEXT = knoe.dev.prole.org
|
||||
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
|
||||
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
|
||||
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:ncw40j3021elkcqU:OO4QXoXGTi4ptxeNvqFV9WXNsh_f2QEqSYnJxIR-MW-0J50oItM9NLQeQT3rY2Hju2C2VlSyeuKi3-MBGsm3wzQl5m1yjuVQ9MKrT20u2_pjyom3GuIhfCEx6agfadRHH_9lAio3wTVdvZqBgrTRyMQ67Um07eZRsXBTUA==}
|
||||
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:DkOrjdIC-ji9iusP:7uc4RUXpALlyQcGfZfX6LBP7BEwOqBtbbZpbIgdbfJvMQ_1klRdLtDQrcG54p6ayHvaMQ6W7FSPHPLeKbcjDZ8eJMhNMaH9Q0kIyfmiZdbX3nN6QGCxJWjIn-4GJ4z6_gF7hjHrqAAqxReNoWkAokyoVgqKTiOoWToD0GA==}
|
||||
SERVICE_NAMESPACE = knoe-system
|
||||
SUPABASE_PV_BASE = /synology/d005
|
||||
SUPABASE_PV_BASE_DIR = /synology/d005
|
||||
SUPABASE_PV_NODE = gandalf.prole.org
|
||||
SYNOLOGY_ROOTS = /synology/d001,/synology/d002,/synology/d003,/synology/d004,/synology/d005
|
||||
|
||||
[Welcome]
|
||||
@ -133,7 +138,7 @@ PROLE_LOGS = /Users/chrisfu/dev/prole/logs
|
||||
PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
|
||||
|
||||
[Monitoring]
|
||||
; No configuration values captured yet for this section.
|
||||
GRAFANA_ADMIN_PASSWORD = ${PROLE_SECRET:v1:st2uaIMwx55ftSPt:HjzyDuKpzGJyIDVzIKWD6YDzyoqZ4iJ9}
|
||||
|
||||
[Kerberos Authentication]
|
||||
; No configuration values captured yet for this section.
|
||||
@ -158,7 +163,7 @@ SUPABASE_ENABLED = False
|
||||
[Initialize Cluster]
|
||||
ENVIRONMENT = service
|
||||
K3S_SERVER_URL = https://myrddin.prole.org:6443
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:ncw40j3021elkcqU:OO4QXoXGTi4ptxeNvqFV9WXNsh_f2QEqSYnJxIR-MW-0J50oItM9NLQeQT3rY2Hju2C2VlSyeuKi3-MBGsm3wzQl5m1yjuVQ9MKrT20u2_pjyom3GuIhfCEx6agfadRHH_9lAio3wTVdvZqBgrTRyMQ67Um07eZRsXBTUA==}
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:sQeWAX7UamXXV4oR:O7sUJk54DE9gUtZAjmN4uiusuVl0T3SDTn-S4Ojgg844EOQV7dMhE1wncgn8kLXXz_klqkQszDzbTqQm2HdaQuFyCqnHGCtHyUAyFvXSAdhhQ3PSkggylLtbPQPPrEP6WFuFywdKf1QgLl2BOcipREupBj5XDfiNfEAigw==}
|
||||
|
||||
[Dev Cluster (k3d)]
|
||||
CLUSTER_ENV = dev
|
||||
@ -170,7 +175,7 @@ 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:ncw40j3021elkcqU:OO4QXoXGTi4ptxeNvqFV9WXNsh_f2QEqSYnJxIR-MW-0J50oItM9NLQeQT3rY2Hju2C2VlSyeuKi3-MBGsm3wzQl5m1yjuVQ9MKrT20u2_pjyom3GuIhfCEx6agfadRHH_9lAio3wTVdvZqBgrTRyMQ67Um07eZRsXBTUA==}
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:sast5Yrr_9mB5BzC:J8Et0XHuUbZbFzAovq-lgqdu_3rR4k3FHOJ7LmzCls-qcZV9D9_NNdmtw5gOTRA8kyZeP5oAFGAXKPGWHgxMJxGO_Lh2oQ1m_68_ExKCK7oeXxlZC3bFCd_fvQ-lP0thBYA9R-GkV_67tJfluOo632xhv6i3Pl1sgBZt5w==}
|
||||
MODE = k3s
|
||||
|
||||
[Prod Cluster (k8s)]
|
||||
@ -183,7 +188,7 @@ MODE = k8s
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
[Initialization Scripts]
|
||||
STATUS = Attempted
|
||||
STATUS = Completed
|
||||
|
||||
[Deployment]
|
||||
MODE = k3s
|
||||
|
||||
10
install.py
10
install.py
@ -1,8 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Thin launch vehicle for the Prole installer.
|
||||
|
||||
Keeps legacy UI available while exposing a stable import surface for tests.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@ -13,16 +11,16 @@ from tkinter import ttk, scrolledtext, messagebox, filedialog # noqa: F401
|
||||
from pathlib import Path # noqa: F401
|
||||
|
||||
from knoe.ui.screens import (
|
||||
ProleInstaller,
|
||||
ProleController,
|
||||
KnoeInstaller,
|
||||
KnoeController,
|
||||
get_resource_path,
|
||||
has_display,
|
||||
main,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ProleInstaller",
|
||||
"ProleController",
|
||||
"KnoeInstaller",
|
||||
"KnoeController",
|
||||
"get_resource_path",
|
||||
"has_display",
|
||||
"main",
|
||||
|
||||
66
knoe-db.iml
66
knoe-db.iml
@ -2,10 +2,74 @@
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$" dumb="true">
|
||||
<sourceFolder url="file://$MODULE_DIR$/lib/python3.14/site-packages" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/authority" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/authority/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/authority/src/main" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/authority/src/test" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/conf" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/docs" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/etc" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/gitea" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/infrastructure" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/k3s" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/k8s" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/knoe" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/knoe-db" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/modes" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/net" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/prod" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/prole" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/prole-app" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/prole-auth" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/prole-auth/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/prole-mssql-db" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/prole-net" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/prole-tools-app" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/scan" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/scripts" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/secrets" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/service" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/sql" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/supabase" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tools" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/deploy/gcp/terraform/.terraform" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/deploy/opentofu/k3s/.terraform" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.ansible" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.vagrant" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.vscode" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/<MagicMock name='mock.Entry().get().strip().strip()' id='4677834960'>" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/bin" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/data" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/deploy" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/dev" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/etc/secrets" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/htmlcov" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/img" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/include" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/knoe-db/data" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/lib" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/logs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/mock_val" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/prole-tools-app" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/ssh-keys" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vault_backup" />
|
||||
</content>
|
||||
</component>
|
||||
<component name="FacetManager">
|
||||
<facet type="Spring" name="Spring">
|
||||
<configuration />
|
||||
</facet>
|
||||
<facet type="Python" name="Python">
|
||||
<configuration sdkName="Python 3.14 (prole)" />
|
||||
</facet>
|
||||
</component>
|
||||
</module>
|
||||
@ -4,17 +4,16 @@ Installer actions and unattended workflow helpers.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import secrets
|
||||
import shlex
|
||||
import socket
|
||||
import string
|
||||
import sys
|
||||
import threading
|
||||
import uuid
|
||||
from typing import Callable, Sequence
|
||||
|
||||
import logging
|
||||
import secrets
|
||||
import string
|
||||
from knoe import prole_conf as prole_conf_mgr
|
||||
from knoe.config import (
|
||||
_expand_path,
|
||||
_collect_cfg_vars,
|
||||
@ -28,8 +27,13 @@ from knoe.config import (
|
||||
_encrypt_cfg_secret,
|
||||
_merge_kubeconfig,
|
||||
)
|
||||
from knoe import prole_conf as prole_conf_mgr
|
||||
from knoe.core.controller import ProleController
|
||||
from knoe.core.build_context import copy_build_context_dir
|
||||
from knoe.core.cnpg_placement import (
|
||||
load_cnpg_placement_plan,
|
||||
plan_cnpg_placement,
|
||||
save_cnpg_placement_plan,
|
||||
)
|
||||
from knoe.core.controller import KnoeController
|
||||
from knoe.core.env import * # noqa: F401,F403
|
||||
from knoe.core.env import (
|
||||
_parse_ollama_host,
|
||||
@ -57,7 +61,6 @@ from knoe.core.env import (
|
||||
_render_prole_cfg,
|
||||
_k3d_prole_data_volume_args,
|
||||
_sync_opentofu_pipeline,
|
||||
_normalize_k3s_token,
|
||||
_read_k3s_cfg,
|
||||
_safe_str,
|
||||
_expand_cfg_value as _expand_env_cfg_value,
|
||||
@ -80,34 +83,29 @@ from knoe.core.milestones import (
|
||||
SupabaseImagePreloadMilestone,
|
||||
SupabaseMilestone,
|
||||
)
|
||||
from knoe.core.stream_exec import run_streaming_cmd
|
||||
from knoe.core.build_context import copy_build_context_dir
|
||||
from knoe.core.cnpg_placement import (
|
||||
load_cnpg_placement_plan,
|
||||
plan_cnpg_placement,
|
||||
save_cnpg_placement_plan,
|
||||
)
|
||||
from knoe.core.policy import (
|
||||
POLICY_CFG_KEY,
|
||||
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES,
|
||||
evaluate_optional_workloads_allowed,
|
||||
)
|
||||
from knoe.core.ops.storage import (
|
||||
ClusterStorageSpec,
|
||||
StorageProvisioningError,
|
||||
provision_cluster_storage,
|
||||
)
|
||||
from knoe.core.ops import garage_store as garage_store_ops
|
||||
from knoe.core.ops import monitoring as monitoring_ops
|
||||
from knoe.core.ops import openbao as openbao_ops
|
||||
from knoe.core.ops import opentofu as opentofu_ops
|
||||
from knoe.core.ops import registry as registry_ops
|
||||
from knoe.core.ops.cloudnative_pg import (
|
||||
initialize as cnpg_initialize,
|
||||
deploy as cnpg_deploy,
|
||||
install_barman_plugin as cnpg_install_barman_plugin,
|
||||
rollout as cnpg_rollout,
|
||||
)
|
||||
from knoe.core.ops import openbao as openbao_ops
|
||||
from knoe.core.ops import registry as registry_ops
|
||||
from knoe.core.ops import garage_store as garage_store_ops
|
||||
from knoe.core.ops import opentofu as opentofu_ops
|
||||
from knoe.core.ops import monitoring as monitoring_ops
|
||||
from knoe.core.ops.storage import (
|
||||
ClusterStorageSpec,
|
||||
StorageProvisioningError,
|
||||
provision_cluster_storage,
|
||||
)
|
||||
from knoe.core.policy import (
|
||||
POLICY_CFG_KEY,
|
||||
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES,
|
||||
evaluate_optional_workloads_allowed,
|
||||
)
|
||||
from knoe.core.stream_exec import run_streaming_cmd
|
||||
from typing import Callable, Sequence
|
||||
|
||||
|
||||
def _configure_unbuffered_io():
|
||||
@ -157,7 +155,7 @@ def _select_existing_kubeconfig(candidates: Sequence[Path]) -> Path | None:
|
||||
return first_existing
|
||||
|
||||
|
||||
class ProleInstaller:
|
||||
class KnoeInstaller:
|
||||
"""Shared business logic for both silent and interactive installer modes.
|
||||
|
||||
Subclasses must provide a :meth:`_get_input` implementation that bridges
|
||||
@ -3095,16 +3093,17 @@ class _TeeWriter:
|
||||
return getattr(self._original, name)
|
||||
|
||||
|
||||
class ProleConsoleInstaller(ProleInstaller):
|
||||
class KnoeConsoleInstaller(KnoeInstaller):
|
||||
"""Console-based unattended installer driven by prole.cfg inputs."""
|
||||
|
||||
silent = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
controller: ProleController,
|
||||
controller: KnoeController,
|
||||
cfg_path: str | None = None,
|
||||
reset_cluster: bool = False,
|
||||
delete_db: bool = False,
|
||||
):
|
||||
self.controller = controller
|
||||
self.project_root = controller.project_root
|
||||
@ -3114,6 +3113,7 @@ class ProleConsoleInstaller(ProleInstaller):
|
||||
self.controller.cfg_path = self.cfg_path
|
||||
self.reset_cluster = bool(reset_cluster)
|
||||
self.reset_requested = bool(reset_cluster)
|
||||
self.delete_db_requested = bool(delete_db)
|
||||
# Keep a persistent indicator for reset-oriented reconcile actions (e.g.
|
||||
# reclaiming safe stale Released PV claimRefs). `_perform_cluster_reset()`
|
||||
# consumes `reset_requested` and clears it, but we still want to run
|
||||
@ -6025,9 +6025,15 @@ class ProleConsoleInstaller(ProleInstaller):
|
||||
# Ensure we have a cert-based kubeconfig (or a valid bearer token config)
|
||||
# before invoking any kubectl-based reset scripts.
|
||||
self._ensure_project_k3s_kubeconfig(server, token)
|
||||
self.log(
|
||||
f"[RESET] Namespaces to clean (PVCs preserved): {', '.join(reset_namespaces)}"
|
||||
)
|
||||
if self.delete_db_requested:
|
||||
self.log(f"[RESET] Namespaces to clean: {', '.join(reset_namespaces)}")
|
||||
self.log(
|
||||
f"[RESET] --delete-db enabled; database PVCs in namespace '{db_ns}' will be deleted."
|
||||
)
|
||||
else:
|
||||
self.log(
|
||||
f"[RESET] Namespaces to clean (PVCs preserved): {', '.join(reset_namespaces)}"
|
||||
)
|
||||
self.log(
|
||||
f"[RESET] Preserving registry workload in namespace: {registry_ns}"
|
||||
)
|
||||
@ -6038,10 +6044,13 @@ class ProleConsoleInstaller(ProleInstaller):
|
||||
token,
|
||||
registry_namespace=registry_ns,
|
||||
clear_node_reservations=True,
|
||||
delete_pvcs_namespaces=[db_ns] if self.delete_db_requested else None,
|
||||
force_delete_bound_pvs=self.delete_db_requested,
|
||||
)
|
||||
self._cleanup_local_k3s_artifacts()
|
||||
|
||||
self.reset_requested = False
|
||||
self.delete_db_requested = False
|
||||
|
||||
def _cleanup_local_k3s_artifacts(self) -> None:
|
||||
paths = [
|
||||
@ -6571,7 +6580,7 @@ def _run_silent_install_test(project_root: Path, cfg_path: Path) -> int:
|
||||
|
||||
|
||||
def _attempt_k3s_repair(
|
||||
controller: ProleController,
|
||||
controller: KnoeController,
|
||||
namespace: str,
|
||||
server: str,
|
||||
token: str,
|
||||
@ -6617,6 +6626,8 @@ def _reset_k3s_namespace(
|
||||
*,
|
||||
registry_namespace: str | None = None,
|
||||
clear_node_reservations: bool = False,
|
||||
delete_pvcs_namespaces: Sequence[str] | None = None,
|
||||
force_delete_bound_pvs: bool = False,
|
||||
) -> None:
|
||||
script = project_root / "scripts" / "reset-ns.sh"
|
||||
if not script.exists():
|
||||
@ -6649,10 +6660,82 @@ def _reset_k3s_namespace(
|
||||
env["KUBECONFIG"] = str(kubeconfig_path)
|
||||
|
||||
reg_ns = (registry_namespace or "").strip()
|
||||
delete_pvcs_set = {
|
||||
str(ns or "").strip()
|
||||
for ns in (delete_pvcs_namespaces or [])
|
||||
if str(ns or "").strip()
|
||||
}
|
||||
|
||||
def _collect_bound_pvs(target_ns: str) -> list[str]:
|
||||
res = subprocess.run(
|
||||
[
|
||||
"kubectl",
|
||||
"-n",
|
||||
target_ns,
|
||||
"get",
|
||||
"pvc",
|
||||
"-o",
|
||||
"jsonpath={range .items[*]}{.spec.volumeName}{\"\\n\"}{end}",
|
||||
],
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if res.returncode != 0:
|
||||
print(
|
||||
f"[WARN] Could not list PVC-bound PVs in {target_ns} (code {res.returncode}).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return []
|
||||
pvs: list[str] = []
|
||||
for line in (res.stdout or "").splitlines():
|
||||
pv = (line or "").strip()
|
||||
if pv and pv not in pvs:
|
||||
pvs.append(pv)
|
||||
return pvs
|
||||
|
||||
def _set_reclaim_policy_delete(pvs: Sequence[str]) -> None:
|
||||
for pv in pvs:
|
||||
patch = '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}'
|
||||
res = subprocess.run(
|
||||
["kubectl", "patch", "pv", pv, "--type=merge", "-p", patch],
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if res.returncode != 0:
|
||||
print(
|
||||
f"[WARN] Failed to set reclaimPolicy=Delete for pv/{pv} (code {res.returncode}).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
def _delete_pvs(pvs: Sequence[str]) -> None:
|
||||
for pv in pvs:
|
||||
res = subprocess.run(
|
||||
["kubectl", "delete", "pv", pv, "--ignore-not-found", "--wait=false"],
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if res.returncode != 0:
|
||||
print(
|
||||
f"[WARN] Failed to delete pv/{pv} (code {res.returncode}).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
for idx, ns in enumerate(namespaces):
|
||||
delete_pvcs = ns in delete_pvcs_set
|
||||
bound_pvs: list[str] = []
|
||||
if delete_pvcs and force_delete_bound_pvs:
|
||||
bound_pvs = _collect_bound_pvs(ns)
|
||||
if bound_pvs:
|
||||
_set_reclaim_policy_delete(bound_pvs)
|
||||
|
||||
cmd = ["bash", str(script), "-n", ns]
|
||||
if reg_ns and ns == reg_ns:
|
||||
cmd.append("--keep-registry")
|
||||
if delete_pvcs:
|
||||
cmd.append("--delete-pvcs")
|
||||
if clear_node_reservations and idx == (len(namespaces) - 1):
|
||||
cmd.append("--clear-node-reservations")
|
||||
res = subprocess.run(cmd, env=env)
|
||||
@ -6661,10 +6744,13 @@ def _reset_k3s_namespace(
|
||||
f"[WARN] Namespace reset failed for {ns} (code {res.returncode})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
continue
|
||||
if delete_pvcs and force_delete_bound_pvs and bound_pvs:
|
||||
_delete_pvs(bound_pvs)
|
||||
|
||||
|
||||
def _prepare_k3s_pipeline(
|
||||
controller: ProleController, log_fn=None, skip_validation: bool = False
|
||||
controller: KnoeController, log_fn=None, skip_validation: bool = False
|
||||
) -> int:
|
||||
"""Prepare OpenTofu k3s pipeline: stage manifests, tfvars, ArgoCD apps.
|
||||
|
||||
@ -6702,7 +6788,7 @@ def _prepare_k3s_pipeline(
|
||||
|
||||
# Load config to resolve namespace and secrets for pipeline tfvars
|
||||
_log("==> Loading knoe config...\n")
|
||||
knoe = ProleConsoleInstaller(controller, str(cfg_path))
|
||||
knoe = KnoeConsoleInstaller(controller, str(cfg_path))
|
||||
try:
|
||||
existing_inputs = knoe._load_inputs_from_cfg()
|
||||
except Exception:
|
||||
|
||||
@ -14,14 +14,14 @@ if TYPE_CHECKING:
|
||||
from knoe.milestone import Milestone
|
||||
|
||||
|
||||
class ProleController:
|
||||
class KnoeController:
|
||||
"""Business logic for Prole installer, separated from UI."""
|
||||
|
||||
def __init__(self, project_root: Path, verbose: bool = False, cfg_path: Path | None = None):
|
||||
self.project_root = project_root
|
||||
self.verbose = verbose
|
||||
self.cfg_path = cfg_path
|
||||
self.logger = logging.getLogger("ProleController")
|
||||
self.logger = logging.getLogger("KnoeController")
|
||||
self.state = InstallerState(controller=self)
|
||||
|
||||
def check_docker_running(self) -> bool:
|
||||
|
||||
@ -9,13 +9,10 @@ import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from knoe.config import _expand_path, _collect_cfg_vars, _expand_cfg_value
|
||||
from knoe import prole_conf
|
||||
from knoe.config import _expand_path, _collect_cfg_vars, _expand_cfg_value
|
||||
from knoe.core.env import (
|
||||
_build_required_port_forwards,
|
||||
_pf_extract_id,
|
||||
_pf_mapping_str,
|
||||
_pf_upsert_mapping,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -21,23 +21,23 @@ Public API
|
||||
rollout(namespace, cluster_name, env, log) -> None
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
import json
|
||||
import logging
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import (
|
||||
ECDSA,
|
||||
SECP256R1,
|
||||
generate_private_key,
|
||||
)
|
||||
from cryptography.x509.oid import NameOID
|
||||
from typing import Callable
|
||||
|
||||
_LOG = logging.getLogger(__name__)
|
||||
_LogFn = Callable[[str], None]
|
||||
|
||||
@ -6,8 +6,8 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
from knoe import prole_conf
|
||||
from knoe.core.controller import ProleController
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
from knoe.core.controller import KnoeController
|
||||
from knoe.core.actions import KnoeConsoleInstaller
|
||||
|
||||
|
||||
def _project_root() -> Path:
|
||||
@ -80,8 +80,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
if not cfg:
|
||||
cfg = str(prole_conf.entrypoint_path(prole_conf.resolve_prole_conf_dir(root)))
|
||||
|
||||
controller = ProleController(project_root=root, verbose=bool(args.verbose), cfg_path=Path(cfg))
|
||||
installer = ProleConsoleInstaller(controller, cfg_path=cfg)
|
||||
controller = KnoeController(project_root=root, verbose=bool(args.verbose), cfg_path=Path(cfg))
|
||||
installer = KnoeConsoleInstaller(controller, cfg_path=cfg)
|
||||
|
||||
try:
|
||||
existing_inputs = knoe._load_inputs_from_cfg()
|
||||
|
||||
@ -18,22 +18,21 @@ import time
|
||||
from pathlib import Path
|
||||
|
||||
from . import config as inst_config
|
||||
from .ncurses_ui import CursesWindow, NavFooter, InputField
|
||||
from .core.stream_exec import run_streaming_cmd
|
||||
from .config import _write_k3s_kubeconfig, _merge_kubeconfig
|
||||
from .core.env import (
|
||||
_resolve_k3s_connection,
|
||||
_verify_k3s_services_status,
|
||||
_find_kubeconfig_file,
|
||||
_read_k3s_cfg,
|
||||
resolve_prole_home,
|
||||
)
|
||||
from .core.ops import openbao as openbao_ops
|
||||
from .core.ops import registry as registry_ops
|
||||
from .core.ops import opentofu as opentofu_ops
|
||||
from .config import _write_k3s_kubeconfig, _merge_kubeconfig
|
||||
from .core.ops import registry as registry_ops
|
||||
from .core.stream_exec import run_streaming_cmd
|
||||
from .ncurses_ui import CursesWindow, NavFooter, InputField
|
||||
|
||||
|
||||
class ProleNcursesInstaller:
|
||||
class KnoeNcursesInstaller:
|
||||
"""Ncurses-based installer matching install.py screen flow."""
|
||||
|
||||
def __init__(self, stdscr, controller):
|
||||
@ -974,7 +973,7 @@ def run_ncurses_installer(controller):
|
||||
"""Entry point for ncurses installer."""
|
||||
|
||||
def _main(stdscr):
|
||||
installer = ProleNcursesInstaller(stdscr, controller)
|
||||
installer = KnoeNcursesInstaller(stdscr, controller)
|
||||
knoe.run()
|
||||
|
||||
try:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"""
|
||||
Prole Service Dependencies Installer — screen package.
|
||||
|
||||
This package splits the monolithic ``ProleInstaller`` class into cohesive
|
||||
This package splits the monolithic ``KnoeInstaller`` class into cohesive
|
||||
mixin modules, one per logical screen / concern. The composed class is
|
||||
assembled here and re-exported so that external callers (``install.py``,
|
||||
tests) see the same public surface as before.
|
||||
@ -10,7 +10,6 @@ tests) see the same public surface as before.
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import platform
|
||||
import signal
|
||||
import socket
|
||||
import threading
|
||||
@ -18,45 +17,18 @@ import tkinter as tk
|
||||
import uuid
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
|
||||
from knoe import deploy as inst_deploy
|
||||
from knoe import prole_conf
|
||||
import platform
|
||||
from knoe import screen as ui
|
||||
from knoe.build import get_build_command as inst_get_build_command
|
||||
from knoe.config import (
|
||||
_expand_cfg_value,
|
||||
_collect_cfg_vars,
|
||||
_is_prole_secret,
|
||||
_decrypt_prole_secret,
|
||||
_is_openbao_ref,
|
||||
_write_k3s_kubeconfig,
|
||||
_encrypt_cfg_secret,
|
||||
_encrypt_prole_secret,
|
||||
)
|
||||
from knoe.core.controller import ProleController
|
||||
from knoe.core.controller import KnoeController
|
||||
from knoe.core.env import (
|
||||
_detect_ansible_topology,
|
||||
_format_ansible_topology_summary,
|
||||
_normalize_k3s_token,
|
||||
_deployment_target_label,
|
||||
_cluster_env_radio_value,
|
||||
_find_kubeconfig_file,
|
||||
_bool_str,
|
||||
_looks_like_k8s_bearer_token,
|
||||
_deployment_mode_from_env,
|
||||
_pf_extract_id,
|
||||
_ensure_ansible_vault_credentials,
|
||||
_openbao_placeholder,
|
||||
_pf_upsert_mapping,
|
||||
_default_opentofu_pipeline_url,
|
||||
_build_required_port_forwards,
|
||||
_render_prole_cfg,
|
||||
_safe_str,
|
||||
_normalize_cluster_env,
|
||||
_k3d_prole_data_volume_args,
|
||||
_push_docker_image,
|
||||
_http_ping_registry,
|
||||
_local_registry_enabled,
|
||||
_collect_images_from_files,
|
||||
)
|
||||
|
||||
# On macOS, set process name early so the menu bar reads correctly.
|
||||
@ -70,10 +42,9 @@ if platform.system() == "Darwin":
|
||||
|
||||
from knoe.core.env import * # noqa: F401,F403
|
||||
from knoe.core.actions import (
|
||||
ProleInstaller,
|
||||
ProleConsoleInstaller,
|
||||
KnoeInstaller,
|
||||
KnoeConsoleInstaller,
|
||||
_prepare_k3s_pipeline,
|
||||
_reset_k3s_namespace,
|
||||
)
|
||||
|
||||
# Keep global frames list in memory (legacy requirement).
|
||||
@ -108,8 +79,8 @@ from knoe.ui.screens.cfg import ConfigMixin
|
||||
# ---------------------------------------------------------------------------
|
||||
# Composed installer class
|
||||
# ---------------------------------------------------------------------------
|
||||
class ProleInstaller(
|
||||
ProleInstaller,
|
||||
class KnoeInstaller(
|
||||
KnoeInstaller,
|
||||
ScreenBaseMixin,
|
||||
NavigationMixin,
|
||||
WelcomeScreenMixin,
|
||||
@ -212,7 +183,7 @@ class ProleInstaller(
|
||||
conf_dir = prole_conf.resolve_prole_conf_dir(PROJECT_ROOT)
|
||||
resolved_cfg = prole_conf.entrypoint_path(conf_dir)
|
||||
|
||||
self.controller = ProleController(
|
||||
self.controller = KnoeController(
|
||||
PROJECT_ROOT, verbose=verbose, cfg_path=resolved_cfg
|
||||
)
|
||||
self.project_root = PROJECT_ROOT
|
||||
@ -734,6 +705,21 @@ class ProleInstaller(
|
||||
self.kerberos_password = tk.StringVar()
|
||||
self.kerberos_kdc = tk.StringVar()
|
||||
self.supabase_enabled = tk.BooleanVar(value=False)
|
||||
_global_cfg = self.prole_cfg_data.get("Global", {}) or {}
|
||||
_supabase_pv_node = (
|
||||
(_global_cfg.get("SUPABASE_PV_NODE", "") or "").strip()
|
||||
or (os.environ.get("SUPABASE_PV_NODE") or "").strip()
|
||||
or "gandalf.prole.org"
|
||||
)
|
||||
_supabase_pv_base = (
|
||||
(_global_cfg.get("SUPABASE_PV_BASE_DIR", "") or "").strip()
|
||||
or (_global_cfg.get("SUPABASE_PV_BASE", "") or "").strip()
|
||||
or (os.environ.get("SUPABASE_PV_BASE_DIR") or "").strip()
|
||||
or (os.environ.get("SUPABASE_PV_BASE") or "").strip()
|
||||
or "/synology/d005"
|
||||
)
|
||||
self.supabase_pv_node = tk.StringVar(value=_supabase_pv_node)
|
||||
self.supabase_pv_base_dir = tk.StringVar(value=_supabase_pv_base)
|
||||
self.at_rest_encryption_enabled = tk.BooleanVar(value=True)
|
||||
self.ollama_server_host = tk.StringVar()
|
||||
self.ollama_server_port = tk.StringVar(value=DEFAULT_OLLAMA_PORT)
|
||||
@ -891,6 +877,12 @@ def main():
|
||||
action="store_true",
|
||||
help="Reset k3d dev cluster or k3s service cluster",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--delete-db",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-gui",
|
||||
action="store_true",
|
||||
@ -919,6 +911,9 @@ def main():
|
||||
parser.add_argument("command_args", nargs="*", help="Arguments for the subcommand")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.delete_db and not args.reset:
|
||||
parser.error("--delete-db requires --reset")
|
||||
|
||||
# Set up log file tee if --log is requested
|
||||
log_file_path = None
|
||||
if args.log is not None:
|
||||
@ -933,7 +928,7 @@ def main():
|
||||
|
||||
setup_logging(verbose=args.verbose, debug=args.debug)
|
||||
|
||||
controller = ProleController(
|
||||
controller = KnoeController(
|
||||
PROJECT_ROOT,
|
||||
verbose=args.verbose,
|
||||
cfg_path=Path(args.config).expanduser() if args.config else None,
|
||||
@ -953,8 +948,11 @@ def main():
|
||||
|
||||
if args.silent:
|
||||
_ensure_ansible_vault_credentials(prompt_ui=False)
|
||||
installer = ProleConsoleInstaller(
|
||||
controller, args.config, reset_cluster=args.reset
|
||||
installer = KnoeConsoleInstaller(
|
||||
controller,
|
||||
args.config,
|
||||
reset_cluster=args.reset,
|
||||
delete_db=args.delete_db,
|
||||
)
|
||||
if log_file_path:
|
||||
installer.enable_log_file(log_file_path)
|
||||
@ -994,7 +992,7 @@ def main():
|
||||
except Exception:
|
||||
root.geometry(f"{window_width}x{window_height}")
|
||||
_ensure_ansible_vault_credentials(prompt_ui=True, root=root)
|
||||
ProleInstaller(
|
||||
KnoeInstaller(
|
||||
root,
|
||||
config_path=args.config,
|
||||
reset_cluster=args.reset,
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
import tkinter as tk
|
||||
from datetime import datetime
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from knoe.screen import TerminalConsole
|
||||
|
||||
from knoe import screen as ui
|
||||
|
||||
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
"""Build screen, terminal console overlay and process management."""
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
import tkinter as tk
|
||||
import webbrowser
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from knoe.screen import TerminalConsole
|
||||
|
||||
import platform
|
||||
from knoe import deploy as inst_deploy
|
||||
from knoe.build import get_build_command as inst_get_build_command
|
||||
from knoe import screen as ui
|
||||
from knoe.build import get_build_command as inst_get_build_command
|
||||
from knoe.core.env import PROJECT_ROOT
|
||||
|
||||
|
||||
|
||||
@ -2,14 +2,17 @@
|
||||
|
||||
import configparser
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
import string
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import tkinter as tk
|
||||
from pathlib import Path
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from knoe import prole_conf
|
||||
|
||||
import platform
|
||||
from knoe.config import (
|
||||
_encrypt_cfg_secret,
|
||||
_filter_cfg_values_for_persistence,
|
||||
)
|
||||
from knoe.core.env import (
|
||||
DEFAULT_ACTION_FLAGS,
|
||||
DEFAULT_OLLAMA_PORT,
|
||||
@ -28,12 +31,6 @@ from knoe.core.env import (
|
||||
_read_k3s_cfg,
|
||||
_render_prole_cfg,
|
||||
)
|
||||
from knoe.config import (
|
||||
_collect_cfg_vars,
|
||||
_encrypt_cfg_secret,
|
||||
_expand_cfg_value,
|
||||
_filter_cfg_values_for_persistence,
|
||||
)
|
||||
|
||||
|
||||
class ConfigMixin:
|
||||
@ -112,6 +109,9 @@ class ConfigMixin:
|
||||
"DATABASE_NAMESPACE": (self.db_namespace.get() or "").strip(),
|
||||
"CLUSTER_NAME": (self.cnpg_cluster_name.get() or "").strip(),
|
||||
"DB_HOST_PORT": (self.db_host_port.get() or "5432").strip(),
|
||||
"SUPABASE_PV_NODE": (self.supabase_pv_node.get() or "").strip(),
|
||||
"SUPABASE_PV_BASE": (self.supabase_pv_base_dir.get() or "").strip(),
|
||||
"SUPABASE_PV_BASE_DIR": (self.supabase_pv_base_dir.get() or "").strip(),
|
||||
"PROLE_OPENTOFU_URL": _default_opentofu_pipeline_url(),
|
||||
}
|
||||
if prole_home_override:
|
||||
@ -477,6 +477,8 @@ class ConfigMixin:
|
||||
DEFAULT_ACTION_FLAGS.get("init_cluster.start_cluster", True),
|
||||
),
|
||||
)
|
||||
_set("supabase_config.pv_node", _get_var(self.supabase_pv_node, ""))
|
||||
_set("supabase_config.pv_base_dir", _get_var(self.supabase_pv_base_dir, ""))
|
||||
|
||||
# Kerberos config
|
||||
_set_bool("kerberos_config.enabled", _get_var(self.kerberos_enabled, False))
|
||||
|
||||
@ -1,34 +1,18 @@
|
||||
"""Cluster lifecycle management (k3d / k3s / k8s)."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
import tkinter as tk
|
||||
from pathlib import Path
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from knoe.screen import TerminalConsole
|
||||
from knoe import screen as ui
|
||||
|
||||
import json
|
||||
from knoe import prole_conf
|
||||
from knoe.core.env import (
|
||||
PROJECT_ROOT,
|
||||
_default_opentofu_pipeline_url,
|
||||
_deployment_mode_from_env,
|
||||
_deployment_target_label,
|
||||
_find_kubeconfig_file,
|
||||
_k3d_prole_data_volume_args,
|
||||
_kubectl_base_cmd_for_k3s as _kubectl_base_cmd_for_k3s_fn,
|
||||
_looks_like_k8s_bearer_token,
|
||||
_normalize_cluster_env,
|
||||
_normalize_k3s_token,
|
||||
_resolve_k3s_connection as _resolve_k3s_connection_fn,
|
||||
_safe_str,
|
||||
)
|
||||
from knoe import screen as ui
|
||||
from knoe.config import (
|
||||
_collect_cfg_vars_from_data,
|
||||
_encrypt_cfg_secret,
|
||||
@ -37,6 +21,18 @@ from knoe.config import (
|
||||
_write_k3s_kubeconfig,
|
||||
)
|
||||
from knoe.core.actions import _reset_k3s_namespace
|
||||
from knoe.core.env import (
|
||||
PROJECT_ROOT,
|
||||
_default_opentofu_pipeline_url,
|
||||
_deployment_mode_from_env,
|
||||
_deployment_target_label,
|
||||
_find_kubeconfig_file,
|
||||
_k3d_prole_data_volume_args,
|
||||
_normalize_cluster_env,
|
||||
_normalize_k3s_token,
|
||||
_resolve_k3s_connection as _resolve_k3s_connection_fn,
|
||||
_safe_str,
|
||||
)
|
||||
from knoe.core.prod_config import ProdConfigApi, build_config, to_api_payload
|
||||
|
||||
|
||||
@ -801,7 +797,10 @@ class ClusterScreenMixin:
|
||||
self._k3s_service_status_console = status_console
|
||||
self._k3s_service_deploy_console = deploy_console
|
||||
|
||||
self._common_services_success = bool(getattr(self, "_all_services_green", False))
|
||||
self._common_services_success = bool(
|
||||
getattr(self, "_common_services_success", False)
|
||||
or getattr(self, "_all_services_green", False)
|
||||
)
|
||||
self.update_footer()
|
||||
self._verify_k3s_services()
|
||||
|
||||
@ -845,7 +844,7 @@ class ClusterScreenMixin:
|
||||
return key
|
||||
|
||||
def _is_valid_k8s_namespace(self, value: str | None) -> bool:
|
||||
ns = (value or "").strip()
|
||||
ns = _safe_str(value).strip()
|
||||
if not ns or len(ns) > 63:
|
||||
return False
|
||||
return re.fullmatch(r"[a-z0-9]([-a-z0-9]*[a-z0-9])?", ns) is not None
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
"""Database Options screen for selecting distribution, version and extensions."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
from pathlib import Path
|
||||
import tkinter as tk
|
||||
from pathlib import Path
|
||||
from tkinter import ttk, messagebox
|
||||
|
||||
import json
|
||||
from knoe import screen as ui
|
||||
from knoe.core.build_context import copy_build_context_dir
|
||||
from knoe.core.env import (
|
||||
@ -18,6 +18,7 @@ from knoe.core.env import (
|
||||
resolve_prole_home,
|
||||
)
|
||||
|
||||
|
||||
class DatabaseOptionsScreenMixin:
|
||||
"""Mixin for the Database Options screen."""
|
||||
|
||||
|
||||
@ -2,12 +2,12 @@
|
||||
|
||||
import os
|
||||
import plistlib
|
||||
import secrets
|
||||
import subprocess
|
||||
import threading
|
||||
from pathlib import Path
|
||||
import tkinter as tk
|
||||
from pathlib import Path
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
|
||||
from knoe import screen as ui
|
||||
from knoe.core.env import PROJECT_ROOT
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
"""Docker image build, registry management and image pre-pull."""
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
@ -12,7 +11,6 @@ from pathlib import Path
|
||||
from knoe.config import get_docker_build_platform_args
|
||||
from knoe.core.env import (
|
||||
PROJECT_ROOT,
|
||||
_collect_images_from_files,
|
||||
_deployment_mode_from_env,
|
||||
_find_kubeconfig_file,
|
||||
_host_from_url,
|
||||
@ -21,7 +19,6 @@ from knoe.core.env import (
|
||||
_local_registry_enabled,
|
||||
_normalize_cluster_env,
|
||||
_push_docker_image,
|
||||
_resolve_supabase_home,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
"""Disk selection, installer packaging, DMG creation and build summary."""
|
||||
|
||||
import os
|
||||
import platform
|
||||
import plistlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import tkinter as tk
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from knoe.screen import TerminalConsole
|
||||
from knoe import screen as ui
|
||||
|
||||
import platform
|
||||
from knoe import config as inst_config
|
||||
from knoe import screen as ui
|
||||
from knoe.core.env import PROJECT_ROOT, get_resource_path
|
||||
|
||||
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
"""Initialization scripts, CNPG deployment and service-layer overlays."""
|
||||
|
||||
import os
|
||||
import platform
|
||||
import secrets
|
||||
import shlex
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import tkinter as tk
|
||||
import webbrowser
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from knoe.screen import TerminalConsole
|
||||
|
||||
import platform
|
||||
from knoe import screen as ui
|
||||
from knoe.core.env import (
|
||||
PROJECT_ROOT,
|
||||
@ -21,21 +20,21 @@ from knoe.core.env import (
|
||||
_deployment_target_label,
|
||||
_normalize_cluster_env,
|
||||
)
|
||||
from knoe.core.policy import (
|
||||
POLICY_CFG_KEY,
|
||||
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES,
|
||||
evaluate_optional_workloads_allowed,
|
||||
)
|
||||
from knoe.core.ops import garage_store as garage_store_ops
|
||||
from knoe.core.ops import monitoring as monitoring_ops
|
||||
from knoe.core.ops import openbao as openbao_ops
|
||||
from knoe.core.ops import opentofu as opentofu_ops
|
||||
from knoe.core.ops import registry as registry_ops
|
||||
from knoe.core.ops.cloudnative_pg import (
|
||||
initialize as cnpg_initialize,
|
||||
deploy as cnpg_deploy,
|
||||
rollout as cnpg_rollout,
|
||||
)
|
||||
from knoe.core.ops import openbao as openbao_ops
|
||||
from knoe.core.ops import registry as registry_ops
|
||||
from knoe.core.ops import garage_store as garage_store_ops
|
||||
from knoe.core.ops import opentofu as opentofu_ops
|
||||
from knoe.core.ops import monitoring as monitoring_ops
|
||||
from knoe.core.policy import (
|
||||
POLICY_CFG_KEY,
|
||||
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES,
|
||||
evaluate_optional_workloads_allowed,
|
||||
)
|
||||
|
||||
|
||||
class ServicesScreenMixin:
|
||||
|
||||
@ -63,6 +63,54 @@ class SupabaseScreenMixin:
|
||||
self._overlay_widgets.append(enable_cb)
|
||||
|
||||
y = 280
|
||||
node_label = ui.canvas_text(
|
||||
self,
|
||||
48,
|
||||
y,
|
||||
"Node selector:",
|
||||
fill="black",
|
||||
font=("SF Pro Text", 12),
|
||||
)
|
||||
self._canvas_items.append(node_label)
|
||||
node_entry = tk.Entry(
|
||||
self.bg_canvas,
|
||||
textvariable=self.supabase_pv_node,
|
||||
font=("SF Pro Text", 11),
|
||||
width=38,
|
||||
relief="solid",
|
||||
bd=1,
|
||||
)
|
||||
node_entry_window = self.bg_canvas.create_window(
|
||||
190, y - 2, window=node_entry, anchor="nw"
|
||||
)
|
||||
self._canvas_items.append(node_entry_window)
|
||||
self._overlay_widgets.append(node_entry)
|
||||
|
||||
y += 36
|
||||
storage_label = ui.canvas_text(
|
||||
self,
|
||||
48,
|
||||
y,
|
||||
"Storage directory:",
|
||||
fill="black",
|
||||
font=("SF Pro Text", 12),
|
||||
)
|
||||
self._canvas_items.append(storage_label)
|
||||
storage_entry = tk.Entry(
|
||||
self.bg_canvas,
|
||||
textvariable=self.supabase_pv_base_dir,
|
||||
font=("SF Pro Text", 11),
|
||||
width=38,
|
||||
relief="solid",
|
||||
bd=1,
|
||||
)
|
||||
storage_entry_window = self.bg_canvas.create_window(
|
||||
190, y - 2, window=storage_entry, anchor="nw"
|
||||
)
|
||||
self._canvas_items.append(storage_entry_window)
|
||||
self._overlay_widgets.append(storage_entry)
|
||||
|
||||
y += 42
|
||||
initial_status = "Ready" if self.supabase_enabled.get() else "Disabled"
|
||||
self._supabase_status_var = tk.StringVar(value=initial_status)
|
||||
status_item = ui.canvas_text(
|
||||
@ -86,10 +134,10 @@ class SupabaseScreenMixin:
|
||||
|
||||
# Standardized Console Output
|
||||
self._supabase_console = self._create_console_output(
|
||||
y=320, title="Deployment Output", width=880, height=380
|
||||
y=390, title="Deployment Output", width=880, height=310
|
||||
)
|
||||
|
||||
y = 740
|
||||
y = 720
|
||||
|
||||
# Deploy Supabase Button
|
||||
self._supabase_deploy_button = tk.Button(
|
||||
@ -143,7 +191,27 @@ class SupabaseScreenMixin:
|
||||
env["PROLE_SERVICE"] = str(PROJECT_ROOT)
|
||||
env["NAMESPACE"] = namespace
|
||||
|
||||
pv_node = ""
|
||||
try:
|
||||
pv_node = (self.supabase_pv_node.get() or "").strip()
|
||||
except Exception:
|
||||
pv_node = ""
|
||||
if pv_node:
|
||||
env["SUPABASE_PV_NODE"] = pv_node
|
||||
|
||||
pv_base_dir = ""
|
||||
try:
|
||||
pv_base_dir = (self.supabase_pv_base_dir.get() or "").strip()
|
||||
except Exception:
|
||||
pv_base_dir = ""
|
||||
if pv_base_dir:
|
||||
env["SUPABASE_PV_BASE_DIR"] = pv_base_dir
|
||||
env["SUPABASE_PV_BASE"] = pv_base_dir
|
||||
|
||||
self._supabase_console.write("Starting Supabase deployment...\n")
|
||||
self._supabase_console.write(
|
||||
f"Placement: node={pv_node or 'auto'} storage={pv_base_dir or 'auto'}\n"
|
||||
)
|
||||
|
||||
script_path = PROJECT_ROOT / "supabase" / "deploy.sh"
|
||||
if not script_path.exists():
|
||||
@ -191,6 +259,12 @@ class SupabaseScreenMixin:
|
||||
cfg_path = None
|
||||
if cfg_path:
|
||||
args.extend(["-c", str(cfg_path)])
|
||||
|
||||
try:
|
||||
self._save_prole_cfg()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if _parse_bool(os.environ.get("SUPABASE_USE_DEV_COMPOSE"), False):
|
||||
args.append("--with-dev-helpers")
|
||||
if _parse_bool(os.environ.get("SUPABASE_FOREGROUND"), False):
|
||||
|
||||
@ -3,10 +3,10 @@
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
import tkinter as tk
|
||||
import webbrowser
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from knoe.screen import TerminalConsole
|
||||
|
||||
from knoe import screen as ui
|
||||
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
150
|
||||
158
|
||||
@ -1,3 +1,3 @@
|
||||
from prole.deployment import ProleDeployment
|
||||
from prole.deployment import KnoeDeployment
|
||||
|
||||
__all__ = ["ProleDeployment"]
|
||||
__all__ = ["KnoeDeployment"]
|
||||
|
||||
27
prole/cli.py
27
prole/cli.py
@ -1,14 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from knoe.core.controller import ProleController
|
||||
from knoe.core.actions import KnoeConsoleInstaller, _prepare_k3s_pipeline
|
||||
from knoe.core.controller import KnoeController
|
||||
from knoe.core.env import _ensure_ansible_vault_credentials, PROJECT_ROOT
|
||||
from knoe.core.actions import ProleConsoleInstaller, _prepare_k3s_pipeline
|
||||
|
||||
|
||||
def main():
|
||||
@ -44,6 +43,12 @@ def main():
|
||||
action="store_true",
|
||||
help="Reset k3d dev cluster or k3s service cluster",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--delete-db",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-gui",
|
||||
action="store_true",
|
||||
@ -63,6 +68,9 @@ def main():
|
||||
prole_conf = os.environ.get("PROLE_CONF")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.delete_db and not args.reset:
|
||||
parser.error("--delete-db requires --reset")
|
||||
|
||||
if args.config is None and prole_conf:
|
||||
potential_cfg = Path(prole_conf) / "prole.cfg"
|
||||
if potential_cfg.exists():
|
||||
@ -72,16 +80,16 @@ def main():
|
||||
|
||||
setup_logging(verbose=args.verbose, debug=args.debug)
|
||||
|
||||
controller = ProleController(
|
||||
controller = KnoeController(
|
||||
PROJECT_ROOT,
|
||||
verbose=args.verbose,
|
||||
cfg_path=Path(args.config).expanduser() if args.config else None,
|
||||
)
|
||||
|
||||
if args.command == "deploy":
|
||||
from prole.deployment import ProleDeployment
|
||||
from prole.deployment import KnoeDeployment
|
||||
|
||||
deployment = ProleDeployment(controller, PROJECT_ROOT)
|
||||
deployment = KnoeDeployment(controller, PROJECT_ROOT)
|
||||
if deployment.duplicate_k3d_to_k3s():
|
||||
if deployment.apply():
|
||||
sys.exit(0)
|
||||
@ -89,8 +97,11 @@ def main():
|
||||
|
||||
if args.silent:
|
||||
_ensure_ansible_vault_credentials(prompt_ui=False)
|
||||
installer = ProleConsoleInstaller(
|
||||
controller, args.config, reset_cluster=args.reset
|
||||
installer = KnoeConsoleInstaller(
|
||||
controller,
|
||||
args.config,
|
||||
reset_cluster=args.reset,
|
||||
delete_db=args.delete_db,
|
||||
)
|
||||
if args.log:
|
||||
log_path = (
|
||||
|
||||
@ -1,17 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import logging
|
||||
from knoe import prole_conf
|
||||
from knoe.core.actions import ProleInstaller
|
||||
from knoe.core.actions import KnoeInstaller
|
||||
from knoe.core.env import (
|
||||
_detect_ansible_topology,
|
||||
_sync_opentofu_pipeline,
|
||||
_normalize_cluster_env,
|
||||
_parse_bool,
|
||||
)
|
||||
from knoe.core.ops.cloudnative_pg import initialize as cnpg_initialize
|
||||
@ -40,7 +39,7 @@ _CFG_KEY_MAP = {
|
||||
}
|
||||
|
||||
|
||||
class ProleDeployment(ProleInstaller):
|
||||
class KnoeDeployment(KnoeInstaller):
|
||||
"""
|
||||
Top-level class for Prole infrastructure deployment via OpenTofu.
|
||||
Duplicate k3d stack and apply to k3s defined in Ansible inventory.
|
||||
|
||||
@ -1,58 +1,53 @@
|
||||
Network Discovery Summary:
|
||||
Primary Router: 10.0.0.1 (eero_5d:50:f2)
|
||||
DNS Servers: 10.0.0.4, 100.100.100.100, 10.0.0.5
|
||||
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.1 [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [53], Services: ['DNS']
|
||||
- 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.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.35\ [7e:19:9a:fe:9f:ed] (7e:19:9a:fe:9f:ed): Ports [], Services: []
|
||||
- 10.0.0.3 [2c:cf:67:8c:2b:47] (RaspberryPi_8c:2b:47): Ports [22, 80, 2049, 445, 5900, 88, 389, 636], Services: ['SSH', 'NFS', 'VNC', 'Web Server', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
|
||||
- 10.0.0.1 [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [], Services: []
|
||||
- 10.0.0.4 [b8:27:eb:b3:6f:2b] (RaspberryPiF_b3:6f:2b): Ports [22, 53, 443], Services: ['DNS', 'SSH', 'Web Server']
|
||||
- 10.0.0.5 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 2049, 5900], Services: ['SSH', 'NFS', 'VNC']
|
||||
- 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.6 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 80, 443], Services: ['SSH', 'Web Server']
|
||||
- 10.0.0.32 [48:a6:b8:a4:8e:cc] (Sonos_a4:8e:cc): Ports [], Services: []
|
||||
- 10.0.0.7 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.2 [dc:a4:ca:ea:1a:2f] (Apple_ea:1a:2f): Ports [], Services: []
|
||||
- 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.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.33 [ec:b5:fa:b0:76:e4] (PhilipsLight_b0:76:e4): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.34 [3a:b4:ce:79:b0:55] (3a:b4:ce:79:b0:55): Ports [], Services: []
|
||||
- 10.0.0.6 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 443], Services: ['SSH', 'Web Server']
|
||||
- 10.0.0.2 [dc:a4:ca:ea:1a:2f] (Apple_ea:1a:2f): Ports [], Services: []
|
||||
- 10.0.0.7 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.22 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.33 [ec:b5:fa:b0:76:e4] (PhilipsLight_b0:76:e4): Ports [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.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.52 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.205 [a8:20:66:28:12:e7] (Apple_28:12:e7): Ports [22, 53, 445, 5900, 88], Services: ['DNS', 'SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
|
||||
- 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.100 [24:fc:e5:51:cf:74] (SamsungElect_51:cf:74): 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.52 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [], Services: []
|
||||
- 10.0.0.41 [b8:27:eb:57:10:d9] (RaspberryPiF_57:10:d9): Ports [], Services: []
|
||||
- 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.94 [f0:20:ff:bf:9a:19] (Intel_bf:9a:19): Ports [], Services: []
|
||||
Note: Potential host for NVIDIA cards (PC Hardware vendor)
|
||||
- 10.0.0.95 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 2049, 5900], Services: ['SSH', 'NFS', 'VNC']
|
||||
- 10.0.0.100 [24:fc:e5:51:cf:74] (SamsungElect_51:cf:74): Ports [], Services: []
|
||||
- 10.0.0.95 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [2049], Services: ['NFS']
|
||||
- 10.0.0.106 [48:a6:b8:a7:50:60] (Sonos_a7:50:60): Ports [], Services: []
|
||||
- 10.0.0.99 [20:28:bc:f1:dc:ba] (Visionscape_f1:dc:ba): Ports [], Services: []
|
||||
- 10.0.0.107 [b8:06:0d:b7:7c:56] (TuyaSmart_b7:7c:56): Ports [], Services: []
|
||||
- 10.0.0.111 [ca:0b:6c:5d:0a:e5] (ca:0b:6c:5d:0a:e5): Ports [], Services: []
|
||||
- 10.0.0.113 [60:81:10:92:02:4e] (Apple_92:02:4e): 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.112 [f8:33:31:40:58:c0] (TexasInstrum_40:58:c0): Ports [80, 443], 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.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.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 [], Services: []
|
||||
- 10.0.0.130 [f8:bb:bf:a3:84:eb] (eero_a3:84:eb): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.143 [60:5f:8d:7a:aa:32] (eero_7a:aa:32): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.205 [a8:20:66:28:12:e7] (Apple_28:12:e7): Ports [22, 53, 445, 5900, 88], Services: ['DNS', 'SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
|
||||
- 10.0.0.128 [a8:20:66:28:12:e7] (Apple_28:12:e7): Ports [], Services: []
|
||||
- 10.0.0.145 [7c:a6:b0:04:db:1c] (7c:a6:b0:04:db:1c): Ports [], Services: []
|
||||
- 10.0.0.175 [28:80:88:e4:10:1a] (Netgear_e4:10:1a): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.155 [62:17:97:b3:06:a1] (62:17:97:b3:06:a1): Ports [], Services: []
|
||||
- 10.0.0.160 [96:c3:fd:58:02:6c] (96:c3:fd:58:02:6c): Ports [], Services: []
|
||||
- 10.0.0.170 [b0:ee:7b:ca:73:99] (Roku_ca:73:99): 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.175 [28:80:88:e4:10:1a] (Netgear_e4:10:1a): Ports [80], 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.196 [60:5f:8d:88:08:f2] (eero_88:08:f2): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.58 [7e:46:74:2a:b0:b2] (7e:46:74:2a:b0:b2): Ports [], Services: []
|
||||
- 10.0.0.199 [7c:a6:b0:01:a7:33] (7c:a6:b0:01:a7:33): Ports [], Services: []
|
||||
- 10.0.0.60 [c6:d0:1a:97:9f:e9] (c6:d0:1a:97:9f:e9): 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.204 [50:eb:f6:56:f3:26] (ASUSTekCOMPU_56:f3:26): Ports [22, 3389, 11434], Services: ['SSH', 'RDP (Windows)', 'Ollama']
|
||||
- 10.0.0.206 [00:a0:de:a2:0b:ef] (Yamaha_a2:0b:ef): Ports [80], Services: ['Web Server']
|
||||
- 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:
|
||||
|
||||
@ -90,7 +90,8 @@ spec:
|
||||
image: "{{ .Values.image.storage.repository }}:{{ .Values.image.storage.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.storage.pullPolicy }}
|
||||
env:
|
||||
{{- range $key, $value := .Values.environment.storage }}
|
||||
{{- $storageEnv := (.Values.environment.storage | default dict) }}
|
||||
{{- range $key, $value := $storageEnv }}
|
||||
{{- if and (ne $key "DB_HOST") (ne $key "DB_PORT") (ne $key "DB_SSL") }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
@ -193,16 +194,24 @@ spec:
|
||||
value: {{ printf "http://%s:%v" (include "supabase.imgproxy.fullname" .) (.Values.service.imgproxy.port | int) | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{- if not (hasKey $storageEnv "STORAGE_BACKEND") }}
|
||||
- name: STORAGE_BACKEND
|
||||
value: {{ if .Values.deployment.minio.enabled }}s3{{ else }}file{{ end }}
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.deployment.minio.enabled }}
|
||||
{{- if not (hasKey $storageEnv "GLOBAL_S3_ENDPOINT") }}
|
||||
- name: GLOBAL_S3_ENDPOINT
|
||||
value: {{ printf "http://%s:%v" (include "supabase.minio.fullname" .) (default 9000 .Values.service.minio.port) | quote }}
|
||||
{{- end }}
|
||||
{{- if not (hasKey $storageEnv "GLOBAL_S3_PROTOCOl") }}
|
||||
- name: GLOBAL_S3_PROTOCOl
|
||||
value: http
|
||||
{{- end }}
|
||||
{{- if not (hasKey $storageEnv "GLOBAL_S3_FORCE_PATH_STYLE") }}
|
||||
- name: GLOBAL_S3_FORCE_PATH_STYLE
|
||||
value: "true"
|
||||
{{- end }}
|
||||
- name: AWS_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
|
||||
@ -67,6 +67,45 @@ def _is_placeholder(value: str) -> bool:
|
||||
return v.startswith("${") and v.endswith("}")
|
||||
|
||||
|
||||
def _as_bool(value: str | bool | None, default: bool = False) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if value is None:
|
||||
return default
|
||||
normalized = str(value).strip().lower()
|
||||
if not normalized:
|
||||
return default
|
||||
if normalized in {"1", "true", "yes", "on", "y"}:
|
||||
return True
|
||||
if normalized in {"0", "false", "no", "off", "n"}:
|
||||
return False
|
||||
return default
|
||||
|
||||
|
||||
def _parse_hosts(raw_hosts: str, fallback: str = "db.prole.org") -> list[str]:
|
||||
entries: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for entry in (raw_hosts or "").split(","):
|
||||
token = (entry or "").strip()
|
||||
if not token or _is_placeholder(token):
|
||||
continue
|
||||
|
||||
if "://" in token:
|
||||
parsed = urllib.parse.urlparse(token)
|
||||
host = (parsed.hostname or "").strip()
|
||||
else:
|
||||
host = token
|
||||
|
||||
if not host or host in seen:
|
||||
continue
|
||||
seen.add(host)
|
||||
entries.append(host)
|
||||
|
||||
if entries:
|
||||
return entries
|
||||
return [fallback]
|
||||
|
||||
|
||||
def _discover_k8s_service_namespace(service_name: str) -> str:
|
||||
try:
|
||||
raw = subprocess.check_output(["kubectl", "get", "svc", "-A", "-o", "json"], text=True)
|
||||
@ -161,7 +200,9 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
anon_key = _jwt(anon_payload, jwt_secret)
|
||||
service_key = _jwt(service_payload, jwt_secret)
|
||||
|
||||
supabase_hostname = _first(
|
||||
supabase_hostname_raw = _first(
|
||||
os.environ.get("SUPABASE_STUDIO_URL", ""),
|
||||
_cfg_get(cfg, "Inputs", "init_cluster.supabase_studio_url"),
|
||||
_cfg_get(cfg, "User", "supabase_hostname"),
|
||||
_cfg_get(cfg, "Global", "supabase_hostname"),
|
||||
_cfg_get(cfg, "User", "SUPABASE_HOSTNAME"),
|
||||
@ -171,16 +212,73 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
default="db.prole.org",
|
||||
).strip()
|
||||
|
||||
if _is_placeholder(supabase_hostname):
|
||||
supabase_hostname = ""
|
||||
if not supabase_hostname:
|
||||
supabase_hostname = "db.prole.org"
|
||||
if _is_placeholder(supabase_hostname_raw):
|
||||
supabase_hostname_raw = ""
|
||||
if not supabase_hostname_raw:
|
||||
supabase_hostname_raw = "db.prole.org"
|
||||
|
||||
studio_enabled = _as_bool(
|
||||
_first(
|
||||
os.environ.get("SUPABASE_STUDIO_ENABLED", ""),
|
||||
_cfg_get(cfg, "Inputs", "init_cluster.supabase_studio_enabled"),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_STUDIO_ENABLED"),
|
||||
default="true",
|
||||
),
|
||||
default=True,
|
||||
)
|
||||
auth_enabled = _as_bool(
|
||||
_first(
|
||||
os.environ.get("SUPABASE_AUTH_ENABLED", ""),
|
||||
_cfg_get(cfg, "Inputs", "init_cluster.supabase_auth_enabled"),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_AUTH_ENABLED"),
|
||||
default="true",
|
||||
),
|
||||
default=True,
|
||||
)
|
||||
realtime_enabled = _as_bool(
|
||||
_first(
|
||||
os.environ.get("SUPABASE_REALTIME_ENABLED", ""),
|
||||
_cfg_get(cfg, "Inputs", "init_cluster.supabase_realtime_enabled"),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_REALTIME_ENABLED"),
|
||||
default="true",
|
||||
),
|
||||
default=True,
|
||||
)
|
||||
meta_enabled = _as_bool(
|
||||
_first(
|
||||
os.environ.get("SUPABASE_META_ENABLED", ""),
|
||||
_cfg_get(cfg, "Inputs", "init_cluster.supabase_meta_enabled"),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_META_ENABLED"),
|
||||
default="true",
|
||||
),
|
||||
default=True,
|
||||
)
|
||||
analytics_enabled = _as_bool(
|
||||
_first(
|
||||
os.environ.get("SUPABASE_ANALYTICS_ENABLED", ""),
|
||||
_cfg_get(cfg, "Inputs", "init_cluster.supabase_analytics_enabled"),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_ANALYTICS_ENABLED"),
|
||||
default="true",
|
||||
),
|
||||
default=True,
|
||||
)
|
||||
|
||||
raw_host_entries: list[str] = []
|
||||
for entry in supabase_hostname_raw.split(","):
|
||||
token = (entry or "").strip()
|
||||
if token and not _is_placeholder(token):
|
||||
raw_host_entries.append(token)
|
||||
if not raw_host_entries:
|
||||
raw_host_entries = ["db.prole.org"]
|
||||
|
||||
host_entries = _parse_hosts(",".join(raw_host_entries), fallback="db.prole.org")
|
||||
ingress_host = host_entries[0]
|
||||
|
||||
# Canonical public URL for Supabase (used for auth callbacks + Studio links).
|
||||
public_url = ""
|
||||
ingress_host = supabase_hostname
|
||||
if "://" in supabase_hostname:
|
||||
parsed = urllib.parse.urlparse(supabase_hostname)
|
||||
primary_raw_host = (raw_host_entries[0] or "").strip()
|
||||
if "://" in primary_raw_host:
|
||||
parsed = urllib.parse.urlparse(primary_raw_host)
|
||||
ingress_host = (parsed.hostname or "").strip() or ingress_host
|
||||
scheme = (parsed.scheme or "https").strip()
|
||||
if parsed.netloc:
|
||||
@ -220,9 +318,13 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
).strip().lower()
|
||||
supabase_primary_node = _first(
|
||||
os.environ.get("SUPABASE_PRIMARY_NODE", ""),
|
||||
os.environ.get("SUPABASE_NODE_SELECTOR", ""),
|
||||
os.environ.get("SUPABASE_PV_NODE", ""),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_PRIMARY_NODE"),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_NODE_SELECTOR"),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_PV_NODE"),
|
||||
_cfg_get(cfg, "Supabase", "PRIMARY_NODE"),
|
||||
default="merlin.prole.org",
|
||||
default="gandalf.prole.org",
|
||||
).strip()
|
||||
if _is_placeholder(supabase_primary_node):
|
||||
supabase_primary_node = ""
|
||||
@ -233,7 +335,7 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
os.environ.get("SUPABASE_STORAGE_CLASS", ""),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_STORAGE_CLASS"),
|
||||
_cfg_get(cfg, "Supabase", "STORAGE_CLASS"),
|
||||
default="merlin-local-iscsi-d002",
|
||||
default="synology-iscsi",
|
||||
).strip()
|
||||
if _is_placeholder(supabase_storage_class):
|
||||
supabase_storage_class = "merlin-local-iscsi-d002"
|
||||
@ -243,20 +345,20 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
"fullnameOverride": "supabase",
|
||||
"deployment": {
|
||||
"db": {"enabled": False},
|
||||
"functions": {"enabled": False},
|
||||
"vector": {"enabled": False},
|
||||
"kong": {"enabled": False},
|
||||
"storage": {"enabled": False},
|
||||
"minio": {"enabled": False},
|
||||
"imgproxy": {"enabled": False},
|
||||
"functions": {"enabled": True},
|
||||
"vector": {"enabled": True},
|
||||
"kong": {"enabled": True},
|
||||
"storage": {"enabled": True},
|
||||
"minio": {"enabled": True},
|
||||
"imgproxy": {"enabled": True},
|
||||
# Explicit fullnameOverride per component strips the chart name
|
||||
# from pod names (avoids 'supabase-prole-supabase-<component>').
|
||||
"analytics": {"fullnameOverride": "supabase-analytics"},
|
||||
"auth": {"fullnameOverride": "supabase-auth"},
|
||||
"meta": {"fullnameOverride": "supabase-meta"},
|
||||
"realtime": {"fullnameOverride": "supabase-realtime"},
|
||||
"rest": {"fullnameOverride": "supabase-rest"},
|
||||
"studio": {"fullnameOverride": "supabase-studio"},
|
||||
"analytics": {"enabled": analytics_enabled, "fullnameOverride": "supabase-analytics"},
|
||||
"auth": {"enabled": auth_enabled, "fullnameOverride": "supabase-auth"},
|
||||
"meta": {"enabled": meta_enabled, "fullnameOverride": "supabase-meta"},
|
||||
"realtime": {"enabled": realtime_enabled, "fullnameOverride": "supabase-realtime"},
|
||||
"rest": {"enabled": True, "fullnameOverride": "supabase-rest"},
|
||||
"studio": {"enabled": studio_enabled, "fullnameOverride": "supabase-studio"},
|
||||
},
|
||||
"externalDatabase": {
|
||||
"enabled": True,
|
||||
@ -287,9 +389,12 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
"ingress": {
|
||||
"enabled": True,
|
||||
"className": "traefik",
|
||||
"hosts": [
|
||||
{"host": ingress_host, "paths": [{"path": "/", "pathType": "Prefix"}]}
|
||||
],
|
||||
"hosts": [{"host": host, "paths": [{"path": "/", "pathType": "Prefix"}]} for host in host_entries],
|
||||
},
|
||||
"studioIngress": {
|
||||
"enabled": True,
|
||||
"className": "traefik",
|
||||
"hosts": [{"host": host, "paths": [{"path": "/", "pathType": "Prefix"}]} for host in host_entries],
|
||||
},
|
||||
}
|
||||
|
||||
@ -304,20 +409,24 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
)
|
||||
# Write to persistence.*.storageClassName — the path the Helm chart actually uses
|
||||
persistence = overlay.setdefault("persistence", {})
|
||||
# Only set storageClass for components that are still deployed; storage/minio/imgproxy are removed.
|
||||
for pvc_key in ("functions", "snippets", "deno"):
|
||||
for pvc_key in ("functions", "snippets", "deno", "imgproxy", "minio", "storage"):
|
||||
persistence.setdefault(pvc_key, {})["storageClassName"] = supabase_storage_class
|
||||
|
||||
if mode == "k3s" and supabase_node_selector:
|
||||
deployment = overlay.setdefault("deployment", {})
|
||||
# Only pin active control-plane components; storage/minio/imgproxy/kong are disabled.
|
||||
for component in (
|
||||
"analytics",
|
||||
"auth",
|
||||
"functions",
|
||||
"imgproxy",
|
||||
"kong",
|
||||
"meta",
|
||||
"minio",
|
||||
"realtime",
|
||||
"rest",
|
||||
"storage",
|
||||
"studio",
|
||||
"vector",
|
||||
):
|
||||
component_cfg = deployment.setdefault(component, {})
|
||||
component_cfg.setdefault("nodeSelector", {})
|
||||
|
||||
@ -23,9 +23,9 @@ def test_actions_save_env_to_file_expands_shell_vars(monkeypatch, tmp_path: Path
|
||||
)
|
||||
(proj / "etc" / "dummy.txt").write_text("ok\n")
|
||||
|
||||
from knoe.core.actions import ProleInstaller
|
||||
from knoe.core.actions import KnoeInstaller
|
||||
|
||||
class Dummy(ProleInstaller):
|
||||
class Dummy(KnoeInstaller):
|
||||
def __init__(self, project_root: Path):
|
||||
self.project_root = project_root
|
||||
self.inputs = {"init_cluster.cluster_env": "dev"}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"""Tests for installer/core/actions.py – ProleInstallerBase helper methods."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
@ -8,11 +9,9 @@ from pathlib import Path
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from knoe.core.actions import (
|
||||
ProleInstaller,
|
||||
ProleConsoleInstaller,
|
||||
KnoeInstaller,
|
||||
KnoeConsoleInstaller,
|
||||
_configure_unbuffered_io,
|
||||
_select_existing_kubeconfig,
|
||||
)
|
||||
@ -23,7 +22,7 @@ from knoe.core.actions import (
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class _TestableInstaller(ProleInstaller):
|
||||
class _TestableInstaller(KnoeInstaller):
|
||||
"""Minimal concrete subclass for testing base-class helpers."""
|
||||
|
||||
def __init__(self, inputs=None, project_root=None):
|
||||
@ -329,8 +328,8 @@ spec:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class _TestableSilentInstaller(ProleConsoleInstaller):
|
||||
"""Testable subclass of ProleConsoleInstaller."""
|
||||
class _TestableSilentInstaller(KnoeConsoleInstaller):
|
||||
"""Testable subclass of KnoeConsoleInstaller."""
|
||||
|
||||
def __init__(self, inputs=None, project_root=None):
|
||||
self._inputs_dict = inputs or {}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from knoe.config import (
|
||||
normalize_version,
|
||||
is_apple_silicon,
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
"""Extended tests for installer/config.py – covering encryption, properties, helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import os
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from knoe.config import (
|
||||
_is_prole_secret,
|
||||
_is_openbao_ref,
|
||||
|
||||
@ -13,7 +13,6 @@ import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
import pytest
|
||||
|
||||
from knoe.config import (
|
||||
_get_secret_key_file,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"""
|
||||
Unit tests for installer/core/controller.py
|
||||
|
||||
Covers ProleController: check_docker_running, get_knoe_db_version, run_script.
|
||||
Covers KnoeController: check_docker_running, get_knoe_db_version, run_script.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@ -9,17 +9,16 @@ import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
import pytest
|
||||
|
||||
from knoe.core.controller import ProleController
|
||||
from knoe.core.controller import KnoeController
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _make_controller(tmp_path: Path, **kwargs) -> ProleController:
|
||||
return ProleController(project_root=tmp_path, **kwargs)
|
||||
def _make_controller(tmp_path: Path, **kwargs) -> KnoeController:
|
||||
return KnoeController(project_root=tmp_path, **kwargs)
|
||||
|
||||
|
||||
def _scaffold_project(tmp_path: Path) -> None:
|
||||
|
||||
@ -20,9 +20,9 @@ def test_controller_run_script_expands_shell_vars(monkeypatch, tmp_path: Path):
|
||||
(proj / "etc").mkdir(parents=True, exist_ok=True)
|
||||
(proj / "etc" / "dummy.sh").write_text("#!/usr/bin/env bash\necho ok\n")
|
||||
|
||||
from knoe.core.controller import ProleController
|
||||
from knoe.core.controller import KnoeController
|
||||
|
||||
controller = ProleController(project_root=proj)
|
||||
controller = KnoeController(project_root=proj)
|
||||
rc = controller.run_script("dummy.sh", env={"PROLE_HOME": "$HOME/prole"})
|
||||
assert rc == 0
|
||||
|
||||
|
||||
@ -6,13 +6,11 @@ import os
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from knoe.state import InstallerState
|
||||
from knoe.milestone import Milestone, ProgressCallback
|
||||
from knoe.build import get_build_command, BuildMilestone
|
||||
from knoe.core.controller import ProleController
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
from knoe.core.actions import KnoeConsoleInstaller
|
||||
from knoe.core.controller import KnoeController
|
||||
from knoe.milestone import Milestone
|
||||
from knoe.state import InstallerState
|
||||
|
||||
|
||||
# ===== InstallerState =====
|
||||
@ -262,31 +260,31 @@ class TestBuildMilestone:
|
||||
assert m.next(InstallerState()) is None
|
||||
|
||||
|
||||
# ===== ProleController =====
|
||||
# ===== KnoeController =====
|
||||
|
||||
|
||||
class TestProleController:
|
||||
class TestKnoeController:
|
||||
def test_init(self, tmp_path):
|
||||
c = ProleController(tmp_path)
|
||||
c = KnoeController(tmp_path)
|
||||
assert c.project_root == tmp_path
|
||||
assert c.verbose is False
|
||||
assert c.state is not None
|
||||
installer = ProleConsoleInstaller(c)
|
||||
installer = KnoeConsoleInstaller(c)
|
||||
defaults = installer._default_inputs()
|
||||
assert defaults.get("kerberos_config.user") == "administrator"
|
||||
assert defaults.get("kerberos_config.password") == ""
|
||||
|
||||
def test_init_verbose(self, tmp_path):
|
||||
c = ProleController(tmp_path, verbose=True)
|
||||
c = KnoeController(tmp_path, verbose=True)
|
||||
assert c.verbose is True
|
||||
|
||||
def test_check_docker_running(self, tmp_path):
|
||||
c = ProleController(tmp_path)
|
||||
c = KnoeController(tmp_path)
|
||||
result = c.check_docker_running()
|
||||
assert isinstance(result, bool)
|
||||
|
||||
def test_get_knoe_db_version_defaults(self, tmp_path):
|
||||
c = ProleController(tmp_path)
|
||||
c = KnoeController(tmp_path)
|
||||
version = c.get_knoe_db_version()
|
||||
assert "17.7" in version # default pg version
|
||||
assert "-" in version
|
||||
@ -298,12 +296,12 @@ class TestProleController:
|
||||
db_dir = tmp_path / "knoe-db"
|
||||
db_dir.mkdir()
|
||||
(db_dir / ".version").write_text("42")
|
||||
c = ProleController(tmp_path)
|
||||
c = KnoeController(tmp_path)
|
||||
version = c.get_knoe_db_version()
|
||||
assert version == "16.2-042"
|
||||
|
||||
def test_run_milestones(self, tmp_path):
|
||||
c = ProleController(tmp_path)
|
||||
c = KnoeController(tmp_path)
|
||||
m1 = _DummyMilestone("step1", "Step 1")
|
||||
m2 = _DummyMilestone("step2", "Step 2")
|
||||
msgs = []
|
||||
@ -312,21 +310,21 @@ class TestProleController:
|
||||
assert "step2" in c.state.completed
|
||||
|
||||
def test_run_milestones_no_callback(self, tmp_path):
|
||||
c = ProleController(tmp_path)
|
||||
c = KnoeController(tmp_path)
|
||||
m = _DummyMilestone("s1", "S1")
|
||||
c.run_milestones([m])
|
||||
assert "s1" in c.state.completed
|
||||
|
||||
def test_run_script_missing(self, tmp_path):
|
||||
c = ProleController(tmp_path)
|
||||
c = KnoeController(tmp_path)
|
||||
# Script doesn't exist but should not crash
|
||||
rc = c.run_script("nonexistent_script.sh", args=[])
|
||||
# Will fail since the script doesn't exist
|
||||
assert rc != 0 or rc is None
|
||||
|
||||
def test_silent_installer_runs_supabase_preload_before_deploy(self, tmp_path):
|
||||
c = ProleController(tmp_path)
|
||||
installer = ProleConsoleInstaller(c)
|
||||
c = KnoeController(tmp_path)
|
||||
installer = KnoeConsoleInstaller(c)
|
||||
|
||||
with mock.patch.object(installer, "_load_inputs_from_cfg", return_value={}), \
|
||||
mock.patch.object(installer, "_write_cfg"), \
|
||||
@ -348,8 +346,8 @@ class TestProleController:
|
||||
def test_silent_installer_does_not_generate_db_password_when_openbao_ref_present(
|
||||
self, tmp_path
|
||||
):
|
||||
c = ProleController(tmp_path)
|
||||
installer = ProleConsoleInstaller(c)
|
||||
c = KnoeController(tmp_path)
|
||||
installer = KnoeConsoleInstaller(c)
|
||||
|
||||
openbao_ref = "${OPENBAO:kv/prole/test/db#password}"
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"""Tests for prole/deployment.py – ProleDeployment class."""
|
||||
"""Tests for prole/deployment.py – KnoeDeployment class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import os
|
||||
import subprocess
|
||||
@ -8,10 +9,8 @@ from pathlib import Path
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock, patch, call
|
||||
|
||||
import pytest
|
||||
|
||||
from prole.deployment import (
|
||||
ProleDeployment,
|
||||
KnoeDeployment,
|
||||
_CFG_KEY_MAP,
|
||||
KUBECONFIG_FILENAME,
|
||||
FETCH_PLAYBOOK,
|
||||
@ -59,7 +58,7 @@ def _make_deployment(tmp_path, sections=None):
|
||||
cfg_path = _make_cfg_file(tmp_path, sections)
|
||||
ctrl = _make_controller(tmp_path)
|
||||
with mock.patch.dict(os.environ, {"PROLE_CONF": str(tmp_path / "conf")}):
|
||||
dep = ProleDeployment(ctrl, tmp_path)
|
||||
dep = KnoeDeployment(ctrl, tmp_path)
|
||||
return dep, ctrl
|
||||
|
||||
|
||||
@ -68,7 +67,7 @@ def _make_deployment(tmp_path, sections=None):
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestProleDeploymentInit:
|
||||
class TestKnoeDeploymentInit:
|
||||
def test_init_loads_cfg(self, tmp_path):
|
||||
dep, _ = _make_deployment(tmp_path)
|
||||
assert dep.prole_cfg_data["Global"]["NAMESPACE"] == "test-ns"
|
||||
@ -86,14 +85,14 @@ class TestProleDeploymentInit:
|
||||
cfg.write(f)
|
||||
ctrl = _make_controller(tmp_path)
|
||||
with mock.patch.dict(os.environ, {"PROLE_CONF": str(conf_dir)}):
|
||||
dep = ProleDeployment(ctrl, tmp_path)
|
||||
dep = KnoeDeployment(ctrl, tmp_path)
|
||||
assert dep.prole_cfg_data["Global"]["NAMESPACE"] == "knoe-db"
|
||||
|
||||
def test_init_no_cfg_file(self, tmp_path):
|
||||
"""When no prole.cfg exists, it still initialises."""
|
||||
ctrl = _make_controller(tmp_path)
|
||||
with mock.patch.dict(os.environ, {"PROLE_CONF": ""}, clear=False):
|
||||
dep = ProleDeployment(ctrl, tmp_path)
|
||||
dep = KnoeDeployment(ctrl, tmp_path)
|
||||
assert dep.prole_cfg_data["Global"]["NAMESPACE"] == "knoe-db"
|
||||
|
||||
def test_load_cfg_from_directory(self, tmp_path):
|
||||
@ -101,7 +100,7 @@ class TestProleDeploymentInit:
|
||||
_make_cfg_file(tmp_path)
|
||||
ctrl = _make_controller(tmp_path)
|
||||
with mock.patch.dict(os.environ, {"PROLE_CONF": str(tmp_path / "conf")}):
|
||||
dep = ProleDeployment(ctrl, tmp_path)
|
||||
dep = KnoeDeployment(ctrl, tmp_path)
|
||||
assert dep.cfg_path == tmp_path / "conf" / "prole.cfg"
|
||||
|
||||
|
||||
@ -284,7 +283,7 @@ class TestApply:
|
||||
dep.prole_cfg_data["Deployment"] = {"OPENTOFU_PIPELINE_DIR": str(pipeline)}
|
||||
assert dep.apply() is False
|
||||
|
||||
@patch.object(ProleDeployment, "_run_post_apply_scripts", return_value=True)
|
||||
@patch.object(KnoeDeployment, "_run_post_apply_scripts", return_value=True)
|
||||
@patch("subprocess.run")
|
||||
def test_apply_success(self, mock_run, mock_post, tmp_path):
|
||||
pipeline = tmp_path / "pipeline"
|
||||
@ -416,7 +415,7 @@ class TestRunSupabaseDeploy:
|
||||
|
||||
|
||||
class TestDuplicateK3dToK3s:
|
||||
@patch.object(ProleDeployment, "_fetch_kubeconfig", return_value=None)
|
||||
@patch.object(KnoeDeployment, "_fetch_kubeconfig", return_value=None)
|
||||
@patch(
|
||||
"prole.deployment._detect_ansible_topology",
|
||||
return_value={"k3s_server_url": "https://example.com:6443"},
|
||||
@ -431,7 +430,7 @@ class TestDuplicateK3dToK3s:
|
||||
assert dep.duplicate_k3d_to_k3s() is False
|
||||
|
||||
@patch("prole.deployment._sync_opentofu_pipeline", side_effect=Exception("boom"))
|
||||
@patch.object(ProleDeployment, "_fetch_kubeconfig")
|
||||
@patch.object(KnoeDeployment, "_fetch_kubeconfig")
|
||||
@patch(
|
||||
"prole.deployment._detect_ansible_topology",
|
||||
return_value={
|
||||
@ -447,7 +446,7 @@ class TestDuplicateK3dToK3s:
|
||||
assert dep.duplicate_k3d_to_k3s() is False
|
||||
|
||||
@patch("prole.deployment._sync_opentofu_pipeline")
|
||||
@patch.object(ProleDeployment, "_fetch_kubeconfig")
|
||||
@patch.object(KnoeDeployment, "_fetch_kubeconfig")
|
||||
@patch(
|
||||
"prole.deployment._detect_ansible_topology",
|
||||
return_value={
|
||||
|
||||
@ -9,8 +9,33 @@ from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# config.py helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
from knoe.config import (
|
||||
_parse_bool as cfg_parse_bool,
|
||||
_expand_path as cfg_expand_path,
|
||||
_expand_cfg_value as cfg_expand_cfg_value,
|
||||
_collect_cfg_vars as cfg_collect_cfg_vars,
|
||||
_filter_cfg_values_for_persistence,
|
||||
_normalize_cfg_value_for_persistence,
|
||||
_extract_yaml_scalar_from_text as cfg_extract_yaml,
|
||||
_extract_inline_vault_block as cfg_extract_vault,
|
||||
_load_properties,
|
||||
_write_k3s_kubeconfig,
|
||||
_merge_kubeconfig,
|
||||
normalize_version,
|
||||
get_resource_path as cfg_get_resource_path,
|
||||
get_docker_build_platform_args as cfg_get_docker_args,
|
||||
_is_prole_secret as cfg_is_prole_secret,
|
||||
_is_openbao_ref as cfg_is_openbao_ref,
|
||||
_encrypt_cfg_secret,
|
||||
_resolve_secret_value,
|
||||
_update_prole_cfg_value,
|
||||
get_properties,
|
||||
get_config_value,
|
||||
setup_logging,
|
||||
)
|
||||
# ---------------------------------------------------------------------------
|
||||
# env.py helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
@ -52,36 +77,6 @@ from knoe.core.env import (
|
||||
_collect_cfg_vars as env_collect_cfg_vars,
|
||||
get_resource_path,
|
||||
is_apple_silicon,
|
||||
get_docker_build_platform_args,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# config.py helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
from knoe.config import (
|
||||
_parse_bool as cfg_parse_bool,
|
||||
_expand_path as cfg_expand_path,
|
||||
_expand_cfg_value as cfg_expand_cfg_value,
|
||||
_collect_cfg_vars as cfg_collect_cfg_vars,
|
||||
_filter_cfg_values_for_persistence,
|
||||
_normalize_cfg_value_for_persistence,
|
||||
_extract_yaml_scalar_from_text as cfg_extract_yaml,
|
||||
_extract_inline_vault_block as cfg_extract_vault,
|
||||
_load_properties,
|
||||
_write_k3s_kubeconfig,
|
||||
_merge_kubeconfig,
|
||||
normalize_version,
|
||||
get_resource_path as cfg_get_resource_path,
|
||||
is_apple_silicon as cfg_is_apple_silicon,
|
||||
get_docker_build_platform_args as cfg_get_docker_args,
|
||||
_is_prole_secret as cfg_is_prole_secret,
|
||||
_is_openbao_ref as cfg_is_openbao_ref,
|
||||
_encrypt_cfg_secret,
|
||||
_resolve_secret_value,
|
||||
_update_prole_cfg_value,
|
||||
get_properties,
|
||||
get_config_value,
|
||||
setup_logging,
|
||||
)
|
||||
|
||||
|
||||
@ -894,7 +889,7 @@ class TestProperties:
|
||||
# ===== _update_prole_cfg_value =====
|
||||
|
||||
|
||||
class TestUpdateProleCfgValue:
|
||||
class TestUpdateKnoeCfgValue:
|
||||
def test_creates_section(self, tmp_path):
|
||||
cfg_file = tmp_path / "conf" / "prole.cfg"
|
||||
cfg_file.parent.mkdir(parents=True)
|
||||
@ -1008,7 +1003,7 @@ class TestPlatformHelpers:
|
||||
# ===== _render_prole_cfg =====
|
||||
|
||||
|
||||
class TestRenderProleCfg:
|
||||
class TestRenderKnoeCfg:
|
||||
def test_minimal(self):
|
||||
from knoe.core.env import _render_prole_cfg
|
||||
|
||||
|
||||
@ -3,8 +3,6 @@ from __future__ import annotations
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_environment_screen_expands_shell_vars_for_filesystem(monkeypatch, tmp_path: Path):
|
||||
"""Regression: `$HOME`/`$PROLE_HOME` must be expanded before mkdir/copy.
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
"""Tests for the GitOps provider choice screen (_render_gitops_choice_page)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
|
||||
@ -11,7 +11,7 @@ from knoe.core.milestones import (
|
||||
DeploymentMilestone,
|
||||
)
|
||||
from knoe.state import InstallerState
|
||||
from knoe.core.controller import ProleController
|
||||
from knoe.core.controller import KnoeController
|
||||
|
||||
|
||||
class TestMilestones(unittest.TestCase):
|
||||
@ -19,7 +19,7 @@ class TestMilestones(unittest.TestCase):
|
||||
self.state = InstallerState()
|
||||
self.state.inputs["dependencies.auto_install_missing"] = "True"
|
||||
self.state.inputs["network_scan.run"] = "True"
|
||||
self.state.controller = MagicMock(spec=ProleController)
|
||||
self.state.controller = MagicMock(spec=KnoeController)
|
||||
|
||||
@patch("knoe.config.get_dep_info")
|
||||
@patch("subprocess.run")
|
||||
|
||||
@ -24,7 +24,7 @@ from knoe.core.milestones import (
|
||||
InitializationScriptsMilestone,
|
||||
)
|
||||
from knoe.state import InstallerState
|
||||
from knoe.core.controller import ProleController
|
||||
from knoe.core.controller import KnoeController
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@ -34,7 +34,7 @@ from knoe.core.controller import ProleController
|
||||
def _make_state(**inputs) -> InstallerState:
|
||||
state = InstallerState()
|
||||
state.inputs.update(inputs)
|
||||
state.controller = MagicMock(spec=ProleController)
|
||||
state.controller = MagicMock(spec=KnoeController)
|
||||
state.controller.project_root = Path("/tmp/fake-prole")
|
||||
state.controller.get_knoe_db_version.return_value = "18-001"
|
||||
state.controller.run_script.return_value = 0
|
||||
|
||||
@ -10,7 +10,6 @@ from __future__ import annotations
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch, call
|
||||
import pytest
|
||||
|
||||
from knoe.core.monitor import (
|
||||
_parse_pf_cfg,
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
from knoe.ui.screens.docker import DockerScreenMixin
|
||||
import knoe.ui.screens.docker as docker_mod
|
||||
from knoe.ui.screens.docker import DockerScreenMixin
|
||||
|
||||
|
||||
class _DummyDockerScreen(DockerScreenMixin):
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import sys
|
||||
from unittest.mock import patch, MagicMock
|
||||
from contextlib import ExitStack
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
import tkinter as tk
|
||||
from contextlib import ExitStack
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class MockVar:
|
||||
def __init__(self, value=None):
|
||||
@ -25,8 +27,7 @@ sys.modules["PIL"] = MagicMock()
|
||||
sys.modules["PIL.Image"] = MagicMock()
|
||||
sys.modules["PIL.ImageTk"] = MagicMock()
|
||||
|
||||
import install
|
||||
from install import ProleInstaller
|
||||
from install import KnoeInstaller
|
||||
|
||||
|
||||
def _generated_dockerfile_path(installer, tmp_path):
|
||||
@ -43,8 +44,8 @@ def mock_installer(tmp_path):
|
||||
patch("knoe.ui.screens.database_options.PROJECT_ROOT", tmp_path),
|
||||
patch("knoe.ui.screens.database_options.resolve_prole_home", return_value=tmp_path),
|
||||
patch("knoe.ui.screens.database_options.get_resource_path", side_effect=lambda name: tmp_path / name),
|
||||
patch("knoe.core.controller.ProleController.run_script"),
|
||||
patch.object(ProleInstaller, "_load_database_versions"),
|
||||
patch("knoe.core.controller.KnoeController.run_script"),
|
||||
patch.object(KnoeInstaller, "_load_database_versions"),
|
||||
patch("knoe.ui.screens.database_options.tk.BooleanVar", side_effect=lambda value=None: MockVar(value)),
|
||||
patch("knoe.ui.screens.database_options.tk.StringVar", side_effect=lambda value=None: MockVar(value)),
|
||||
patch("knoe.ui.screens.database_options.tk.ttk.Treeview"),
|
||||
@ -67,7 +68,7 @@ def mock_installer(tmp_path):
|
||||
(tmp_path / "conf" / "postgresql").mkdir(parents=True)
|
||||
(tmp_path / "etc").mkdir()
|
||||
|
||||
app = ProleInstaller(root)
|
||||
app = KnoeInstaller(root)
|
||||
app.bg_canvas = MagicMock()
|
||||
app.root = root
|
||||
yield app
|
||||
|
||||
@ -17,17 +17,16 @@ import pytest
|
||||
from pathlib import Path
|
||||
|
||||
# Now import install.py after mocking
|
||||
import install
|
||||
from install import ProleInstaller
|
||||
from install import KnoeInstaller
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def installer(tmp_path):
|
||||
# Mocking os.environ to avoid messing with real env
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
# We need to mock ProleInstaller.__init__ because it creates GUI elements
|
||||
with patch.object(ProleInstaller, "__init__", return_value=None):
|
||||
ins = ProleInstaller()
|
||||
# We need to mock KnoeInstaller.__init__ because it creates GUI elements
|
||||
with patch.object(KnoeInstaller, "__init__", return_value=None):
|
||||
ins = KnoeInstaller()
|
||||
# Core helpers expect `inputs` to exist; bypassing `__init__` means
|
||||
# we must seed minimal state for method calls.
|
||||
ins.inputs = {}
|
||||
|
||||
@ -2,8 +2,8 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
from knoe.core.controller import ProleController
|
||||
from knoe.core.actions import KnoeConsoleInstaller
|
||||
from knoe.core.controller import KnoeController
|
||||
|
||||
|
||||
def test_silent_installer_service_namespace_defaults_to_knoe_system_for_k3s(tmp_path, monkeypatch):
|
||||
@ -11,8 +11,8 @@ def test_silent_installer_service_namespace_defaults_to_knoe_system_for_k3s(tmp_
|
||||
|
||||
monkeypatch.delenv("SERVICE_NAMESPACE", raising=False)
|
||||
|
||||
c = ProleController(tmp_path)
|
||||
installer = ProleConsoleInstaller(c)
|
||||
c = KnoeController(tmp_path)
|
||||
installer = KnoeConsoleInstaller(c)
|
||||
installer.inputs["init_cluster.cluster_env"] = "service"
|
||||
installer.prole_cfg_data = {"Global": {}}
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import sys
|
||||
from unittest.mock import patch, MagicMock
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
# Mock tkinter and other GUI/macOS specific imports
|
||||
sys.modules["tkinter"] = MagicMock()
|
||||
@ -15,8 +16,7 @@ sys.modules["PIL"] = MagicMock()
|
||||
sys.modules["PIL.Image"] = MagicMock()
|
||||
sys.modules["PIL.ImageTk"] = MagicMock()
|
||||
|
||||
import install
|
||||
from install import ProleInstaller
|
||||
from install import KnoeInstaller
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -35,13 +35,13 @@ def mock_installer(tmp_path):
|
||||
), patch(
|
||||
"install.Path.mkdir"
|
||||
), patch.object(
|
||||
ProleInstaller, "_save_env_to_file"
|
||||
KnoeInstaller, "_save_env_to_file"
|
||||
), patch.object(
|
||||
ProleInstaller, "_save_prole_cfg"
|
||||
KnoeInstaller, "_save_prole_cfg"
|
||||
), patch.object(
|
||||
ProleInstaller, "_save_ansible_prole_vault"
|
||||
KnoeInstaller, "_save_ansible_prole_vault"
|
||||
), patch.object(
|
||||
ProleInstaller, "_after_env_saved"
|
||||
KnoeInstaller, "_after_env_saved"
|
||||
), patch(
|
||||
"threading.Thread"
|
||||
):
|
||||
@ -52,7 +52,7 @@ def mock_installer(tmp_path):
|
||||
root.winfo_screenwidth.return_value = 1920
|
||||
root.winfo_screenheight.return_value = 1080
|
||||
|
||||
app = ProleInstaller(root, config_path=str(cfg_path))
|
||||
app = KnoeInstaller(root, config_path=str(cfg_path))
|
||||
return app
|
||||
|
||||
|
||||
|
||||
@ -5,13 +5,13 @@ from types import SimpleNamespace
|
||||
import pytest
|
||||
|
||||
import knoe.core.actions as actions
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
from knoe.core.controller import ProleController
|
||||
from knoe.core.actions import KnoeConsoleInstaller
|
||||
from knoe.core.controller import KnoeController
|
||||
|
||||
|
||||
def _mk_installer(tmp_path) -> ProleConsoleInstaller:
|
||||
c = ProleController(tmp_path)
|
||||
return ProleConsoleInstaller(c)
|
||||
def _mk_installer(tmp_path) -> KnoeConsoleInstaller:
|
||||
c = KnoeController(tmp_path)
|
||||
return KnoeConsoleInstaller(c)
|
||||
|
||||
|
||||
def test_repair_flow_classifies_blockers_attempts_reclaim_and_avoids_blind_health_wait(
|
||||
|
||||
@ -6,8 +6,8 @@ import os
|
||||
from types import SimpleNamespace
|
||||
|
||||
import knoe.core.actions as actions
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
from knoe.core.controller import ProleController
|
||||
from knoe.core.actions import KnoeConsoleInstaller
|
||||
from knoe.core.controller import KnoeController
|
||||
|
||||
|
||||
@contextmanager
|
||||
@ -30,9 +30,9 @@ def _preserve_namespace_env():
|
||||
os.environ[k] = v
|
||||
|
||||
|
||||
def _mk_installer(tmp_path) -> ProleConsoleInstaller:
|
||||
c = ProleController(tmp_path)
|
||||
return ProleConsoleInstaller(c)
|
||||
def _mk_installer(tmp_path) -> KnoeConsoleInstaller:
|
||||
c = KnoeController(tmp_path)
|
||||
return KnoeConsoleInstaller(c)
|
||||
|
||||
|
||||
def test_match_stale_released_pv_happy_path(tmp_path):
|
||||
|
||||
@ -67,19 +67,19 @@ NAMESPACE = test-ns
|
||||
|
||||
|
||||
def _make_installer(tmp_path: Path):
|
||||
"""Return a minimal ProleConsoleInstaller with mocked controller."""
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
from knoe.core.controller import ProleController
|
||||
"""Return a minimal KnoeConsoleInstaller with mocked controller."""
|
||||
from knoe.core.actions import KnoeConsoleInstaller
|
||||
from knoe.core.controller import KnoeController
|
||||
|
||||
cfg_path = tmp_path / "prole.cfg"
|
||||
if not cfg_path.exists():
|
||||
_write_cfg(cfg_path, "[Inputs]\n[Global]\n[Install]\nSTATUS = New\n")
|
||||
|
||||
controller = MagicMock(spec=ProleController)
|
||||
controller = MagicMock(spec=KnoeController)
|
||||
controller.project_root = tmp_path
|
||||
controller.state = MagicMock()
|
||||
|
||||
installer = ProleConsoleInstaller.__new__(ProleConsoleInstaller)
|
||||
installer = KnoeConsoleInstaller.__new__(KnoeConsoleInstaller)
|
||||
installer.controller = controller
|
||||
installer.cfg_path = cfg_path
|
||||
installer.inputs = {}
|
||||
@ -92,14 +92,14 @@ def _make_installer(tmp_path: Path):
|
||||
|
||||
def _defaults(installer):
|
||||
"""Call _default_inputs() with all internal deps stubbed."""
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
from knoe.core.actions import KnoeConsoleInstaller
|
||||
|
||||
with (
|
||||
patch.object(ProleConsoleInstaller, "_load_inputs_from_cfg", return_value={}),
|
||||
patch.object(ProleConsoleInstaller, "_initial_db_namespace", return_value="test-ns"),
|
||||
patch.object(ProleConsoleInstaller, "_cnpg_cluster_name", return_value="knoe-db"),
|
||||
patch.object(ProleConsoleInstaller, "_get_local_owner", return_value="root"),
|
||||
patch.object(ProleConsoleInstaller, "_env_defaults", return_value={}),
|
||||
patch.object(KnoeConsoleInstaller, "_load_inputs_from_cfg", return_value={}),
|
||||
patch.object(KnoeConsoleInstaller, "_initial_db_namespace", return_value="test-ns"),
|
||||
patch.object(KnoeConsoleInstaller, "_cnpg_cluster_name", return_value="knoe-db"),
|
||||
patch.object(KnoeConsoleInstaller, "_get_local_owner", return_value="root"),
|
||||
patch.object(KnoeConsoleInstaller, "_env_defaults", return_value={}),
|
||||
):
|
||||
return installer._default_inputs()
|
||||
|
||||
@ -151,8 +151,8 @@ def test_main_update_flag_dispatches_to_run_update(tmp_path, monkeypatch):
|
||||
|
||||
monkeypatch.setattr(sys, "argv", ["install.py", "--update", "-c", str(cfg_path)])
|
||||
monkeypatch.setattr(screens_mod, "_ensure_ansible_vault_credentials", lambda **_: None)
|
||||
monkeypatch.setattr(screens_mod, "ProleController", MagicMock(return_value=MagicMock()))
|
||||
monkeypatch.setattr(screens_mod, "ProleConsoleInstaller", MagicMock(return_value=installer))
|
||||
monkeypatch.setattr(screens_mod, "KnoeController", MagicMock(return_value=MagicMock()))
|
||||
monkeypatch.setattr(screens_mod, "KnoeConsoleInstaller", MagicMock(return_value=installer))
|
||||
|
||||
with pytest.raises(SystemExit) as ex:
|
||||
screens_mod.main()
|
||||
@ -198,7 +198,7 @@ def test_main_rejects_delete_db_without_reset(tmp_path, monkeypatch):
|
||||
|
||||
|
||||
def test_main_delete_db_is_forwarded_to_console_installer(tmp_path, monkeypatch):
|
||||
"""install.py --delete-db must be forwarded to ProleConsoleInstaller in silent mode."""
|
||||
"""install.py --delete-db must be forwarded to KnoeConsoleInstaller in silent mode."""
|
||||
import knoe.ui.screens as screens_mod
|
||||
|
||||
cfg_path = tmp_path / "prole.cfg"
|
||||
@ -217,8 +217,8 @@ def test_main_delete_db_is_forwarded_to_console_installer(tmp_path, monkeypatch)
|
||||
["install.py", "--silent", "--reset", "--delete-db", "-c", str(cfg_path)],
|
||||
)
|
||||
monkeypatch.setattr(screens_mod, "_ensure_ansible_vault_credentials", lambda **_: None)
|
||||
monkeypatch.setattr(screens_mod, "ProleController", MagicMock(return_value=MagicMock()))
|
||||
monkeypatch.setattr(screens_mod, "ProleConsoleInstaller", installer_factory)
|
||||
monkeypatch.setattr(screens_mod, "KnoeController", MagicMock(return_value=MagicMock()))
|
||||
monkeypatch.setattr(screens_mod, "KnoeConsoleInstaller", installer_factory)
|
||||
|
||||
with pytest.raises(SystemExit) as ex:
|
||||
screens_mod.main()
|
||||
@ -233,7 +233,7 @@ def test_main_delete_db_is_forwarded_to_console_installer(tmp_path, monkeypatch)
|
||||
|
||||
def test_repair_flag_does_not_trigger_silent_install(tmp_path):
|
||||
"""--repair must dispatch to run_repair(), NOT run() (full install)."""
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
from knoe.core.actions import KnoeConsoleInstaller
|
||||
|
||||
installer = _make_installer(tmp_path)
|
||||
|
||||
@ -241,9 +241,9 @@ def test_repair_flag_does_not_trigger_silent_install(tmp_path):
|
||||
run_repair_called = []
|
||||
|
||||
with (
|
||||
patch.object(ProleConsoleInstaller, "run", side_effect=lambda: run_called.append(1) or 0),
|
||||
patch.object(KnoeConsoleInstaller, "run", side_effect=lambda: run_called.append(1) or 0),
|
||||
patch.object(
|
||||
ProleConsoleInstaller,
|
||||
KnoeConsoleInstaller,
|
||||
"run_repair",
|
||||
side_effect=lambda: run_repair_called.append(1) or 0,
|
||||
),
|
||||
@ -256,38 +256,38 @@ def test_repair_flag_does_not_trigger_silent_install(tmp_path):
|
||||
|
||||
def test_update_calls_repair_first(tmp_path):
|
||||
"""run_update() must invoke run_repair() before applying config changes."""
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
from knoe.core.actions import KnoeConsoleInstaller
|
||||
|
||||
installer = _make_installer(tmp_path)
|
||||
call_order: list[str] = []
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
ProleConsoleInstaller,
|
||||
KnoeConsoleInstaller,
|
||||
"run_repair",
|
||||
side_effect=lambda: call_order.append("repair") or 0,
|
||||
),
|
||||
patch.object(ProleConsoleInstaller, "_load_inputs_from_cfg", return_value={}),
|
||||
patch.object(ProleConsoleInstaller, "_default_inputs", return_value={}),
|
||||
patch.object(ProleConsoleInstaller, "_service_namespace", return_value="test-ns"),
|
||||
patch.object(ProleConsoleInstaller, "_script_env_for_namespace", return_value={}),
|
||||
patch.object(KnoeConsoleInstaller, "_load_inputs_from_cfg", return_value={}),
|
||||
patch.object(KnoeConsoleInstaller, "_default_inputs", return_value={}),
|
||||
patch.object(KnoeConsoleInstaller, "_service_namespace", return_value="test-ns"),
|
||||
patch.object(KnoeConsoleInstaller, "_script_env_for_namespace", return_value={}),
|
||||
patch.object(
|
||||
ProleConsoleInstaller,
|
||||
KnoeConsoleInstaller,
|
||||
"_reconcile_supabase",
|
||||
side_effect=lambda env: call_order.append("reconcile"),
|
||||
),
|
||||
patch.object(
|
||||
ProleConsoleInstaller,
|
||||
KnoeConsoleInstaller,
|
||||
"_step_db_build",
|
||||
side_effect=lambda: call_order.append("db_build"),
|
||||
),
|
||||
patch.object(
|
||||
ProleConsoleInstaller,
|
||||
KnoeConsoleInstaller,
|
||||
"_step_cnpg_deploy",
|
||||
side_effect=lambda: call_order.append("cnpg_deploy"),
|
||||
),
|
||||
patch.object(ProleConsoleInstaller, "_write_cfg", return_value=None),
|
||||
patch.object(ProleConsoleInstaller, "_close_log_file", return_value=None),
|
||||
patch.object(KnoeConsoleInstaller, "_write_cfg", return_value=None),
|
||||
patch.object(KnoeConsoleInstaller, "_close_log_file", return_value=None),
|
||||
):
|
||||
installer.run_update()
|
||||
|
||||
@ -298,15 +298,15 @@ def test_update_calls_repair_first(tmp_path):
|
||||
|
||||
def test_update_forces_cnpg_rollout_only_for_deploy_step(tmp_path):
|
||||
"""run_update() must force rollout for CNPG deploy, then restore configured value."""
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
from knoe.core.actions import KnoeConsoleInstaller
|
||||
|
||||
installer = _make_installer(tmp_path)
|
||||
seen_force_rollout: list[bool] = []
|
||||
|
||||
with (
|
||||
patch.object(ProleConsoleInstaller, "run_repair", return_value=0),
|
||||
patch.object(KnoeConsoleInstaller, "run_repair", return_value=0),
|
||||
patch.object(
|
||||
ProleConsoleInstaller,
|
||||
KnoeConsoleInstaller,
|
||||
"_load_inputs_from_cfg",
|
||||
return_value={
|
||||
"init_cnpg_deploy.run_deploy": "true",
|
||||
@ -315,13 +315,13 @@ def test_update_forces_cnpg_rollout_only_for_deploy_step(tmp_path):
|
||||
"init_cluster.supabase_enabled": "false",
|
||||
},
|
||||
),
|
||||
patch.object(ProleConsoleInstaller, "_default_inputs", return_value={}),
|
||||
patch.object(ProleConsoleInstaller, "_service_namespace", return_value="test-ns"),
|
||||
patch.object(ProleConsoleInstaller, "_script_env_for_namespace", return_value={}),
|
||||
patch.object(ProleConsoleInstaller, "_write_cfg", return_value=None),
|
||||
patch.object(ProleConsoleInstaller, "_close_log_file", return_value=None),
|
||||
patch.object(KnoeConsoleInstaller, "_default_inputs", return_value={}),
|
||||
patch.object(KnoeConsoleInstaller, "_service_namespace", return_value="test-ns"),
|
||||
patch.object(KnoeConsoleInstaller, "_script_env_for_namespace", return_value={}),
|
||||
patch.object(KnoeConsoleInstaller, "_write_cfg", return_value=None),
|
||||
patch.object(KnoeConsoleInstaller, "_close_log_file", return_value=None),
|
||||
patch.object(
|
||||
ProleConsoleInstaller,
|
||||
KnoeConsoleInstaller,
|
||||
"_step_cnpg_deploy",
|
||||
side_effect=lambda: seen_force_rollout.append(
|
||||
installer._get_input_bool("init_cnpg_deploy.force_rollout", False)
|
||||
@ -490,7 +490,7 @@ def test_render_supabase_component_flags_default_to_enabled(tmp_path, monkeypatc
|
||||
|
||||
def test_reconcile_supabase_skips_studio_when_disabled(tmp_path):
|
||||
"""_reconcile_supabase() must skip Studio when supabase_studio_enabled=false."""
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
from knoe.core.actions import KnoeConsoleInstaller
|
||||
|
||||
installer = _make_installer(tmp_path)
|
||||
base = _defaults(installer)
|
||||
@ -502,7 +502,7 @@ def test_reconcile_supabase_skips_studio_when_disabled(tmp_path):
|
||||
|
||||
studio_reconcile_called = []
|
||||
with patch.object(
|
||||
ProleConsoleInstaller,
|
||||
KnoeConsoleInstaller,
|
||||
"_reconcile_supabase_studio",
|
||||
side_effect=lambda env: studio_reconcile_called.append(1),
|
||||
):
|
||||
@ -515,7 +515,7 @@ def test_reconcile_supabase_skips_studio_when_disabled(tmp_path):
|
||||
|
||||
def test_reconcile_supabase_calls_studio_when_enabled(tmp_path):
|
||||
"""_reconcile_supabase() must invoke Studio reconcile when enabled."""
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
from knoe.core.actions import KnoeConsoleInstaller
|
||||
|
||||
installer = _make_installer(tmp_path)
|
||||
base = _defaults(installer)
|
||||
@ -527,7 +527,7 @@ def test_reconcile_supabase_calls_studio_when_enabled(tmp_path):
|
||||
|
||||
studio_reconcile_called = []
|
||||
with patch.object(
|
||||
ProleConsoleInstaller,
|
||||
KnoeConsoleInstaller,
|
||||
"_reconcile_supabase_studio",
|
||||
side_effect=lambda env: studio_reconcile_called.append(1),
|
||||
):
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import sys
|
||||
from contextlib import ExitStack
|
||||
from unittest.mock import patch, MagicMock
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
# Mock tkinter and other GUI/macOS specific imports
|
||||
sys.modules["tkinter"] = MagicMock()
|
||||
@ -15,8 +16,7 @@ sys.modules["PIL"] = MagicMock()
|
||||
sys.modules["PIL.Image"] = MagicMock()
|
||||
sys.modules["PIL.ImageTk"] = MagicMock()
|
||||
|
||||
import install
|
||||
from install import ProleInstaller
|
||||
from install import KnoeInstaller
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -38,10 +38,10 @@ def mock_installer(tmp_path):
|
||||
stack.enter_context(patch("install.ttk.Style"))
|
||||
stack.enter_context(patch("install.Path.exists", return_value=True))
|
||||
stack.enter_context(patch("install.Path.mkdir"))
|
||||
stack.enter_context(patch.object(ProleInstaller, "_save_env_to_file"))
|
||||
stack.enter_context(patch.object(ProleInstaller, "_save_prole_cfg"))
|
||||
stack.enter_context(patch.object(ProleInstaller, "_save_ansible_prole_vault"))
|
||||
stack.enter_context(patch.object(ProleInstaller, "_after_env_saved"))
|
||||
stack.enter_context(patch.object(KnoeInstaller, "_save_env_to_file"))
|
||||
stack.enter_context(patch.object(KnoeInstaller, "_save_prole_cfg"))
|
||||
stack.enter_context(patch.object(KnoeInstaller, "_save_ansible_prole_vault"))
|
||||
stack.enter_context(patch.object(KnoeInstaller, "_after_env_saved"))
|
||||
stack.enter_context(patch("threading.Thread"))
|
||||
|
||||
mock_entry.return_value.get.return_value.strip.return_value = "mock_val"
|
||||
@ -49,7 +49,7 @@ def mock_installer(tmp_path):
|
||||
root.winfo_screenwidth.return_value = 1920
|
||||
root.winfo_screenheight.return_value = 1080
|
||||
|
||||
app = ProleInstaller(root, config_path=str(cfg_path))
|
||||
app = KnoeInstaller(root, config_path=str(cfg_path))
|
||||
try:
|
||||
app.bg_canvas.winfo_width.return_value = 1000
|
||||
except Exception:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user