@docupace/pipeline 1.0.9 → 1.0.10-alpha.1

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/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@docupace/pipeline",
3
- "version": "1.0.9",
3
+ "version": "1.0.10-alpha.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "dp-changeset-cli": "src/changeset/cli.js",
9
- "dp-next-cli": "src/next/cli.js"
9
+ "dp-next-cli": "src/next/cli.js",
10
+ "dp-auth0-cli": "src/auth0/cli.js"
10
11
  },
11
12
  "keywords": [
12
13
  "bash"
@@ -15,6 +16,7 @@
15
16
  "license": "ISC",
16
17
  "dependencies": {
17
18
  "@changesets/cli": "^2.27.10",
19
+ "auth0-deploy-cli": "^7.24.3",
18
20
  "axios": "^1.7.9",
19
21
  "commander": "^12.1.0",
20
22
  "dotenv": "^16.4.7",
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import {
5
+ configureFromSite
6
+ } from './configure.js';
7
+ import {
8
+ getVaultSecrets
9
+ } from './vault.js'
10
+
11
+ import dotenv from 'dotenv';
12
+
13
+ dotenv.config({ path: ['.env.pipeline.local', 'config/.env.pipeline.local'] });
14
+
15
+ const program = new Command();
16
+
17
+ program
18
+ .command('configureFromSite <fromSite> <toSite> <env> <deployUI> <deployGraphQLServer> <useNewDomain>')
19
+ .description('Configure the Auth0 tenants in env for the given site')
20
+ .action(async (fromSite, toSite, env, deployUI, deployGraphQLServer, useNewDomain) => {
21
+ await configureFromSite(fromSite, toSite, env, deployUI, deployGraphQLServer, useNewDomain);
22
+ });
23
+
24
+ program
25
+ .command('getVaultSecrets <vaultPath>')
26
+ .description('Get Secrets for Vault PATH')
27
+ .action(async (vaultPath) => {
28
+ await getVaultSecrets(vaultPath);
29
+ });
30
+
31
+ program.parse(process.argv);
@@ -0,0 +1,607 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import yaml from 'js-yaml';
4
+ import { $ } from 'zx';
5
+ import axios from 'axios';
6
+ import crypto from 'crypto';
7
+ import { deploy } from 'auth0-deploy-cli';
8
+
9
+ import {
10
+ cloneEnvRepository,
11
+ cloneRepository,
12
+ readDeployConfig,
13
+ pushDeployConfig
14
+ } from '../next/deploy.js';
15
+
16
+ import {
17
+ getVaultSecrets,
18
+ updateVaultSecrets
19
+ } from './vault.js';
20
+
21
+ import {
22
+ getVaultSecretsConfigs,
23
+ environmentConfig
24
+ } from '../next/config.js';
25
+
26
+ async function ensureDirectoryExists(dirPath) {
27
+ try {
28
+ await fs.promises.mkdir(dirPath, { recursive: true });
29
+ console.log(`Directory created or already exists: ${dirPath}`);
30
+ } catch (err) {
31
+ console.error(`Failed to create directory: ${err.message}`);
32
+ }
33
+ }
34
+
35
+ async function addApplicationToApi ({application, auth0Domain, auth0AuthToken, coreApiIdentifier}){
36
+ console.log("Adding application to API authorized applications...");
37
+
38
+ try {
39
+ const auth0Response = await axios.get(`https://${auth0Domain}/api/v2/clients`, {
40
+ headers: {authorization: `Bearer ${auth0AuthToken}`},
41
+ });
42
+
43
+ const appAuth0ClientIdData = auth0Response.data.find(client => client.name === `${application}`);
44
+ const appAuth0ClientId = appAuth0ClientIdData.client_id;
45
+
46
+ await axios.post(
47
+ `https://${auth0Domain}/api/v2/client-grants`,
48
+ {
49
+ client_id: appAuth0ClientId,
50
+ audience: `${coreApiIdentifier}`,
51
+ scope: [],
52
+ },
53
+ {
54
+ headers: {
55
+ Authorization: `Bearer ${auth0AuthToken}`,
56
+ "Content-Type": "application/json",
57
+ },
58
+ }
59
+ );
60
+ console.log(`Application (${appAuth0ClientId}) authorized for API: ${coreApiIdentifier}`);
61
+ } catch (err) {
62
+ console.error(`Failed to add application: ${application}: ${err.message}`);
63
+ }
64
+
65
+ }
66
+
67
+ async function initializeAuth0Configuration({
68
+ initConfiguration,
69
+ auth0Domain,
70
+ auth0AuthToken,
71
+ coreApiIdentifier,
72
+ }) {
73
+ if (initConfiguration === "true") {
74
+ try {
75
+ // Create a new resource server in Auth0
76
+ console.log("Creating resource server in Auth0...");
77
+
78
+ const resourceServerResponse = await axios.post(
79
+ `https://${auth0Domain}/api/v2/resource-servers`,
80
+ {
81
+ name: "docupace-core",
82
+ identifier: `${coreApiIdentifier}`,
83
+ token_lifetime: 86400,
84
+ allow_offline_access: false,
85
+ signing_alg: "RS256",
86
+ skip_consent_for_verifiable_first_party_clients: true,
87
+ scopes: [],
88
+ },
89
+ {
90
+ headers: {
91
+ Authorization: `Bearer ${auth0AuthToken}`,
92
+ "Content-Type": "application/json",
93
+ },
94
+ }
95
+ );
96
+
97
+ const apiId = resourceServerResponse.data.id;
98
+
99
+ // Update tenant settings with the default audience
100
+ console.log("Updating tenant settings...");
101
+ await axios.patch(
102
+ `https://${auth0Domain}/api/v2/tenants/settings`,
103
+ {
104
+ default_audience: `${coreApiIdentifier}`,
105
+ },
106
+ {
107
+ headers: {
108
+ Authorization: `Bearer ${auth0AuthToken}`,
109
+ "Content-Type": "application/json",
110
+ },
111
+ }
112
+ );
113
+ console.log("Tenant settings updated successfully.");
114
+ } catch (error) {
115
+ console.error("Error during Auth0 configuration:", error.response?.data || error.message);
116
+ }
117
+ }
118
+ }
119
+
120
+ async function importAuth0Templates(directoryPath, auth0Domain, auth0ClientID, auth0ClientSecret) {
121
+ try {
122
+ const config = {
123
+ input_file: directoryPath,
124
+ config: {
125
+ AUTH0_DOMAIN: auth0Domain,
126
+ AUTH0_CLIENT_ID: auth0ClientID,
127
+ AUTH0_CLIENT_SECRET: auth0ClientSecret,
128
+ },
129
+ debug: true,
130
+ };
131
+
132
+ console.log('Starting Auth0 template import...');
133
+ await deploy(config);
134
+ console.log('Auth0 template import completed successfully!');
135
+ } catch (error) {
136
+ console.error('Error during Auth0 template import:', error.message);
137
+ process.exit(1); // Exit with an error code
138
+ }
139
+ }
140
+
141
+ async function updateJsonFile(filePath, updates) {
142
+ const content = await fs.promises.readFile(filePath, 'utf8');
143
+ const jsonContent = JSON.parse(content);
144
+ const updatedContent = JSON.stringify({...jsonContent, ...updates}, null, 2);
145
+ await fs.promises.writeFile(filePath, updatedContent);
146
+ }
147
+
148
+ async function replaceInFile(filePath, replacements) {
149
+ let content = await fs.promises.readFile(filePath, 'utf8');
150
+
151
+ for (const [search, replace] of replacements) {
152
+ const escapedSearch = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); // Escape special chars
153
+ content = content.replace(new RegExp(escapedSearch, 'g'), replace);
154
+ }
155
+ await fs.promises.writeFile(filePath, content, 'utf8');
156
+ }
157
+
158
+ async function templateAuth0(siteUrl, coreApiContext, env, filePaths, deployUI, deployGraphQLServer, commonSecrets) {
159
+ try {
160
+ let uiAllowedCallbacksUrls,
161
+ uiAllowedLogoutUrls;
162
+ let graphqlAllowedCallbacksUrls,
163
+ graphqlAllowedLogoutUrls;
164
+
165
+ // Configure URLs based on environment
166
+ if (env === 'prd') {
167
+ uiAllowedCallbacksUrls = [`https://${siteUrl}/api/auth/callback`];
168
+ uiAllowedLogoutUrls = [`https://${siteUrl}`];
169
+
170
+ graphqlAllowedCallbacksUrls = [`https://${siteUrl}/api/graphql/callback`];
171
+ graphqlAllowedLogoutUrls = [`https://${siteUrl}/api/graphql/`];
172
+ } else {
173
+ uiAllowedCallbacksUrls = [
174
+ `https://${siteUrl}/api/auth/callback`,
175
+ `http://localhost:3000/api/auth/callback`,
176
+ ];
177
+ uiAllowedLogoutUrls = [
178
+ `https://${siteUrl}`,
179
+ `http://localhost:3000`,
180
+ ];
181
+
182
+ graphqlAllowedCallbacksUrls = [
183
+ `https://${siteUrl}/api/graphql/callback`,
184
+ `http://localhost:4000/api/graphql/callback`,
185
+ ];
186
+ graphqlAllowedLogoutUrls = [
187
+ `http://localhost:4000/api/graphql/`,
188
+ `https://${siteUrl}/api/graphql/`,
189
+ ];
190
+ }
191
+
192
+ const configureUI = deployUI === 'true';
193
+ if (configureUI) {
194
+ console.log('ui_allowed_callbacks_urls ->', uiAllowedCallbacksUrls);
195
+ console.log('ui_allowed_logout_urls ->', uiAllowedLogoutUrls);
196
+
197
+ await updateJsonFile(filePaths.uiJson, {
198
+ callbacks: uiAllowedCallbacksUrls,
199
+ allowed_logout_urls: uiAllowedLogoutUrls,
200
+ });
201
+ console.log('Updated URLs for main UI...');
202
+ } else {
203
+ await fs.promises.unlink(filePaths.uiJson);
204
+ await updateJsonFile(filePaths.databaseJson, {
205
+ enabled_clients: (content) => content.enabled_clients.filter((client) => client !== 'docupace-ui-main'),
206
+ });
207
+ console.log("Removed 'docupace-ui-main' from enabled_clients in database.json");
208
+ }
209
+
210
+ const configureGraphqlServer = deployGraphQLServer === 'true';
211
+ if (configureGraphqlServer) {
212
+ console.log('graphql_allowed_callbacks_urls ->', graphqlAllowedCallbacksUrls);
213
+ console.log('graphql_allowed_logout_urls ->', graphqlAllowedLogoutUrls);
214
+
215
+ await updateJsonFile(filePaths.graphqlJson, {
216
+ callbacks: graphqlAllowedCallbacksUrls,
217
+ allowed_logout_urls: graphqlAllowedLogoutUrls,
218
+ });
219
+ console.log('Updated URLs for GraphQL server...');
220
+ } else {
221
+ await fs.promises.unlink(filePaths.graphqlJson);
222
+ await updateJsonFile(filePaths.databaseJson, {
223
+ enabled_clients: (content) => content.enabled_clients.filter((client) => client !== 'docupace-graphql'),
224
+ });
225
+ console.log("Removed 'docupace-graphql' from enabled_clients in database.json");
226
+ }
227
+
228
+ // TWILIO configuration
229
+ await replaceInFile(filePaths.twilioJson, [
230
+ ['twilioauthtokenplaceholder', commonSecrets.TWILIO_AUTH_TOKEN],
231
+ ['twiliosidplaceholder', commonSecrets.TWILIO_SID],
232
+ ['twiliofromplaceholder', commonSecrets.TWILIO_FROM_NUMBER],
233
+ ]);
234
+
235
+ console.log('TWILIO configuration updated...');
236
+
237
+ // Database configuration
238
+ await replaceInFile(filePaths.databaseLoginJs, [
239
+ ['${docupaceHost}', siteUrl],
240
+ ['${docupaceContext}', coreApiContext],
241
+ ]);
242
+
243
+ // MFA configuration
244
+ // if (process.env.AUTH0_MFA === 'ENABLE') {
245
+ // console.log('AUTH0 MFA is enabled');
246
+ // await execAsync('rsync -av ./auth0template/templateMFA/ ./auth0template/template/');
247
+ // } else if (process.env.AUTH0_MFA === 'DISABLE') {
248
+ // console.log('AUTH0 MFA is disabled');
249
+ // await execAsync('rsync -av ./auth0template/templateNoMFA/ ./auth0template/template/');
250
+ // }
251
+
252
+ console.log('Template configuration completed.');
253
+ } catch (err) {
254
+ console.error('Error configuring Auth0 templates:', err);
255
+ }
256
+ }
257
+
258
+ async function getAuth0AuthToken(auth0Domain, clientId, clientSecret) {
259
+ try {
260
+ const response = await axios.post(`https://${auth0Domain}/oauth/token`, {
261
+ client_id: clientId,
262
+ client_secret: clientSecret,
263
+ audience: `https://${auth0Domain}/api/v2/`,
264
+ grant_type: 'client_credentials',
265
+ }, {
266
+ headers: {
267
+ 'Content-Type': 'application/json',
268
+ },
269
+ });
270
+
271
+ const authToken = response.data.access_token;
272
+ // console.log("Auth0 Auth Token:", authToken);
273
+ return authToken;
274
+ } catch (error) {
275
+ console.error("Error fetching Auth0 token:", error.response ? error.response.data : error.message);
276
+ }
277
+ }
278
+
279
+ async function configureUI(siteHost, coreURL, docupaceHost, site, env, useNewDomain, auth0Domain, auth0ClientId, auth0ClientSecret, auth0Token, vaultSiteSecretsPath, imageTag, repoPath) {
280
+ console.log(`Configuring UI for site: ${site} Host: ${siteHost} in env: ${env}`);
281
+
282
+ try {
283
+ const auth0Response = await axios.get(`https://${auth0Domain}/api/v2/clients`, {
284
+ headers: {authorization: `Bearer ${auth0Token}`},
285
+ });
286
+
287
+ const uiClient = auth0Response.data.find(client => client.name === "docupace-ui-main");
288
+ const uiAuth0ClientId = uiClient.client_id;
289
+ const uiAuth0ClientSecret = uiClient.client_secret;
290
+
291
+ // Generate Auth0 Secret
292
+ const uiAuth0Secret = crypto.randomBytes(48).toString('base64').replace(/[^a-zA-Z0-9]/g, '');
293
+
294
+ // Prepare data payload
295
+ console.log("Preparing Vault UI data payload...");
296
+ const uiSecretData = JSON.stringify({
297
+ data: {
298
+ AUTH0_CLIENT_ID: uiAuth0ClientId,
299
+ AUTH0_CLIENT_SECRET: uiAuth0ClientSecret,
300
+ AUTH0_SECRET: uiAuth0Secret,
301
+ },
302
+ });
303
+
304
+ await updateVaultSecrets(vaultSiteSecretsPath, uiSecretData);
305
+
306
+ // Prepare ui.app.yaml
307
+ const uiAppYaml = `
308
+ repo: docupace-ui-main
309
+ revision: master
310
+ `.trim();
311
+
312
+ // Prepare ui.values.yaml
313
+ const uiValuesYaml = `
314
+ uiMain:
315
+ auth0BaseUrl: https://${siteHost}
316
+ auth0IssuerBaseUrl: https://${auth0Domain}
317
+ auth0ClientId: vault:secret/data/${site}/ui/config#AUTH0_CLIENT_ID
318
+ graphqlClientUrl: https://${siteHost}/api/graphql
319
+ graphqlServerUrl: https://${siteHost}/api/graphql
320
+ connectUrl: https://${siteHost}
321
+ coreUrl: https://${coreURL}
322
+ env:
323
+ AUTH0_CLIENT_SECRET: vault:secret/data/${site}/ui/config#AUTH0_CLIENT_SECRET
324
+ AUTH0_SECRET: vault:secret/data/${site}/ui/config#AUTH0_SECRET
325
+ image:
326
+ repository: registry.paperout.com/docupace-ui-main-dev-docker-local/docupace-ui-main
327
+ tag: ${imageTag}
328
+ imagePullSecrets:
329
+ - name: jf-artifactory
330
+ ingress:
331
+ enabled: true
332
+ className: nginx
333
+ annotations:
334
+ external-dns.alpha.kubernetes.io/hostname: ${siteHost}
335
+ external-dns.alpha.kubernetes.io/zone: public
336
+ cert-manager.io/cluster-issuer: letsencrypt
337
+ nginx.ingress.kubernetes.io/proxy-buffer-size: 128k
338
+ hosts:
339
+ - host: ${siteHost}
340
+ paths:
341
+ - path: /
342
+ pathType: Prefix
343
+ tls:
344
+ - hosts:
345
+ - ${siteHost}
346
+ secretName: dp-next-ui-cert
347
+ externalConnectIngress:
348
+ enabled: ${useNewDomain}
349
+ className: nginx
350
+ annotations:
351
+ cert-manager.io/cluster-issuer: letsencrypt
352
+ external-dns.alpha.kubernetes.io/hostname: ${siteHost}
353
+ external-dns.alpha.kubernetes.io/zone: public
354
+ nginx.ingress.kubernetes.io/backend-protocol: HTTPS
355
+ nginx.ingress.kubernetes.io/configuration-snippet: |
356
+ proxy_set_header Host ${siteHost};
357
+ nginx.ingress.kubernetes.io/proxy-buffer-size: 8k
358
+ hosts:
359
+ - host: ${siteHost}
360
+ paths:
361
+ - path: /core
362
+ - path: /core_b2b
363
+ - path: /core_reports
364
+ - path: /corerealm
365
+ - path: /connect
366
+ - path: /webstart
367
+ - path: /core_ws
368
+ - path: /core_docupload
369
+ - path: /core_ariws
370
+ - path: /core_restws
371
+ - path: /core_webdav
372
+ tls:
373
+ - hosts:
374
+ - ${siteHost}
375
+ secretName: dp-next-ui-cert
376
+ externalConnectService:
377
+ enabled: ${useNewDomain}
378
+ name: external-connect
379
+ domain: ${docupaceHost}
380
+ `.trim();
381
+
382
+ // Write configuration files
383
+ console.log("Writing configuration files...");
384
+ await ensureDirectoryExists(path.resolve(`${repoPath}/dp/${site}`));
385
+
386
+ await fs.writeFile(`${repoPath}/dp/${site}/ui.app.yaml`, uiAppYaml, (err) => {
387
+ if (err) {
388
+ console.error("Failed to write the file:", err);
389
+ } else {
390
+ console.log("File written successfully.");
391
+ }
392
+ });
393
+
394
+ await fs.writeFile(`${repoPath}/dp/${site}/ui.values.yaml`, uiValuesYaml, (err) => {
395
+ if (err) {
396
+ console.error("Failed to write the file:", err);
397
+ } else {
398
+ console.log("File written successfully.");
399
+ }
400
+ });
401
+ console.log("Done!");
402
+ } catch (error) {
403
+ console.error("Error configuring UI:", error);
404
+ }
405
+ }
406
+
407
+ async function configureGraphQLServer(siteHost, coreURL, docupaceHost, site, env, auth0Domain, auth0ClientId, auth0ClientSecret, auth0Token, vaultSiteSecretsPath, imageTag, standardConfigRemoteBranch, repoPath, commonSecrets) {
408
+ console.log(`Configuring GraphQL Server for site: ${site} Host: ${siteHost} in env: ${env}`);
409
+
410
+ try {
411
+ const auth0Response = await axios.get(`https://${auth0Domain}/api/v2/clients`, {
412
+ headers: {authorization: `Bearer ${auth0Token}`},
413
+ });
414
+
415
+ const graphQLServerClient = auth0Response.data.find(client => client.name === "docupace-graphql");
416
+ const graphQLServerClientCredentials = auth0Response.data.find(client => client.name === "docupace-graphql-client-credentials");
417
+
418
+ const graphQLServerAuth0ClientId = graphQLServerClient.client_id;
419
+ const graphQLServerAuth0ClientSecret = graphQLServerClient.client_secret;
420
+
421
+ const graphQLServerCCAuth0ClientId = graphQLServerClientCredentials.client_id;
422
+ const graphQLServerCCAuth0ClientSecret = graphQLServerClientCredentials.client_secret;
423
+
424
+ // Prepare data payload
425
+ console.log("Preparing Vault GraphQL Server data payload...");
426
+ const graphQLServerSecretData = JSON.stringify({
427
+ data: {
428
+ AUTH0_CLIENT_ID: graphQLServerAuth0ClientId,
429
+ AUTH0_CLIENT_SECRET: graphQLServerAuth0ClientSecret,
430
+ AUTH0_CC_CLIENT_ID: graphQLServerCCAuth0ClientId,
431
+ AUTH0_CC_CLIENT_SECRET: graphQLServerCCAuth0ClientSecret,
432
+ CORE_PASSWORD: commonSecrets.CORE_PASSWORD,
433
+ REMOTE_CONFIG_PATH: commonSecrets.REMOTE_CONFIG_PATH,
434
+ STANDARD_CONFIG_REMOTE_PATH: commonSecrets.STANDARD_CONFIG_REMOTE_PATH,
435
+ },
436
+ });
437
+
438
+ await updateVaultSecrets(vaultSiteSecretsPath, graphQLServerSecretData);
439
+
440
+ // Prepare ui.app.yaml
441
+ const graphQLServerAppYaml = `
442
+ repo: docupace-graphql-server
443
+ revision: ${standardConfigRemoteBranch}
444
+ `.trim();
445
+
446
+ // Prepare ui.values.yaml
447
+ const graphQLServerValuesYaml = `
448
+ graphqlServer:
449
+ auth0Domain: https://${auth0Domain}
450
+ auth0ClientId: vault:secret/data/${site}/graphql-server/config#AUTH0_CLIENT_ID
451
+ auth0Audience: ${commonSecrets.CORE_API_IDENTIFIER}
452
+ baseUrl: https://${siteHost}/api/graphql
453
+ coreUrl: https://${coreURL}
454
+ integrationHubUrl: "https://${commonSecrets.INTEGRATION_HUB_URL}"
455
+ standardConfigRemoteBranch: ${standardConfigRemoteBranch}
456
+ apiLevel: "2"
457
+ env:
458
+ AUTH0_CLIENT_SECRET: vault:secret/data/${site}/graphql-server/config#AUTH0_CLIENT_SECRET
459
+ STANDARD_CONFIG_REMOTE_PATH: vault:secret/data/common/config#STANDARD_CONFIG_REMOTE_PATH
460
+ AUTH0_CC_CLIENT_ID: vault:secret/data/${site}/graphql-server/config#AUTH0_CC_CLIENT_ID
461
+ AUTH0_CC_CLIENT_SECRET: vault:secret/data/${site}/graphql-server/config#AUTH0_CC_CLIENT_SECRET
462
+ image:
463
+ repository: registry.paperout.com/docupace-graphql-server-dev-docker-local/docupace-graphql-server
464
+ tag: ${imageTag}
465
+ imagePullSecrets:
466
+ - name: jf-artifactory
467
+ ingress:
468
+ annotations:
469
+ external-dns.alpha.kubernetes.io/hostname: ${siteHost}
470
+ external-dns.alpha.kubernetes.io/zone: public
471
+ cert-manager.io/cluster-issuer: letsencrypt
472
+ nginx.ingress.kubernetes.io/proxy-buffer-size: 128k
473
+ enabled: true
474
+ className: nginx
475
+ hosts:
476
+ - host: ${siteHost}
477
+ paths:
478
+ - path: /api/graphql
479
+ pathType: Prefix
480
+ tls:
481
+ - hosts:
482
+ - ${siteHost}
483
+ secretName: dp-next-ui-cert
484
+ `.trim();
485
+
486
+ // Write configuration files
487
+ console.log("Writing ArgoCD GraphQL Server configuration files...");
488
+ await ensureDirectoryExists(path.resolve(`${repoPath}/dp/${site}`));
489
+
490
+ await fs.writeFile(`${repoPath}/dp/${site}/graphql-server.app.yaml`, graphQLServerAppYaml, (err) => {
491
+ if (err) {
492
+ console.error("Failed to write the file:", err);
493
+ } else {
494
+ console.log("File written successfully.");
495
+ }
496
+ });
497
+
498
+ await fs.writeFile(`${repoPath}/dp/${site}/graphql-server.values.yaml`, graphQLServerValuesYaml, (err) => {
499
+ if (err) {
500
+ console.error("Failed to write the file:", err);
501
+ } else {
502
+ console.log("File written successfully.");
503
+ }
504
+ });
505
+ console.log("Done!");
506
+ } catch (error) {
507
+ console.error("Error configuring GraphQL Server:", error);
508
+ }
509
+ }
510
+
511
+ export async function configureFromSite(fromSite, toSite, env, deployUI, deployGraphQLServer, useNewDomain) {
512
+ console.log(`Configuring Auth0 tenants and ArgoCD apps for site: ${toSite} in env: ${env} from site: ${fromSite}`);
513
+ // Common Secrets
514
+ const commonSecretsPath = getVaultSecretsConfigs(toSite).commonSecretsPath;
515
+ console.log(`commonSecretsPath => ${commonSecretsPath}`)
516
+ const commonSecrets = await getVaultSecrets(commonSecretsPath);
517
+
518
+ // Site Common Secrets
519
+ const siteCommonSecretsPath = getVaultSecretsConfigs(toSite).siteCommonSecretsPath;
520
+ console.log(`siteCommonSecretsPath => ${siteCommonSecretsPath}`)
521
+ const siteCommonSecrets = await getVaultSecrets(siteCommonSecretsPath);
522
+
523
+ const auth0Domain = `docupace-${env}-${toSite}.us.auth0.com`
524
+ const auth0ClientId = siteCommonSecrets.AUTH0_CLIENT_ID;
525
+ const auth0ClientSecret = siteCommonSecrets.AUTH0_CLIENT_SECRET;
526
+ const auth0Token = await getAuth0AuthToken(auth0Domain, auth0ClientId, auth0ClientSecret);
527
+
528
+ // UI app
529
+ const uiSecretsPath = getVaultSecretsConfigs(toSite).uiSecretsPath;
530
+ console.log(`uiSecretsPath => ${uiSecretsPath}`)
531
+
532
+ // GraphQL-Server app
533
+ const graphqlSecretsPath = getVaultSecretsConfigs(toSite).graphqlServerSecretsPath;
534
+ console.log(`graphqlSecretsPath => ${graphqlSecretsPath}`)
535
+
536
+ // Clone repositories
537
+ const repoPath = await cloneEnvRepository(env);
538
+ const auth0RepoPath = await cloneRepository('auth0-site-setup');
539
+
540
+ // Read deploy configs
541
+ const { deployConfig: deployUIConfig } = await readDeployConfig('ui', env, fromSite, 'values');
542
+ const uiImageTag = deployUIConfig.image.tag;
543
+
544
+ const { deployConfig: deployGraphQLServerConfig} = await readDeployConfig('graphql-server', env, fromSite, 'values');
545
+ const graphqlImageTag = deployGraphQLServerConfig.image.tag;
546
+ const graphqlStandardConfigRemoteBranch = deployGraphQLServerConfig.graphqlServer.standardConfigRemoteBranch;
547
+
548
+ // File paths
549
+ const filePaths = {
550
+ clientJson: './auth0-site-setup/template/clients/docupace-connect.json',
551
+ databaseLoginJs: './auth0-site-setup/template/database-connections/docupace/login.js',
552
+ loginHtml: './auth0-site-setup/template/pages/login.html',
553
+ graphqlJson: './auth0-site-setup/template/clients/docupace-graphql.json',
554
+ uiJson: './auth0-site-setup/template/clients/docupace-ui-main.json',
555
+ databaseJson: './auth0-site-setup/template/database-connections/docupace/database.json',
556
+ twilioJson: './auth0-site-setup/template/guardian/providers/sms-twilio.json',
557
+ };
558
+
559
+ console.log(`Copying Latest UI Image Tag: ${deployUIConfig.image.tag} from site: ${fromSite} to site: ${toSite}`);
560
+ console.log(`Copying Latest GraphQL Server Image Tag: ${deployGraphQLServerConfig.image.tag} from site: ${fromSite} to site: ${toSite}`);
561
+
562
+ const envConfig = environmentConfig[env];
563
+
564
+ let CORE_URL,
565
+ SITE_HOST,
566
+ CORE_API_CONTEXT,
567
+ CONNECT_URL,
568
+ DOCUPACE_HOST;
569
+ if (useNewDomain) {
570
+ const newUrl = envConfig.newUrl(toSite);
571
+ CORE_URL = `${newUrl}/core`;
572
+ SITE_HOST = newUrl;
573
+ CORE_API_CONTEXT = "core";
574
+ CONNECT_URL = `${newUrl}/connect`;
575
+ DOCUPACE_HOST = envConfig.docupaceHost();
576
+ } else {
577
+ const oldUrl = envConfig.oldUrl(toSite);
578
+ CORE_URL = oldUrl;
579
+ SITE_HOST = oldUrl;
580
+ CORE_API_CONTEXT = toSite;
581
+ CONNECT_URL = `${oldUrl}_ui`;
582
+ }
583
+
584
+ await templateAuth0(SITE_HOST, CORE_API_CONTEXT, env, filePaths, deployUI, deployGraphQLServer, commonSecrets);
585
+ await importAuth0Templates('./auth0-site-setup/template', auth0Domain, auth0ClientId, auth0ClientSecret);
586
+
587
+ if (deployUI === 'true') {
588
+ await configureUI(SITE_HOST, CORE_URL, DOCUPACE_HOST, toSite, env, useNewDomain, auth0Domain, auth0ClientId, auth0ClientSecret, auth0Token, uiSecretsPath, uiImageTag, repoPath);
589
+ }
590
+
591
+ await configureGraphQLServer(SITE_HOST, CORE_URL, DOCUPACE_HOST, toSite, env, auth0Domain, auth0ClientId, auth0ClientSecret, auth0Token, graphqlSecretsPath, graphqlImageTag, graphqlStandardConfigRemoteBranch, repoPath, commonSecrets);
592
+ await pushDeployConfig(repoPath, `Config update for ${toSite} from ${fromSite} with UI: ${uiImageTag} and GraphQL: ${graphqlImageTag}`);
593
+
594
+ await initializeAuth0Configuration({
595
+ initConfiguration: "true",
596
+ auth0Domain: auth0Domain,
597
+ auth0AuthToken: auth0Token,
598
+ coreApiIdentifier: commonSecrets.CORE_API_IDENTIFIER,
599
+ });
600
+
601
+ await addApplicationToApi({
602
+ application: 'docupace-graphql-client-credentials',
603
+ auth0Domain: auth0Domain,
604
+ auth0AuthToken: auth0Token,
605
+ coreApiIdentifier: commonSecrets.CORE_API_IDENTIFIER
606
+ })
607
+ }
@@ -0,0 +1,107 @@
1
+ import axios from 'axios';
2
+
3
+ /**
4
+ * Fetch secrets from Vault.
5
+ * @param {string} vaultPath - The path in Vault to fetch secrets from.
6
+ * @returns {Promise<Object>} - The secret data as a JSON object.
7
+ */
8
+ export async function getVaultSecrets(vaultPath) {
9
+ if (!vaultPath) {
10
+ console.error("Error: VAULT_PATH is required.");
11
+ throw new Error("VAULT_PATH is required.");
12
+ }
13
+
14
+ const vaultToken = process.env.VAULT_TOKEN;
15
+ const vaultAddr = process.env.VAULT_ADDR;
16
+
17
+ if (!vaultToken || !vaultAddr) {
18
+ console.error("Error: VAULT_TOKEN and VAULT_ADDR must be set.");
19
+ throw new Error("VAULT_TOKEN and VAULT_ADDR must be set.");
20
+ }
21
+
22
+ try {
23
+ // Disable SSL verification globally by setting environment variable
24
+ if (process.env.VAULT_SKIP_VERIFY === 'true') {
25
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
26
+ }
27
+
28
+ // Fetch the secret from Vault
29
+ const response = await axios.get(`${vaultAddr}/v1/${vaultPath}`, {
30
+ headers: {
31
+ 'X-Vault-Token': vaultToken,
32
+ },
33
+ });
34
+
35
+ // Validate the response contains JSON data
36
+ const data = response.data;
37
+ if (data && typeof data === 'object') {
38
+ // console.log(`Secrets fetched successfully from Vault at ${JSON.stringify(data.data.data)})`);
39
+ return data.data.data;
40
+ } else {
41
+ console.error("Error: Invalid JSON received from Vault.");
42
+ throw new Error("Invalid JSON received from Vault.");
43
+ }
44
+ } catch (error) {
45
+ if (error.response) {
46
+ console.error(
47
+ `Error: Failed to connect to Vault server at ${vaultAddr}. Response: ${error.response.status} ${error.response.statusText}`
48
+ );
49
+ } else {
50
+ console.error(`Error: ${error.message}`);
51
+ }
52
+ // throw error;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Update a secret in Vault.
58
+ * @param {string} vaultPath - The path in Vault where the secret should be updated.
59
+ * @param {Object} secretData - The secret data to be stored (as a JSON object).
60
+ * @returns {Promise<Object>} - The response from Vault as a JSON object.
61
+ */
62
+ export async function updateVaultSecrets(vaultPath, secretData) {
63
+ if (!vaultPath || !secretData) {
64
+ console.error("Usage: updateVaultSecret(vaultPath, secretData)");
65
+ throw new Error("Both vaultPath and secretData are required.");
66
+ }
67
+
68
+ const vaultToken = process.env.VAULT_TOKEN;
69
+ const vaultAddr = process.env.VAULT_ADDR;
70
+
71
+ if (!vaultToken || !vaultAddr) {
72
+ console.error("Error: VAULT_TOKEN and VAULT_ADDR must be set.");
73
+ throw new Error("VAULT_TOKEN and VAULT_ADDR must be set.");
74
+ }
75
+
76
+ try {
77
+ // Disable SSL verification globally if VAULT_SKIP_VERIFY is set
78
+ if (process.env.VAULT_SKIP_VERIFY === 'true') {
79
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
80
+ }
81
+
82
+ // Make the POST request to update the Vault secret
83
+ const response = await axios.post(
84
+ `${vaultAddr}/v1/${vaultPath}`,
85
+ secretData,
86
+ {
87
+ headers: {
88
+ 'X-Vault-Token': vaultToken,
89
+ 'Content-Type': 'application/json',
90
+ },
91
+ }
92
+ );
93
+
94
+ // Return the response from Vault
95
+ console.log(response.data);
96
+ return response.data;
97
+ } catch (error) {
98
+ if (error.response) {
99
+ console.error(
100
+ `Error: Failed to update secret at ${vaultPath}. Response: ${error.response.status} ${error.response.statusText}`
101
+ );
102
+ } else {
103
+ console.error(`Error: ${error.message}`);
104
+ }
105
+ // throw error;
106
+ }
107
+ }
@@ -4,23 +4,27 @@ export const environmentConfig = {
4
4
  dev: {
5
5
  newUrl: (site) => `https://${site}.dev.dpai.tech`,
6
6
  oldUrl: (site) => `https://www.dev.docupaceinc.com/${site}`,
7
+ docupaceHost: () => 'www.dev.docupaceinc.com',
7
8
  },
8
9
  qa: {
9
10
  newUrl: (site) => `https://${site}.qa.dpai.tech`,
10
11
  oldUrl: (site) => `https://www.preprod.docupaceinc.com/${site}`,
12
+ docupaceHost: () => 'www.preprod.docupaceinc.com',
11
13
  },
12
14
  uat: {
13
15
  newUrl: (site) => `https://${site}.qa.dpai.tech`,
14
16
  oldUrl: (site) => `https://www2.paperout.com/${site}_uat`,
17
+ docupaceHost: () => 'www2.paperout.com',
15
18
  },
16
19
  stage: {
17
20
  newUrl: (site) => `https://${site}.stage.dpai.tech`,
18
21
  oldUrl: (site) => `https://www2.paperout.com/${site}_staging`,
22
+ docupaceHost: () => 'www2.paperout.com',
19
23
  },
20
-
21
24
  prod: {
22
25
  newUrl: (site) => `https://${site}.dpai.tech`,
23
26
  oldUrl: (site) => `https://www2.paperout.com/${site}`,
27
+ docupaceHost: () => 'www2.paperout.com',
24
28
  },
25
29
  };
26
30
 
@@ -65,8 +69,16 @@ export const getBitbucketInfo = () => {
65
69
 
66
70
  export const getRepUrl = (repoName) => {
67
71
  const { username, password, workspace } = getBitbucketInfo();
68
- const repoUrl = `https://${username}:${password}@bitbucket.org/${workspace}/${repoName}.git`;
69
- return repoUrl;
72
+ return `https://${username}:${password}@bitbucket.org/${workspace}/${repoName}.git`;
70
73
  };
71
74
 
72
75
  export const jfRegistryUrl = 'registry.paperout.com';
76
+
77
+ export const getVaultSecretsConfigs = (site) => {
78
+ return {
79
+ commonSecretsPath: 'secret/data/common/config',
80
+ siteCommonSecretsPath: `secret/data/${site}/common/config`,
81
+ graphqlServerSecretsPath: `secret/data/${site}/graphql-server/config`,
82
+ uiSecretsPath: `secret/data/${site}/ui/config`
83
+ }
84
+ };
@@ -22,11 +22,40 @@ function getRepoLocalPath(env) {
22
22
  return { repoPath, repoName };
23
23
  }
24
24
 
25
- async function cloneEnvRepository(env) {
25
+ function getRepoLocalPathByRepoSlug(repoSlug) {
26
+ const repoName = `${repoSlug}`;
27
+ const repoPath = path.join(process.cwd(), repoName);
28
+ return { repoPath, repoName };
29
+ }
30
+
31
+ function cleanUpRepoPath(repoPath) {
32
+ // Check and handle repo directory
33
+ if (fs.existsSync(repoPath)) {
34
+ console.log(`${repoPath} directory exists. Removing it...`);
35
+ fs.rmSync(repoPath, {
36
+ recursive: true,
37
+ force: true
38
+ });
39
+ }
40
+ }
41
+
42
+ export async function cloneEnvRepository(env) {
26
43
  const { repoPath, repoName } = getRepoLocalPath(env);
27
44
  const repoUrl = getRepUrl(repoName);
28
45
  console.log(`Cloning repository: ${repoName}`);
29
- await $`git clone ${repoUrl}`;
46
+ await $`git clone --depth 1 ${repoUrl}`;
47
+ console.log(`Repository cloned to: ${repoPath}`);
48
+ return repoPath;
49
+ }
50
+
51
+ export async function cloneRepository(repoSlug) {
52
+ const { repoPath, repoName } = getRepoLocalPathByRepoSlug(repoSlug);
53
+ const repoUrl = getRepUrl(repoName);
54
+
55
+ await cleanUpRepoPath(repoPath)
56
+
57
+ console.log(`Cloning repository: ${repoName}`);
58
+ await $`git clone --depth 1 ${repoUrl}`;
30
59
  console.log(`Repository cloned to: ${repoPath}`);
31
60
  return repoPath;
32
61
  }
@@ -41,7 +70,7 @@ function getConfigPath(application, env, site, name) {
41
70
  return { repoPath, deployConfigFilePath };
42
71
  }
43
72
 
44
- async function readDeployConfig(application, env, site, name) {
73
+ export async function readDeployConfig(application, env, site, name) {
45
74
  const { deployConfigFilePath } = getConfigPath(application, env, site, name);
46
75
 
47
76
  let deployConfig;