@oondemand/oon-core-back 0.6.8 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/delivery.js CHANGED
@@ -5,6 +5,7 @@ const fs = require("node:fs");
5
5
  const path = require("node:path");
6
6
  const { getContextWithPhase, setState, addEvidence } = require("../src/delivery/api");
7
7
  const { provisionRuntime, deployRuntime, waitHttp, activateMember } = require("../src/delivery/runtime");
8
+ const { canonicalizeDeliveryContext } = require("../src/delivery/technicalIdentity");
8
9
 
9
10
  function flag(name, fallback) {
10
11
  const args = process.argv.slice(3);
@@ -16,6 +17,7 @@ function flag(name, fallback) {
16
17
  }
17
18
  function required(value, name) { if (!value) throw new Error(`${name} obrigatório.`); return value; }
18
19
  function secret() { return required(process.env.OON_DELIVERY_SHARED_SECRET, "OON_DELIVERY_SHARED_SECRET"); }
20
+ function normalizedContext(value) { return { ...canonicalizeDeliveryContext(value) }; }
19
21
 
20
22
  async function main() {
21
23
  const command = process.argv[2];
@@ -25,7 +27,8 @@ async function main() {
25
27
  if (command === "context") {
26
28
  const phase = flag("--phase", "build");
27
29
  const output = required(flag("--output"), "--output");
28
- const context = await getContextWithPhase({ apiUrl: required(apiUrl, "--api"), releaseId: required(releaseId, "--release"), token: secret(), phase });
30
+ const fetched = await getContextWithPhase({ apiUrl: required(apiUrl, "--api"), releaseId: required(releaseId, "--release"), token: secret(), phase });
31
+ const context = normalizedContext(fetched);
29
32
  fs.writeFileSync(path.resolve(output), JSON.stringify(context, null, 2), { mode: 0o600 });
30
33
  return;
31
34
  }
@@ -40,7 +43,7 @@ async function main() {
40
43
  if (command === "deploy") {
41
44
  const contextFile = required(flag("--context"), "--context");
42
45
  const imageDigest = required(flag("--image-digest"), "--image-digest");
43
- const context = JSON.parse(fs.readFileSync(path.resolve(contextFile), "utf8"));
46
+ const context = normalizedContext(JSON.parse(fs.readFileSync(path.resolve(contextFile), "utf8")));
44
47
  let failureState = "falha_infra";
45
48
  try {
46
49
  await setState({ apiUrl, releaseId, token: secret(), etapa: "provisionando", imageDigest });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oondemand/oon-core-back",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/oondemand/oon-platform.git",
@@ -113,6 +113,10 @@ function technicalIdentity({ appCode, instanceUid, environment }) {
113
113
  const mongoUsername = `${base}-rw`;
114
114
  const serviceAccount = `${base}-runtime`;
115
115
  const secret = `${base}-runtime`;
116
+ const provisionJob = `${base}-provision`;
117
+ const frontendDnsLabel = base;
118
+ const backendDnsLabel = `${base}-api`;
119
+ const tlsSecret = `${base}-tls`;
116
120
 
117
121
  const lengths = {
118
122
  base: base.length,
@@ -120,6 +124,10 @@ function technicalIdentity({ appCode, instanceUid, environment }) {
120
124
  mongoUsername: mongoUsername.length,
121
125
  serviceAccount: serviceAccount.length,
122
126
  secret: secret.length,
127
+ provisionJob: provisionJob.length,
128
+ frontendDnsLabel: frontendDnsLabel.length,
129
+ backendDnsLabel: backendDnsLabel.length,
130
+ tlsSecret: tlsSecret.length,
123
131
  };
124
132
  const invalidLength = Object.entries(lengths).find(([, length]) => length > 63);
125
133
  if (invalidLength) {
@@ -141,6 +149,10 @@ function technicalIdentity({ appCode, instanceUid, environment }) {
141
149
  mongoUsername,
142
150
  serviceAccount,
143
151
  secret,
152
+ provisionJob,
153
+ frontendDnsLabel,
154
+ backendDnsLabel,
155
+ tlsSecret,
144
156
  });
145
157
  }
146
158
 
@@ -159,4 +171,4 @@ module.exports = Object.freeze({
159
171
  encodeInstanceUid,
160
172
  generateInstanceUid,
161
173
  technicalIdentity,
162
- });
174
+ });
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  const { mongo } = require("mongoose");
4
+ const { technicalIdentity } = require("../core/technicalIdentity");
4
5
 
5
6
  const DIGITALOCEAN_API = "https://api.digitalocean.com/v2";
6
7
  const DIGITALOCEAN_HOST_SUFFIX = ".mongo.ondigitalocean.com";
@@ -24,8 +25,19 @@ function dnsLabel(value, max = 63) {
24
25
  .replace(/-+$/, "");
25
26
  }
26
27
 
27
- function mongoIdentity(appCode, environment) {
28
+ function mongoIdentity(appCode, environment, { instanceUid } = {}) {
29
+ if (instanceUid != null) {
30
+ const identity = technicalIdentity({ appCode, instanceUid, environment });
31
+ return {
32
+ mode: "canonical",
33
+ identityVersion: identity.version,
34
+ instanceUid: identity.instanceUid,
35
+ dbName: identity.database,
36
+ username: identity.mongoUsername,
37
+ };
38
+ }
28
39
  return {
40
+ mode: "legacy",
29
41
  dbName: dnsLabel(`${appCode}_${environment}`, 48).replace(/-/g, "_"),
30
42
  username: dnsLabel(`${appCode}-${environment}`, 48),
31
43
  };
@@ -129,8 +141,9 @@ async function waitForMongoUri(
129
141
  );
130
142
  }
131
143
 
132
- async function provisionNativeMongo({ adminUri, appCode, environment }) {
133
- const { dbName, username } = mongoIdentity(appCode, environment);
144
+ async function provisionNativeMongo({ adminUri, appCode, environment, instanceUid }) {
145
+ const identity = mongoIdentity(appCode, environment, { instanceUid });
146
+ const { dbName, username } = identity;
134
147
  const password = randomSecret(30);
135
148
  const client = new mongo.MongoClient(adminUri, {
136
149
  serverSelectionTimeoutMS: 15000,
@@ -163,6 +176,9 @@ async function provisionNativeMongo({ adminUri, appCode, environment }) {
163
176
  return {
164
177
  dbName,
165
178
  username,
179
+ ...(identity.mode === "canonical"
180
+ ? { instanceUid: identity.instanceUid, technicalIdentityVersion: identity.identityVersion }
181
+ : {}),
166
182
  mongoUri: buildRuntimeMongoUri({
167
183
  adminUri,
168
184
  username,
@@ -383,6 +399,7 @@ async function provisionDigitalOceanMongo(
383
399
  adminUri,
384
400
  appCode,
385
401
  environment,
402
+ instanceUid,
386
403
  digitalOceanToken,
387
404
  digitalOceanClusterId,
388
405
  },
@@ -399,7 +416,8 @@ async function provisionDigitalOceanMongo(
399
416
  );
400
417
  }
401
418
 
402
- const { dbName, username } = mongoIdentity(appCode, environment);
419
+ const identity = mongoIdentity(appCode, environment, { instanceUid });
420
+ const { dbName, username } = identity;
403
421
  const apiOptions = {
404
422
  token: digitalOceanToken,
405
423
  clusterId: digitalOceanClusterId,
@@ -425,6 +443,9 @@ async function provisionDigitalOceanMongo(
425
443
  return {
426
444
  dbName,
427
445
  username: user.name || username,
446
+ ...(identity.mode === "canonical"
447
+ ? { instanceUid: identity.instanceUid, technicalIdentityVersion: identity.identityVersion }
448
+ : {}),
428
449
  mongoUri,
429
450
  provider: "digitalocean",
430
451
  };
@@ -462,4 +483,4 @@ module.exports = {
462
483
  provisionDigitalOceanUser,
463
484
  provisionDigitalOceanMongo,
464
485
  provisionMongo,
465
- };
486
+ };
@@ -5,6 +5,7 @@ const { spawnSync } = require("node:child_process");
5
5
  const { kubectl } = require("./command");
6
6
  const { validateInfraFromCluster } = require("../deploy/contracts/infra-contract");
7
7
  const { provisionMongo, validateMongoUri } = require("./mongoProvisioning");
8
+ const { canonicalizeDeliveryContext } = require("./technicalIdentity");
8
9
 
9
10
  const RESOURCE_PROFILES = {
10
11
  nano: { cpuRequest: "50m", cpuLimit: "250m", memoryRequest: "96Mi", memoryLimit: "256Mi" },
@@ -36,24 +37,40 @@ function readSecret(namespace, name, key) {
36
37
  } catch { return null; }
37
38
  }
38
39
 
40
+ function canonicalRuntimeContext(context) {
41
+ return canonicalizeDeliveryContext(context);
42
+ }
43
+
44
+ function identityLabels(identity) {
45
+ if (identity.mode !== "canonical") return "";
46
+ return `\n oon.runtime/instance-uid: ${identity.instanceUid}\n oon.runtime/identity-version: \"${identity.version}\"`;
47
+ }
48
+
49
+ function podIdentityLabels(identity) {
50
+ if (identity.mode !== "canonical") return "";
51
+ return `\n oon.runtime/instance-uid: ${identity.instanceUid}\n oon.runtime/identity-version: \"${identity.version}\"`;
52
+ }
53
+
39
54
  function baselineManifest({ context }) {
55
+ const normalized = canonicalRuntimeContext(context);
56
+ const identity = normalized.deliveryTechnicalIdentity;
40
57
  return `apiVersion: v1
41
58
  kind: Namespace
42
59
  metadata:
43
- name: ${context.namespace}
60
+ name: ${normalized.namespace}
44
61
  labels:
45
- oon.dev/app-code: ${context.appCode}
46
- oon.dev/environment: ${context.environment}
47
- app.kubernetes.io/managed-by: oon-platform
62
+ oon.dev/app-code: ${normalized.appCode}
63
+ oon.dev/environment: ${normalized.environment}
64
+ app.kubernetes.io/managed-by: oon-platform${identityLabels(identity)}
48
65
  ---
49
66
  apiVersion: v1
50
67
  kind: ServiceAccount
51
- metadata: { name: oon-runtime, namespace: ${context.namespace} }
68
+ metadata: { name: ${identity.serviceAccount}, namespace: ${normalized.namespace} }
52
69
  automountServiceAccountToken: false
53
70
  ---
54
71
  apiVersion: v1
55
72
  kind: LimitRange
56
- metadata: { name: oon-runtime-defaults, namespace: ${context.namespace} }
73
+ metadata: { name: oon-runtime-defaults, namespace: ${normalized.namespace} }
57
74
  spec:
58
75
  limits:
59
76
  - type: Container
@@ -62,13 +79,13 @@ spec:
62
79
  ---
63
80
  apiVersion: v1
64
81
  kind: ResourceQuota
65
- metadata: { name: oon-runtime-quota, namespace: ${context.namespace} }
82
+ metadata: { name: oon-runtime-quota, namespace: ${normalized.namespace} }
66
83
  spec:
67
84
  hard: { requests.cpu: "2", requests.memory: 2Gi, limits.cpu: "4", limits.memory: 4Gi, pods: "20", services: "10" }
68
85
  ---
69
86
  apiVersion: networking.k8s.io/v1
70
87
  kind: NetworkPolicy
71
- metadata: { name: oon-runtime-baseline, namespace: ${context.namespace} }
88
+ metadata: { name: oon-runtime-baseline, namespace: ${normalized.namespace} }
72
89
  spec:
73
90
  podSelector: {}
74
91
  policyTypes: [Ingress, Egress]
@@ -82,35 +99,37 @@ spec:
82
99
  }
83
100
 
84
101
  function runtimeManifest({ context, infra, imageRef, profile = "small" }) {
102
+ const normalized = canonicalRuntimeContext(context);
103
+ const identity = normalized.deliveryTechnicalIdentity;
85
104
  const resources = RESOURCE_PROFILES[profile] || RESOURCE_PROFILES.small;
86
- const releaseLabel = kubernetesLabelValue(context.releaseId);
87
- const releaseAnnotation = JSON.stringify(String(context.releaseId));
105
+ const releaseLabel = kubernetesLabelValue(normalized.releaseId);
106
+ const releaseAnnotation = JSON.stringify(String(normalized.releaseId));
88
107
  return `apiVersion: apps/v1
89
108
  kind: Deployment
90
109
  metadata:
91
- name: ${context.appCode}
92
- namespace: ${context.namespace}
110
+ name: ${normalized.appCode}
111
+ namespace: ${normalized.namespace}
93
112
  spec:
94
113
  replicas: 1
95
114
  selector:
96
- matchLabels: { app.kubernetes.io/name: ${context.appCode} }
115
+ matchLabels: { app.kubernetes.io/name: ${normalized.appCode} }
97
116
  template:
98
117
  metadata:
99
118
  labels:
100
- app.kubernetes.io/name: ${context.appCode}
119
+ app.kubernetes.io/name: ${normalized.appCode}
101
120
  app.kubernetes.io/part-of: oon-central
102
- oon.dev/release-id: ${releaseLabel}
121
+ oon.dev/release-id: ${releaseLabel}${podIdentityLabels(identity)}
103
122
  annotations:
104
123
  oon.dev/release-id: ${releaseAnnotation}
105
124
  spec:
106
- serviceAccountName: oon-runtime
125
+ serviceAccountName: ${identity.serviceAccount}
107
126
  imagePullSecrets: [{ name: ${infra.REGISTRY_PULL_SECRET_NAME || "ghcr-oon"} }]
108
127
  containers:
109
128
  - name: central
110
129
  image: ${imageRef}
111
130
  imagePullPolicy: IfNotPresent
112
131
  ports: [{ name: http, containerPort: 8080 }]
113
- envFrom: [{ secretRef: { name: oon-runtime } }]
132
+ envFrom: [{ secretRef: { name: ${identity.secret} } }]
114
133
  resources:
115
134
  requests: { cpu: ${resources.cpuRequest}, memory: ${resources.memoryRequest} }
116
135
  limits: { cpu: ${resources.cpuLimit}, memory: ${resources.memoryLimit} }
@@ -123,28 +142,28 @@ spec:
123
142
  ---
124
143
  apiVersion: v1
125
144
  kind: Service
126
- metadata: { name: ${context.appCode}, namespace: ${context.namespace} }
145
+ metadata: { name: ${normalized.appCode}, namespace: ${normalized.namespace} }
127
146
  spec:
128
- selector: { app.kubernetes.io/name: ${context.appCode} }
147
+ selector: { app.kubernetes.io/name: ${normalized.appCode} }
129
148
  ports: [{ name: http, port: 80, targetPort: http }]
130
149
  ---
131
150
  apiVersion: networking.k8s.io/v1
132
151
  kind: Ingress
133
152
  metadata:
134
- name: ${context.appCode}
135
- namespace: ${context.namespace}
153
+ name: ${normalized.appCode}
154
+ namespace: ${normalized.namespace}
136
155
  annotations: { cert-manager.io/cluster-issuer: ${infra.CLUSTER_ISSUER} }
137
156
  spec:
138
157
  ingressClassName: ${infra.INGRESS_CLASS}
139
- tls: [{ hosts: [${context.hostname}], secretName: ${context.appCode}-tls }]
158
+ tls: [{ hosts: [${normalized.hostname}], secretName: ${identity.tlsSecret} }]
140
159
  rules:
141
- - host: ${context.hostname}
160
+ - host: ${normalized.hostname}
142
161
  http:
143
162
  paths:
144
163
  - path: /
145
164
  pathType: Prefix
146
165
  backend:
147
- service: { name: ${context.appCode}, port: { name: http } }
166
+ service: { name: ${normalized.appCode}, port: { name: http } }
148
167
  `;
149
168
  }
150
169
 
@@ -159,26 +178,36 @@ function copyPullSecret(namespace, infra) {
159
178
  }
160
179
 
161
180
  function applyRuntimeSecret({ context, mongoUri, encryptionKey, extraSecrets = {} }) {
181
+ const normalized = canonicalRuntimeContext(context);
182
+ const identity = normalized.deliveryTechnicalIdentity;
162
183
  const literals = {
163
184
  MONGO_URI: mongoUri,
164
185
  NODE_ENV: "production",
165
- APP_ENVIRONMENT: context.environment,
166
- APP_CODE: context.appCode,
167
- PUBLIC_APP_URL: context.publicUrl,
168
- CENTRAL_ATIVACAO_URL: context.activationApiUrl,
169
- CENTRAL_ATIVACAO_API_URL: context.activationApiUrl,
186
+ APP_ENVIRONMENT: normalized.environment,
187
+ APP_CODE: normalized.appCode,
188
+ PUBLIC_APP_URL: normalized.publicUrl,
189
+ CENTRAL_ATIVACAO_URL: normalized.activationApiUrl,
190
+ CENTRAL_ATIVACAO_API_URL: normalized.activationApiUrl,
170
191
  INSTANCE_CREDENTIAL_ENCRYPTION_KEY: encryptionKey,
192
+ ...(identity.mode === "canonical"
193
+ ? {
194
+ OON_INSTANCE_UID: identity.instanceUid,
195
+ OON_TECHNICAL_IDENTITY_VERSION: String(identity.version),
196
+ }
197
+ : {}),
171
198
  ...extraSecrets,
172
199
  };
173
- const args = ["-n", context.namespace, "create", "secret", "generic", "oon-runtime"];
200
+ const args = ["-n", normalized.namespace, "create", "secret", "generic", identity.secret];
174
201
  for (const [key, value] of Object.entries(literals)) args.push(`--from-literal=${key}=${value}`);
175
202
  args.push("--dry-run=client", "-o", "yaml");
176
203
  applyYaml(kubectl(args, { capture: true }));
177
204
  }
178
205
 
179
206
  async function provisionRuntime({ context, extraSecrets = {} }) {
207
+ const normalized = canonicalRuntimeContext(context);
208
+ const identity = normalized.deliveryTechnicalIdentity;
180
209
  const infra = validateInfraFromCluster().config;
181
- applyYaml(baselineManifest({ context, infra }));
210
+ applyYaml(baselineManifest({ context: normalized, infra }));
182
211
 
183
212
  const platformNamespace = infra.PLATFORM_NAMESPACE || "oon-system";
184
213
  const adminSecretName = infra.MONGO_ADMIN_SECRET_NAME || "oon-mongo-admin";
@@ -205,7 +234,7 @@ async function provisionRuntime({ context, extraSecrets = {} }) {
205
234
  "DIGITALOCEAN_DATABASE_CLUSTER_ID",
206
235
  );
207
236
 
208
- let mongoUri = readSecret(context.namespace, "oon-runtime", "MONGO_URI");
237
+ let mongoUri = readSecret(normalized.namespace, identity.secret, "MONGO_URI");
209
238
  let databaseName = "";
210
239
  let credentialRotated = false;
211
240
 
@@ -225,8 +254,9 @@ async function provisionRuntime({ context, extraSecrets = {} }) {
225
254
  if (!mongoUri) {
226
255
  const provisioned = await provisionMongo({
227
256
  adminUri,
228
- appCode: context.appCode,
229
- environment: context.environment,
257
+ appCode: normalized.appCode,
258
+ environment: normalized.environment,
259
+ ...(identity.mode === "canonical" ? { instanceUid: identity.instanceUid } : {}),
230
260
  provisioner: mongoProvisioner,
231
261
  digitalOceanToken,
232
262
  digitalOceanClusterId,
@@ -238,20 +268,32 @@ async function provisionRuntime({ context, extraSecrets = {} }) {
238
268
 
239
269
  const encryptionKey =
240
270
  readSecret(
241
- context.namespace,
242
- "oon-runtime",
271
+ normalized.namespace,
272
+ identity.secret,
243
273
  "INSTANCE_CREDENTIAL_ENCRYPTION_KEY",
244
274
  ) || randomSecret(48);
245
- applyRuntimeSecret({ context, mongoUri, encryptionKey, extraSecrets });
246
- return { infra, databaseName, credentialRotated };
275
+ applyRuntimeSecret({ context: normalized, mongoUri, encryptionKey, extraSecrets });
276
+ return {
277
+ infra,
278
+ databaseName,
279
+ credentialRotated,
280
+ ...(identity.mode === "canonical" ? { technicalIdentity: identity } : {}),
281
+ };
247
282
  }
248
283
 
249
284
  function deployRuntime({ context, infra, imageDigest, resourceProfile = "small" }) {
250
- copyPullSecret(context.namespace, infra);
251
- const imageRef = `${context.imageRepository}@${imageDigest}`;
252
- applyYaml(runtimeManifest({ context, infra, imageRef, profile: resourceProfile }));
253
- kubectl(["-n", context.namespace, "rollout", "status", `deployment/${context.appCode}`, "--timeout=10m"]);
254
- return { infraVersion: infra.INFRA_SCHEMA_VERSION, imageRef };
285
+ const normalized = canonicalRuntimeContext(context);
286
+ copyPullSecret(normalized.namespace, infra);
287
+ const imageRef = `${normalized.imageRepository}@${imageDigest}`;
288
+ applyYaml(runtimeManifest({ context: normalized, infra, imageRef, profile: resourceProfile }));
289
+ kubectl(["-n", normalized.namespace, "rollout", "status", `deployment/${normalized.appCode}`, "--timeout=10m"]);
290
+ return {
291
+ infraVersion: infra.INFRA_SCHEMA_VERSION,
292
+ imageRef,
293
+ ...(normalized.deliveryTechnicalIdentity.mode === "canonical"
294
+ ? { technicalIdentity: normalized.deliveryTechnicalIdentity }
295
+ : {}),
296
+ };
255
297
  }
256
298
 
257
299
  async function waitHttp(url, { timeoutMs = 300000, expectedStatus = 200 } = {}) {
@@ -269,19 +311,20 @@ async function waitHttp(url, { timeoutMs = 300000, expectedStatus = 200 } = {})
269
311
  }
270
312
 
271
313
  async function activateMember(context) {
272
- const statusResponse = await waitHttp(`${context.publicUrl}/api/ativacao/status`);
314
+ const normalized = canonicalRuntimeContext(context);
315
+ const statusResponse = await waitHttp(`${normalized.publicUrl}/api/ativacao/status`);
273
316
  const status = await statusResponse.json();
274
317
  if (status.status === "ativa") return { alreadyActive: true, instanceId: status.instanceId };
275
- if (!context.activationCode) throw new Error("Instância não está ativa e não há código de ativação autorizado.");
276
- const response = await fetch(`${context.publicUrl}/api/ativacao/concluir`, {
318
+ if (!normalized.activationCode) throw new Error("Instância não está ativa e não há código de ativação autorizado.");
319
+ const response = await fetch(`${normalized.publicUrl}/api/ativacao/concluir`, {
277
320
  method: "POST",
278
- headers: { "Content-Type": "application/json", "Idempotency-Key": context.releaseId },
321
+ headers: { "Content-Type": "application/json", "Idempotency-Key": normalized.releaseId },
279
322
  body: JSON.stringify({
280
- codigoAtivacao: context.activationCode,
281
- nomeInstancia: `${context.appCode} dev`,
323
+ codigoAtivacao: normalized.activationCode,
324
+ nomeInstancia: `${normalized.appCode} dev`,
282
325
  parametrosGlobais: {
283
- nomeInstancia: `${context.appCode} dev`, ambiente: context.environment,
284
- urlPublica: context.publicUrl, emailAdministrativo: context.administrativeEmail,
326
+ nomeInstancia: `${normalized.appCode} dev`, ambiente: normalized.environment,
327
+ urlPublica: normalized.publicUrl, emailAdministrativo: normalized.administrativeEmail,
285
328
  idioma: "pt-BR", timezone: "America/Sao_Paulo", moeda: "BRL", pais: "BR", formatoData: "DD/MM/YYYY",
286
329
  },
287
330
  parametrosAplicacao: {},
@@ -292,4 +335,18 @@ async function activateMember(context) {
292
335
  return data;
293
336
  }
294
337
 
295
- module.exports = { RESOURCE_PROFILES, randomSecret, dnsLabel, kubernetesLabelValue, provisionMongo, validateMongoUri, baselineManifest, runtimeManifest, provisionRuntime, deployRuntime, waitHttp, activateMember };
338
+ module.exports = {
339
+ RESOURCE_PROFILES,
340
+ randomSecret,
341
+ dnsLabel,
342
+ kubernetesLabelValue,
343
+ canonicalRuntimeContext,
344
+ provisionMongo,
345
+ validateMongoUri,
346
+ baselineManifest,
347
+ runtimeManifest,
348
+ provisionRuntime,
349
+ deployRuntime,
350
+ waitHttp,
351
+ activateMember,
352
+ };
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+
3
+ const technical = require("../core/technicalIdentity");
4
+
5
+ const SUPPORTED_DELIVERY_IDENTITY_VERSION = 1;
6
+ const CANONICAL_FIELDS = Object.freeze([
7
+ "version",
8
+ "appCode",
9
+ "instanceUid",
10
+ "environment",
11
+ "resourceBase",
12
+ "namespace",
13
+ "database",
14
+ "mongoUsername",
15
+ "serviceAccount",
16
+ "secret",
17
+ "provisionJob",
18
+ "frontendDnsLabel",
19
+ "backendDnsLabel",
20
+ "tlsSecret",
21
+ ]);
22
+ const CANONICAL_SIGNAL_FIELDS = Object.freeze([
23
+ "instanceUid",
24
+ "technicalIdentityVersion",
25
+ "technicalInstanceId",
26
+ "technicalIdentity",
27
+ ]);
28
+
29
+ function deliveryIdentityError(code, message, details = {}) {
30
+ const error = new Error(message);
31
+ error.name = "DeliveryTechnicalIdentityError";
32
+ error.code = code;
33
+ error.details = details;
34
+ return error;
35
+ }
36
+
37
+ function hasOwn(object, field) {
38
+ return Object.prototype.hasOwnProperty.call(object || {}, field);
39
+ }
40
+
41
+ function hasCanonicalIdentitySignal(context = {}) {
42
+ return CANONICAL_SIGNAL_FIELDS.some((field) => hasOwn(context, field));
43
+ }
44
+
45
+ function legacyDeliveryIdentity(context = {}) {
46
+ return Object.freeze({
47
+ mode: "legacy",
48
+ version: 0,
49
+ appCode: String(context.appCode || ""),
50
+ environment: String(context.environment || ""),
51
+ namespace: String(context.namespace || ""),
52
+ serviceAccount: "oon-runtime",
53
+ secret: "oon-runtime",
54
+ tlsSecret: `${String(context.appCode || "")}-tls`,
55
+ });
56
+ }
57
+
58
+ function assertProjection(canonical, context = {}) {
59
+ const mismatches = [];
60
+ if (context.namespace != null && String(context.namespace) !== canonical.namespace) {
61
+ mismatches.push("namespace");
62
+ }
63
+
64
+ if (hasOwn(context, "technicalIdentity")) {
65
+ const projected = context.technicalIdentity;
66
+ if (!projected || typeof projected !== "object" || Array.isArray(projected)) {
67
+ throw deliveryIdentityError(
68
+ "DELIVERY_TECHNICAL_IDENTITY_PROJECTION_INVALID",
69
+ "A projeção da identidade técnica do delivery é inválida.",
70
+ );
71
+ }
72
+ for (const field of CANONICAL_FIELDS) {
73
+ if (projected[field] == null) continue;
74
+ if (String(projected[field]) !== String(canonical[field])) mismatches.push(field);
75
+ }
76
+ }
77
+
78
+ if (mismatches.length) {
79
+ throw deliveryIdentityError(
80
+ "DELIVERY_TECHNICAL_IDENTITY_MISMATCH",
81
+ "O contexto de delivery diverge da identidade técnica canônica.",
82
+ { mismatches: [...new Set(mismatches)] },
83
+ );
84
+ }
85
+ }
86
+
87
+ function resolveDeliveryTechnicalIdentity(context = {}, { requireCanonical = false } = {}) {
88
+ if (!hasCanonicalIdentitySignal(context)) {
89
+ if (requireCanonical) {
90
+ throw deliveryIdentityError(
91
+ "DELIVERY_TECHNICAL_IDENTITY_REQUIRED",
92
+ "O contexto de delivery exige identidade técnica canônica.",
93
+ );
94
+ }
95
+ return legacyDeliveryIdentity(context);
96
+ }
97
+
98
+ if (context.instanceUid == null || context.technicalIdentityVersion == null) {
99
+ throw deliveryIdentityError(
100
+ "DELIVERY_TECHNICAL_IDENTITY_INCOMPLETE",
101
+ "O contexto de delivery possui identidade técnica parcial.",
102
+ );
103
+ }
104
+ if (Number(context.technicalIdentityVersion) !== SUPPORTED_DELIVERY_IDENTITY_VERSION) {
105
+ throw deliveryIdentityError(
106
+ "DELIVERY_TECHNICAL_IDENTITY_VERSION_UNSUPPORTED",
107
+ "A versão da identidade técnica do delivery não é suportada.",
108
+ { received: context.technicalIdentityVersion, supported: SUPPORTED_DELIVERY_IDENTITY_VERSION },
109
+ );
110
+ }
111
+
112
+ const canonical = technical.technicalIdentity({
113
+ appCode: context.appCode,
114
+ instanceUid: context.instanceUid,
115
+ environment: context.environment,
116
+ });
117
+ assertProjection(canonical, context);
118
+ return Object.freeze({ mode: "canonical", ...canonical });
119
+ }
120
+
121
+ function canonicalizeDeliveryContext(context = {}, options = {}) {
122
+ const identity = resolveDeliveryTechnicalIdentity(context, options);
123
+ if (identity.mode !== "canonical") {
124
+ return Object.freeze({ ...context, deliveryTechnicalIdentity: identity });
125
+ }
126
+ return Object.freeze({
127
+ ...context,
128
+ namespace: identity.namespace,
129
+ deliveryTechnicalIdentity: identity,
130
+ });
131
+ }
132
+
133
+ module.exports = Object.freeze({
134
+ CANONICAL_FIELDS,
135
+ CANONICAL_SIGNAL_FIELDS,
136
+ SUPPORTED_DELIVERY_IDENTITY_VERSION,
137
+ canonicalizeDeliveryContext,
138
+ hasCanonicalIdentitySignal,
139
+ resolveDeliveryTechnicalIdentity,
140
+ });