@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
|
|
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
|
@@ -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
|
|
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
|
|
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
|
+
};
|
package/src/delivery/runtime.js
CHANGED
|
@@ -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: ${
|
|
60
|
+
name: ${normalized.namespace}
|
|
44
61
|
labels:
|
|
45
|
-
oon.dev/app-code: ${
|
|
46
|
-
oon.dev/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:
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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(
|
|
87
|
-
const releaseAnnotation = JSON.stringify(String(
|
|
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: ${
|
|
92
|
-
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: ${
|
|
115
|
+
matchLabels: { app.kubernetes.io/name: ${normalized.appCode} }
|
|
97
116
|
template:
|
|
98
117
|
metadata:
|
|
99
118
|
labels:
|
|
100
|
-
app.kubernetes.io/name: ${
|
|
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:
|
|
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:
|
|
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: ${
|
|
145
|
+
metadata: { name: ${normalized.appCode}, namespace: ${normalized.namespace} }
|
|
127
146
|
spec:
|
|
128
|
-
selector: { app.kubernetes.io/name: ${
|
|
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: ${
|
|
135
|
-
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: [${
|
|
158
|
+
tls: [{ hosts: [${normalized.hostname}], secretName: ${identity.tlsSecret} }]
|
|
140
159
|
rules:
|
|
141
|
-
- host: ${
|
|
160
|
+
- host: ${normalized.hostname}
|
|
142
161
|
http:
|
|
143
162
|
paths:
|
|
144
163
|
- path: /
|
|
145
164
|
pathType: Prefix
|
|
146
165
|
backend:
|
|
147
|
-
service: { name: ${
|
|
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:
|
|
166
|
-
APP_CODE:
|
|
167
|
-
PUBLIC_APP_URL:
|
|
168
|
-
CENTRAL_ATIVACAO_URL:
|
|
169
|
-
CENTRAL_ATIVACAO_API_URL:
|
|
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",
|
|
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(
|
|
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:
|
|
229
|
-
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
|
-
|
|
242
|
-
|
|
271
|
+
normalized.namespace,
|
|
272
|
+
identity.secret,
|
|
243
273
|
"INSTANCE_CREDENTIAL_ENCRYPTION_KEY",
|
|
244
274
|
) || randomSecret(48);
|
|
245
|
-
applyRuntimeSecret({ context, mongoUri, encryptionKey, extraSecrets });
|
|
246
|
-
return {
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
|
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 (!
|
|
276
|
-
const response = await fetch(`${
|
|
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":
|
|
321
|
+
headers: { "Content-Type": "application/json", "Idempotency-Key": normalized.releaseId },
|
|
279
322
|
body: JSON.stringify({
|
|
280
|
-
codigoAtivacao:
|
|
281
|
-
nomeInstancia: `${
|
|
323
|
+
codigoAtivacao: normalized.activationCode,
|
|
324
|
+
nomeInstancia: `${normalized.appCode} dev`,
|
|
282
325
|
parametrosGlobais: {
|
|
283
|
-
nomeInstancia: `${
|
|
284
|
-
urlPublica:
|
|
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 = {
|
|
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
|
+
});
|