feat: migrate todo note to nc01 github storage
This commit is contained in:
@@ -122,12 +122,14 @@ const configPath = process.argv[2] ?? "config/unidesk-host-k8s.yaml";
|
||||
const config = Bun.YAML.parse(readFileSync(configPath, "utf8"));
|
||||
const target = record(config.target, "target");
|
||||
const runtime = record(config.runtime, "runtime");
|
||||
const delivery = config.delivery === undefined ? null : record(config.delivery, "delivery");
|
||||
const images = record(config.images, "images");
|
||||
const database = record(config.database, "database");
|
||||
const services = record(config.services, "services");
|
||||
const backend = record(services.backendCore, "services.backendCore");
|
||||
const frontend = record(services.frontend, "services.frontend");
|
||||
const decisionCenter = optionalRecord(services.decisionCenter, "services.decisionCenter");
|
||||
const todoNote = optionalRecord(services.todoNote, "services.todoNote");
|
||||
const runtimeConfig = record(config.runtimeConfig, "runtimeConfig");
|
||||
const runtimeAuth = record(config.runtimeAuth, "runtimeAuth");
|
||||
const runtimeAuthSourceRef = record(runtimeAuth.sourceRef, "runtimeAuth.sourceRef");
|
||||
@@ -152,6 +154,10 @@ for (const key of requiredSecretKeys) {
|
||||
}
|
||||
const secretFingerprint = sha(requiredSecretKeys.map((key) => `${key}=${secrets[key]}`).join("\n"));
|
||||
const runtimeConfigName = "unidesk-runtime-config";
|
||||
const deliveryServiceRef = delivery === null ? null : required(delivery.serviceRef, "delivery.serviceRef");
|
||||
if (delivery !== null && delivery.enabled !== true && delivery.enabled !== false) {
|
||||
throw new Error("delivery.enabled must be boolean");
|
||||
}
|
||||
const databaseMode = database.mode === undefined ? "k8s-postgres" : required(database.mode, "database.mode");
|
||||
if (databaseMode !== "k8s-postgres" && databaseMode !== "host-native") throw new Error("database.mode must be k8s-postgres or host-native");
|
||||
const dbService = required(database.serviceName, "database.serviceName");
|
||||
@@ -162,21 +168,21 @@ if (secrets[secretKeys.databaseUrl] !== `postgres://${secrets[secretKeys.postgre
|
||||
throw new Error(`DATABASE_URL in ${sourcePath} must target ${dbHost}:${dbPort}/${dbName}`);
|
||||
}
|
||||
|
||||
function readDecisionCenterDatabase(decision) {
|
||||
if (decision === null) return null;
|
||||
const database = record(decision.database, "services.decisionCenter.database");
|
||||
const sourceRef = record(database.sourceRef, "services.decisionCenter.database.sourceRef");
|
||||
const sourcePath = resolve(required(sourceRef.path, "services.decisionCenter.database.sourceRef.path"));
|
||||
const sourceKey = required(sourceRef.key, "services.decisionCenter.database.sourceRef.key");
|
||||
function readServiceDatabase(service, servicePath) {
|
||||
if (service === null) return null;
|
||||
const database = record(service.database, `${servicePath}.database`);
|
||||
const sourceRef = record(database.sourceRef, `${servicePath}.database.sourceRef`);
|
||||
const sourcePath = resolve(required(sourceRef.path, `${servicePath}.database.sourceRef.path`));
|
||||
const sourceKey = required(sourceRef.key, `${servicePath}.database.sourceRef.key`);
|
||||
const sourceValues = readEnvFile(sourcePath);
|
||||
const value = sourceValues[sourceKey];
|
||||
if (typeof value !== "string" || value.length === 0) throw new Error(`missing ${sourceKey} in ${sourcePath}`);
|
||||
return {
|
||||
sourcePath,
|
||||
sourceRefPath: required(sourceRef.path, "services.decisionCenter.database.sourceRef.path"),
|
||||
sourceRefPath: required(sourceRef.path, `${servicePath}.database.sourceRef.path`),
|
||||
sourceKey,
|
||||
secretName: required(database.secretName, "services.decisionCenter.database.secretName"),
|
||||
secretKey: required(database.secretKey, "services.decisionCenter.database.secretKey"),
|
||||
secretName: required(database.secretName, `${servicePath}.database.secretName`),
|
||||
secretKey: required(database.secretKey, `${servicePath}.database.secretKey`),
|
||||
value: removeUrlSearchParam(value, "uselibpqcompat"),
|
||||
fingerprint: sha(`${sourceKey}=${removeUrlSearchParam(value, "uselibpqcompat")}`),
|
||||
};
|
||||
@@ -194,37 +200,37 @@ function readFileSource(sourceRef, path) {
|
||||
};
|
||||
}
|
||||
|
||||
function readDecisionCenterStorage(decision) {
|
||||
if (decision === null) return null;
|
||||
const storage = optionalRecord(decision.storage, "services.decisionCenter.storage");
|
||||
function readServiceStorage(service, servicePath) {
|
||||
if (service === null) return null;
|
||||
const storage = optionalRecord(service.storage, `${servicePath}.storage`);
|
||||
if (storage === null) return null;
|
||||
const primary = required(storage.primary, "services.decisionCenter.storage.primary");
|
||||
if (primary !== "postgres" && primary !== "github-repo") throw new Error("services.decisionCenter.storage.primary must be postgres or github-repo");
|
||||
const github = primary === "github-repo" ? record(storage.github, "services.decisionCenter.storage.github") : null;
|
||||
const primary = required(storage.primary, `${servicePath}.storage.primary`);
|
||||
if (primary !== "postgres" && primary !== "github-repo") throw new Error(`${servicePath}.storage.primary must be postgres or github-repo`);
|
||||
const github = primary === "github-repo" ? record(storage.github, `${servicePath}.storage.github`) : null;
|
||||
if (github === null) return { primary };
|
||||
const ssh = record(github.ssh, "services.decisionCenter.storage.github.ssh");
|
||||
const privateKey = record(ssh.privateKey, "services.decisionCenter.storage.github.ssh.privateKey");
|
||||
const knownHosts = record(ssh.knownHosts, "services.decisionCenter.storage.github.ssh.knownHosts");
|
||||
const privateKeySource = readFileSource(record(privateKey.sourceRef, "services.decisionCenter.storage.github.ssh.privateKey.sourceRef"), "services.decisionCenter.storage.github.ssh.privateKey.sourceRef");
|
||||
const knownHostsSource = readFileSource(record(knownHosts.sourceRef, "services.decisionCenter.storage.github.ssh.knownHosts.sourceRef"), "services.decisionCenter.storage.github.ssh.knownHosts.sourceRef");
|
||||
const ssh = record(github.ssh, `${servicePath}.storage.github.ssh`);
|
||||
const privateKey = record(ssh.privateKey, `${servicePath}.storage.github.ssh.privateKey`);
|
||||
const knownHosts = record(ssh.knownHosts, `${servicePath}.storage.github.ssh.knownHosts`);
|
||||
const privateKeySource = readFileSource(record(privateKey.sourceRef, `${servicePath}.storage.github.ssh.privateKey.sourceRef`), `${servicePath}.storage.github.ssh.privateKey.sourceRef`);
|
||||
const knownHostsSource = readFileSource(record(knownHosts.sourceRef, `${servicePath}.storage.github.ssh.knownHosts.sourceRef`), `${servicePath}.storage.github.ssh.knownHosts.sourceRef`);
|
||||
return {
|
||||
primary,
|
||||
github: {
|
||||
repo: required(github.repo, "services.decisionCenter.storage.github.repo"),
|
||||
sshUrl: required(github.sshUrl, "services.decisionCenter.storage.github.sshUrl"),
|
||||
branch: required(github.branch, "services.decisionCenter.storage.github.branch"),
|
||||
basePath: required(github.basePath, "services.decisionCenter.storage.github.basePath"),
|
||||
worktreePath: required(github.worktreePath, "services.decisionCenter.storage.github.worktreePath"),
|
||||
commitMessagePrefix: required(github.commitMessagePrefix, "services.decisionCenter.storage.github.commitMessagePrefix"),
|
||||
repo: required(github.repo, `${servicePath}.storage.github.repo`),
|
||||
sshUrl: required(github.sshUrl, `${servicePath}.storage.github.sshUrl`),
|
||||
branch: required(github.branch, `${servicePath}.storage.github.branch`),
|
||||
basePath: required(github.basePath, `${servicePath}.storage.github.basePath`),
|
||||
worktreePath: required(github.worktreePath, `${servicePath}.storage.github.worktreePath`),
|
||||
commitMessagePrefix: required(github.commitMessagePrefix, `${servicePath}.storage.github.commitMessagePrefix`),
|
||||
author: {
|
||||
name: required(record(github.author, "services.decisionCenter.storage.github.author").name, "services.decisionCenter.storage.github.author.name"),
|
||||
email: required(record(github.author, "services.decisionCenter.storage.github.author").email, "services.decisionCenter.storage.github.author.email"),
|
||||
name: required(record(github.author, `${servicePath}.storage.github.author`).name, `${servicePath}.storage.github.author.name`),
|
||||
email: required(record(github.author, `${servicePath}.storage.github.author`).email, `${servicePath}.storage.github.author.email`),
|
||||
},
|
||||
ssh: {
|
||||
secretName: required(ssh.secretName, "services.decisionCenter.storage.github.ssh.secretName"),
|
||||
mountPath: required(ssh.mountPath, "services.decisionCenter.storage.github.ssh.mountPath"),
|
||||
privateKeyKey: required(privateKey.secretKey, "services.decisionCenter.storage.github.ssh.privateKey.secretKey"),
|
||||
knownHostsKey: required(knownHosts.secretKey, "services.decisionCenter.storage.github.ssh.knownHosts.secretKey"),
|
||||
secretName: required(ssh.secretName, `${servicePath}.storage.github.ssh.secretName`),
|
||||
mountPath: required(ssh.mountPath, `${servicePath}.storage.github.ssh.mountPath`),
|
||||
privateKeyKey: required(privateKey.secretKey, `${servicePath}.storage.github.ssh.privateKey.secretKey`),
|
||||
knownHostsKey: required(knownHosts.secretKey, `${servicePath}.storage.github.ssh.knownHosts.secretKey`),
|
||||
privateKeySource,
|
||||
knownHostsSource,
|
||||
fingerprint: sha(`${privateKeySource.fingerprint}\n${knownHostsSource.fingerprint}`),
|
||||
@@ -233,53 +239,65 @@ function readDecisionCenterStorage(decision) {
|
||||
};
|
||||
}
|
||||
|
||||
const decisionCenterDatabase = readDecisionCenterDatabase(decisionCenter);
|
||||
const decisionCenterStorage = readDecisionCenterStorage(decisionCenter);
|
||||
const effectiveMicroservicesJson = decisionCenter === null
|
||||
? required(runtimeConfig.microservicesJson, "runtimeConfig.microservicesJson")
|
||||
: JSON.stringify([
|
||||
{
|
||||
id: "decision-center",
|
||||
name: "Decision Center",
|
||||
providerId: required(target.id, "target.id"),
|
||||
description: "Decision Center is managed by the NC01 YAML-first k8s runtime for UniDesk decisions, requirements, meetings, and work diaries.",
|
||||
repository: {
|
||||
url: required(record(decisionCenter.repository, "services.decisionCenter.repository").url, "services.decisionCenter.repository.url"),
|
||||
commitId: required(runtime.commit, "runtime.commit"),
|
||||
dockerfile: required(record(decisionCenter.repository, "services.decisionCenter.repository").dockerfile, "services.decisionCenter.repository.dockerfile"),
|
||||
composeFile: required(runtime.deployRef, "runtime.deployRef"),
|
||||
composeService: required(decisionCenter.deploymentName, "services.decisionCenter.deploymentName"),
|
||||
containerName: required(decisionCenter.containerName, "services.decisionCenter.containerName"),
|
||||
},
|
||||
backend: {
|
||||
nodeBaseUrl: `http://${required(decisionCenter.serviceName, "services.decisionCenter.serviceName")}.${namespace}.svc.cluster.local:${required(String(decisionCenter.containerPort), "services.decisionCenter.containerPort")}`,
|
||||
nodeBindHost: `${required(decisionCenter.serviceName, "services.decisionCenter.serviceName")}.${namespace}.svc.cluster.local`,
|
||||
nodePort: positiveInteger(decisionCenter.containerPort, "services.decisionCenter.containerPort"),
|
||||
proxyMode: "cluster-service-http",
|
||||
frontendOnly: true,
|
||||
public: false,
|
||||
allowedMethods: ["GET", "HEAD", "POST", "PUT", "DELETE"],
|
||||
allowedPathPrefixes: ["/health", "/live", "/logs", "/api/"],
|
||||
healthPath: required(decisionCenter.healthPath, "services.decisionCenter.healthPath"),
|
||||
timeoutMs: 30000,
|
||||
},
|
||||
development: {
|
||||
providerId: required(target.id, "target.id"),
|
||||
sshPassthrough: true,
|
||||
worktreePath: required(record(decisionCenter.repository, "services.decisionCenter.repository").worktreePath, "services.decisionCenter.repository.worktreePath"),
|
||||
},
|
||||
frontend: {
|
||||
route: required(record(decisionCenter.frontend, "services.decisionCenter.frontend").route, "services.decisionCenter.frontend.route"),
|
||||
integrated: record(decisionCenter.frontend, "services.decisionCenter.frontend").integrated !== false,
|
||||
},
|
||||
deployment: {
|
||||
mode: "internal-sidecar",
|
||||
namespace,
|
||||
expectedNodeIds: [required(target.id, "target.id")],
|
||||
activeNodeId: required(target.id, "target.id"),
|
||||
},
|
||||
},
|
||||
]);
|
||||
function managedMicroservice(service, servicePath, id, name, description) {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
providerId: required(target.id, "target.id"),
|
||||
description,
|
||||
repository: {
|
||||
url: required(record(service.repository, `${servicePath}.repository`).url, `${servicePath}.repository.url`),
|
||||
commitId: required(runtime.commit, "runtime.commit"),
|
||||
dockerfile: required(record(service.repository, `${servicePath}.repository`).dockerfile, `${servicePath}.repository.dockerfile`),
|
||||
composeFile: required(runtime.deployRef, "runtime.deployRef"),
|
||||
composeService: required(service.deploymentName, `${servicePath}.deploymentName`),
|
||||
containerName: required(service.containerName, `${servicePath}.containerName`),
|
||||
},
|
||||
backend: {
|
||||
nodeBaseUrl: `http://${required(service.serviceName, `${servicePath}.serviceName`)}.${namespace}.svc.cluster.local:${required(String(service.containerPort), `${servicePath}.containerPort`)}`,
|
||||
nodeBindHost: `${required(service.serviceName, `${servicePath}.serviceName`)}.${namespace}.svc.cluster.local`,
|
||||
nodePort: positiveInteger(service.containerPort, `${servicePath}.containerPort`),
|
||||
proxyMode: "cluster-service-http",
|
||||
frontendOnly: true,
|
||||
public: false,
|
||||
allowedMethods: ["GET", "HEAD", "POST", "PUT", "DELETE"],
|
||||
allowedPathPrefixes: ["/health", "/live", "/logs", "/api/"],
|
||||
healthPath: required(service.healthPath, `${servicePath}.healthPath`),
|
||||
timeoutMs: 30000,
|
||||
},
|
||||
development: {
|
||||
providerId: required(target.id, "target.id"),
|
||||
sshPassthrough: true,
|
||||
worktreePath: required(record(service.repository, `${servicePath}.repository`).worktreePath, `${servicePath}.repository.worktreePath`),
|
||||
},
|
||||
frontend: {
|
||||
route: required(record(service.frontend, `${servicePath}.frontend`).route, `${servicePath}.frontend.route`),
|
||||
integrated: record(service.frontend, `${servicePath}.frontend`).integrated !== false,
|
||||
},
|
||||
deployment: {
|
||||
mode: "internal-sidecar",
|
||||
namespace,
|
||||
expectedNodeIds: [required(target.id, "target.id")],
|
||||
activeNodeId: required(target.id, "target.id"),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const decisionCenterDatabase = readServiceDatabase(decisionCenter, "services.decisionCenter");
|
||||
const decisionCenterStorage = readServiceStorage(decisionCenter, "services.decisionCenter");
|
||||
const todoNoteDatabase = readServiceDatabase(todoNote, "services.todoNote");
|
||||
const todoNoteStorage = readServiceStorage(todoNote, "services.todoNote");
|
||||
const configuredMicroservices = JSON.parse(required(runtimeConfig.microservicesJson, "runtimeConfig.microservicesJson"));
|
||||
if (!Array.isArray(configuredMicroservices)) throw new Error("runtimeConfig.microservicesJson must encode an array");
|
||||
const managedMicroservices = [
|
||||
...(decisionCenter === null ? [] : [managedMicroservice(decisionCenter, "services.decisionCenter", "decision-center", "Decision Center", "NC01 YAML-first Decision Center for decisions, requirements, meetings, and work diaries.")]),
|
||||
...(todoNote === null ? [] : [managedMicroservice(todoNote, "services.todoNote", "todo-note", "Todo Note", "NC01 YAML-first Todo Note backed by the shared decision-center-data GitHub repository.")]),
|
||||
];
|
||||
const managedIds = new Set(managedMicroservices.map((service) => service.id));
|
||||
const effectiveMicroservicesJson = JSON.stringify([
|
||||
...configuredMicroservices.filter((service) => typeof service !== "object" || service === null || !managedIds.has(service.id)),
|
||||
...managedMicroservices,
|
||||
]);
|
||||
const runtimeConfigFingerprint = sha(JSON.stringify({ ...runtimeConfig, microservicesJson: effectiveMicroservicesJson }));
|
||||
|
||||
const docs = [];
|
||||
@@ -331,39 +349,60 @@ data:
|
||||
MICROSERVICES_JSON: ${yamlScalar(effectiveMicroservicesJson)}
|
||||
NO_PROXY: ${yamlScalar(required(runtime.noProxy, "runtime.noProxy"))}
|
||||
no_proxy: ${yamlScalar(required(runtime.noProxy, "runtime.noProxy"))}`);
|
||||
if (decisionCenter !== null && decisionCenterDatabase !== null) {
|
||||
docs.push(`apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ${decisionCenterDatabase.secretName}
|
||||
namespace: ${namespace}
|
||||
labels:
|
||||
${indent(labels(config), 4)}
|
||||
app.kubernetes.io/name: decision-center
|
||||
annotations:
|
||||
unidesk.ai/source-ref: ${yamlScalar(decisionCenterDatabase.sourceRefPath)}
|
||||
unidesk.ai/fingerprint: ${yamlScalar(decisionCenterDatabase.fingerprint)}
|
||||
type: Opaque
|
||||
stringData:
|
||||
${decisionCenterDatabase.secretKey}: ${yamlScalar(decisionCenterDatabase.value)}`);
|
||||
const serviceDatabaseSecrets = new Map();
|
||||
for (const databaseSecret of [decisionCenterDatabase, todoNoteDatabase].filter(Boolean)) {
|
||||
const existing = serviceDatabaseSecrets.get(databaseSecret.secretName);
|
||||
if (existing !== undefined && (existing.secretKey !== databaseSecret.secretKey || existing.value !== databaseSecret.value)) {
|
||||
throw new Error(`conflicting managed service database Secret: ${databaseSecret.secretName}`);
|
||||
}
|
||||
serviceDatabaseSecrets.set(databaseSecret.secretName, databaseSecret);
|
||||
}
|
||||
if (decisionCenterStorage?.github !== undefined) {
|
||||
docs.push(`apiVersion: v1
|
||||
for (const databaseSecret of serviceDatabaseSecrets.values()) {
|
||||
docs.push(`apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ${decisionCenterStorage.github.ssh.secretName}
|
||||
name: ${databaseSecret.secretName}
|
||||
namespace: ${namespace}
|
||||
labels:
|
||||
${indent(labels(config), 4)}
|
||||
app.kubernetes.io/name: decision-center
|
||||
annotations:
|
||||
unidesk.ai/private-key-source-ref: ${yamlScalar(decisionCenterStorage.github.ssh.privateKeySource.sourceRefPath)}
|
||||
unidesk.ai/known-hosts-source-ref: ${yamlScalar(decisionCenterStorage.github.ssh.knownHostsSource.sourceRefPath)}
|
||||
unidesk.ai/fingerprint: ${yamlScalar(decisionCenterStorage.github.ssh.fingerprint)}
|
||||
unidesk.ai/source-ref: ${yamlScalar(databaseSecret.sourceRefPath)}
|
||||
unidesk.ai/fingerprint: ${yamlScalar(databaseSecret.fingerprint)}
|
||||
type: Opaque
|
||||
stringData:
|
||||
${decisionCenterStorage.github.ssh.privateKeyKey}: ${yamlScalar(decisionCenterStorage.github.ssh.privateKeySource.value)}
|
||||
${decisionCenterStorage.github.ssh.knownHostsKey}: ${yamlScalar(decisionCenterStorage.github.ssh.knownHostsSource.value)}`);
|
||||
${databaseSecret.secretKey}: ${yamlScalar(databaseSecret.value)}`);
|
||||
}
|
||||
const serviceGitSecrets = new Map();
|
||||
for (const storage of [decisionCenterStorage, todoNoteStorage]) {
|
||||
if (storage?.github === undefined) continue;
|
||||
const gitSecret = storage.github.ssh;
|
||||
const existing = serviceGitSecrets.get(gitSecret.secretName);
|
||||
if (existing !== undefined && (
|
||||
existing.privateKeyKey !== gitSecret.privateKeyKey
|
||||
|| existing.knownHostsKey !== gitSecret.knownHostsKey
|
||||
|| existing.privateKeySource.value !== gitSecret.privateKeySource.value
|
||||
|| existing.knownHostsSource.value !== gitSecret.knownHostsSource.value
|
||||
)) {
|
||||
throw new Error(`conflicting managed service Git SSH Secret: ${gitSecret.secretName}`);
|
||||
}
|
||||
serviceGitSecrets.set(gitSecret.secretName, gitSecret);
|
||||
}
|
||||
for (const gitSecret of serviceGitSecrets.values()) {
|
||||
docs.push(`apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ${gitSecret.secretName}
|
||||
namespace: ${namespace}
|
||||
labels:
|
||||
${indent(labels(config), 4)}
|
||||
annotations:
|
||||
unidesk.ai/private-key-source-ref: ${yamlScalar(gitSecret.privateKeySource.sourceRefPath)}
|
||||
unidesk.ai/known-hosts-source-ref: ${yamlScalar(gitSecret.knownHostsSource.sourceRefPath)}
|
||||
unidesk.ai/fingerprint: ${yamlScalar(gitSecret.fingerprint)}
|
||||
type: Opaque
|
||||
stringData:
|
||||
${gitSecret.privateKeyKey}: ${yamlScalar(gitSecret.privateKeySource.value)}
|
||||
${gitSecret.knownHostsKey}: ${yamlScalar(gitSecret.knownHostsSource.value)}`);
|
||||
}
|
||||
if (databaseMode === "k8s-postgres") {
|
||||
docs.push(`apiVersion: v1
|
||||
@@ -585,44 +624,77 @@ ${indent(resourceBlock(backend.resources), 10)}
|
||||
volumes:
|
||||
- name: logs
|
||||
emptyDir: {}`);
|
||||
if (decisionCenter !== null) {
|
||||
function managedStorageEnv(envPrefix, storage) {
|
||||
const lines = [
|
||||
`- name: ${envPrefix}_STORAGE_PRIMARY`,
|
||||
` value: ${yamlScalar(storage?.primary ?? "postgres")}`,
|
||||
];
|
||||
if (storage?.github === undefined) return lines.join("\n");
|
||||
lines.push(
|
||||
`- name: ${envPrefix}_GIT_REPO`,
|
||||
` value: ${yamlScalar(storage.github.repo)}`,
|
||||
`- name: ${envPrefix}_GIT_REPO_SSH_URL`,
|
||||
` value: ${yamlScalar(storage.github.sshUrl)}`,
|
||||
`- name: ${envPrefix}_GIT_BRANCH`,
|
||||
` value: ${yamlScalar(storage.github.branch)}`,
|
||||
`- name: ${envPrefix}_GIT_BASE_PATH`,
|
||||
` value: ${yamlScalar(storage.github.basePath)}`,
|
||||
`- name: ${envPrefix}_GIT_WORKTREE`,
|
||||
` value: ${yamlScalar(storage.github.worktreePath)}`,
|
||||
`- name: ${envPrefix}_GIT_AUTHOR_NAME`,
|
||||
` value: ${yamlScalar(storage.github.author.name)}`,
|
||||
`- name: ${envPrefix}_GIT_AUTHOR_EMAIL`,
|
||||
` value: ${yamlScalar(storage.github.author.email)}`,
|
||||
`- name: ${envPrefix}_GIT_COMMIT_PREFIX`,
|
||||
` value: ${yamlScalar(storage.github.commitMessagePrefix)}`,
|
||||
"- name: GIT_SSH_COMMAND",
|
||||
` value: ${yamlScalar(`ssh -i ${storage.github.ssh.mountPath}/${storage.github.ssh.privateKeyKey} -o UserKnownHostsFile=${storage.github.ssh.mountPath}/${storage.github.ssh.knownHostsKey} -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes`)}`,
|
||||
);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function appendManagedServiceDeployment({ service, servicePath, serviceId, imageKey, envPrefix, databaseConfig, storageConfig }) {
|
||||
if (service === null) return;
|
||||
if (deliveryServiceRef === servicePath) return;
|
||||
if (databaseConfig === null) throw new Error(`${servicePath}.database is required`);
|
||||
const github = storageConfig?.github;
|
||||
docs.push(`apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ${required(decisionCenter.serviceName, "services.decisionCenter.serviceName")}
|
||||
name: ${required(service.serviceName, `${servicePath}.serviceName`)}
|
||||
namespace: ${namespace}
|
||||
labels:
|
||||
${indent(labels(config), 4)}
|
||||
app.kubernetes.io/name: decision-center
|
||||
app.kubernetes.io/name: ${serviceId}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: decision-center
|
||||
app.kubernetes.io/name: ${serviceId}
|
||||
ports:
|
||||
- name: http
|
||||
port: ${positiveInteger(decisionCenter.containerPort, "services.decisionCenter.containerPort")}
|
||||
port: ${positiveInteger(service.containerPort, `${servicePath}.containerPort`)}
|
||||
targetPort: http`);
|
||||
docs.push(`apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ${required(decisionCenter.deploymentName, "services.decisionCenter.deploymentName")}
|
||||
name: ${required(service.deploymentName, `${servicePath}.deploymentName`)}
|
||||
namespace: ${namespace}
|
||||
labels:
|
||||
${indent(labels(config), 4)}
|
||||
app.kubernetes.io/name: decision-center
|
||||
app.kubernetes.io/name: ${serviceId}
|
||||
spec:
|
||||
replicas: 1
|
||||
replicas: ${positiveInteger(service.replicas, `${servicePath}.replicas`)}
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: decision-center
|
||||
app.kubernetes.io/name: ${serviceId}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: decision-center
|
||||
app.kubernetes.io/name: ${serviceId}
|
||||
app.kubernetes.io/part-of: unidesk
|
||||
annotations:
|
||||
unidesk.ai/secret-fingerprint: ${yamlScalar(decisionCenterDatabase?.fingerprint ?? "")}
|
||||
unidesk.ai/storage-secret-fingerprint: ${yamlScalar(decisionCenterStorage?.github?.ssh.fingerprint ?? "")}
|
||||
unidesk.ai/secret-fingerprint: ${yamlScalar(databaseConfig.fingerprint)}
|
||||
unidesk.ai/storage-secret-fingerprint: ${yamlScalar(github?.ssh.fingerprint ?? "")}
|
||||
unidesk.ai/runtime-config-fingerprint: ${yamlScalar(runtimeConfigFingerprint)}
|
||||
spec:
|
||||
initContainers:
|
||||
@@ -633,65 +705,48 @@ spec:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ${required(record(decisionCenter.database, "services.decisionCenter.database").secretName, "services.decisionCenter.database.secretName")}
|
||||
key: ${required(record(decisionCenter.database, "services.decisionCenter.database").secretKey, "services.decisionCenter.database.secretKey")}
|
||||
name: ${databaseConfig.secretName}
|
||||
key: ${databaseConfig.secretKey}
|
||||
command: ["sh", "-ec", "db_url=\\"$(printf '%s' \\"$DATABASE_URL\\" | sed 's/[?&]uselibpqcompat=true//g')\\"; until psql \\"$db_url\\" -Atqc 'SELECT 1' >/dev/null 2>&1; do sleep 2; done"]
|
||||
containers:
|
||||
- name: ${required(decisionCenter.containerName, "services.decisionCenter.containerName")}
|
||||
image: ${required(images.decisionCenter, "images.decisionCenter")}
|
||||
- name: ${required(service.containerName, `${servicePath}.containerName`)}
|
||||
image: ${required(images[imageKey], `images.${imageKey}`)}
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: ${positiveInteger(decisionCenter.containerPort, "services.decisionCenter.containerPort")}
|
||||
containerPort: ${positiveInteger(service.containerPort, `${servicePath}.containerPort`)}
|
||||
env:
|
||||
- name: HOST
|
||||
value: "0.0.0.0"
|
||||
- name: PORT
|
||||
value: ${yamlScalar(decisionCenter.containerPort)}
|
||||
value: ${yamlScalar(service.containerPort)}
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ${required(record(decisionCenter.database, "services.decisionCenter.database").secretName, "services.decisionCenter.database.secretName")}
|
||||
key: ${required(record(decisionCenter.database, "services.decisionCenter.database").secretKey, "services.decisionCenter.database.secretKey")}
|
||||
name: ${databaseConfig.secretName}
|
||||
key: ${databaseConfig.secretKey}
|
||||
- name: DATABASE_POOL_MAX
|
||||
value: "2"
|
||||
value: ${yamlScalar(required(runtimeConfig.databasePoolMax, "runtimeConfig.databasePoolMax"))}
|
||||
- name: UNIDESK_DEPLOY_SERVICE_ID
|
||||
value: "decision-center"
|
||||
value: ${yamlScalar(serviceId)}
|
||||
- name: UNIDESK_DEPLOY_REPO
|
||||
value: ${yamlScalar(required(record(decisionCenter.repository, "services.decisionCenter.repository").url, "services.decisionCenter.repository.url"))}
|
||||
value: ${yamlScalar(required(record(service.repository, `${servicePath}.repository`).url, `${servicePath}.repository.url`))}
|
||||
- name: UNIDESK_DEPLOY_COMMIT
|
||||
value: ${yamlScalar(required(runtime.commit, "runtime.commit"))}
|
||||
- name: UNIDESK_DEPLOY_REQUESTED_COMMIT
|
||||
value: ${yamlScalar(required(runtime.commit, "runtime.commit"))}
|
||||
- name: LOG_FILE
|
||||
value: ${yamlScalar(required(decisionCenter.logFile, "services.decisionCenter.logFile"))}
|
||||
- name: DECISION_CENTER_STORAGE_PRIMARY
|
||||
value: ${yamlScalar(decisionCenterStorage?.primary ?? "postgres")}
|
||||
${decisionCenterStorage?.github !== undefined ? indent(`- name: DECISION_CENTER_GIT_REPO_SSH_URL
|
||||
value: ${yamlScalar(decisionCenterStorage.github.sshUrl)}
|
||||
- name: DECISION_CENTER_GIT_BRANCH
|
||||
value: ${yamlScalar(decisionCenterStorage.github.branch)}
|
||||
- name: DECISION_CENTER_GIT_BASE_PATH
|
||||
value: ${yamlScalar(decisionCenterStorage.github.basePath)}
|
||||
- name: DECISION_CENTER_GIT_WORKTREE
|
||||
value: ${yamlScalar(decisionCenterStorage.github.worktreePath)}
|
||||
- name: DECISION_CENTER_GIT_AUTHOR_NAME
|
||||
value: ${yamlScalar(decisionCenterStorage.github.author.name)}
|
||||
- name: DECISION_CENTER_GIT_AUTHOR_EMAIL
|
||||
value: ${yamlScalar(decisionCenterStorage.github.author.email)}
|
||||
- name: DECISION_CENTER_GIT_COMMIT_PREFIX
|
||||
value: ${yamlScalar(decisionCenterStorage.github.commitMessagePrefix)}
|
||||
- name: GIT_SSH_COMMAND
|
||||
value: ${yamlScalar(`ssh -i ${decisionCenterStorage.github.ssh.mountPath}/${decisionCenterStorage.github.ssh.privateKeyKey} -o UserKnownHostsFile=${decisionCenterStorage.github.ssh.mountPath}/${decisionCenterStorage.github.ssh.knownHostsKey} -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes`)}`, 12) : ""}
|
||||
value: ${yamlScalar(required(service.logFile, `${servicePath}.logFile`))}
|
||||
${indent(managedStorageEnv(envPrefix, storageConfig), 12)}
|
||||
volumeMounts:
|
||||
- name: logs
|
||||
mountPath: /var/log/unidesk
|
||||
${decisionCenterStorage?.github !== undefined ? indent(`- name: github-ssh
|
||||
mountPath: ${yamlScalar(decisionCenterStorage.github.ssh.mountPath)}
|
||||
readOnly: true`, 12) : ""}
|
||||
${github === undefined ? "" : indent(`- name: github-ssh
|
||||
mountPath: ${yamlScalar(github.ssh.mountPath)}
|
||||
readOnly: true`, 12)}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: ${required(decisionCenter.healthPath, "services.decisionCenter.healthPath")}
|
||||
path: ${required(service.healthPath, `${servicePath}.healthPath`)}
|
||||
port: http
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
@@ -710,18 +765,34 @@ ${decisionCenterStorage?.github !== undefined ? indent(`- name: github-ssh
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 30
|
||||
${indent(resourceBlock(decisionCenter.resources), 10)}
|
||||
${indent(resourceBlock(service.resources), 10)}
|
||||
volumes:
|
||||
- name: logs
|
||||
emptyDir: {}`);
|
||||
if (decisionCenterStorage?.github !== undefined) {
|
||||
docs[docs.length - 1] += `
|
||||
- name: github-ssh
|
||||
secret:
|
||||
secretName: ${decisionCenterStorage.github.ssh.secretName}
|
||||
defaultMode: 0400`;
|
||||
}
|
||||
emptyDir: {}
|
||||
${github === undefined ? "" : indent(`- name: github-ssh
|
||||
secret:
|
||||
secretName: ${github.ssh.secretName}
|
||||
defaultMode: 0400`, 8)}`);
|
||||
}
|
||||
|
||||
appendManagedServiceDeployment({
|
||||
service: decisionCenter,
|
||||
servicePath: "services.decisionCenter",
|
||||
serviceId: "decision-center",
|
||||
imageKey: "decisionCenter",
|
||||
envPrefix: "DECISION_CENTER",
|
||||
databaseConfig: decisionCenterDatabase,
|
||||
storageConfig: decisionCenterStorage,
|
||||
});
|
||||
appendManagedServiceDeployment({
|
||||
service: todoNote,
|
||||
servicePath: "services.todoNote",
|
||||
serviceId: "todo-note",
|
||||
imageKey: "todoNote",
|
||||
envPrefix: "TODO_NOTE",
|
||||
databaseConfig: todoNoteDatabase,
|
||||
storageConfig: todoNoteStorage,
|
||||
});
|
||||
docs.push(`apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
|
||||
Reference in New Issue
Block a user