mirror of
https://github.com/dredx/prole.git
synced 2026-09-24 15:04:31 +00:00
Fix installer ctx apply flow and registry namespace
- Add explicit Apply button for kubectl context switching to avoid half-applied changes - Allow kubeconfig/context-based auth without requiring K3S_TOKEN when kubeconfig is valid - Prompt cleanup/reset of previous Common Core services namespace to prevent resource collisions - Remove hardcoded 'common-services' registry namespace; default registry deploy/check to SERVICE_NAMESPACE/REGISTRY_NAMESPACE - Update mocks and add regression tests for namespace resolution and installer behavior
This commit is contained in:
parent
436c6214df
commit
d397b3000e
@ -1,5 +1,5 @@
|
|||||||
; Prole Master Configuration File
|
; Prole Master Configuration File
|
||||||
; Generated by install.py on 2026-03-07 03:07:50
|
; Generated by install.py on 2026-03-07 10:32:42
|
||||||
; This file is used as input for Ansible deployment and k8s cluster creation.
|
; This file is used as input for Ansible deployment and k8s cluster creation.
|
||||||
|
|
||||||
[User]
|
[User]
|
||||||
@ -10,7 +10,7 @@ PROLE_DATA = /Users/chrisfu/.prole/data
|
|||||||
PROLE_HOME = /Users/chrisfu/dev/prole
|
PROLE_HOME = /Users/chrisfu/dev/prole
|
||||||
PROLE_LOGS = /opt/prole/logs/chrisfu
|
PROLE_LOGS = /opt/prole/logs/chrisfu
|
||||||
PROLE_SERVICE = ${PROLE_HOME}/etc
|
PROLE_SERVICE = ${PROLE_HOME}/etc
|
||||||
SERVICE_NAMESPACE = default
|
SERVICE_NAMESPACE = knoe-system
|
||||||
|
|
||||||
[Inputs]
|
[Inputs]
|
||||||
; Screen-scoped inputs used for unattended replays (-S)
|
; Screen-scoped inputs used for unattended replays (-S)
|
||||||
@ -42,7 +42,7 @@ dependencies.opentofu.install = true
|
|||||||
dependencies.python.install = true
|
dependencies.python.install = true
|
||||||
dependencies.verify_all = false
|
dependencies.verify_all = false
|
||||||
disk_selection.disk_type = local
|
disk_selection.disk_type = local
|
||||||
disk_selection.local_path = /Users/chrisfu
|
disk_selection.local_path = ${PROLE_HOME}/prole-tools-app/dist
|
||||||
disk_selection.removable_mount =
|
disk_selection.removable_mount =
|
||||||
env_setup.NAMESPACE = ${NAMESPACE}
|
env_setup.NAMESPACE = ${NAMESPACE}
|
||||||
env_setup.PROLE_CONF = ${PROLE_CONF}
|
env_setup.PROLE_CONF = ${PROLE_CONF}
|
||||||
@ -51,13 +51,13 @@ env_setup.PROLE_HOME = ${PROLE_HOME}
|
|||||||
env_setup.PROLE_LOGS = ${PROLE_LOGS}
|
env_setup.PROLE_LOGS = ${PROLE_LOGS}
|
||||||
env_setup.PROLE_SERVICE = ${PROLE_SERVICE}
|
env_setup.PROLE_SERVICE = ${PROLE_SERVICE}
|
||||||
init_cluster.at_rest_encryption_enabled = true
|
init_cluster.at_rest_encryption_enabled = true
|
||||||
init_cluster.cluster_env = dev
|
init_cluster.cluster_env = service
|
||||||
init_cluster.deployment_target = prole-dev-cluster
|
init_cluster.deployment_target = prole-service-cluster
|
||||||
init_cluster.gitops_enabled = false
|
init_cluster.gitops_enabled = false
|
||||||
init_cluster.k3s_server_url =
|
init_cluster.k3s_server_url =
|
||||||
init_cluster.k3s_token =
|
init_cluster.k3s_token = ${PROLE_SECRET:v1:38WOjNObQUt_S1W_:q7cZ6C5FcsiNPfxtmUI5RtsuJ4iaBAdvLbw_1hpOx87Cvv2KL-HjbdE7s5J4aPegFw02IrwWuPP5WXe5nGkg7uTXreCsvg5uWYXc1vxjWHUzKEwVfcP2nPEKBjJrRuai1VxD8WaPbwcUMQfXEtqNrFrkBSIICYltM1hmmA==}
|
||||||
init_cluster.kerberos_enabled = false
|
init_cluster.kerberos_enabled = true
|
||||||
init_cluster.mode = k3d
|
init_cluster.mode = k3s
|
||||||
init_cluster.start_cluster = true
|
init_cluster.start_cluster = true
|
||||||
init_cluster.supabase_enabled = false
|
init_cluster.supabase_enabled = false
|
||||||
init_cnpg_deploy.force_rollout = false
|
init_cnpg_deploy.force_rollout = false
|
||||||
@ -70,10 +70,10 @@ init_password.db_password_confirm =
|
|||||||
init_password.db_username = root
|
init_password.db_username = root
|
||||||
init_password.generate_ssh_key = true
|
init_password.generate_ssh_key = true
|
||||||
init_scripts.run_scripts = true
|
init_scripts.run_scripts = true
|
||||||
kerberos_config.enabled = false
|
kerberos_config.enabled = true
|
||||||
kerberos_config.kdc =
|
kerberos_config.kdc = 10.0.0.3
|
||||||
kerberos_config.password =
|
kerberos_config.password = ${PROLE_SECRET:v1:q3AVwS1x8Xr8V6n9:IRDXFhRi8uBOZvDmLoT-zKVjwGsiiDQvB4c430CqJS1PWz3X}
|
||||||
kerberos_config.realm =
|
kerberos_config.realm = PROLE.ORG
|
||||||
kerberos_config.test_connection = false
|
kerberos_config.test_connection = false
|
||||||
kerberos_config.user = administrator
|
kerberos_config.user = administrator
|
||||||
network_scan.run = true
|
network_scan.run = true
|
||||||
@ -83,18 +83,18 @@ ollama_config.server_port = 11434
|
|||||||
|
|
||||||
[Global]
|
[Global]
|
||||||
; Variables used by name in more than one place or assumed global scope
|
; Variables used by name in more than one place or assumed global scope
|
||||||
CLUSTER_ENV = dev
|
CLUSTER_ENV = service
|
||||||
DB_HOST_PORT = 5432
|
DB_HOST_PORT = 5432
|
||||||
DB_PASSWORD =
|
DB_PASSWORD =
|
||||||
DEPLOYMENT_MODE = k3d
|
DEPLOYMENT_MODE = k3s
|
||||||
DEPLOYMENT_TARGET = prole-dev-cluster
|
DEPLOYMENT_TARGET = prole-service-cluster
|
||||||
NAMESPACE = ${NAMESPACE}
|
NAMESPACE = ${NAMESPACE}
|
||||||
PROLE_DB_USER = root
|
PROLE_DB_USER = root
|
||||||
PROLE_HOME = ${PROLE_HOME}
|
PROLE_HOME = ${PROLE_HOME}
|
||||||
PROLE_K3S_SERVER =
|
PROLE_K3S_SERVER =
|
||||||
PROLE_K3S_TOKEN =
|
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:38WOjNObQUt_S1W_:q7cZ6C5FcsiNPfxtmUI5RtsuJ4iaBAdvLbw_1hpOx87Cvv2KL-HjbdE7s5J4aPegFw02IrwWuPP5WXe5nGkg7uTXreCsvg5uWYXc1vxjWHUzKEwVfcP2nPEKBjJrRuai1VxD8WaPbwcUMQfXEtqNrFrkBSIICYltM1hmmA==}
|
||||||
PROLE_OPENTOFU_URL = http://127.0.0.1:8080
|
PROLE_OPENTOFU_URL = http://127.0.0.1:8080
|
||||||
SERVICE_NAMESPACE = default
|
SERVICE_NAMESPACE = knoe-system
|
||||||
|
|
||||||
[Welcome]
|
[Welcome]
|
||||||
; No configuration values captured yet for this section.
|
; No configuration values captured yet for this section.
|
||||||
@ -111,6 +111,8 @@ ANSIBLE_INVENTORY = ${PROLE_HOME}/infrastructure/inventory
|
|||||||
ANSIBLE_REALM = PROLE.ORG
|
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":"","server_host":"","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"],"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":"","server_host":"","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"],"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"]}
|
||||||
KDC_ANSIBLE_DETECTED = 10.0.0.3
|
KDC_ANSIBLE_DETECTED = 10.0.0.3
|
||||||
|
KDC_AUTO_DETECTED = 10.0.0.3
|
||||||
|
KERBEROS_AUTO_ENABLED = True
|
||||||
|
|
||||||
[Port Forwards]
|
[Port Forwards]
|
||||||
; No configuration values captured yet for this section.
|
; No configuration values captured yet for this section.
|
||||||
@ -132,7 +134,10 @@ PROLE_SERVICE = ${PROLE_SERVICE}
|
|||||||
; No configuration values captured yet for this section.
|
; No configuration values captured yet for this section.
|
||||||
|
|
||||||
[Optional Features]
|
[Optional Features]
|
||||||
; No configuration values captured yet for this section.
|
AT_REST_ENCRYPTION_ENABLED = True
|
||||||
|
GITOPS_ENABLED = False
|
||||||
|
KERBEROS_ENABLED = True
|
||||||
|
SUPABASE_ENABLED = False
|
||||||
|
|
||||||
[GitOps]
|
[GitOps]
|
||||||
; No configuration values captured yet for this section.
|
; No configuration values captured yet for this section.
|
||||||
@ -141,24 +146,26 @@ PROLE_SERVICE = ${PROLE_SERVICE}
|
|||||||
NAMESPACE = ${NAMESPACE}
|
NAMESPACE = ${NAMESPACE}
|
||||||
|
|
||||||
[Initialize Cluster]
|
[Initialize Cluster]
|
||||||
; No configuration values captured yet for this section.
|
ENVIRONMENT = service
|
||||||
|
K3S_SERVER_URL =
|
||||||
|
K3S_TOKEN = ${PROLE_SECRET:v1:38WOjNObQUt_S1W_:q7cZ6C5FcsiNPfxtmUI5RtsuJ4iaBAdvLbw_1hpOx87Cvv2KL-HjbdE7s5J4aPegFw02IrwWuPP5WXe5nGkg7uTXreCsvg5uWYXc1vxjWHUzKEwVfcP2nPEKBjJrRuai1VxD8WaPbwcUMQfXEtqNrFrkBSIICYltM1hmmA==}
|
||||||
|
|
||||||
[Dev Cluster (k3d)]
|
[Dev Cluster (k3d)]
|
||||||
CLUSTER_ENV = dev
|
CLUSTER_ENV = dev
|
||||||
DISPLAY_NAME = knoe-dev-cluster
|
DISPLAY_NAME = knoe-dev-cluster
|
||||||
KUBECTL_CONTEXT =
|
KUBECTL_CONTEXT = prole-k3s
|
||||||
MODE = k3d
|
MODE = k3d
|
||||||
|
|
||||||
[Service Cluster (k3s)]
|
[Service Cluster (k3s)]
|
||||||
CLUSTER_ENV = prole-service-cluster
|
CLUSTER_ENV = prole-service-cluster
|
||||||
DISPLAY_NAME = prole-service-cluster
|
DISPLAY_NAME = prole-service-cluster
|
||||||
K3S_SERVER_URL =
|
K3S_SERVER_URL =
|
||||||
K3S_TOKEN =
|
K3S_TOKEN = ${PROLE_SECRET:v1:38WOjNObQUt_S1W_:q7cZ6C5FcsiNPfxtmUI5RtsuJ4iaBAdvLbw_1hpOx87Cvv2KL-HjbdE7s5J4aPegFw02IrwWuPP5WXe5nGkg7uTXreCsvg5uWYXc1vxjWHUzKEwVfcP2nPEKBjJrRuai1VxD8WaPbwcUMQfXEtqNrFrkBSIICYltM1hmmA==}
|
||||||
MODE = k3s
|
MODE = k3s
|
||||||
PIPELINE_URL = http://127.0.0.1:8080
|
PIPELINE_URL = http://127.0.0.1:8080
|
||||||
|
|
||||||
[Prod Cluster (k8s)]
|
[Prod Cluster (k8s)]
|
||||||
ARTIFACTS_DIR = /private/var/folders/rt/pywlnmxj3dn7t5552vwdcpp80000gn/T/pytest-of-chrisfu/pytest-132/test_encryption_toggle0/data/staging
|
ARTIFACTS_DIR = /Users/chrisfu/dev/prole/data/staging
|
||||||
CLUSTER_ENV = prole-prod-cluster
|
CLUSTER_ENV = prole-prod-cluster
|
||||||
DISPLAY_NAME = prole-prod-cluster
|
DISPLAY_NAME = prole-prod-cluster
|
||||||
MODE = k8s
|
MODE = k8s
|
||||||
@ -171,8 +178,8 @@ PIPELINE_URL = http://127.0.0.1:8080
|
|||||||
; No configuration values captured yet for this section.
|
; No configuration values captured yet for this section.
|
||||||
|
|
||||||
[Deployment]
|
[Deployment]
|
||||||
MODE = k3d
|
MODE = k3s
|
||||||
TARGET = prole-dev-cluster
|
TARGET = prole-service-cluster
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
; No configuration values captured yet for this section.
|
; No configuration values captured yet for this section.
|
||||||
|
|||||||
@ -48,7 +48,7 @@ fi
|
|||||||
|
|
||||||
ACTION="$COMMON_CORE_ACTION"
|
ACTION="$COMMON_CORE_ACTION"
|
||||||
# cert-manager always runs in its own dedicated namespace; never follows the
|
# cert-manager always runs in its own dedicated namespace; never follows the
|
||||||
# common-services namespace override.
|
# Registry/service namespace override.
|
||||||
CERTMGR_NAMESPACE="cert-manager"
|
CERTMGR_NAMESPACE="cert-manager"
|
||||||
common_core_apply_namespace "$CERTMGR_NAMESPACE"
|
common_core_apply_namespace "$CERTMGR_NAMESPACE"
|
||||||
|
|
||||||
|
|||||||
@ -724,7 +724,7 @@ _push_to_k3s_registry() {
|
|||||||
# Fallback: port-forward the in-cluster registry service and push via localhost.
|
# Fallback: port-forward the in-cluster registry service and push via localhost.
|
||||||
# This avoids relying on hostPort / firewall rules for ${push_host}.
|
# This avoids relying on hostPort / firewall rules for ${push_host}.
|
||||||
if command -v kubectl >/dev/null 2>&1 && command -v skopeo >/dev/null 2>&1; then
|
if command -v kubectl >/dev/null 2>&1 && command -v skopeo >/dev/null 2>&1; then
|
||||||
local reg_ns="${REGISTRY_NAMESPACE:-${COMMON_SERVICES_NAMESPACE:-common-services}}"
|
local reg_ns="${REGISTRY_NAMESPACE:-${SERVICE_NAMESPACE:-${NAMESPACE:-default}}}"
|
||||||
local pf_port="${PROLE_REGISTRY_PORT_FORWARD_LOCAL:-55000}"
|
local pf_port="${PROLE_REGISTRY_PORT_FORWARD_LOCAL:-55000}"
|
||||||
local pf_log
|
local pf_log
|
||||||
pf_log="$(mktemp -t prole-registry-pf.XXXXXX)"
|
pf_log="$(mktemp -t prole-registry-pf.XXXXXX)"
|
||||||
|
|||||||
@ -87,7 +87,8 @@ if [ -z "$NS" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
ARGOCD_NS="${ARGOCD_NAMESPACE:-argocd}"
|
ARGOCD_NS="${ARGOCD_NAMESPACE:-argocd}"
|
||||||
REGISTRY_NS="${REGISTRY_NAMESPACE:-${COMMON_SERVICES_NAMESPACE:-common-services}}"
|
# Registry should live in the common-core/service namespace unless explicitly overridden.
|
||||||
|
REGISTRY_NS="${REGISTRY_NAMESPACE:-${NS}}"
|
||||||
|
|
||||||
prole_ensure_kubeconfig >/dev/null 2>&1 || true
|
prole_ensure_kubeconfig >/dev/null 2>&1 || true
|
||||||
echo "DEBUG: prole_ensure_kubeconfig finished" >&2
|
echo "DEBUG: prole_ensure_kubeconfig finished" >&2
|
||||||
@ -392,7 +393,7 @@ _preload_docker_images() {
|
|||||||
if [[ -z "$registry" ]]; then
|
if [[ -z "$registry" ]]; then
|
||||||
echo " [WARN] Could not resolve registry host (set LOCAL_REGISTRY or PROLE_K3S_SERVER)." >&2
|
echo " [WARN] Could not resolve registry host (set LOCAL_REGISTRY or PROLE_K3S_SERVER)." >&2
|
||||||
echo " Images will be pulled at deploy time." >&2
|
echo " Images will be pulled at deploy time." >&2
|
||||||
;;
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
_registry_ref_exists() {
|
_registry_ref_exists() {
|
||||||
@ -413,7 +414,7 @@ _preload_docker_images() {
|
|||||||
if ! curl -fsS "http://${registry}/v2/" >/dev/null 2>&1; then
|
if ! curl -fsS "http://${registry}/v2/" >/dev/null 2>&1; then
|
||||||
echo " [WARN] Registry not reachable at http://${registry}/v2/." >&2
|
echo " [WARN] Registry not reachable at http://${registry}/v2/." >&2
|
||||||
echo " Images will be pulled at deploy time." >&2
|
echo " Images will be pulled at deploy time." >&2
|
||||||
;;
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local tar_file
|
local tar_file
|
||||||
|
|||||||
@ -24,7 +24,7 @@ registry_host_from_url() {
|
|||||||
|
|
||||||
K3S_REGISTRY_HOST=${K3S_REGISTRY_HOST:-$(registry_host_from_url "${PROLE_K3S_SERVER:-${K3S_SERVER_URL:-}}")}
|
K3S_REGISTRY_HOST=${K3S_REGISTRY_HOST:-$(registry_host_from_url "${PROLE_K3S_SERVER:-${K3S_SERVER_URL:-}}")}
|
||||||
K3S_REGISTRY_PORT=${K3S_REGISTRY_PORT:-5000}
|
K3S_REGISTRY_PORT=${K3S_REGISTRY_PORT:-5000}
|
||||||
K3S_REGISTRY_NAMESPACE=${K3S_REGISTRY_NAMESPACE:-${REGISTRY_NAMESPACE:-${COMMON_SERVICES_NAMESPACE:-common-services}}}
|
K3S_REGISTRY_NAMESPACE=${K3S_REGISTRY_NAMESPACE:-${REGISTRY_NAMESPACE:-${SERVICE_NAMESPACE:-${PROLE_NAMESPACE:-default}}}}
|
||||||
K3S_REGISTRY_FILE=${K3S_REGISTRY_FILE:-/etc/rancher/k3s/registries.yaml}
|
K3S_REGISTRY_FILE=${K3S_REGISTRY_FILE:-/etc/rancher/k3s/registries.yaml}
|
||||||
|
|
||||||
ensure_root() {
|
ensure_root() {
|
||||||
|
|||||||
@ -56,15 +56,31 @@ KRB5_KDC=${KRB5_KDC:-}
|
|||||||
KRB5_ADMIN=${KRB5_ADMIN:-}
|
KRB5_ADMIN=${KRB5_ADMIN:-}
|
||||||
|
|
||||||
local_registry_enabled() {
|
local_registry_enabled() {
|
||||||
case "${PROLE_ENABLE_LOCAL_REGISTRY:-${ENABLE_LOCAL_REGISTRY:-}}" in
|
local raw="${PROLE_ENABLE_LOCAL_REGISTRY:-${ENABLE_LOCAL_REGISTRY:-}}"
|
||||||
|
if [[ -n "$raw" ]]; then
|
||||||
|
case "$raw" in
|
||||||
1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
|
1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
|
||||||
esac
|
esac
|
||||||
return 1
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local mode=""
|
||||||
|
if command -v prole_normalize_mode >/dev/null 2>&1; then
|
||||||
|
mode=$(prole_normalize_mode "${PROLE_MODE:-${DEPLOYMENT_MODE:-${CLUSTER_ENV:-}}}")
|
||||||
|
else
|
||||||
|
mode="${PROLE_MODE:-${DEPLOYMENT_MODE:-${CLUSTER_ENV:-}}}"
|
||||||
|
fi
|
||||||
|
[[ "$mode" == "k3d" || "$mode" == "k3s" ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
if ! local_registry_enabled; then
|
if ! local_registry_enabled; then
|
||||||
|
# Only clear default local-registry settings; preserve explicit registry host values.
|
||||||
|
_default_host="${LOCAL_REGISTRY:-localhost:5000}"
|
||||||
|
if [[ -z "${PROLE_KDC_REGISTRY_HOST:-}" || "${PROLE_KDC_REGISTRY_HOST:-}" == "$_default_host" ]]; then
|
||||||
PROLE_KDC_REGISTRY_HOST=""
|
PROLE_KDC_REGISTRY_HOST=""
|
||||||
|
fi
|
||||||
PROLE_KDC_REGISTRY_INTERNAL=""
|
PROLE_KDC_REGISTRY_INTERNAL=""
|
||||||
|
unset _default_host
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log() { printf '%s\n' "$*"; }
|
log() { printf '%s\n' "$*"; }
|
||||||
@ -264,7 +280,7 @@ resolve_prole_kdc_defaults() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ -z "$PROLE_KDC_REGISTRY_INTERNAL" ]]; then
|
if [[ -z "$PROLE_KDC_REGISTRY_INTERNAL" ]]; then
|
||||||
local reg_ns="${REGISTRY_NAMESPACE:-${COMMON_SERVICES_NAMESPACE:-common-services}}"
|
local reg_ns="${REGISTRY_NAMESPACE:-${SERVICE_NAMESPACE:-${NAMESPACE:-default}}}"
|
||||||
PROLE_KDC_REGISTRY_INTERNAL="registry.${reg_ns}.svc.cluster.local:5000"
|
PROLE_KDC_REGISTRY_INTERNAL="registry.${reg_ns}.svc.cluster.local:5000"
|
||||||
unset reg_ns
|
unset reg_ns
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -136,7 +136,8 @@ if [[ -n "$REGISTRY_NAMESPACE_OVERRIDE" ]]; then
|
|||||||
elif [[ -n "${REGISTRY_NAMESPACE:-}" ]]; then
|
elif [[ -n "${REGISTRY_NAMESPACE:-}" ]]; then
|
||||||
REGISTRY_NAMESPACE="$REGISTRY_NAMESPACE"
|
REGISTRY_NAMESPACE="$REGISTRY_NAMESPACE"
|
||||||
else
|
else
|
||||||
REGISTRY_NAMESPACE="${COMMON_SERVICES_NAMESPACE:-common-services}"
|
# Default to the configured service namespace (driven by prole.cfg via prole_cfg.sh).
|
||||||
|
REGISTRY_NAMESPACE="${SERVICE_NAMESPACE:-${PROLE_NAMESPACE:-default}}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# In k3s mode, the in-cluster registry is a common core service and should live
|
# In k3s mode, the in-cluster registry is a common core service and should live
|
||||||
@ -146,7 +147,7 @@ if declare -F prole_normalize_mode >/dev/null 2>&1; then
|
|||||||
_mode_resolved="$(prole_normalize_mode "$_mode_resolved")"
|
_mode_resolved="$(prole_normalize_mode "$_mode_resolved")"
|
||||||
fi
|
fi
|
||||||
if [[ "$_mode_resolved" == "k3s" && ( -z "${REGISTRY_NAMESPACE:-}" || "${REGISTRY_NAMESPACE}" == "default" ) ]]; then
|
if [[ "$_mode_resolved" == "k3s" && ( -z "${REGISTRY_NAMESPACE:-}" || "${REGISTRY_NAMESPACE}" == "default" ) ]]; then
|
||||||
REGISTRY_NAMESPACE="${COMMON_SERVICES_NAMESPACE:-common-services}"
|
REGISTRY_NAMESPACE="${SERVICE_NAMESPACE:-${PROLE_NAMESPACE:-default}}"
|
||||||
fi
|
fi
|
||||||
unset _mode_resolved
|
unset _mode_resolved
|
||||||
|
|
||||||
|
|||||||
@ -116,7 +116,8 @@ fi
|
|||||||
|
|
||||||
SERVICE_LAYER_NAMESPACE="$SERVICE_NAMESPACE"
|
SERVICE_LAYER_NAMESPACE="$SERVICE_NAMESPACE"
|
||||||
ARGOCD_NS="${ARGOCD_NAMESPACE:-argocd}"
|
ARGOCD_NS="${ARGOCD_NAMESPACE:-argocd}"
|
||||||
REGISTRY_NS="${REGISTRY_NAMESPACE:-${COMMON_SERVICES_NAMESPACE:-common-services}}"
|
# Registry should live in the service-layer namespace unless explicitly overridden.
|
||||||
|
REGISTRY_NS="${REGISTRY_NAMESPACE:-${SERVICE_NAMESPACE}}"
|
||||||
|
|
||||||
STATE_DIR=""
|
STATE_DIR=""
|
||||||
if [[ -n "${PROLE_SERVICE:-}" && -f "$PROLE_SERVICE/prole_cfg.sh" ]]; then
|
if [[ -n "${PROLE_SERVICE:-}" && -f "$PROLE_SERVICE/prole_cfg.sh" ]]; then
|
||||||
|
|||||||
@ -314,7 +314,8 @@ if [[ -n "${PROLE_NAMESPACE:-}" ]]; then
|
|||||||
esac
|
esac
|
||||||
case "$_prole_mode_resolved" in
|
case "$_prole_mode_resolved" in
|
||||||
k3s|k8s)
|
k3s|k8s)
|
||||||
_prole_registry_ns="${REGISTRY_NAMESPACE:-${COMMON_SERVICES_NAMESPACE:-common-services}}"
|
# Registry namespace is driven by config (SERVICE_NAMESPACE) unless explicitly overridden.
|
||||||
|
_prole_registry_ns="${REGISTRY_NAMESPACE:-${SERVICE_NAMESPACE:-${PROLE_NAMESPACE:-}}}"
|
||||||
if [[ -n "${LOCAL_REGISTRY_INTERNAL:-}" ]]; then
|
if [[ -n "${LOCAL_REGISTRY_INTERNAL:-}" ]]; then
|
||||||
:
|
:
|
||||||
elif _prole_local_registry_enabled; then
|
elif _prole_local_registry_enabled; then
|
||||||
|
|||||||
@ -381,7 +381,7 @@
|
|||||||
k3s_registry_namespace_resolved: >-
|
k3s_registry_namespace_resolved: >-
|
||||||
{{ (k3s_registry_namespace | default('')) if (k3s_registry_namespace | default('') | length > 0 and k3s_registry_namespace != '${SERVICE_NAMESPACE}')
|
{{ (k3s_registry_namespace | default('')) if (k3s_registry_namespace | default('') | length > 0 and k3s_registry_namespace != '${SERVICE_NAMESPACE}')
|
||||||
else (k3s_prole_service_namespace_cfg | default('')) if (k3s_prole_service_namespace_cfg | default('') | length > 0 and k3s_prole_service_namespace_cfg != '${SERVICE_NAMESPACE}')
|
else (k3s_prole_service_namespace_cfg | default('')) if (k3s_prole_service_namespace_cfg | default('') | length > 0 and k3s_prole_service_namespace_cfg != '${SERVICE_NAMESPACE}')
|
||||||
else 'common-services' }}
|
else 'knoe-system' }}
|
||||||
when: k3s_state == "present" and k3s_registry_config_enabled | bool
|
when: k3s_state == "present" and k3s_registry_config_enabled | bool
|
||||||
|
|
||||||
- name: Validate k3s registry host
|
- name: Validate k3s registry host
|
||||||
|
|||||||
@ -73,7 +73,7 @@
|
|||||||
k3s_registry_namespace_resolved: >-
|
k3s_registry_namespace_resolved: >-
|
||||||
{{ (k3s_registry_namespace | default('')) if (k3s_registry_namespace | default('') | length > 0 and k3s_registry_namespace != '${SERVICE_NAMESPACE}')
|
{{ (k3s_registry_namespace | default('')) if (k3s_registry_namespace | default('') | length > 0 and k3s_registry_namespace != '${SERVICE_NAMESPACE}')
|
||||||
else (k3s_prole_service_namespace_cfg | default('')) if (k3s_prole_service_namespace_cfg | default('') | length > 0 and k3s_prole_service_namespace_cfg != '${SERVICE_NAMESPACE}')
|
else (k3s_prole_service_namespace_cfg | default('')) if (k3s_prole_service_namespace_cfg | default('') | length > 0 and k3s_prole_service_namespace_cfg != '${SERVICE_NAMESPACE}')
|
||||||
else 'common-services' }}
|
else 'knoe-system' }}
|
||||||
when: k3s_registry_config_enabled | bool
|
when: k3s_registry_config_enabled | bool
|
||||||
|
|
||||||
- name: Validate k3s registry host
|
- name: Validate k3s registry host
|
||||||
|
|||||||
@ -1971,7 +1971,11 @@ class ProleSilentInstaller(ProleInstallerBase):
|
|||||||
)
|
)
|
||||||
if not ns:
|
if not ns:
|
||||||
ns = (os.environ.get("REGISTRY_NAMESPACE") or "").strip()
|
ns = (os.environ.get("REGISTRY_NAMESPACE") or "").strip()
|
||||||
return ns or "common-services"
|
if ns:
|
||||||
|
return ns
|
||||||
|
|
||||||
|
# Registry is a common-core service; default to the configured service namespace.
|
||||||
|
return self._service_namespace()
|
||||||
|
|
||||||
def _ensure_registry_defaults(self, mode: str) -> None:
|
def _ensure_registry_defaults(self, mode: str) -> None:
|
||||||
db = self.prole_cfg_data.get("Docker Build", {}) or {}
|
db = self.prole_cfg_data.get("Docker Build", {}) or {}
|
||||||
|
|||||||
@ -386,10 +386,6 @@ def _verify_k3s_services_status(
|
|||||||
mapped to status strings (``Good``, ``Failing``, ``Error``, …).
|
mapped to status strings (``Good``, ``Failing``, ``Error``, …).
|
||||||
"""
|
"""
|
||||||
status = {"registry": "Unknown", "openbao": "Unknown", "opentofu": "Unknown"}
|
status = {"registry": "Unknown", "openbao": "Unknown", "opentofu": "Unknown"}
|
||||||
server, token = _resolve_k3s_connection(project_root=project_root)
|
|
||||||
if not token:
|
|
||||||
return {k: "Missing token" for k in status}
|
|
||||||
|
|
||||||
base_cmd = _kubectl_base_cmd_for_k3s(
|
base_cmd = _kubectl_base_cmd_for_k3s(
|
||||||
managed_kubeconfig=managed_kubeconfig, project_root=project_root
|
managed_kubeconfig=managed_kubeconfig, project_root=project_root
|
||||||
)
|
)
|
||||||
@ -400,6 +396,10 @@ def _verify_k3s_services_status(
|
|||||||
text=True,
|
text=True,
|
||||||
timeout=15,
|
timeout=15,
|
||||||
)
|
)
|
||||||
|
if res.returncode != 0:
|
||||||
|
# Avoid reporting a misleading "Missing token" when the user is
|
||||||
|
# authenticated via kubeconfig/context instead of K3S_TOKEN.
|
||||||
|
return {k: "Error" for k in status}
|
||||||
out = res.stdout.lower() if res.stdout else ""
|
out = res.stdout.lower() if res.stdout else ""
|
||||||
status["registry"] = "Good" if "registry" in out else "Failing"
|
status["registry"] = "Good" if "registry" in out else "Failing"
|
||||||
status["openbao"] = "Good" if ("openbao" in out or "bao" in out) else "Failing"
|
status["openbao"] = "Good" if ("openbao" in out or "bao" in out) else "Failing"
|
||||||
|
|||||||
@ -215,14 +215,20 @@ class ClusterScreenMixin:
|
|||||||
)
|
)
|
||||||
combo.bind("<<ComboboxSelected>>", self._on_kubectx_select)
|
combo.bind("<<ComboboxSelected>>", self._on_kubectx_select)
|
||||||
if not self.selected_kubectx.get() and values:
|
if not self.selected_kubectx.get() and values:
|
||||||
if selected_env_key == "service" and "prole-k3s" in values:
|
# Default to the current context if it exists in the list; otherwise pick
|
||||||
|
# a sensible default without switching immediately.
|
||||||
|
current_ctx = (getattr(self, "_kubectx_applied", "") or "").strip()
|
||||||
|
if not current_ctx:
|
||||||
|
current_ctx = (self._current_kubectl_context() or "").strip()
|
||||||
|
if current_ctx:
|
||||||
|
self._kubectx_applied = current_ctx
|
||||||
|
|
||||||
|
if current_ctx and current_ctx in values:
|
||||||
|
self.selected_kubectx.set(current_ctx)
|
||||||
|
elif selected_env_key == "service" and "prole-k3s" in values:
|
||||||
self.selected_kubectx.set("prole-k3s")
|
self.selected_kubectx.set("prole-k3s")
|
||||||
# Auto-switch to prole-k3s if we just loaded the page and it's selected
|
|
||||||
self._switch_kubectx("prole-k3s")
|
|
||||||
else:
|
else:
|
||||||
self.selected_kubectx.set(values[0])
|
self.selected_kubectx.set(values[0])
|
||||||
# No auto-switch for others to avoid surprises, but we should probably
|
|
||||||
# if it's the only one.
|
|
||||||
combo_win = self.bg_canvas.create_window(
|
combo_win = self.bg_canvas.create_window(
|
||||||
x_label + 190, y - 6, window=combo, anchor="nw"
|
x_label + 190, y - 6, window=combo, anchor="nw"
|
||||||
)
|
)
|
||||||
@ -239,6 +245,30 @@ class ClusterScreenMixin:
|
|||||||
fill="#6e6e73",
|
fill="#6e6e73",
|
||||||
font=("SF Pro Text", 10),
|
font=("SF Pro Text", 10),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
apply_btn = tk.Button(
|
||||||
|
self.bg_canvas,
|
||||||
|
text="Apply",
|
||||||
|
command=self._on_kubectx_apply,
|
||||||
|
bg="#F5F5DC",
|
||||||
|
fg="black",
|
||||||
|
activebackground="#E5E5D5",
|
||||||
|
highlightbackground="#F5F5DC",
|
||||||
|
highlightthickness=0,
|
||||||
|
relief="flat",
|
||||||
|
font=("SF Pro Text", 10),
|
||||||
|
padx=10,
|
||||||
|
state="disabled",
|
||||||
|
)
|
||||||
|
apply_win = self.bg_canvas.create_window(
|
||||||
|
x_label + 670, y - 10, window=apply_btn, anchor="nw"
|
||||||
|
)
|
||||||
|
self._canvas_items.append(apply_win)
|
||||||
|
self._overlay_widgets.append(apply_btn)
|
||||||
|
# Keep references for UI tests / layout verification.
|
||||||
|
self._kubectx_apply_btn = apply_btn
|
||||||
|
self._kubectx_apply_btn_canvas_window = apply_win
|
||||||
|
self._update_kubectx_apply_button()
|
||||||
y += 34
|
y += 34
|
||||||
|
|
||||||
if selected_env_key == "prod":
|
if selected_env_key == "prod":
|
||||||
@ -542,10 +572,14 @@ class ClusterScreenMixin:
|
|||||||
self._apply_k3s_defaults()
|
self._apply_k3s_defaults()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
# Auto-switch to prole-k3s if it exists
|
# Prefer prole-k3s context if present, but require explicit Apply.
|
||||||
if hasattr(self, "selected_kubectx"):
|
try:
|
||||||
|
values = self._get_kubectx_list()
|
||||||
|
except Exception:
|
||||||
|
values = []
|
||||||
|
if hasattr(self, "selected_kubectx") and "prole-k3s" in (values or []):
|
||||||
self.selected_kubectx.set("prole-k3s")
|
self.selected_kubectx.set("prole-k3s")
|
||||||
self._switch_kubectx("prole-k3s")
|
self._update_kubectx_apply_button()
|
||||||
elif env_key == "dev":
|
elif env_key == "dev":
|
||||||
name = self.selected_k3d_cluster.get()
|
name = self.selected_k3d_cluster.get()
|
||||||
if name:
|
if name:
|
||||||
@ -1128,16 +1162,50 @@ class ClusterScreenMixin:
|
|||||||
|
|
||||||
def _on_kubectx_select(self, *args):
|
def _on_kubectx_select(self, *args):
|
||||||
"""Called when a generic kubernetes context is selected from the dropdown."""
|
"""Called when a generic kubernetes context is selected from the dropdown."""
|
||||||
ctx = self.selected_kubectx.get()
|
self._update_kubectx_apply_button()
|
||||||
|
ctx = (self.selected_kubectx.get() or "").strip()
|
||||||
if ctx:
|
if ctx:
|
||||||
self._switch_kubectx(ctx)
|
self._set_cluster_env_message(
|
||||||
|
f"Context change pending: {ctx} (click Apply)",
|
||||||
|
"#ff9500",
|
||||||
|
clear_after_ms=4000,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _on_kubectx_apply(self):
|
||||||
|
ctx = (self.selected_kubectx.get() or "").strip()
|
||||||
|
if not ctx:
|
||||||
|
return
|
||||||
|
ok = self._switch_kubectx(ctx)
|
||||||
|
if ok:
|
||||||
|
self._kubectx_applied = ctx
|
||||||
|
self._update_kubectx_apply_button()
|
||||||
|
self._set_cluster_env_message(
|
||||||
|
f"Context applied: {ctx}", "#34c759", clear_after_ms=3000
|
||||||
|
)
|
||||||
self._verify_k3s_services()
|
self._verify_k3s_services()
|
||||||
self.show_page("init_cluster")
|
self.show_page("init_cluster")
|
||||||
|
|
||||||
def _switch_kubectx(self, context_name: str):
|
def _update_kubectx_apply_button(self):
|
||||||
|
btn = getattr(self, "_kubectx_apply_btn", None)
|
||||||
|
if not btn:
|
||||||
|
return
|
||||||
|
selected = ""
|
||||||
|
try:
|
||||||
|
if hasattr(self, "selected_kubectx") and self.selected_kubectx is not None:
|
||||||
|
selected = (self.selected_kubectx.get() or "").strip()
|
||||||
|
except Exception:
|
||||||
|
selected = ""
|
||||||
|
applied = (getattr(self, "_kubectx_applied", "") or "").strip()
|
||||||
|
state = "normal" if (selected and selected != applied) else "disabled"
|
||||||
|
try:
|
||||||
|
btn.config(state=state)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _switch_kubectx(self, context_name: str) -> bool:
|
||||||
"""Switch the current kubernetes context using kubectx or kubectl."""
|
"""Switch the current kubernetes context using kubectx or kubectl."""
|
||||||
if not context_name:
|
if not context_name:
|
||||||
return
|
return False
|
||||||
|
|
||||||
# Determine which kubeconfig to modify.
|
# Determine which kubeconfig to modify.
|
||||||
# Use the same logic as the rest of the application.
|
# Use the same logic as the rest of the application.
|
||||||
@ -1151,24 +1219,76 @@ class ClusterScreenMixin:
|
|||||||
res = subprocess.run(
|
res = subprocess.run(
|
||||||
["kubectx", context_name], capture_output=True, text=True, env=env
|
["kubectx", context_name], capture_output=True, text=True, env=env
|
||||||
)
|
)
|
||||||
if res.returncode != 0:
|
ok = res.returncode == 0
|
||||||
|
err = (res.stderr or "").strip()
|
||||||
|
if not ok:
|
||||||
# Fallback to kubectl config use-context
|
# Fallback to kubectl config use-context
|
||||||
subprocess.run(
|
res2 = subprocess.run(
|
||||||
["kubectl", "config", "use-context", context_name],
|
["kubectl", "config", "use-context", context_name],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
env=env
|
env=env
|
||||||
)
|
)
|
||||||
|
ok = res2.returncode == 0
|
||||||
|
if not ok:
|
||||||
|
err2 = (res2.stderr or "").strip()
|
||||||
|
err = err2 or err
|
||||||
|
|
||||||
# Update the UI dropdown to match (but don't trigger re-select)
|
# Update the UI dropdown to match (but don't trigger re-select)
|
||||||
if hasattr(self, "selected_kubectx") and self.selected_kubectx.get() != context_name:
|
if (
|
||||||
|
ok
|
||||||
|
and hasattr(self, "selected_kubectx")
|
||||||
|
and self.selected_kubectx.get() != context_name
|
||||||
|
):
|
||||||
|
try:
|
||||||
# Check if it's in the current values list
|
# Check if it's in the current values list
|
||||||
|
current_values = []
|
||||||
|
if hasattr(self, "kubectx_list") and self.kubectx_list is not None:
|
||||||
current_values = list(self.kubectx_list.get() or [])
|
current_values = list(self.kubectx_list.get() or [])
|
||||||
if context_name in current_values:
|
if not current_values:
|
||||||
|
current_values = self._get_kubectx_list()
|
||||||
|
if context_name in (current_values or []):
|
||||||
self.selected_kubectx.set(context_name)
|
self.selected_kubectx.set(context_name)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if not ok:
|
||||||
|
try:
|
||||||
|
messagebox.showerror(
|
||||||
|
"Kubernetes Context",
|
||||||
|
f"Failed to switch to context '{context_name}'.\n\n{err or 'Unknown error'}",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return ok
|
||||||
|
except Exception as e:
|
||||||
|
try:
|
||||||
|
messagebox.showerror(
|
||||||
|
"Kubernetes Context",
|
||||||
|
f"Failed to switch to context '{context_name}': {e}",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _current_kubectl_context(self) -> str:
|
||||||
|
kc = _find_kubeconfig_file()
|
||||||
|
env = os.environ.copy()
|
||||||
|
if kc:
|
||||||
|
env["KUBECONFIG"] = kc
|
||||||
|
try:
|
||||||
|
res = subprocess.run(
|
||||||
|
["kubectl", "config", "current-context"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
if res.returncode == 0:
|
||||||
|
return (res.stdout or "").strip()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return ""
|
||||||
|
|
||||||
def _get_kubectx_list(self) -> list[str]:
|
def _get_kubectx_list(self) -> list[str]:
|
||||||
"""Get list of kubernetes contexts."""
|
"""Get list of kubernetes contexts."""
|
||||||
kc = _find_kubeconfig_file()
|
kc = _find_kubeconfig_file()
|
||||||
@ -1197,6 +1317,15 @@ class ClusterScreenMixin:
|
|||||||
def _validate_and_save_cluster_config(self) -> bool:
|
def _validate_and_save_cluster_config(self) -> bool:
|
||||||
"""Verify the config then write the values to prole.cfg."""
|
"""Verify the config then write the values to prole.cfg."""
|
||||||
env_key = self._cluster_env_key()
|
env_key = self._cluster_env_key()
|
||||||
|
prev_service_ns = ""
|
||||||
|
try:
|
||||||
|
prev_service_ns = (
|
||||||
|
(self.prole_cfg_data.get("Global", {}) or {})
|
||||||
|
.get("SERVICE_NAMESPACE", "")
|
||||||
|
.strip()
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
prev_service_ns = ""
|
||||||
if env_key == "dev":
|
if env_key == "dev":
|
||||||
cluster_val = self.selected_k3d_cluster.get() or "knoe-dev-cluster"
|
cluster_val = self.selected_k3d_cluster.get() or "knoe-dev-cluster"
|
||||||
if not cluster_val:
|
if not cluster_val:
|
||||||
@ -1215,13 +1344,18 @@ class ClusterScreenMixin:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
elif env_key == "service":
|
elif env_key == "service":
|
||||||
if not (self.k3s_server_url.get() or "").strip():
|
# Service deployments can authenticate via an existing kubeconfig/context
|
||||||
messagebox.showwarning(
|
# (preferred), or via a token-based generated kubeconfig.
|
||||||
"Validation", "Please specify the K3s server URL."
|
kubeconfig = _find_kubeconfig_file()
|
||||||
|
has_token_flow = bool((self.k3s_server_url.get() or "").strip()) and bool(
|
||||||
|
(self.k3s_token.get() or "").strip()
|
||||||
|
)
|
||||||
|
if not kubeconfig and not has_token_flow:
|
||||||
|
messagebox.showwarning(
|
||||||
|
"Validation",
|
||||||
|
"Missing cluster credentials. Please select a working Kubernetes context (kubectx) "
|
||||||
|
"or provide K3s server URL + token.",
|
||||||
)
|
)
|
||||||
return False
|
|
||||||
if not (self.k3s_token.get() or "").strip():
|
|
||||||
messagebox.showwarning("Validation", "Please specify the K3s token.")
|
|
||||||
return False
|
return False
|
||||||
cluster_val = self.cluster_env.get()
|
cluster_val = self.cluster_env.get()
|
||||||
elif env_key == "prod":
|
elif env_key == "prod":
|
||||||
@ -1255,6 +1389,7 @@ class ClusterScreenMixin:
|
|||||||
self.prole_cfg_data["Global"][
|
self.prole_cfg_data["Global"][
|
||||||
"SERVICE_NAMESPACE"
|
"SERVICE_NAMESPACE"
|
||||||
] = self._get_service_namespace()
|
] = self._get_service_namespace()
|
||||||
|
new_service_ns = self.prole_cfg_data["Global"]["SERVICE_NAMESPACE"]
|
||||||
self.prole_cfg_data["Optional Features"]["SUPABASE_ENABLED"] = str(
|
self.prole_cfg_data["Optional Features"]["SUPABASE_ENABLED"] = str(
|
||||||
self.supabase_enabled.get()
|
self.supabase_enabled.get()
|
||||||
)
|
)
|
||||||
@ -1275,6 +1410,62 @@ class ClusterScreenMixin:
|
|||||||
|
|
||||||
# Save config
|
# Save config
|
||||||
self._save_prole_cfg()
|
self._save_prole_cfg()
|
||||||
|
|
||||||
|
# Namespace change cleanup (prevents collisions when switching namespaces).
|
||||||
|
try:
|
||||||
|
prev = (prev_service_ns or "").strip()
|
||||||
|
new = (new_service_ns or "").strip()
|
||||||
|
if prev and new and prev != new:
|
||||||
|
do_cleanup = False
|
||||||
|
try:
|
||||||
|
do_cleanup = messagebox.askyesno(
|
||||||
|
"Namespace Changed",
|
||||||
|
(
|
||||||
|
f"Common Core Services namespace changed from '{prev}' to '{new}'.\n\n"
|
||||||
|
f"Reset the OLD namespace '{prev}' to avoid resource collisions?\n\n"
|
||||||
|
"This deletes workloads/services in that namespace (PVCs are preserved)."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
do_cleanup = False
|
||||||
|
|
||||||
|
if do_cleanup:
|
||||||
|
|
||||||
|
def _cleanup_worker():
|
||||||
|
try:
|
||||||
|
self._set_cluster_env_message(
|
||||||
|
f"Resetting old namespace: {prev}",
|
||||||
|
"#ff9500",
|
||||||
|
clear_after_ms=3000,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
self._ensure_k3s_kubeconfig_merged()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
server, token = ("", "")
|
||||||
|
try:
|
||||||
|
server, token = self._k3s_connection_info()
|
||||||
|
except Exception:
|
||||||
|
server, token = ("", "")
|
||||||
|
controller = getattr(self, "controller", None)
|
||||||
|
project_root = getattr(controller, "project_root", None)
|
||||||
|
if project_root:
|
||||||
|
_reset_k3s_namespace(project_root, prev, server, token)
|
||||||
|
self._set_cluster_env_message(
|
||||||
|
f"Old namespace reset complete: {prev}",
|
||||||
|
"#34c759",
|
||||||
|
clear_after_ms=4000,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self._set_cluster_env_message(
|
||||||
|
f"Namespace reset failed: {e}",
|
||||||
|
"#ff3b30",
|
||||||
|
clear_after_ms=6000,
|
||||||
|
)
|
||||||
|
|
||||||
|
threading.Thread(target=_cleanup_worker, daemon=True).start()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _cluster_status_snapshot(self) -> dict:
|
def _cluster_status_snapshot(self) -> dict:
|
||||||
|
|||||||
@ -1594,7 +1594,7 @@ class ServicesScreenMixin:
|
|||||||
# Pre-pull dependent images into local registry before initialization
|
# Pre-pull dependent images into local registry before initialization
|
||||||
if mode == "k3s":
|
if mode == "k3s":
|
||||||
console.write(
|
console.write(
|
||||||
"[INFO] Skipping image pre-pull in k3s mode (images are handled by k3s/common-services import).\n"
|
"[INFO] Skipping image pre-pull in k3s mode (images are handled by the in-cluster registry/import).\n"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -48,7 +48,7 @@ fi
|
|||||||
|
|
||||||
ACTION="$COMMON_CORE_ACTION"
|
ACTION="$COMMON_CORE_ACTION"
|
||||||
# cert-manager always runs in its own dedicated namespace; never follows the
|
# cert-manager always runs in its own dedicated namespace; never follows the
|
||||||
# common-services namespace override.
|
# Registry/service namespace override.
|
||||||
CERTMGR_NAMESPACE="cert-manager"
|
CERTMGR_NAMESPACE="cert-manager"
|
||||||
common_core_apply_namespace "$CERTMGR_NAMESPACE"
|
common_core_apply_namespace "$CERTMGR_NAMESPACE"
|
||||||
|
|
||||||
|
|||||||
@ -86,7 +86,7 @@ if [ -z "$NS" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
ARGOCD_NS="${ARGOCD_NAMESPACE:-argocd}"
|
ARGOCD_NS="${ARGOCD_NAMESPACE:-argocd}"
|
||||||
REGISTRY_NS="${REGISTRY_NAMESPACE:-${COMMON_SERVICES_NAMESPACE:-common-services}}"
|
REGISTRY_NS="${REGISTRY_NAMESPACE:-${NS}}"
|
||||||
|
|
||||||
prole_ensure_kubeconfig >/dev/null 2>&1 || true
|
prole_ensure_kubeconfig >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
|||||||
@ -263,7 +263,7 @@ resolve_prole_kdc_defaults() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ -z "$PROLE_KDC_REGISTRY_INTERNAL" ]]; then
|
if [[ -z "$PROLE_KDC_REGISTRY_INTERNAL" ]]; then
|
||||||
local reg_ns="${REGISTRY_NAMESPACE:-${COMMON_SERVICES_NAMESPACE:-common-services}}"
|
local reg_ns="${REGISTRY_NAMESPACE:-${SERVICE_NAMESPACE:-${NAMESPACE:-default}}}"
|
||||||
PROLE_KDC_REGISTRY_INTERNAL="registry.${reg_ns}.svc.cluster.local:5000"
|
PROLE_KDC_REGISTRY_INTERNAL="registry.${reg_ns}.svc.cluster.local:5000"
|
||||||
unset reg_ns
|
unset reg_ns
|
||||||
fi
|
fi
|
||||||
|
|||||||
155
tests/etc/test_init_kdc_k3s_registry_default.sh
Normal file
155
tests/etc/test_init_kdc_k3s_registry_default.sh
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Regression test: etc/init_kdc.sh should treat local registry as enabled by default in k3s mode
|
||||||
|
# (matching etc/prole_cfg.sh defaults), so it must not fail with “No registry host configured for k3s”.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
|
||||||
|
SOURCE_ETC_DIR="$PROLE_HOME/etc"
|
||||||
|
|
||||||
|
TMP_DIR=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||||
|
|
||||||
|
WORK_DIR="$TMP_DIR/work"
|
||||||
|
BIN_DIR="$TMP_DIR/bin"
|
||||||
|
mkdir -p "$WORK_DIR/etc" "$WORK_DIR/conf" "$WORK_DIR/authority" "$BIN_DIR"
|
||||||
|
|
||||||
|
cp "$SOURCE_ETC_DIR/init_kdc.sh" "$WORK_DIR/etc/init_kdc.sh"
|
||||||
|
cp "$SOURCE_ETC_DIR/prole_cfg.sh" "$WORK_DIR/etc/prole_cfg.sh"
|
||||||
|
chmod +x "$WORK_DIR/etc/init_kdc.sh"
|
||||||
|
|
||||||
|
cat <<EOF > "$WORK_DIR/env.sh"
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
export PROLE_HOME="$WORK_DIR"
|
||||||
|
export PROLE_CONF="$WORK_DIR/conf"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF > "$WORK_DIR/conf/prole.cfg"
|
||||||
|
[globals]
|
||||||
|
prole.home = $WORK_DIR
|
||||||
|
prole.mode = k3s
|
||||||
|
NAMESPACE = prole-db-a0001
|
||||||
|
EOF
|
||||||
|
|
||||||
|
export KUBECTL_LOG="$TMP_DIR/kubectl.log"
|
||||||
|
export DOCKER_LOG="$TMP_DIR/docker.log"
|
||||||
|
|
||||||
|
cat <<'EOF' > "$BIN_DIR/kubectl"
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
args=("$@")
|
||||||
|
printf '%s\n' "${args[*]}" >> "${KUBECTL_LOG}"
|
||||||
|
|
||||||
|
sub="${1:-}"
|
||||||
|
case "$sub" in
|
||||||
|
config)
|
||||||
|
case "${2:-}" in
|
||||||
|
current-context)
|
||||||
|
printf '%s' "docker-desktop"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
use-context|get-contexts)
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
get)
|
||||||
|
# For namespace existence checks, succeed.
|
||||||
|
if [[ "${2:-}" == "namespace" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# For deployment checks, report missing to exercise the deploy-time timeout path.
|
||||||
|
if [[ "${2:-}" == "deploy" || "${2:-}" == "deployment" ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
create)
|
||||||
|
# init_kdc.sh pipes YAML from create-secret into kubectl apply.
|
||||||
|
if [[ "${2:-}" == "secret" ]]; then
|
||||||
|
cat <<YAML
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: dummy
|
||||||
|
YAML
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [[ "${2:-}" == "namespace" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
apply)
|
||||||
|
cat >/dev/null || true
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
rollout|wait|exec|delete|describe|logs|patch)
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
EOF
|
||||||
|
chmod +x "$BIN_DIR/kubectl"
|
||||||
|
|
||||||
|
cat <<'EOF' > "$BIN_DIR/docker"
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
args=("$@")
|
||||||
|
printf '%s\n' "${args[*]}" >> "${DOCKER_LOG}"
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
info|build|tag|push)
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
chmod +x "$BIN_DIR/docker"
|
||||||
|
|
||||||
|
export PATH="$BIN_DIR:$PATH"
|
||||||
|
|
||||||
|
export PROLE_HOME="$WORK_DIR"
|
||||||
|
export PROLE_CONF="$WORK_DIR/conf"
|
||||||
|
export PROLE_MODE="k3s"
|
||||||
|
|
||||||
|
# Avoid secret generation paths (python/openssl) by providing explicit values.
|
||||||
|
export PROLE_KDC_ADMIN_PASSWORD="test-admin"
|
||||||
|
export PROLE_KDC_MASTER_PASSWORD="test-master"
|
||||||
|
export PROLE_KDC_TRUST_PASSWORD="test-trust"
|
||||||
|
export PROLE_KDC_TRUST_SHARED_PASSWORD="test-shared"
|
||||||
|
|
||||||
|
if "$WORK_DIR/etc/init_kdc.sh" update >"$TMP_DIR/stdout" 2>"$TMP_DIR/stderr"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
echo "FAILURE: init_kdc.sh update exited non-zero"
|
||||||
|
cat "$TMP_DIR/stderr"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "No registry host configured for k3s" "$TMP_DIR/stderr"; then
|
||||||
|
echo "FAILURE: unexpected missing-registry-host error for k3s mode"
|
||||||
|
cat "$TMP_DIR/stderr"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "push" "$DOCKER_LOG"; then
|
||||||
|
echo "FAILURE: expected docker push to be attempted"
|
||||||
|
cat "$DOCKER_LOG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "push localhost:5000/prole-authority:latest" "$DOCKER_LOG"; then
|
||||||
|
echo "FAILURE: expected docker push to default localhost registry in k3s mode"
|
||||||
|
cat "$DOCKER_LOG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "SUCCESS"
|
||||||
@ -324,10 +324,12 @@ class TestNamespaceHelpers:
|
|||||||
assert inst._registry_namespace() == "env-reg"
|
assert inst._registry_namespace() == "env-reg"
|
||||||
|
|
||||||
def test_registry_default(self):
|
def test_registry_default(self):
|
||||||
inst = _TestableSilentInstaller()
|
# Registry is a common-core service; default should follow SERVICE_NAMESPACE.
|
||||||
|
# For k3s/service clusters, SERVICE_NAMESPACE defaults to knoe-system.
|
||||||
|
inst = _TestableSilentInstaller(inputs={"init_cluster.cluster_env": "service"})
|
||||||
with mock.patch.dict(os.environ, {}, clear=False):
|
with mock.patch.dict(os.environ, {}, clear=False):
|
||||||
os.environ.pop("REGISTRY_NAMESPACE", None)
|
os.environ.pop("REGISTRY_NAMESPACE", None)
|
||||||
assert inst._registry_namespace() == "common-services"
|
assert inst._registry_namespace() == "knoe-system"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -27,7 +27,7 @@ class _DummyClusterApp(ScreenBaseMixin, ClusterScreenMixin):
|
|||||||
self.cluster_env = tk.StringVar(master=root, value="prod")
|
self.cluster_env = tk.StringVar(master=root, value="prod")
|
||||||
self.selected_k3d_cluster = tk.StringVar(master=root, value="")
|
self.selected_k3d_cluster = tk.StringVar(master=root, value="")
|
||||||
self.selected_kubectx = tk.StringVar(master=root, value="")
|
self.selected_kubectx = tk.StringVar(master=root, value="")
|
||||||
self.service_namespace = tk.StringVar(master=root, value="common-services")
|
self.service_namespace = tk.StringVar(master=root, value="knoe-system")
|
||||||
self.prod_artifacts_path = tk.StringVar(master=root, value="/tmp")
|
self.prod_artifacts_path = tk.StringVar(master=root, value="/tmp")
|
||||||
|
|
||||||
# Legacy variables still referenced by non-UI logic elsewhere
|
# Legacy variables still referenced by non-UI logic elsewhere
|
||||||
@ -35,6 +35,8 @@ class _DummyClusterApp(ScreenBaseMixin, ClusterScreenMixin):
|
|||||||
self.k3s_token = tk.StringVar(master=root, value="")
|
self.k3s_token = tk.StringVar(master=root, value="")
|
||||||
|
|
||||||
self._kubectx_values = kubectx_values
|
self._kubectx_values = kubectx_values
|
||||||
|
# Avoid invoking real `kubectl` during layout tests.
|
||||||
|
self._kubectx_applied = kubectx_values[0] if kubectx_values else ""
|
||||||
|
|
||||||
# ---- Stubs for callbacks / helpers invoked by rendering ----
|
# ---- Stubs for callbacks / helpers invoked by rendering ----
|
||||||
def _on_cluster_env_change(self, *_args):
|
def _on_cluster_env_change(self, *_args):
|
||||||
@ -56,7 +58,10 @@ class _DummyClusterApp(ScreenBaseMixin, ClusterScreenMixin):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _switch_kubectx(self, _context_name: str):
|
def _switch_kubectx(self, _context_name: str):
|
||||||
return None
|
return True
|
||||||
|
|
||||||
|
def _current_kubectl_context(self) -> str:
|
||||||
|
return self._kubectx_applied
|
||||||
|
|
||||||
def _get_kubectx_list(self) -> list[str]:
|
def _get_kubectx_list(self) -> list[str]:
|
||||||
return list(self._kubectx_values)
|
return list(self._kubectx_values)
|
||||||
@ -149,3 +154,80 @@ def test_cluster_environment_layout_large_mock_data(env: str):
|
|||||||
assert app._k3s_service_status_console.text.cget("state") == "disabled"
|
assert app._k3s_service_status_console.text.cget("state") == "disabled"
|
||||||
finally:
|
finally:
|
||||||
root.destroy()
|
root.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
def test_kubectx_selection_requires_apply():
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
try:
|
||||||
|
canvas = tk.Canvas(root, width=975, height=780, bg="white")
|
||||||
|
canvas.pack(fill="both", expand=False)
|
||||||
|
root.update()
|
||||||
|
|
||||||
|
class _KubectxApplyApp(ScreenBaseMixin, ClusterScreenMixin):
|
||||||
|
def __init__(self, root: tk.Tk, canvas: tk.Canvas):
|
||||||
|
self.root = root
|
||||||
|
self.bg_canvas = canvas
|
||||||
|
self._canvas_items: list[int] = []
|
||||||
|
self._overlay_widgets: list[tk.Widget] = []
|
||||||
|
|
||||||
|
self.cluster_env = tk.StringVar(master=root, value="prod")
|
||||||
|
self.selected_k3d_cluster = tk.StringVar(master=root, value="")
|
||||||
|
self.selected_kubectx = tk.StringVar(master=root, value="ctx-000")
|
||||||
|
self.service_namespace = tk.StringVar(master=root, value="knoe-system")
|
||||||
|
self.prod_artifacts_path = tk.StringVar(master=root, value="/tmp")
|
||||||
|
self.k3s_server_url = tk.StringVar(master=root, value="")
|
||||||
|
self.k3s_token = tk.StringVar(master=root, value="")
|
||||||
|
|
||||||
|
self._kubectx_applied = "ctx-000"
|
||||||
|
self._switch_calls: list[str] = []
|
||||||
|
|
||||||
|
def _on_cluster_env_change(self, *_args):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_k3d_cluster_list(self) -> list[str]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _on_create_k3d_cluster(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _on_delete_k3d_cluster(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _on_k3d_cluster_select(self, *_args):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_kubectx_list(self) -> list[str]:
|
||||||
|
return ["ctx-000", "ctx-001"]
|
||||||
|
|
||||||
|
def _switch_kubectx(self, context_name: str) -> bool:
|
||||||
|
self._switch_calls.append(context_name)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _verify_k3s_services(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def show_page(self, _page: str):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _validate_and_save_cluster_config(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
app = _KubectxApplyApp(root, canvas)
|
||||||
|
app._render_init_cluster_page()
|
||||||
|
root.update()
|
||||||
|
|
||||||
|
# No pending change initially.
|
||||||
|
assert app._kubectx_apply_btn.cget("state") == "disabled"
|
||||||
|
|
||||||
|
# Selecting a different context should NOT switch immediately.
|
||||||
|
app.selected_kubectx.set("ctx-001")
|
||||||
|
app._on_kubectx_select()
|
||||||
|
assert app._switch_calls == []
|
||||||
|
assert app._kubectx_apply_btn.cget("state") == "normal"
|
||||||
|
|
||||||
|
# Apply triggers the switch.
|
||||||
|
app._on_kubectx_apply()
|
||||||
|
assert app._switch_calls == ["ctx-001"]
|
||||||
|
finally:
|
||||||
|
root.destroy()
|
||||||
|
|||||||
39
tests/installer/test_verify_k3s_services_status.py
Normal file
39
tests/installer/test_verify_k3s_services_status.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
# Ensure `installer` is importable when tests are invoked directly via `pytest`
|
||||||
|
# (the project also provides `tests/run_tests.sh` which sets `PYTHONPATH=.`).
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
if str(PROJECT_ROOT) not in sys.path:
|
||||||
|
sys.path.insert(0, str(PROJECT_ROOT))
|
||||||
|
|
||||||
|
|
||||||
|
from installer.core import env as core_env
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_k3s_services_status_does_not_require_token_when_kubeconfig_available(
|
||||||
|
monkeypatch, tmp_path
|
||||||
|
):
|
||||||
|
kubeconfig = tmp_path / "kubeconfig"
|
||||||
|
kubeconfig.write_text("dummy")
|
||||||
|
|
||||||
|
def fake_run(cmd, capture_output=True, text=True, timeout=None, env=None):
|
||||||
|
return subprocess.CompletedProcess(
|
||||||
|
args=cmd,
|
||||||
|
returncode=0,
|
||||||
|
stdout="registry\nopenbao\nopentofu\n",
|
||||||
|
stderr="",
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr(core_env.subprocess, "run", fake_run)
|
||||||
|
|
||||||
|
status = core_env._verify_k3s_services_status(
|
||||||
|
project_root=PROJECT_ROOT,
|
||||||
|
managed_kubeconfig=str(kubeconfig),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert status["registry"] == "Good"
|
||||||
|
assert status["openbao"] == "Good"
|
||||||
|
assert status["opentofu"] == "Good"
|
||||||
13
tests/test_no_common_services_literal_in_etc_scripts.py
Normal file
13
tests/test_no_common_services_literal_in_etc_scripts.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_common_services_literal_in_etc_init_scripts():
|
||||||
|
"""Regression: registry namespace must be driven by config; do not hardcode `common-services`."""
|
||||||
|
|
||||||
|
init_scripts = sorted(Path("etc").glob("init_*.sh"))
|
||||||
|
assert init_scripts, "expected init_*.sh scripts under etc/"
|
||||||
|
for p in init_scripts:
|
||||||
|
text = p.read_text(encoding="utf-8")
|
||||||
|
assert "common-services" not in text, f"{p} hardcodes common-services"
|
||||||
Loading…
Reference in New Issue
Block a user