feat(hwlab): bootstrap v02 OpenFGA prerequisites
This commit is contained in:
+395
-12
@@ -32,6 +32,12 @@ const V02_RUNTIME_PATH = "deploy/gitops/g14/runtime-v02";
|
||||
const V02_RUNTIME_NAMESPACE = "hwlab-v02";
|
||||
const V02_DEVICE_POD_API_KEY_SECRET = "hwlab-v02-device-pod-api-key";
|
||||
const V02_DEVICE_POD_API_KEY_SECRET_KEY = "api-key";
|
||||
const V02_OPENFGA_SECRET = "hwlab-v02-openfga";
|
||||
const V02_OPENFGA_AUTHN_SECRET_KEY = "authn-preshared-key";
|
||||
const V02_OPENFGA_DATASTORE_URI_SECRET_KEY = "datastore-uri";
|
||||
const V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY = "postgres-password";
|
||||
const V02_OPENFGA_DB_NAME = "hwlab_openfga";
|
||||
const V02_OPENFGA_DB_USER = "hwlab_openfga";
|
||||
const V02_REGISTRY_PREFIX = "127.0.0.1:5000/hwlab";
|
||||
const V02_BASE_IMAGE = "127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim";
|
||||
const GIT_MIRROR_NAMESPACE = "devops-infra";
|
||||
@@ -131,6 +137,15 @@ interface G14ToolsImageOptions {
|
||||
timeoutSeconds: number;
|
||||
}
|
||||
|
||||
interface G14UpstreamImageOptions {
|
||||
action: "status" | "ensure";
|
||||
name: "openfga";
|
||||
tag: string;
|
||||
dryRun: boolean;
|
||||
confirm: boolean;
|
||||
timeoutSeconds: number;
|
||||
}
|
||||
|
||||
interface G14GitMirrorOptions {
|
||||
action: "status" | "apply" | "sync" | "flush";
|
||||
dryRun: boolean;
|
||||
@@ -153,8 +168,9 @@ interface G14SecretOptions {
|
||||
lane: "v02";
|
||||
dryRun: boolean;
|
||||
confirm: boolean;
|
||||
name: typeof V02_DEVICE_POD_API_KEY_SECRET;
|
||||
key: typeof V02_DEVICE_POD_API_KEY_SECRET_KEY;
|
||||
name: typeof V02_DEVICE_POD_API_KEY_SECRET | typeof V02_OPENFGA_SECRET;
|
||||
key?: typeof V02_DEVICE_POD_API_KEY_SECRET_KEY | typeof V02_OPENFGA_AUTHN_SECRET_KEY | typeof V02_OPENFGA_DATASTORE_URI_SECRET_KEY | typeof V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY;
|
||||
preset: "device-pod-api-key" | "openfga";
|
||||
timeoutSeconds: number;
|
||||
}
|
||||
|
||||
@@ -370,6 +386,28 @@ function parseToolsImageOptions(args: string[]): G14ToolsImageOptions {
|
||||
};
|
||||
}
|
||||
|
||||
function parseUpstreamImageOptions(args: string[]): G14UpstreamImageOptions {
|
||||
const [actionRaw] = args;
|
||||
if (actionRaw !== "status" && actionRaw !== "ensure") {
|
||||
throw new Error("upstream-image usage: status|ensure --name openfga --tag v1.17.0 [--dry-run|--confirm]");
|
||||
}
|
||||
const name = optionValue(args, "--name") ?? "openfga";
|
||||
if (name !== "openfga") throw new Error("upstream-image currently supports --name openfga");
|
||||
const tag = optionValue(args, "--tag") ?? "v1.17.0";
|
||||
if (tag !== "v1.17.0") throw new Error("upstream-image currently supports OpenFGA tag v1.17.0");
|
||||
const confirm = args.includes("--confirm");
|
||||
const explicitDryRun = args.includes("--dry-run");
|
||||
if (confirm && explicitDryRun) throw new Error("upstream-image accepts only one of --confirm or --dry-run");
|
||||
return {
|
||||
action: actionRaw,
|
||||
name,
|
||||
tag,
|
||||
confirm,
|
||||
dryRun: actionRaw === "status" ? true : explicitDryRun || !confirm,
|
||||
timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 600, 1800),
|
||||
};
|
||||
}
|
||||
|
||||
function parseGitMirrorOptions(args: string[]): G14GitMirrorOptions {
|
||||
const [actionRaw] = args;
|
||||
if (actionRaw !== "status" && actionRaw !== "apply" && actionRaw !== "sync" && actionRaw !== "flush") {
|
||||
@@ -411,14 +449,22 @@ function parseObservabilityOptions(args: string[]): G14ObservabilityOptions {
|
||||
function parseSecretOptions(args: string[]): G14SecretOptions {
|
||||
const [actionRaw] = args;
|
||||
if (actionRaw !== "status" && actionRaw !== "ensure") {
|
||||
throw new Error("secret usage: status|ensure --lane v02 --name hwlab-v02-device-pod-api-key --key api-key [--dry-run|--confirm]");
|
||||
throw new Error("secret usage: status|ensure --lane v02 --name hwlab-v02-device-pod-api-key --key api-key | --name hwlab-v02-openfga [--dry-run|--confirm]");
|
||||
}
|
||||
const lane = optionValue(args, "--lane") ?? "v02";
|
||||
if (lane !== "v02") throw new Error("secret currently supports --lane v02");
|
||||
const name = optionValue(args, "--name") ?? V02_DEVICE_POD_API_KEY_SECRET;
|
||||
if (name !== V02_DEVICE_POD_API_KEY_SECRET) throw new Error(`secret currently supports --name ${V02_DEVICE_POD_API_KEY_SECRET}`);
|
||||
const key = optionValue(args, "--key") ?? V02_DEVICE_POD_API_KEY_SECRET_KEY;
|
||||
if (key !== V02_DEVICE_POD_API_KEY_SECRET_KEY) throw new Error(`secret currently supports --key ${V02_DEVICE_POD_API_KEY_SECRET_KEY}`);
|
||||
if (name !== V02_DEVICE_POD_API_KEY_SECRET && name !== V02_OPENFGA_SECRET) {
|
||||
throw new Error(`secret currently supports --name ${V02_DEVICE_POD_API_KEY_SECRET} or ${V02_OPENFGA_SECRET}`);
|
||||
}
|
||||
const key = optionValue(args, "--key");
|
||||
const preset = name === V02_OPENFGA_SECRET ? "openfga" : "device-pod-api-key";
|
||||
if (preset === "device-pod-api-key") {
|
||||
const effectiveKey = key ?? V02_DEVICE_POD_API_KEY_SECRET_KEY;
|
||||
if (effectiveKey !== V02_DEVICE_POD_API_KEY_SECRET_KEY) throw new Error(`secret ${V02_DEVICE_POD_API_KEY_SECRET} supports --key ${V02_DEVICE_POD_API_KEY_SECRET_KEY}`);
|
||||
} else if (key !== undefined && key !== V02_OPENFGA_AUTHN_SECRET_KEY && key !== V02_OPENFGA_DATASTORE_URI_SECRET_KEY && key !== V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY) {
|
||||
throw new Error(`secret ${V02_OPENFGA_SECRET} supports keys ${V02_OPENFGA_AUTHN_SECRET_KEY}, ${V02_OPENFGA_DATASTORE_URI_SECRET_KEY}, and ${V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY}`);
|
||||
}
|
||||
const confirm = args.includes("--confirm");
|
||||
const explicitDryRun = args.includes("--dry-run");
|
||||
if (confirm && explicitDryRun) throw new Error("secret accepts only one of --confirm or --dry-run");
|
||||
@@ -428,7 +474,8 @@ function parseSecretOptions(args: string[]): G14SecretOptions {
|
||||
confirm,
|
||||
dryRun: actionRaw === "status" ? true : explicitDryRun || !confirm,
|
||||
name,
|
||||
key,
|
||||
key: preset === "device-pod-api-key" ? V02_DEVICE_POD_API_KEY_SECRET_KEY : key,
|
||||
preset,
|
||||
timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 120, 600),
|
||||
};
|
||||
}
|
||||
@@ -3193,6 +3240,8 @@ function runV02ControlPlane(options: G14ControlPlaneOptions): Record<string, unk
|
||||
}
|
||||
|
||||
function v02SecretScript(options: G14SecretOptions): string {
|
||||
if (options.preset === "openfga") return v02OpenFgaSecretScript(options);
|
||||
const key = options.key ?? V02_DEVICE_POD_API_KEY_SECRET_KEY;
|
||||
const emitAfterStatus = [
|
||||
"after_exists=$(secret_exists_flag)",
|
||||
"after_b64=$(secret_b64)",
|
||||
@@ -3216,7 +3265,8 @@ function v02SecretScript(options: G14SecretOptions): string {
|
||||
"set +e",
|
||||
`namespace=${shellQuote(V02_RUNTIME_NAMESPACE)}`,
|
||||
`name=${shellQuote(options.name)}`,
|
||||
`key=${shellQuote(options.key)}`,
|
||||
`key=${shellQuote(key)}`,
|
||||
"preset=device-pod-api-key",
|
||||
`action_request=${shellQuote(options.action)}`,
|
||||
`dry_run=${shellQuote(options.dryRun ? "true" : "false")}`,
|
||||
`field_manager=${shellQuote(V02_SECRET_FIELD_MANAGER)}`,
|
||||
@@ -3253,8 +3303,219 @@ function v02SecretScript(options: G14SecretOptions): string {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function v02OpenFgaSecretScript(options: G14SecretOptions): string {
|
||||
const selectedKey = options.key ?? "";
|
||||
return [
|
||||
"set +e",
|
||||
`namespace=${shellQuote(V02_RUNTIME_NAMESPACE)}`,
|
||||
`name=${shellQuote(V02_OPENFGA_SECRET)}`,
|
||||
`selected_key=${shellQuote(selectedKey)}`,
|
||||
`authn_key=${shellQuote(V02_OPENFGA_AUTHN_SECRET_KEY)}`,
|
||||
`datastore_uri_key=${shellQuote(V02_OPENFGA_DATASTORE_URI_SECRET_KEY)}`,
|
||||
`postgres_password_key=${shellQuote(V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY)}`,
|
||||
`db_name=${shellQuote(V02_OPENFGA_DB_NAME)}`,
|
||||
`db_user=${shellQuote(V02_OPENFGA_DB_USER)}`,
|
||||
"db_host=hwlab-v02-postgres.hwlab-v02.svc.cluster.local",
|
||||
`action_request=${shellQuote(options.action)}`,
|
||||
`dry_run=${shellQuote(options.dryRun ? "true" : "false")}`,
|
||||
`field_manager=${shellQuote(V02_SECRET_FIELD_MANAGER)}`,
|
||||
"preset=openfga",
|
||||
"secret_exists_flag() { kubectl -n \"$namespace\" get secret \"$name\" >/dev/null 2>&1 && printf yes || printf no; }",
|
||||
"secret_b64_key() { kubectl -n \"$namespace\" get secret \"$name\" -o \"go-template={{ index .data \\\"$1\\\" }}\" 2>/dev/null || true; }",
|
||||
"decoded_value() { if [ -n \"$1\" ]; then printf '%s' \"$1\" | base64 -d 2>/dev/null || true; fi; }",
|
||||
"decoded_length() { if [ -n \"$1\" ]; then printf '%s' \"$1\" | base64 -d 2>/dev/null | wc -c | tr -d ' '; else printf '0'; fi; }",
|
||||
"psql_scalar() { kubectl -n \"$namespace\" exec statefulset/hwlab-v02-postgres -c postgres -- env PGPASSWORD=\"$postgres_admin_password\" psql -U hwlab_v02 -d postgres -tAc \"$1\" 2>/dev/null | tr -d '[:space:]'; }",
|
||||
"probe_db() {",
|
||||
" role_result=unknown",
|
||||
" database_result=unknown",
|
||||
" probe_exit=missing-postgres-admin-secret",
|
||||
" if [ -n \"$postgres_admin_password\" ]; then",
|
||||
` role_result=$(psql_scalar "select exists(select 1 from pg_roles where rolname='${V02_OPENFGA_DB_USER}');")`,
|
||||
" role_exit=$?",
|
||||
` database_result=$(psql_scalar "select exists(select 1 from pg_database where datname='${V02_OPENFGA_DB_NAME}');")`,
|
||||
" database_exit=$?",
|
||||
" if [ \"$role_exit\" -eq 0 ] && [ \"$database_exit\" -eq 0 ]; then probe_exit=0; else probe_exit=$role_exit/$database_exit; fi",
|
||||
" fi",
|
||||
"}",
|
||||
"before_exists=$(secret_exists_flag)",
|
||||
"before_authn_b64=$(secret_b64_key \"$authn_key\")",
|
||||
"before_uri_b64=$(secret_b64_key \"$datastore_uri_key\")",
|
||||
"before_pg_password_b64=$(secret_b64_key \"$postgres_password_key\")",
|
||||
"before_authn_present=$([ -n \"$before_authn_b64\" ] && printf yes || printf no)",
|
||||
"before_uri_present=$([ -n \"$before_uri_b64\" ] && printf yes || printf no)",
|
||||
"before_pg_password_present=$([ -n \"$before_pg_password_b64\" ] && printf yes || printf no)",
|
||||
"before_authn_bytes=$(decoded_length \"$before_authn_b64\")",
|
||||
"before_uri_bytes=$(decoded_length \"$before_uri_b64\")",
|
||||
"before_pg_password_bytes=$(decoded_length \"$before_pg_password_b64\")",
|
||||
"authn_value=$(decoded_value \"$before_authn_b64\")",
|
||||
"datastore_uri=$(decoded_value \"$before_uri_b64\")",
|
||||
"pg_password=$(decoded_value \"$before_pg_password_b64\")",
|
||||
"postgres_admin_b64=$(kubectl -n \"$namespace\" get secret hwlab-v02-postgres -o 'go-template={{ index .data \"POSTGRES_PASSWORD\" }}' 2>/dev/null || true)",
|
||||
"postgres_admin_present=$([ -n \"$postgres_admin_b64\" ] && printf yes || printf no)",
|
||||
"postgres_admin_password=$(decoded_value \"$postgres_admin_b64\")",
|
||||
"probe_db",
|
||||
"db_role_exists_before=$role_result",
|
||||
"db_database_exists_before=$database_result",
|
||||
"db_probe_exit_before=$probe_exit",
|
||||
"action=observed",
|
||||
"mutation=false",
|
||||
"apply_exit=",
|
||||
"db_ensure_exit=",
|
||||
"if [ \"$action_request\" = ensure ]; then",
|
||||
" missing_secret=false",
|
||||
" [ \"$before_authn_present\" = yes ] && [ \"$before_authn_bytes\" -gt 0 ] || missing_secret=true",
|
||||
" [ \"$before_uri_present\" = yes ] && [ \"$before_uri_bytes\" -gt 0 ] || missing_secret=true",
|
||||
" [ \"$before_pg_password_present\" = yes ] && [ \"$before_pg_password_bytes\" -gt 0 ] || missing_secret=true",
|
||||
" missing_db=false",
|
||||
" [ \"$db_role_exists_before\" = t ] || missing_db=true",
|
||||
" [ \"$db_database_exists_before\" = t ] || missing_db=true",
|
||||
" if [ \"$dry_run\" = true ]; then",
|
||||
" if [ \"$missing_secret\" = true ] || [ \"$missing_db\" = true ]; then action=would-ensure; else action=kept; fi",
|
||||
" elif ! command -v openssl >/dev/null 2>&1; then",
|
||||
" action=openssl-missing",
|
||||
" apply_exit=127",
|
||||
" elif [ -z \"$postgres_admin_password\" ]; then",
|
||||
" action=postgres-admin-secret-missing",
|
||||
" apply_exit=44",
|
||||
" else",
|
||||
" [ -n \"$authn_value\" ] || authn_value=$(openssl rand -base64 48)",
|
||||
" [ -n \"$pg_password\" ] || pg_password=$(openssl rand -hex 24)",
|
||||
" [ -n \"$datastore_uri\" ] || datastore_uri=\"postgres://$db_user:$pg_password@$db_host:5432/$db_name?sslmode=disable\"",
|
||||
" kubectl -n \"$namespace\" create secret generic \"$name\" --from-literal=\"$authn_key=$authn_value\" --from-literal=\"$datastore_uri_key=$datastore_uri\" --from-literal=\"$postgres_password_key=$pg_password\" --dry-run=client -o yaml | kubectl apply --server-side --force-conflicts --field-manager=\"$field_manager\" -f -",
|
||||
" apply_exit=$?",
|
||||
" if [ \"$apply_exit\" -eq 0 ]; then",
|
||||
" kubectl -n \"$namespace\" exec -i statefulset/hwlab-v02-postgres -c postgres -- env PGPASSWORD=\"$postgres_admin_password\" psql -v ON_ERROR_STOP=1 -U hwlab_v02 -d postgres -v db_name=\"$db_name\" -v db_user=\"$db_user\" -v db_pass=\"$pg_password\" >/tmp/hwlab-openfga-psql.out 2>/tmp/hwlab-openfga-psql.err <<'SQL'",
|
||||
"SELECT format('CREATE ROLE %I LOGIN PASSWORD %L', :'db_user', :'db_pass')",
|
||||
"WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = :'db_user')",
|
||||
"\\gexec",
|
||||
"ALTER ROLE :\"db_user\" LOGIN PASSWORD :'db_pass';",
|
||||
"SELECT format('CREATE DATABASE %I OWNER %I', :'db_name', :'db_user')",
|
||||
"WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = :'db_name')",
|
||||
"\\gexec",
|
||||
"ALTER DATABASE :\"db_name\" OWNER TO :\"db_user\";",
|
||||
"SQL",
|
||||
" db_ensure_exit=$?",
|
||||
" if [ \"$db_ensure_exit\" -eq 0 ]; then action=ensured; mutation=true; else action=db-ensure-failed; fi",
|
||||
" else",
|
||||
" action=apply-failed",
|
||||
" fi",
|
||||
" fi",
|
||||
"fi",
|
||||
"after_exists=$(secret_exists_flag)",
|
||||
"after_authn_b64=$(secret_b64_key \"$authn_key\")",
|
||||
"after_uri_b64=$(secret_b64_key \"$datastore_uri_key\")",
|
||||
"after_pg_password_b64=$(secret_b64_key \"$postgres_password_key\")",
|
||||
"after_authn_present=$([ -n \"$after_authn_b64\" ] && printf yes || printf no)",
|
||||
"after_uri_present=$([ -n \"$after_uri_b64\" ] && printf yes || printf no)",
|
||||
"after_pg_password_present=$([ -n \"$after_pg_password_b64\" ] && printf yes || printf no)",
|
||||
"after_authn_bytes=$(decoded_length \"$after_authn_b64\")",
|
||||
"after_uri_bytes=$(decoded_length \"$after_uri_b64\")",
|
||||
"after_pg_password_bytes=$(decoded_length \"$after_pg_password_b64\")",
|
||||
"probe_db",
|
||||
"db_role_exists_after=$role_result",
|
||||
"db_database_exists_after=$database_result",
|
||||
"db_probe_exit_after=$probe_exit",
|
||||
"printf 'namespace\\t%s\\n' \"$namespace\"",
|
||||
"printf 'secret\\t%s\\n' \"$name\"",
|
||||
"printf 'key\\t%s\\n' \"$selected_key\"",
|
||||
"printf 'preset\\t%s\\n' \"$preset\"",
|
||||
"printf 'action\\t%s\\n' \"$action\"",
|
||||
"printf 'dryRun\\t%s\\n' \"$dry_run\"",
|
||||
"printf 'mutation\\t%s\\n' \"$mutation\"",
|
||||
"printf 'beforeExists\\t%s\\n' \"$before_exists\"",
|
||||
"printf 'beforeAuthnPresent\\t%s\\n' \"$before_authn_present\"",
|
||||
"printf 'beforeAuthnBytes\\t%s\\n' \"$before_authn_bytes\"",
|
||||
"printf 'beforeDatastoreUriPresent\\t%s\\n' \"$before_uri_present\"",
|
||||
"printf 'beforeDatastoreUriBytes\\t%s\\n' \"$before_uri_bytes\"",
|
||||
"printf 'beforePostgresPasswordPresent\\t%s\\n' \"$before_pg_password_present\"",
|
||||
"printf 'beforePostgresPasswordBytes\\t%s\\n' \"$before_pg_password_bytes\"",
|
||||
"printf 'afterExists\\t%s\\n' \"$after_exists\"",
|
||||
"printf 'afterAuthnPresent\\t%s\\n' \"$after_authn_present\"",
|
||||
"printf 'afterAuthnBytes\\t%s\\n' \"$after_authn_bytes\"",
|
||||
"printf 'afterDatastoreUriPresent\\t%s\\n' \"$after_uri_present\"",
|
||||
"printf 'afterDatastoreUriBytes\\t%s\\n' \"$after_uri_bytes\"",
|
||||
"printf 'afterPostgresPasswordPresent\\t%s\\n' \"$after_pg_password_present\"",
|
||||
"printf 'afterPostgresPasswordBytes\\t%s\\n' \"$after_pg_password_bytes\"",
|
||||
"printf 'postgresAdminSecretPresent\\t%s\\n' \"$postgres_admin_present\"",
|
||||
"printf 'dbName\\t%s\\n' \"$db_name\"",
|
||||
"printf 'dbUser\\t%s\\n' \"$db_user\"",
|
||||
"printf 'dbRoleExistsBefore\\t%s\\n' \"$db_role_exists_before\"",
|
||||
"printf 'dbDatabaseExistsBefore\\t%s\\n' \"$db_database_exists_before\"",
|
||||
"printf 'dbProbeExitCodeBefore\\t%s\\n' \"$db_probe_exit_before\"",
|
||||
"printf 'dbRoleExistsAfter\\t%s\\n' \"$db_role_exists_after\"",
|
||||
"printf 'dbDatabaseExistsAfter\\t%s\\n' \"$db_database_exists_after\"",
|
||||
"printf 'dbProbeExitCodeAfter\\t%s\\n' \"$db_probe_exit_after\"",
|
||||
"printf 'applyExitCode\\t%s\\n' \"$apply_exit\"",
|
||||
"printf 'dbEnsureExitCode\\t%s\\n' \"$db_ensure_exit\"",
|
||||
"authn_value=",
|
||||
"datastore_uri=",
|
||||
"pg_password=",
|
||||
"postgres_admin_password=",
|
||||
"if [ -n \"$apply_exit\" ] && [ \"$apply_exit\" != 0 ]; then exit \"$apply_exit\"; fi",
|
||||
"if [ -n \"$db_ensure_exit\" ] && [ \"$db_ensure_exit\" != 0 ]; then exit \"$db_ensure_exit\"; fi",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function v02SecretStatusFromText(text: string, commandOk: boolean, exitCode: number | null, stderr: string): Record<string, unknown> {
|
||||
const fields = keyValueLinesFromText(text);
|
||||
if (fields.preset === "openfga") {
|
||||
const afterAuthnBytes = numericField(fields.afterAuthnBytes);
|
||||
const afterUriBytes = numericField(fields.afterDatastoreUriBytes);
|
||||
const afterPasswordBytes = numericField(fields.afterPostgresPasswordBytes);
|
||||
const keysHealthy =
|
||||
fields.afterExists === "yes" &&
|
||||
fields.afterAuthnPresent === "yes" &&
|
||||
fields.afterDatastoreUriPresent === "yes" &&
|
||||
fields.afterPostgresPasswordPresent === "yes" &&
|
||||
typeof afterAuthnBytes === "number" && afterAuthnBytes > 0 &&
|
||||
typeof afterUriBytes === "number" && afterUriBytes > 0 &&
|
||||
typeof afterPasswordBytes === "number" && afterPasswordBytes > 0;
|
||||
const databaseHealthy = fields.dbRoleExistsAfter === "t" && fields.dbDatabaseExistsAfter === "t";
|
||||
const healthy = keysHealthy && databaseHealthy;
|
||||
return {
|
||||
ok: commandOk && healthy,
|
||||
namespace: fields.namespace || V02_RUNTIME_NAMESPACE,
|
||||
secret: fields.secret || V02_OPENFGA_SECRET,
|
||||
key: fields.key || null,
|
||||
preset: "openfga",
|
||||
action: fields.action || null,
|
||||
dryRun: fields.dryRun === "true",
|
||||
mutation: fields.mutation === "true",
|
||||
before: {
|
||||
exists: fields.beforeExists === "yes",
|
||||
authnPresharedKey: { keyPresent: fields.beforeAuthnPresent === "yes", valueBytes: numericField(fields.beforeAuthnBytes) },
|
||||
datastoreUri: { keyPresent: fields.beforeDatastoreUriPresent === "yes", valueBytes: numericField(fields.beforeDatastoreUriBytes) },
|
||||
postgresPassword: { keyPresent: fields.beforePostgresPasswordPresent === "yes", valueBytes: numericField(fields.beforePostgresPasswordBytes) },
|
||||
database: {
|
||||
roleExists: fields.dbRoleExistsBefore || "unknown",
|
||||
databaseExists: fields.dbDatabaseExistsBefore || "unknown",
|
||||
probeExitCode: fields.dbProbeExitCodeBefore || null,
|
||||
},
|
||||
},
|
||||
after: {
|
||||
exists: fields.afterExists === "yes",
|
||||
authnPresharedKey: { keyPresent: fields.afterAuthnPresent === "yes", valueBytes: afterAuthnBytes },
|
||||
datastoreUri: { keyPresent: fields.afterDatastoreUriPresent === "yes", valueBytes: afterUriBytes },
|
||||
postgresPassword: { keyPresent: fields.afterPostgresPasswordPresent === "yes", valueBytes: afterPasswordBytes },
|
||||
database: {
|
||||
roleExists: fields.dbRoleExistsAfter || "unknown",
|
||||
databaseExists: fields.dbDatabaseExistsAfter || "unknown",
|
||||
probeExitCode: fields.dbProbeExitCodeAfter || null,
|
||||
},
|
||||
},
|
||||
postgresAdminSecretPresent: fields.postgresAdminSecretPresent === "yes",
|
||||
dbName: fields.dbName || V02_OPENFGA_DB_NAME,
|
||||
dbUser: fields.dbUser || V02_OPENFGA_DB_USER,
|
||||
applyExitCode: numericField(fields.applyExitCode),
|
||||
dbEnsureExitCode: numericField(fields.dbEnsureExitCode),
|
||||
exitCode,
|
||||
stderr: commandOk ? "" : stderr.trim().slice(0, 2000),
|
||||
valuesRedacted: true,
|
||||
summary: healthy
|
||||
? `${fields.secret || V02_OPENFGA_SECRET} keys and Postgres database exist`
|
||||
: `${fields.secret || V02_OPENFGA_SECRET} keys or Postgres database missing`,
|
||||
};
|
||||
}
|
||||
const afterExists = fields.afterExists === "yes";
|
||||
const afterKeyPresent = fields.afterKeyPresent === "yes";
|
||||
const afterValueBytes = numericField(fields.afterValueBytes);
|
||||
@@ -3299,7 +3560,8 @@ function runG14Secret(options: G14SecretOptions): Record<string, unknown> {
|
||||
lane: options.lane,
|
||||
namespace: V02_RUNTIME_NAMESPACE,
|
||||
secret: options.name,
|
||||
key: options.key,
|
||||
key: options.key ?? null,
|
||||
preset: options.preset,
|
||||
mode: options.action === "status" ? "status" : options.dryRun ? "dry-run" : "confirmed-ensure",
|
||||
status,
|
||||
mutation: status.mutation === true,
|
||||
@@ -3307,7 +3569,7 @@ function runG14Secret(options: G14SecretOptions): Record<string, unknown> {
|
||||
valuesRedacted: true,
|
||||
next: ok && options.action === "status"
|
||||
? undefined
|
||||
: { ensure: `bun scripts/cli.ts hwlab g14 secret ensure --lane v02 --name ${options.name} --key ${options.key} --confirm` },
|
||||
: { ensure: `bun scripts/cli.ts hwlab g14 secret ensure --lane v02 --name ${options.name}${options.key ? ` --key ${options.key}` : ""} --confirm` },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4837,6 +5099,117 @@ function runG14ToolsImage(options: G14ToolsImageOptions): Record<string, unknown
|
||||
return runG14ToolsImageBuild(options);
|
||||
}
|
||||
|
||||
function g14UpstreamImageTarget(options: G14UpstreamImageOptions): string {
|
||||
return `${V02_REGISTRY_PREFIX}/${options.name}:${options.tag}`;
|
||||
}
|
||||
|
||||
function g14UpstreamImageSource(options: G14UpstreamImageOptions): string {
|
||||
if (options.name === "openfga") return `docker.io/openfga/openfga:${options.tag}`;
|
||||
return `${options.name}:${options.tag}`;
|
||||
}
|
||||
|
||||
function runG14UpstreamImageStatus(options: G14UpstreamImageOptions): Record<string, unknown> {
|
||||
const sourceImage = g14UpstreamImageSource(options);
|
||||
const targetImage = g14UpstreamImageTarget(options);
|
||||
const script = [
|
||||
"set +e",
|
||||
`source_image=${shellQuote(sourceImage)}`,
|
||||
`target_image=${shellQuote(targetImage)}`,
|
||||
`repo_path=${shellQuote(`hwlab/${options.name}`)}`,
|
||||
`tag=${shellQuote(options.tag)}`,
|
||||
"local_id=$(docker image inspect \"$target_image\" --format '{{.Id}}' 2>/dev/null || true)",
|
||||
"local_created=$(docker image inspect \"$target_image\" --format '{{.Created}}' 2>/dev/null || true)",
|
||||
"registry_tags=$(curl -fsS --max-time 10 \"http://127.0.0.1:5000/v2/$repo_path/tags/list\" 2>/dev/null || true)",
|
||||
"registry_has_tag=false",
|
||||
"if printf '%s' \"$registry_tags\" | grep -F '\"'$tag'\"' >/dev/null 2>&1; then registry_has_tag=true; fi",
|
||||
"export source_image target_image local_id local_created registry_tags registry_has_tag",
|
||||
"node <<'NODE'",
|
||||
"const env = process.env;",
|
||||
"console.log(JSON.stringify({",
|
||||
" sourceImage: env.source_image,",
|
||||
" targetImage: env.target_image,",
|
||||
" localExists: Boolean(env.local_id),",
|
||||
" localImageId: env.local_id || null,",
|
||||
" localCreated: env.local_created || null,",
|
||||
" registryHasTag: env.registry_has_tag === 'true',",
|
||||
" registryTagsRaw: env.registry_tags || null",
|
||||
"}, null, 2));",
|
||||
"NODE",
|
||||
].join("\n");
|
||||
const result = g14HostScript(script, 120_000);
|
||||
let parsedStatus: unknown = null;
|
||||
const text = statusText(result);
|
||||
if (text.length > 0) {
|
||||
try {
|
||||
parsedStatus = JSON.parse(text) as unknown;
|
||||
} catch {
|
||||
parsedStatus = null;
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: isCommandSuccess(result) && record(parsedStatus).registryHasTag === true,
|
||||
command: "hwlab g14 upstream-image status --name openfga",
|
||||
name: options.name,
|
||||
tag: options.tag,
|
||||
sourceImage,
|
||||
targetImage,
|
||||
status: parsedStatus ?? text,
|
||||
result,
|
||||
};
|
||||
}
|
||||
|
||||
function runG14UpstreamImageEnsure(options: G14UpstreamImageOptions): Record<string, unknown> {
|
||||
const sourceImage = g14UpstreamImageSource(options);
|
||||
const targetImage = g14UpstreamImageTarget(options);
|
||||
if (options.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
command: "hwlab g14 upstream-image ensure --name openfga",
|
||||
mode: "dry-run",
|
||||
name: options.name,
|
||||
tag: options.tag,
|
||||
sourceImage,
|
||||
targetImage,
|
||||
mutation: false,
|
||||
next: { confirm: `bun scripts/cli.ts hwlab g14 upstream-image ensure --name ${options.name} --tag ${options.tag} --confirm` },
|
||||
};
|
||||
}
|
||||
const script = [
|
||||
"set -eu",
|
||||
`source_image=${shellQuote(sourceImage)}`,
|
||||
`target_image=${shellQuote(targetImage)}`,
|
||||
"export HTTP_PROXY=http://127.0.0.1:10808 HTTPS_PROXY=http://127.0.0.1:10808 http_proxy=http://127.0.0.1:10808 https_proxy=http://127.0.0.1:10808",
|
||||
"export NO_PROXY=localhost,127.0.0.1,::1,host.docker.internal,74.48.78.17,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,10.42.0.0/16,10.43.0.0/16,.svc,.svc.cluster.local,.cluster.local,kubernetes,kubernetes.default,kubernetes.default.svc,127.0.0.1:5000,localhost:5000",
|
||||
"export no_proxy=$NO_PROXY",
|
||||
"echo \"{\\\"phase\\\":\\\"pull\\\",\\\"sourceImage\\\":\\\"$source_image\\\"}\"",
|
||||
"docker pull \"$source_image\"",
|
||||
"docker tag \"$source_image\" \"$target_image\"",
|
||||
"echo \"{\\\"phase\\\":\\\"push\\\",\\\"targetImage\\\":\\\"$target_image\\\"}\"",
|
||||
"docker push \"$target_image\"",
|
||||
"digest=$(docker image inspect \"$target_image\" --format '{{index .RepoDigests 0}}' 2>/dev/null || true)",
|
||||
"echo \"{\\\"phase\\\":\\\"published\\\",\\\"targetImage\\\":\\\"$target_image\\\",\\\"digest\\\":\\\"$digest\\\"}\"",
|
||||
].join("\n");
|
||||
const result = g14HostScript(script, options.timeoutSeconds * 1000);
|
||||
const status = runG14UpstreamImageStatus(options);
|
||||
return {
|
||||
ok: isCommandSuccess(result) && status.ok === true,
|
||||
command: "hwlab g14 upstream-image ensure --name openfga",
|
||||
mode: "confirmed-ensure",
|
||||
name: options.name,
|
||||
tag: options.tag,
|
||||
sourceImage,
|
||||
targetImage,
|
||||
mutation: true,
|
||||
result,
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
||||
function runG14UpstreamImage(options: G14UpstreamImageOptions): Record<string, unknown> {
|
||||
if (options.action === "status") return runG14UpstreamImageStatus(options);
|
||||
return runG14UpstreamImageEnsure(options);
|
||||
}
|
||||
|
||||
function listOpenG14PullRequests(): CommandJsonResult {
|
||||
return cliJson(["gh", "pr", "list", "--repo", HWLAB_REPO, "--state", "open", "--limit", "30", "--json", "number,title,state,url,head,base,draft,headRefName,baseRefName"], 60_000);
|
||||
}
|
||||
@@ -6167,6 +6540,9 @@ export function hwlabG14Help(): Record<string, unknown> {
|
||||
"bun scripts/cli.ts hwlab g14 secret status --lane v02 --name hwlab-v02-device-pod-api-key --key api-key",
|
||||
"bun scripts/cli.ts hwlab g14 secret ensure --lane v02 --name hwlab-v02-device-pod-api-key --key api-key --dry-run",
|
||||
"bun scripts/cli.ts hwlab g14 secret ensure --lane v02 --name hwlab-v02-device-pod-api-key --key api-key --confirm",
|
||||
"bun scripts/cli.ts hwlab g14 secret status --lane v02 --name hwlab-v02-openfga",
|
||||
"bun scripts/cli.ts hwlab g14 secret ensure --lane v02 --name hwlab-v02-openfga --dry-run",
|
||||
"bun scripts/cli.ts hwlab g14 secret ensure --lane v02 --name hwlab-v02-openfga --confirm",
|
||||
"bun scripts/cli.ts hwlab g14 git-mirror status",
|
||||
"bun scripts/cli.ts hwlab g14 git-mirror apply --confirm",
|
||||
"bun scripts/cli.ts hwlab g14 git-mirror sync --confirm",
|
||||
@@ -6179,9 +6555,12 @@ export function hwlabG14Help(): Record<string, unknown> {
|
||||
"bun scripts/cli.ts hwlab g14 observability query --promql 'up{namespace=\"hwlab-v02\"}'",
|
||||
"bun scripts/cli.ts hwlab g14 tools-image status --name ci-node-tools --tag node22-alpine-bun-v1",
|
||||
"bun scripts/cli.ts hwlab g14 tools-image build --name ci-node-tools --tag node22-alpine-bun-v1 --confirm",
|
||||
"bun scripts/cli.ts hwlab g14 upstream-image status --name openfga --tag v1.17.0",
|
||||
"bun scripts/cli.ts hwlab g14 upstream-image ensure --name openfga --tag v1.17.0 --dry-run",
|
||||
"bun scripts/cli.ts hwlab g14 upstream-image ensure --name openfga --tag v1.17.0 --confirm",
|
||||
"bun scripts/cli.ts job status <jobId> --tail-bytes 30000",
|
||||
],
|
||||
description: "G14 HWLAB PR monitor, DEV rollout command, bounded v0.2 control-plane bootstrap/cleanup/runtime-migration helper, v0.2 runtime SecretRef bootstrap, devops-infra git mirror and observability maintenance, and controlled CI tools image build/status entry. The public monitor starts a fire-and-forget job. Default monitor lane is base=G14; --lane v02 monitors base=v0.2 PRs, waits for GitHub preflight/CI readiness, automatically merges ready PRs without waiting for other active v0.2 PipelineRuns, triggers v0.2 CD with latest-only GitOps writeback, flushes the git mirror when needed, and posts deduplicated PR comments for pending, blocked/conflict, success, superseded, failure, or timeout states. confirmed control-plane trigger-current and git-mirror sync/flush also return async jobs by default, with --wait reserved for explicit synchronous debugging. control-plane status/closeout/apply/cleanup-runs/cleanup-released-pvs/runtime-migration uses UniDesk G14:k3s routes for v0.2 Tekton/Argo control resources, runtime migration, historical PipelineRun/source-commit closeout verdicts, GitOps mirror flush state, and completed CI workspace retention only. secret status/ensure is the standard v0.2 runtime SecretRef bootstrap path; it never reads or prints secret values. git-mirror status/apply/sync/flush is the manual devops-infra mirror/relay control path and does not install a CronJob. observability status/apply/query owns the shared Prometheus Operator and Prometheus instance in devops-infra, while HWLAB lane manifests own only ServiceMonitor and PrometheusRule objects.",
|
||||
description: "G14 HWLAB PR monitor, DEV rollout command, bounded v0.2 control-plane bootstrap/cleanup/runtime-migration helper, v0.2 runtime SecretRef bootstrap, devops-infra git mirror and observability maintenance, controlled CI tools image build/status entry, and allowlisted upstream image mirroring. The public monitor starts a fire-and-forget job. Default monitor lane is base=G14; --lane v02 monitors base=v0.2 PRs, waits for GitHub preflight/CI readiness, automatically merges ready PRs without waiting for other active v0.2 PipelineRuns, triggers v0.2 CD with latest-only GitOps writeback, flushes the git mirror when needed, and posts deduplicated PR comments for pending, blocked/conflict, success, superseded, failure, or timeout states. confirmed control-plane trigger-current and git-mirror sync/flush also return async jobs by default, with --wait reserved for explicit synchronous debugging. control-plane status/closeout/apply/cleanup-runs/cleanup-released-pvs/runtime-migration uses UniDesk G14:k3s routes for v0.2 Tekton/Argo control resources, runtime migration, historical PipelineRun/source-commit closeout verdicts, GitOps mirror flush state, and completed CI workspace retention only. secret status/ensure is the standard v0.2 runtime SecretRef bootstrap path; it never reads or prints secret values. upstream-image status/ensure only mirrors allowlisted upstream runtime images into the G14 local registry. git-mirror status/apply/sync/flush is the manual devops-infra mirror/relay control path and does not install a CronJob. observability status/apply/query owns the shared Prometheus Operator and Prometheus instance in devops-infra, while HWLAB lane manifests own only ServiceMonitor and PrometheusRule objects.",
|
||||
defaults: {
|
||||
repo: HWLAB_REPO,
|
||||
base: G14_SOURCE_BRANCH,
|
||||
@@ -6295,6 +6674,10 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
|
||||
const options = parseToolsImageOptions(args.slice(1));
|
||||
return runG14ToolsImage(options);
|
||||
}
|
||||
if (action === "upstream-image") {
|
||||
const options = parseUpstreamImageOptions(args.slice(1));
|
||||
return runG14UpstreamImage(options);
|
||||
}
|
||||
if (action === "git-mirror") {
|
||||
const options = parseGitMirrorOptions(args.slice(1));
|
||||
if ((options.action === "sync" || options.action === "flush") && options.confirm && !options.dryRun && !options.wait) {
|
||||
@@ -6307,7 +6690,7 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
|
||||
return runG14Observability(options);
|
||||
}
|
||||
if (action !== "monitor-prs") {
|
||||
return { ok: false, command: `hwlab g14 ${action ?? ""}`.trim(), degradedReason: "unsupported-command", message: "supported commands: hwlab g14 monitor-prs, hwlab g14 record-rollout, hwlab g14 control-plane, hwlab g14 secret, hwlab g14 git-mirror, hwlab g14 observability, hwlab g14 tools-image" };
|
||||
return { ok: false, command: `hwlab g14 ${action ?? ""}`.trim(), degradedReason: "unsupported-command", message: "supported commands: hwlab g14 monitor-prs, hwlab g14 record-rollout, hwlab g14 control-plane, hwlab g14 secret, hwlab g14 git-mirror, hwlab g14 observability, hwlab g14 tools-image, hwlab g14 upstream-image" };
|
||||
}
|
||||
const options = parseOptions(args.slice(1));
|
||||
if (options.worker) return runMonitorWorker(options);
|
||||
|
||||
Reference in New Issue
Block a user