feat: move D601 HWLAB GitOps to UniDesk YAML

This commit is contained in:
Codex
2026-06-12 21:06:57 +00:00
parent 17b54c685a
commit d1448cf583
10 changed files with 3175 additions and 100 deletions
+96 -38
View File
@@ -242,6 +242,8 @@ interface RemoteFacts {
logDirExists: boolean;
roleExists: boolean;
databaseExists: boolean;
roleExistsByName?: Record<string, boolean>;
databaseExistsByName?: Record<string, boolean>;
serverVersion: string | null;
sslOn: boolean;
sslCertFile: string | null;
@@ -383,12 +385,14 @@ async function status(config: UniDeskConfig, options: PlatformDbOptions): Promis
const facts = remote.parsed;
const controllerConnection = controllerConnectionProbe(pg, secrets);
const endpointHealthy = controllerConnection.ok === true && controllerConnection.ssl === true;
const rolesReady = facts !== null && pg.objects.roles.every((role, index) => facts.postgres.roleExistsByName?.[role.name] ?? (index === 0 ? facts.postgres.roleExists : false));
const databasesReady = facts !== null && pg.objects.databases.every((database, index) => facts.postgres.databaseExistsByName?.[database.name] ?? (index === 0 ? facts.postgres.databaseExists : false));
const deploymentHealthy = facts !== null
&& facts.postgres.packageInstalled
&& facts.postgres.serviceActive
&& facts.postgres.sslOn
&& facts.postgres.roleExists
&& facts.postgres.databaseExists
&& rolesReady
&& databasesReady
&& facts.network.port5432Listening
&& facts.postgres.appConnectionOk === true
&& facts.postgres.appConnectionSsl === true;
@@ -421,6 +425,10 @@ async function status(config: UniDeskConfig, options: PlatformDbOptions): Promis
dnsAddresses: facts.network.dns.addresses,
roleExists: facts.postgres.roleExists,
databaseExists: facts.postgres.databaseExists,
roleExistsByName: facts.postgres.roleExistsByName ?? {},
databaseExistsByName: facts.postgres.databaseExistsByName ?? {},
rolesReady,
databasesReady,
appConnectionOk: facts.postgres.appConnectionOk,
appConnectionSsl: facts.postgres.appConnectionSsl,
appConnectionHost: facts.postgres.appConnectionHost,
@@ -548,7 +556,7 @@ function readPostgresHostConfig(pathArg: string): PostgresHostConfig {
const entries = arrayOfRecords(secrets.entries, `${configPath}.secrets.entries`).map((entry, index) => secretEntry(entry, `${configPath}.secrets.entries[${index}]`));
const roles = arrayOfRecords(objects.roles, `${configPath}.objects.roles`).map((role, index) => roleConfig(role, `${configPath}.objects.roles[${index}]`));
const databases = arrayOfRecords(objects.databases, `${configPath}.objects.databases`).map((database, index) => databaseConfig(database, `${configPath}.objects.databases[${index}]`));
if (roles.length !== 1 || databases.length !== 1) throw new Error(`${configPath}.objects must declare exactly one role and one database for the first PK01 Sub2API rollout`);
if (roles.length === 0 || databases.length === 0) throw new Error(`${configPath}.objects.roles/databases must each contain at least one item`);
const connectionStrings = arrayOfRecords(exportsConfig.connectionStrings, `${configPath}.exports.connectionStrings`).map((item, index) => connectionStringExport(item, `${configPath}.exports.connectionStrings[${index}]`));
return {
configPath: relativeConfigPath(configPath),
@@ -849,7 +857,7 @@ function connectionStringExport(item: Record<string, unknown>, path: string): Co
consumers: arrayOfRecords(item.consumers, `${path}.consumers`).map((consumer, index) => ({
scope: stringField(consumer, "scope", `${path}.consumers[${index}]`),
secret: stringField(consumer, "secret", `${path}.consumers[${index}]`),
key: envKey(stringField(consumer, "key", `${path}.consumers[${index}]`), `${path}.consumers[${index}].key`),
key: kubernetesSecretKey(stringField(consumer, "key", `${path}.consumers[${index}]`), `${path}.consumers[${index}].key`),
})),
};
}
@@ -880,6 +888,11 @@ function envKey(value: string, path: string): string {
return value;
}
function kubernetesSecretKey(value: string, path: string): string {
if (!/^[A-Za-z0-9._-]+$/u.test(value)) throw new Error(`${path} must be a Kubernetes Secret key`);
return value;
}
function configSummary(pg: PostgresHostConfig): Record<string, unknown> {
return {
path: pg.configPath,
@@ -994,8 +1007,8 @@ function validateSub2ApiSecretConsistency(pg: PostgresHostConfig, inspection: Se
const database = pg.objects.databases[0];
const material = inspection.materials.get(role.passwordRef.sourceRef);
if (material === undefined) throw new Error(`secret source not found for role passwordRef: ${role.passwordRef.sourceRef}`);
if (material.values.SUB2API_DB_USER !== role.name) throw new Error("SUB2API_DB_USER must match YAML objects.roles[0].name");
if (material.values.SUB2API_DB_NAME !== database.name) throw new Error("SUB2API_DB_NAME must match YAML objects.databases[0].name");
if (material.values.SUB2API_DB_USER !== undefined && material.values.SUB2API_DB_USER !== role.name) throw new Error("SUB2API_DB_USER must match YAML objects.roles[0].name");
if (material.values.SUB2API_DB_NAME !== undefined && material.values.SUB2API_DB_NAME !== database.name) throw new Error("SUB2API_DB_NAME must match YAML objects.databases[0].name");
}
function writeConnectionStringExport(pg: PostgresHostConfig, inspection: SecretInspection, item: ConnectionStringExportConfig): Record<string, unknown> {
@@ -1093,8 +1106,8 @@ function secretSummary(secrets: SecretInspection): Record<string, unknown> {
}
function desiredSummary(pg: PostgresHostConfig, secrets: SecretInspection, facts: RemoteFacts | null): Record<string, unknown> {
const role = pg.objects.roles[0];
const database = pg.objects.databases[0];
const roleExistsByName = facts?.postgres.roleExistsByName ?? {};
const databaseExistsByName = facts?.postgres.databaseExistsByName ?? {};
return {
package: {
name: `postgresql-${pg.postgres.package.version}`,
@@ -1131,8 +1144,15 @@ function desiredSummary(pg: PostgresHostConfig, secrets: SecretInspection, facts
},
pgHbaManagedBlock: { start: managedHbaStart, end: managedHbaEnd, rules: pg.postgres.auth.pgHba.length },
pgHbaRemoteTransport: "hostssl",
role: { name: role.name, action: facts?.postgres.roleExists ? "none" : "create-or-update" },
database: { name: database.name, owner: database.owner, action: facts?.postgres.databaseExists ? "none" : "create" },
roles: pg.objects.roles.map((role, index) => ({
name: role.name,
action: (facts === null ? false : roleExistsByName[role.name] ?? (index === 0 ? facts.postgres.roleExists : false)) ? "none" : "create-or-update",
})),
databases: pg.objects.databases.map((database, index) => ({
name: database.name,
owner: database.owner,
action: (facts === null ? false : databaseExistsByName[database.name] ?? (index === 0 ? facts.postgres.databaseExists : false)) ? "none" : "create",
})),
},
secrets: secretSummary(secrets),
exports: pg.exports.connectionStrings.map((item) => ({
@@ -1234,6 +1254,8 @@ function factsScript(pg: PostgresHostConfig, probe: { user: string; password: st
const logDir = pg.postgres.paths.logDir;
const role = pg.objects.roles[0].name;
const database = pg.objects.databases[0].name;
const roleSqlList = pg.objects.roles.map((item) => `'${item.name.replace(/'/gu, "''")}'`).join(",");
const databaseSqlList = pg.objects.databases.map((item) => `'${item.name.replace(/'/gu, "''")}'`).join(",");
const dnsHost = pg.postgres.network.publicDns;
const appHost = pg.postgres.network.listenAddresses.find((item) => item !== "127.0.0.1" && item !== "0.0.0.0") ?? "127.0.0.1";
const probeEnv = probe === null
@@ -1285,6 +1307,8 @@ if command -v runuser >/dev/null 2>&1 && command -v psql >/dev/null 2>&1 && id p
$root_prefix runuser -u postgres -- psql -Atqc "SHOW ssl_key_file;" >"$tmp/sslKeyFile" 2>/dev/null
$root_prefix runuser -u postgres -- psql -Atqc "SELECT 1 FROM pg_roles WHERE rolname='${role}'" >"$tmp/roleExists" 2>/dev/null
$root_prefix runuser -u postgres -- psql -Atqc "SELECT 1 FROM pg_database WHERE datname='${database}'" >"$tmp/databaseExists" 2>/dev/null
$root_prefix runuser -u postgres -- psql -Atqc "SELECT rolname FROM pg_roles WHERE rolname IN (${roleSqlList})" >"$tmp/rolesExisting" 2>/dev/null
$root_prefix runuser -u postgres -- psql -Atqc "SELECT datname FROM pg_database WHERE datname IN (${databaseSqlList})" >"$tmp/databasesExisting" 2>/dev/null
fi
fi
if [ -n "\${APP_PROBE_PASSWORD:-}" ] && command -v psql >/dev/null 2>&1; then
@@ -1308,6 +1332,12 @@ def int_or_none(name):
return None
def yes_file(name, expected="0"):
return text(name) == expected
def set_lines(name):
return {line.strip() for line in text(name).splitlines() if line.strip()}
expected_roles = ${JSON.stringify(pg.objects.roles.map((item) => item.name))}
expected_databases = ${JSON.stringify(pg.objects.databases.map((item) => item.name))}
existing_roles = set_lines("rolesExisting")
existing_databases = set_lines("databasesExisting")
release_rc = text("releaserc")
payload = {
"ok": True,
@@ -1349,6 +1379,8 @@ payload = {
"logDirExists": yes_file("logDirRc"),
"roleExists": text("roleExists") == "1",
"databaseExists": text("databaseExists") == "1",
"roleExistsByName": {name: name in existing_roles for name in expected_roles},
"databaseExistsByName": {name: name in existing_databases for name in expected_databases},
"serverVersion": text("serverVersion") or None,
"sslOn": text("sslOn").lower() in {"on", "true", "1"},
"sslCertFile": text("sslCertFile") or None,
@@ -1386,10 +1418,18 @@ async function startRemoteApplyJob(config: UniDeskConfig, pg: PostgresHostConfig
function remoteApplyPayload(pg: PostgresHostConfig, secrets: SecretInspection): Record<string, unknown> {
const role = pg.objects.roles[0];
const database = pg.objects.databases[0];
const material = secrets.materials.get(role.passwordRef.sourceRef);
if (material === undefined) throw new Error(`missing material for ${role.passwordRef.sourceRef}`);
const dbPassword = material.values[role.passwordRef.key];
if (dbPassword === undefined || dbPassword.length === 0) throw new Error(`missing ${role.passwordRef.key}`);
const roles = pg.objects.roles.map((item) => {
const material = secrets.materials.get(item.passwordRef.sourceRef);
if (material === undefined) throw new Error(`missing material for ${item.passwordRef.sourceRef}`);
const password = material.values[item.passwordRef.key];
if (password === undefined || password.length === 0) throw new Error(`missing ${item.passwordRef.key}`);
return {
name: item.name,
password,
login: item.login,
attributes: item.attributes,
};
});
return {
clusterId: pg.metadata.id,
pgVersion: pg.postgres.package.version,
@@ -1404,11 +1444,13 @@ function remoteApplyPayload(pg: PostgresHostConfig, secrets: SecretInspection):
passwordEncryption: pg.postgres.auth.passwordEncryption,
role: {
name: role.name,
password: dbPassword,
password: roles[0].password,
login: role.login,
attributes: role.attributes,
},
roles,
database,
databases: pg.objects.databases,
backup: pg.backup.logicalDump,
hbaMarkers: { start: managedHbaStart, end: managedHbaEnd },
};
@@ -1556,24 +1598,27 @@ if name == "pg_hba":
print(f'{rule["type"]} {rule["database"]} {rule["user"]} {rule["address"]} {rule["method"]}')
print(markers["end"])
elif name == "sql":
role = data["role"]
name = role["name"]
password = role["password"].replace("'", "''")
attrs = []
attrs.append("LOGIN" if role.get("login") else "NOLOGIN")
attrs.append("CREATEDB" if role["attributes"].get("createdb") else "NOCREATEDB")
attrs.append("CREATEROLE" if role["attributes"].get("createrole") else "NOCREATEROLE")
attrs.append("SUPERUSER" if role["attributes"].get("superuser") else "NOSUPERUSER")
attr_sql = " ".join(attrs)
print("DO $unidesk$")
print("BEGIN")
print(f" IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '{name}') THEN")
print(f" CREATE ROLE {name} {attr_sql} PASSWORD '{password}';")
print(" ELSE")
print(f" ALTER ROLE {name} WITH {attr_sql} PASSWORD '{password}';")
print(" END IF;")
print("END")
print("$unidesk$;")
for role in data.get("roles", [data["role"]]):
name = role["name"]
password = role["password"].replace("'", "''")
attrs = []
attrs.append("LOGIN" if role.get("login") else "NOLOGIN")
attrs.append("CREATEDB" if role["attributes"].get("createdb") else "NOCREATEDB")
attrs.append("CREATEROLE" if role["attributes"].get("createrole") else "NOCREATEROLE")
attrs.append("SUPERUSER" if role["attributes"].get("superuser") else "NOSUPERUSER")
attr_sql = " ".join(attrs)
print("DO $unidesk$")
print("BEGIN")
print(f" IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '{name}') THEN")
print(f" CREATE ROLE {name} {attr_sql} PASSWORD '{password}';")
print(" ELSE")
print(f" ALTER ROLE {name} WITH {attr_sql} PASSWORD '{password}';")
print(" END IF;")
print("END")
print("$unidesk$;")
elif name == "databases":
for db in data.get("databases", [data["database"]]):
print("\t".join([db["name"], db["owner"], db["encoding"], db["locale"]]))
elif name == "backup_script":
backup = data["backup"]
role = data["role"]["name"]
@@ -1704,9 +1749,14 @@ render_file sql > "$sql"
runuser -u postgres -- psql -v ON_ERROR_STOP=1 < "$sql"
write_state running create-database
if ! runuser -u postgres -- psql -Atqc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'" | grep -q 1; then
runuser -u postgres -- createdb -O "$DB_OWNER" -E "$DB_ENCODING" --locale="$DB_LOCALE" --template=template0 "$DB_NAME"
fi
databases_tsv="$job_dir/databases.tsv"
render_file databases > "$databases_tsv"
while IFS=' ' read -r db_name db_owner db_encoding db_locale; do
[ -n "$db_name" ] || continue
if ! runuser -u postgres -- psql -Atqc "SELECT 1 FROM pg_database WHERE datname='$db_name'" | grep -q 1; then
runuser -u postgres -- createdb -O "$db_owner" -E "$db_encoding" --locale="$db_locale" --template=template0 "$db_name"
fi
done < "$databases_tsv"
if [ "$BACKUP_ENABLED" = "true" ]; then
write_state running configure-backup
@@ -1739,8 +1789,16 @@ fi
write_state running final-check
runuser -u postgres -- psql -Atqc "SHOW server_version;" >/dev/null
runuser -u postgres -- psql -Atqc "SELECT 1 FROM pg_roles WHERE rolname='$DB_OWNER'" | grep -q 1
runuser -u postgres -- psql -Atqc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'" | grep -q 1
python3 - "$payload" <<'PY' > "$job_dir/final-check.sql"
import json, sys
data=json.load(open(sys.argv[1], encoding="utf-8"))
for role in data.get("roles", [data["role"]]):
print("SELECT 'role', %r, EXISTS (SELECT 1 FROM pg_roles WHERE rolname = %r);" % (role["name"], role["name"]))
for db in data.get("databases", [data["database"]]):
print("SELECT 'database', %r, EXISTS (SELECT 1 FROM pg_database WHERE datname = %r);" % (db["name"], db["name"]))
PY
chmod 0644 "$job_dir/final-check.sql"
runuser -u postgres -- psql -Atq < "$job_dir/final-check.sql" | awk -F'|' '{ if ($3 != "t") exit 1 }'
write_state succeeded complete 0
trap - EXIT
exit 0