@docupace/pipeline 1.0.7-alpha.7 → 1.0.7-alpha.8

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.7-alpha.7",
3
+ "version": "1.0.7-alpha.8",
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",
@@ -35,7 +37,7 @@
35
37
  },
36
38
  "scripts": {
37
39
  "test-deploy-core": "dp-next-cli deploy-core-pipeline dev template",
38
- "test-deploy-from-site": "dp-next-cli deploy-from-site --from-env qa --to-env qa --from-site rockefeller --to-site rockefeller --deploy-core true",
40
+ "test-deploy-from-site": "dp-next-cli deploy-from-site --from-env dev --to-env dev --from-site financialdev --to-site template --deploy-core true",
39
41
  "test-run-pipline": "dp-next-cli run-pipeline -r docupace-next-e2e -b master -p run-tests-qa"
40
42
  }
41
43
  }
@@ -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> <deployClientCredentials> <useNewDomain>')
19
+ .description('Configure the Auth0 tenants in env for the given site')
20
+ .action(async (fromSite, toSite, env, deployUI, deployGraphQLServer, deployClientCredentials, useNewDomain) => {
21
+ await configureFromSite(fromSite, toSite, env, deployUI, deployGraphQLServer, deployClientCredentials, 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,579 @@
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 initializeAuth0Configuration({
36
+ initConfiguration,
37
+ auth0Domain,
38
+ auth0AuthToken,
39
+ coreApiIdentifier,
40
+ }) {
41
+ if (initConfiguration === "true") {
42
+ try {
43
+ // Create a new resource server in Auth0
44
+ console.log("Creating resource server in Auth0...");
45
+ console.log(`Secret => ${coreApiIdentifier}`);
46
+
47
+ const resourceServerResponse = await axios.post(
48
+ `https://${auth0Domain}/api/v2/resource-servers`,
49
+ {
50
+ name: "docupace-core",
51
+ identifier: `${coreApiIdentifier}`,
52
+ token_lifetime: 86400,
53
+ allow_offline_access: false,
54
+ signing_alg: "RS256",
55
+ skip_consent_for_verifiable_first_party_clients: true,
56
+ scopes: [],
57
+ },
58
+ {
59
+ headers: {
60
+ Authorization: `Bearer ${auth0AuthToken}`,
61
+ "Content-Type": "application/json",
62
+ },
63
+ }
64
+ );
65
+
66
+ const apiId = resourceServerResponse.data.id;
67
+ console.log(`API ID: ${apiId}`);
68
+
69
+ // Update tenant settings with the default audience
70
+ console.log("Updating tenant settings...");
71
+ await axios.patch(
72
+ `https://${auth0Domain}/api/v2/tenants/settings`,
73
+ {
74
+ default_audience: `${coreApiIdentifier}`,
75
+ },
76
+ {
77
+ headers: {
78
+ Authorization: `Bearer ${auth0AuthToken}`,
79
+ "Content-Type": "application/json",
80
+ },
81
+ }
82
+ );
83
+ console.log(`coreApiIdentifier => ${coreApiIdentifier}`)
84
+ console.log("Tenant settings updated successfully.");
85
+ } catch (error) {
86
+ console.error("Error during Auth0 configuration:", error.response?.data || error.message);
87
+ }
88
+ }
89
+ }
90
+
91
+ async function importAuth0Templates(directoryPath, auth0Domain, auth0ClientID, auth0ClientSecret) {
92
+ try {
93
+ const config = {
94
+ input_file: directoryPath,
95
+ config: {
96
+ AUTH0_DOMAIN: auth0Domain,
97
+ AUTH0_CLIENT_ID: auth0ClientID,
98
+ AUTH0_CLIENT_SECRET: auth0ClientSecret,
99
+ },
100
+ debug: true,
101
+ };
102
+
103
+ console.log('Starting Auth0 template import...');
104
+ await deploy(config);
105
+ console.log('Auth0 template import completed successfully!');
106
+ } catch (error) {
107
+ console.error('Error during Auth0 template import:', error.message);
108
+ process.exit(1); // Exit with an error code
109
+ }
110
+ }
111
+
112
+ async function updateJsonFile(filePath, updates) {
113
+ const content = await fs.promises.readFile(filePath, 'utf8');
114
+ const jsonContent = JSON.parse(content);
115
+ const updatedContent = JSON.stringify({...jsonContent, ...updates}, null, 2);
116
+ await fs.promises.writeFile(filePath, updatedContent);
117
+ }
118
+
119
+ async function replaceInFile(filePath, replacements) {
120
+ let content = await fs.promises.readFile(filePath, 'utf8');
121
+ for (const [search, replace] of replacements) {
122
+ content = content.replace(new RegExp(search, 'g'), replace);
123
+ }
124
+ await fs.promises.writeFile(filePath, content);
125
+ }
126
+
127
+ async function templateAuth0(siteUrl, coreApiContext, env, filePaths, deployUI, deployGraphQLServer, deployClientCredentials, commonSecrets) {
128
+ try {
129
+ let uiAllowedCallbacksUrls,
130
+ uiAllowedLogoutUrls;
131
+ let graphqlAllowedCallbacksUrls,
132
+ graphqlAllowedLogoutUrls;
133
+
134
+ // Configure URLs based on environment
135
+ if (env === 'prd') {
136
+ uiAllowedCallbacksUrls = [`https://${siteUrl}/api/auth/callback`];
137
+ uiAllowedLogoutUrls = [`https://${siteUrl}`];
138
+
139
+ graphqlAllowedCallbacksUrls = [`https://${siteUrl}/api/graphql/callback`];
140
+ graphqlAllowedLogoutUrls = [`https://${siteUrl}/api/graphql/`];
141
+ } else {
142
+ uiAllowedCallbacksUrls = [
143
+ `https://${siteUrl}/api/auth/callback`,
144
+ `http://localhost:3000/api/auth/callback`,
145
+ ];
146
+ uiAllowedLogoutUrls = [
147
+ `https://${siteUrl}`,
148
+ `http://localhost:3000`,
149
+ ];
150
+
151
+ graphqlAllowedCallbacksUrls = [
152
+ `https://${siteUrl}/api/graphql/callback`,
153
+ `http://localhost:4000/api/graphql/callback`,
154
+ ];
155
+ graphqlAllowedLogoutUrls = [
156
+ `http://localhost:4000/api/graphql/`,
157
+ `https://${siteUrl}/api/graphql/`,
158
+ ];
159
+ }
160
+
161
+ const configureUI = deployUI === 'true';
162
+ if (configureUI) {
163
+ console.log('ui_allowed_callbacks_urls ->', uiAllowedCallbacksUrls);
164
+ console.log('ui_allowed_logout_urls ->', uiAllowedLogoutUrls);
165
+
166
+ await updateJsonFile(filePaths.uiJson, {
167
+ callbacks: uiAllowedCallbacksUrls,
168
+ allowed_logout_urls: uiAllowedLogoutUrls,
169
+ });
170
+ console.log('Updated URLs for main UI...');
171
+ } else {
172
+ await fs.promises.unlink(filePaths.uiJson);
173
+ await updateJsonFile(filePaths.databaseJson, {
174
+ enabled_clients: (content) => content.enabled_clients.filter((client) => client !== 'docupace-ui-main'),
175
+ });
176
+ console.log("Removed 'docupace-ui-main' from enabled_clients in database.json");
177
+ }
178
+
179
+ const configureGraphqlServer = deployGraphQLServer === 'true';
180
+ if (configureGraphqlServer) {
181
+ console.log('graphql_allowed_callbacks_urls ->', graphqlAllowedCallbacksUrls);
182
+ console.log('graphql_allowed_logout_urls ->', graphqlAllowedLogoutUrls);
183
+
184
+ await updateJsonFile(filePaths.graphqlJson, {
185
+ callbacks: graphqlAllowedCallbacksUrls,
186
+ allowed_logout_urls: graphqlAllowedLogoutUrls,
187
+ });
188
+ console.log('Updated URLs for GraphQL server...');
189
+ } else {
190
+ await fs.promises.unlink(filePaths.graphqlJson);
191
+ await updateJsonFile(filePaths.databaseJson, {
192
+ enabled_clients: (content) => content.enabled_clients.filter((client) => client !== 'docupace-graphql'),
193
+ });
194
+ console.log("Removed 'docupace-graphql' from enabled_clients in database.json");
195
+ }
196
+
197
+ // TWILIO configuration
198
+ await replaceInFile(filePaths.twilioJson, [
199
+ ['twilioauthtokenplaceholder', commonSecrets.TWILIO_AUTH_TOKEN],
200
+ ['twiliosidplaceholder', commonSecrets.TWILIO_SID],
201
+ ['twiliofromplaceholder', commonSecrets.TWILIO_FROM_NUMBER],
202
+ ]);
203
+
204
+ console.log('TWILIO configuration updated...');
205
+
206
+ // Database configuration
207
+ await replaceInFile(filePaths.databaseLoginJs, [
208
+ ['"${docupaceHost}"', `"${siteUrl}"`],
209
+ ['"${docupaceContext}"', `"${coreApiContext}"`],
210
+ ]);
211
+
212
+ // MFA configuration
213
+ // if (process.env.AUTH0_MFA === 'ENABLE') {
214
+ // console.log('AUTH0 MFA is enabled');
215
+ // await execAsync('rsync -av ./auth0template/templateMFA/ ./auth0template/template/');
216
+ // } else if (process.env.AUTH0_MFA === 'DISABLE') {
217
+ // console.log('AUTH0 MFA is disabled');
218
+ // await execAsync('rsync -av ./auth0template/templateNoMFA/ ./auth0template/template/');
219
+ // }
220
+
221
+ console.log('Template configuration completed.');
222
+ } catch (err) {
223
+ console.error('Error configuring Auth0 templates:', err);
224
+ }
225
+ }
226
+
227
+ async function getAuth0AuthToken(auth0Domain, clientId, clientSecret) {
228
+ try {
229
+ const response = await axios.post(`https://${auth0Domain}/oauth/token`, {
230
+ client_id: clientId,
231
+ client_secret: clientSecret,
232
+ audience: `https://${auth0Domain}/api/v2/`,
233
+ grant_type: 'client_credentials',
234
+ }, {
235
+ headers: {
236
+ 'Content-Type': 'application/json',
237
+ },
238
+ });
239
+
240
+ const authToken = response.data.access_token;
241
+ // console.log("Auth0 Auth Token:", authToken);
242
+ return authToken;
243
+ } catch (error) {
244
+ console.error("Error fetching Auth0 token:", error.response ? error.response.data : error.message);
245
+ }
246
+ }
247
+
248
+ async function configureUI(siteHost, coreURL, docupaceHost, site, env, useNewDomain, auth0Domain, auth0ClientId, auth0ClientSecret, auth0Token, vaultSiteSecretsPath, imageTag, repoPath) {
249
+ console.log(`Configuring UI for site: ${site} Host: ${siteHost} in env: ${env}`);
250
+
251
+ try {
252
+ const auth0Response = await axios.get(`https://${auth0Domain}/api/v2/clients`, {
253
+ headers: {authorization: `Bearer ${auth0Token}`},
254
+ });
255
+
256
+ const uiClient = auth0Response.data.find(client => client.name === "docupace-ui-main");
257
+ const uiAuth0ClientId = uiClient.client_id;
258
+ const uiAuth0ClientSecret = uiClient.client_secret;
259
+
260
+ // Generate Auth0 Secret
261
+ const uiAuth0Secret = crypto.randomBytes(48).toString('base64').replace(/[^a-zA-Z0-9]/g, '');
262
+
263
+ // Prepare data payload
264
+ console.log("Preparing Vault UI data payload...");
265
+ const uiSecretData = JSON.stringify({
266
+ data: {
267
+ AUTH0_CLIENT_ID: uiAuth0ClientId,
268
+ AUTH0_CLIENT_SECRET: uiAuth0ClientSecret,
269
+ AUTH0_SECRET: uiAuth0Secret,
270
+ },
271
+ });
272
+
273
+ await updateVaultSecrets(vaultSiteSecretsPath, uiSecretData);
274
+
275
+ // Prepare ui.app.yaml
276
+ const uiAppYaml = `
277
+ repo: docupace-ui-main
278
+ revision: master
279
+ `.trim();
280
+
281
+ // Prepare ui.values.yaml
282
+ const uiValuesYaml = `
283
+ uiMain:
284
+ auth0BaseUrl: https://${siteHost}
285
+ auth0IssuerBaseUrl: https://${auth0Domain}
286
+ auth0ClientId: vault:secret/data/${site}/ui/config#AUTH0_CLIENT_ID
287
+ graphqlClientUrl: https://${siteHost}/api/graphql
288
+ graphqlServerUrl: https://${siteHost}/api/graphql
289
+ connectUrl: https://${siteHost}
290
+ coreUrl: https://${coreURL}
291
+ env:
292
+ AUTH0_CLIENT_SECRET: vault:secret/data/${site}/ui/config#AUTH0_CLIENT_SECRET
293
+ AUTH0_SECRET: vault:secret/data/${site}/ui/config#AUTH0_SECRET
294
+ image:
295
+ repository: registry.paperout.com/docupace-ui-main-dev-docker-local/docupace-ui-main
296
+ tag: ${imageTag}
297
+ imagePullSecrets:
298
+ - name: jf-artifactory
299
+ ingress:
300
+ enabled: true
301
+ className: nginx
302
+ annotations:
303
+ external-dns.alpha.kubernetes.io/hostname: ${siteHost}
304
+ external-dns.alpha.kubernetes.io/zone: public
305
+ cert-manager.io/cluster-issuer: letsencrypt
306
+ nginx.ingress.kubernetes.io/proxy-buffer-size: 128k
307
+ hosts:
308
+ - host: ${siteHost}
309
+ paths:
310
+ - path: /
311
+ pathType: Prefix
312
+ tls:
313
+ - hosts:
314
+ - ${siteHost}
315
+ secretName: dp-next-ui-cert
316
+ externalConnectIngress:
317
+ enabled: ${useNewDomain}
318
+ className: nginx
319
+ annotations:
320
+ cert-manager.io/cluster-issuer: letsencrypt
321
+ external-dns.alpha.kubernetes.io/hostname: ${siteHost}
322
+ external-dns.alpha.kubernetes.io/zone: public
323
+ nginx.ingress.kubernetes.io/backend-protocol: HTTPS
324
+ nginx.ingress.kubernetes.io/configuration-snippet: |
325
+ proxy_set_header Host ${siteHost};
326
+ nginx.ingress.kubernetes.io/proxy-buffer-size: 8k
327
+ hosts:
328
+ - host: ${siteHost}
329
+ paths:
330
+ - path: /core
331
+ - path: /core_b2b
332
+ - path: /core_reports
333
+ - path: /corerealm
334
+ - path: /connect
335
+ - path: /webstart
336
+ - path: /core_ws
337
+ - path: /core_docupload
338
+ - path: /core_ariws
339
+ - path: /core_restws
340
+ - path: /core_webdav
341
+ tls:
342
+ - hosts:
343
+ - ${siteHost}
344
+ secretName: dp-next-ui-cert
345
+ externalConnectService:
346
+ enabled: ${useNewDomain}
347
+ name: external-connect
348
+ domain: ${docupaceHost}
349
+ `.trim();
350
+
351
+ // Write configuration files
352
+ console.log("Writing configuration files...");
353
+ await ensureDirectoryExists(path.resolve(`${repoPath}/dp/${site}`));
354
+
355
+ await fs.writeFile(`${repoPath}/dp/${site}/ui.app.yaml`, uiAppYaml, (err) => {
356
+ if (err) {
357
+ console.error("Failed to write the file:", err);
358
+ } else {
359
+ console.log("File written successfully.");
360
+ }
361
+ });
362
+
363
+ await fs.writeFile(`${repoPath}/dp/${site}/ui.values.yaml`, uiValuesYaml, (err) => {
364
+ if (err) {
365
+ console.error("Failed to write the file:", err);
366
+ } else {
367
+ console.log("File written successfully.");
368
+ }
369
+ });
370
+ console.log("Done!");
371
+ } catch (error) {
372
+ console.error("Error configuring UI:", error);
373
+ }
374
+ }
375
+
376
+ async function configureGraphQLServer(siteHost, coreURL, docupaceHost, site, env, auth0Domain, auth0ClientId, auth0ClientSecret, auth0Token, vaultSiteSecretsPath, imageTag, shortTemplateLatestCommitId, repoPath, commonSecrets) {
377
+ console.log(`Configuring GraphQL Server for site: ${site} Host: ${siteHost} in env: ${env}`);
378
+
379
+ try {
380
+ const auth0Response = await axios.get(`https://${auth0Domain}/api/v2/clients`, {
381
+ headers: {authorization: `Bearer ${auth0Token}`},
382
+ });
383
+
384
+ const graphQLServerClient = auth0Response.data.find(client => client.name === "docupace-graphql");
385
+ const graphQLServerAuth0ClientId = graphQLServerClient.client_id;
386
+ const graphQLServerAuth0ClientSecret = graphQLServerClient.client_secret;
387
+
388
+ // Prepare data payload
389
+ console.log("Preparing Vault GraphQL Server data payload...");
390
+ const graphQLServerSecretData = JSON.stringify({
391
+ data: {
392
+ AUTH0_CLIENT_ID: graphQLServerAuth0ClientId,
393
+ AUTH0_CLIENT_SECRET: graphQLServerAuth0ClientSecret,
394
+ CORE_PASSWORD: commonSecrets.CORE_PASSWORD,
395
+ REMOTE_CONFIG_PATH: commonSecrets.REMOTE_CONFIG_PATH,
396
+ STANDARD_CONFIG_REMOTE_PATH: commonSecrets.STANDARD_CONFIG_REMOTE_PATH,
397
+ },
398
+ });
399
+
400
+ await updateVaultSecrets(vaultSiteSecretsPath, graphQLServerSecretData);
401
+
402
+ // Prepare ui.app.yaml
403
+ const graphQLServerAppYaml = `
404
+ repo: docupace-graphql-server
405
+ revision: master
406
+ `.trim();
407
+
408
+ // Prepare ui.values.yaml
409
+ const graphQLServerValuesYaml = `
410
+ graphqlServer:
411
+ auth0Domain: https://${auth0Domain}
412
+ auth0ClientId: vault:secret/data/${site}/graphql-server/config#AUTH0_CLIENT_ID
413
+ auth0Audience: ${commonSecrets.CORE_API_IDENTIFIER}
414
+ baseUrl: https://${siteHost}/api/graphql
415
+ coreUrl: https://${coreURL}
416
+ integrationHubUrl: "https://${commonSecrets.INTEGRATION_HUB_URL}"
417
+ standardConfigRemoteBranch: ${shortTemplateLatestCommitId}
418
+ apiLevel: "2"
419
+ env:
420
+ AUTH0_CLIENT_SECRET: vault:secret/data/${site}/graphql-server/config#AUTH0_CLIENT_SECRET
421
+ CORE_PASSWORD: vault:secret/data/common/config#CORE_PASSWORD
422
+ REMOTE_CONFIG_PATH: vault:secret/data/common/config#REMOTE_CONFIG_PATH
423
+ STANDARD_CONFIG_REMOTE_PATH: vault:secret/data/common/config#STANDARD_CONFIG_REMOTE_PATH
424
+ image:
425
+ repository: registry.paperout.com/docupace-graphql-server-dev-docker-local/docupace-graphql-server
426
+ tag: ${imageTag}
427
+ imagePullSecrets:
428
+ - name: jf-artifactory
429
+ ingress:
430
+ annotations:
431
+ external-dns.alpha.kubernetes.io/hostname: ${siteHost}
432
+ external-dns.alpha.kubernetes.io/zone: public
433
+ cert-manager.io/cluster-issuer: letsencrypt
434
+ nginx.ingress.kubernetes.io/proxy-buffer-size: 128k
435
+ enabled: true
436
+ className: nginx
437
+ hosts:
438
+ - host: ${siteHost}
439
+ paths:
440
+ - path: /api/graphql
441
+ pathType: Prefix
442
+ tls:
443
+ - hosts:
444
+ - ${siteHost}
445
+ secretName: dp-next-ui-cert
446
+ `.trim();
447
+
448
+ // Write configuration files
449
+ console.log("Writing ArgoCD GraphQL Server configuration files...");
450
+ await ensureDirectoryExists(path.resolve(`${repoPath}/dp/${site}`));
451
+
452
+ await fs.writeFile(`${repoPath}/dp/${site}/graphql-server.app.yaml`, graphQLServerAppYaml, (err) => {
453
+ if (err) {
454
+ console.error("Failed to write the file:", err);
455
+ } else {
456
+ console.log("File written successfully.");
457
+ }
458
+ });
459
+
460
+ await fs.writeFile(`${repoPath}/dp/${site}/graphql-server.values.yaml`, graphQLServerValuesYaml, (err) => {
461
+ if (err) {
462
+ console.error("Failed to write the file:", err);
463
+ } else {
464
+ console.log("File written successfully.");
465
+ }
466
+ });
467
+ console.log("Done!");
468
+ } catch (error) {
469
+ console.error("Error configuring GraphQL Server:", error);
470
+ }
471
+ }
472
+
473
+ export async function configureFromSite(fromSite, toSite, env, deployUI, deployGraphQLServer, useNewDomain) {
474
+ console.log(`Configuring Auth0 tenants and ArgoCD apps for site: ${toSite} in env: ${env} from site: ${fromSite}`);
475
+ // Common Secrets
476
+ const commonSecretsPath = getVaultSecretsConfigs(toSite).commonSecretsPath;
477
+ console.log(`commonSecretsPath => ${commonSecretsPath}`)
478
+ const commonSecrets = await getVaultSecrets(commonSecretsPath);
479
+
480
+ // Site Common Secrets
481
+ const siteCommonSecretsPath = getVaultSecretsConfigs(toSite).siteCommonSecretsPath;
482
+ console.log(`siteCommonSecretsPath => ${siteCommonSecretsPath}`)
483
+ const siteCommonSecrets = await getVaultSecrets(siteCommonSecretsPath);
484
+
485
+ const auth0Domain = `docupace-${env}-${toSite}.us.auth0.com`
486
+ const auth0ClientId = siteCommonSecrets.AUTH0_CLIENT_ID;
487
+ const auth0ClientSecret = siteCommonSecrets.AUTH0_CLIENT_SECRET;
488
+ const auth0Token = await getAuth0AuthToken(auth0Domain, auth0ClientId, auth0ClientSecret);
489
+
490
+ // UI app
491
+ const uiSecretsPath = getVaultSecretsConfigs(toSite).uiSecretsPath;
492
+ console.log(`uiSecretsPath => ${uiSecretsPath}`)
493
+
494
+ // GraphQL-Server app
495
+ const graphqlSecretsPath = getVaultSecretsConfigs(toSite).graphqlServerSecretsPath;
496
+ console.log(`graphqlSecretsPath => ${graphqlSecretsPath}`)
497
+
498
+ // Clone repositories
499
+ const repoPath = await cloneEnvRepository(env);
500
+ const templateRepoPath = await cloneRepository('template');
501
+ const auth0RepoPath = await cloneRepository('auth0-site-setup');
502
+
503
+ // Read deploy configs
504
+ const { deployConfig: deployUIConfig } = await readDeployConfig('ui', env, fromSite);
505
+ const uiImageTag = deployUIConfig.image.tag;
506
+
507
+ const { deployConfig: deployGraphQLServerConfig} = await readDeployConfig('graphql-server', env, fromSite);
508
+ const graphqlImageTag = deployGraphQLServerConfig.image.tag;
509
+
510
+ // File paths
511
+ const filePaths = {
512
+ clientJson: './auth0-site-setup/template/clients/docupace-connect.json',
513
+ databaseLoginJs: './auth0-site-setup/template/database-connections/docupace/login.js',
514
+ loginHtml: './auth0-site-setup/template/pages/login.html',
515
+ graphqlJson: './auth0-site-setup/template/clients/docupace-graphql.json',
516
+ uiJson: './auth0-site-setup/template/clients/docupace-ui-main.json',
517
+ databaseJson: './auth0-site-setup/template/database-connections/docupace/database.json',
518
+ twilioJson: './auth0-site-setup/template/guardian/providers/sms-twilio.json',
519
+ };
520
+
521
+ console.log(`Copying Latest UI Image Tag: ${deployUIConfig.image.tag} from site: ${fromSite} to site: ${toSite}`);
522
+ console.log(`Copying Latest GraphQL Server Image Tag: ${deployGraphQLServerConfig.image.tag} from site: ${fromSite} to site: ${toSite}`);
523
+
524
+ // Get latest commit ID from TEMPLATE_DIR
525
+ let shortCommitId;
526
+ try {
527
+ process.chdir(templateRepoPath);
528
+ shortCommitId = (await $`git log --pretty=format:'%h' -n 1`).stdout.trim();
529
+
530
+ if (!shortCommitId) {
531
+ console.error("Error: Latest Template commit ID is empty or not set");
532
+ }
533
+
534
+ console.log(`Latest Template commit ID: ${shortCommitId}`);
535
+ process.chdir('..'); // Return to the parent directory
536
+ } catch (error) {
537
+ console.error("Error retrieving the latest template commit ID:", error.message);
538
+ }
539
+
540
+ const envConfig = environmentConfig[env];
541
+
542
+ let CORE_URL,
543
+ SITE_HOST,
544
+ CORE_API_CONTEXT,
545
+ CONNECT_URL,
546
+ DOCUPACE_HOST;
547
+ if (useNewDomain) {
548
+ const newUrl = envConfig.newUrl(toSite);
549
+ CORE_URL = `${newUrl}/core`;
550
+ SITE_HOST = newUrl;
551
+ CORE_API_CONTEXT = "core";
552
+ CONNECT_URL = `${newUrl}/connect`;
553
+ DOCUPACE_HOST = envConfig.docupaceHost();
554
+ } else {
555
+ const oldUrl = envConfig.oldUrl(toSite);
556
+ CORE_URL = oldUrl;
557
+ SITE_HOST = oldUrl;
558
+ CORE_API_CONTEXT = toSite;
559
+ CONNECT_URL = `${oldUrl}_ui`;
560
+ }
561
+
562
+ await templateAuth0(SITE_HOST, CORE_API_CONTEXT, env, filePaths, deployUI, deployGraphQLServer, false, commonSecrets);
563
+ await importAuth0Templates('./auth0-site-setup/template', auth0Domain, auth0ClientId, auth0ClientSecret);
564
+
565
+ if (deployUI === 'true') {
566
+ await configureUI(SITE_HOST, CORE_URL, DOCUPACE_HOST, toSite, env, true, auth0Domain, auth0ClientId, auth0ClientSecret, auth0Token, uiSecretsPath, uiImageTag, repoPath);
567
+ }
568
+
569
+ await configureGraphQLServer(SITE_HOST, CORE_URL, DOCUPACE_HOST, toSite, env, auth0Domain, auth0ClientId, auth0ClientSecret, auth0Token, graphqlSecretsPath, graphqlImageTag, shortCommitId, repoPath, commonSecrets);
570
+ await pushDeployConfig(repoPath, `Config update for ${toSite} from ${fromSite} with UI: ${uiImageTag} and GraphQL: ${graphqlImageTag}`);
571
+
572
+
573
+ await initializeAuth0Configuration({
574
+ initConfiguration: "true",
575
+ auth0Domain: auth0Domain,
576
+ auth0AuthToken: auth0Token,
577
+ coreApiIdentifier: commonSecrets.CORE_API_IDENTIFIER,
578
+ });
579
+ }
@@ -0,0 +1,125 @@
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
+ }
108
+
109
+ // Example usage
110
+ // (async () => {
111
+ // try {
112
+ // const secretPath = 'secret/data/common/config';
113
+ // const secretData = {
114
+ // data: {
115
+ // key1: 'value1',
116
+ // key2: 'value2',
117
+ // },
118
+ // };
119
+ //
120
+ // const result = await updateVaultSecret(secretPath, secretData);
121
+ // console.log('Secret updated successfully:', result);
122
+ // } catch (err) {
123
+ // console.error(err.message);
124
+ // }
125
+ // })();
package/src/next/cli.js CHANGED
@@ -15,7 +15,7 @@ const program = new Command();
15
15
 
16
16
  // GIT SHA computation
17
17
  import { $ } from 'zx';
18
- import {deployConnect, deployCore} from './jenkins.js';
18
+ import { deployCore } from './jenkins.js';
19
19
  import { runPipeline } from './pipline.js';
20
20
  const GIT_SHA = (await $`git log --pretty=format:'%h' -n 1`).stdout.trim();
21
21
 
@@ -69,15 +69,6 @@ program
69
69
  await deployCore(env, site, options.liquibase, options.branch);
70
70
  });
71
71
 
72
- program
73
- .command('deploy-connect <env> <site>')
74
- .description('Deploy Core')
75
- .option('-l, --liquibase <liquibase>', 'Application name', 'true')
76
- .option('-b, --branch <branch>', 'Branch', 'master')
77
- .action(async (env, site, options) => {
78
- await deployConnect(env, site, options.liquibase, options.branch);
79
- });
80
-
81
72
  program
82
73
  .command('deploy-core-pipeline <env> <site>')
83
74
  .description('Deploy Core')
@@ -2,25 +2,29 @@ export const environments = ['dev', 'qa', 'uat', 'stage', 'prod'];
2
2
 
3
3
  export const environmentConfig = {
4
4
  dev: {
5
- newUrl: (site) => `https://${site}.dev.dpai.tech`,
5
+ newUrl: (site) => `${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
- newUrl: (site) => `https://${site}.qa.dpai.tech`,
10
+ newUrl: (site) => `${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
- newUrl: (site) => `https://${site}.qa.dpai.tech`,
15
+ newUrl: (site) => `${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
- newUrl: (site) => `https://${site}.stage.dpai.tech`,
20
+ newUrl: (site) => `${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
- newUrl: (site) => `https://${site}.dpai.tech`,
25
+ newUrl: (site) => `${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) {
41
70
  return { repoPath, deployConfigFilePath };
42
71
  }
43
72
 
44
- async function readDeployConfig(application, env, site) {
73
+ export async function readDeployConfig(application, env, site) {
45
74
  const { deployConfigFilePath } = getConfigPath(application, env, site);
46
75
 
47
76
  let deployConfig;
@@ -244,9 +273,7 @@ export async function deployFromSite(
244
273
  ) {
245
274
  if (isDeployCore === 'true') {
246
275
  const branchName = await getCoreBranchName(fromEnv, fromSite);
247
-
248
- const coreEnv = toEnv == 'qa' ? 'preprod' : toEnv;
249
- await deployCore(coreEnv, toSite, 'true', branchName);
276
+ await deployCore(toEnv, toSite, 'true', branchName);
250
277
 
251
278
  // To be added back when splitting between on prem and in cloud
252
279
  /*await runPipeline('site-deploy', 'deploy-core', 'main', {
@@ -1,14 +1,9 @@
1
1
  import axios from 'axios';
2
2
 
3
- const CORE_JENKINS_URL = 'http://devops.corp.docupace.com';
4
- const CORE_JOB_NAME = 'auto-deploy-site';
5
- const CORE_USERNAME = process.env.CORE_JENKINS_USERNAME;
6
- const CORE_API_TOKEN = process.env.CORE_JENKINS_API_TOKEN;
7
-
8
- const CONNECT_JENKINS_URL = 'http://usdoc-s-buildui.corp.docupace.com/jenkins';
9
- const CONNECT_JOB_NAME = 'docupace_build_and_run_site_v2';
10
- const CONNECT_USERNAME = process.env.CONNECT_JENKINS_USERNAME;
11
- const CONNECT_API_TOKEN = process.env.CONNECT_JENKINS_API_TOKEN;
3
+ const JENKINS_URL = 'http://devops.corp.docupace.com';
4
+ const JOB_NAME = 'auto-deploy-site';
5
+ const USERNAME = process.env.CORE_JENKINS_USERNAME;
6
+ const API_TOKEN = process.env.CORE_JENKINS_API_TOKEN;
12
7
 
13
8
  export async function deployCore(env, site, liquibase, gitBranch) {
14
9
  const parameters = {
@@ -18,43 +13,14 @@ export async function deployCore(env, site, liquibase, gitBranch) {
18
13
  gitBranch,
19
14
  };
20
15
 
21
- return callJenkinsJob({
22
- jenkinsUrl: CORE_JENKINS_URL,
23
- jobName: CORE_JOB_NAME,
24
- auth: { username: CORE_USERNAME, password: CORE_API_TOKEN },
25
- parameters,
26
- });
27
- }
28
-
29
- export async function deployConnect(env, site, liquibase, branch) {
30
- const parameters = {
31
- BUILD_ENVIRONMENT: env,
32
- BUILD_SITE: site,
33
- RUN_LIQUIBASE: liquibase,
34
- };
35
-
36
- return callJenkinsJob({
37
- jenkinsUrl: CONNECT_JENKINS_URL,
38
- jobName: CONNECT_JOB_NAME,
39
- auth: { username: CONNECT_USERNAME, password: CONNECT_API_TOKEN },
40
- parameters,
41
- });
42
- }
43
-
44
- export async function callJenkinsJob({
45
- jenkinsUrl,
46
- jobName,
47
- auth,
48
- parameters,
49
- }) {
50
16
  try {
51
17
  // Trigger the job
52
18
  console.log('Triggering Jenkins job...');
53
19
  const buildResponse = await axios.post(
54
- `${jenkinsUrl}/job/${jobName}/buildWithParameters`,
20
+ `${JENKINS_URL}/job/${JOB_NAME}/buildWithParameters`,
55
21
  new URLSearchParams(parameters),
56
22
  {
57
- auth,
23
+ auth: { username: USERNAME, password: API_TOKEN },
58
24
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
59
25
  validateStatus: (status) => status === 201 || status === 302, // Accept 201 and 302 as valid statuses
60
26
  },
@@ -67,7 +33,7 @@ export async function callJenkinsJob({
67
33
  let jobUrl = null;
68
34
  while (!jobUrl) {
69
35
  const queueResponse = await axios.get(`${queueUrl}api/json`, {
70
- auth,
36
+ auth: { username: USERNAME, password: API_TOKEN },
71
37
  });
72
38
  jobUrl = queueResponse.data.executable?.url || null;
73
39
 
@@ -88,7 +54,7 @@ export async function callJenkinsJob({
88
54
  console.log('Streaming job output...');
89
55
  while (true) {
90
56
  const consoleResponse = await axios.get(consoleUrl, {
91
- auth,
57
+ auth: { username: USERNAME, password: API_TOKEN },
92
58
  responseType: 'text',
93
59
  });
94
60
 
@@ -99,16 +65,16 @@ export async function callJenkinsJob({
99
65
 
100
66
  // Check job status
101
67
  const jobResponse = await axios.get(`${jobUrl}api/json`, {
102
- auth,
68
+ auth: { username: USERNAME, password: API_TOKEN },
103
69
  });
104
70
  const result = jobResponse.data.result;
105
71
 
106
72
  if (result) {
107
73
  console.log(`\nJob completed with result: ${result}`);
108
74
  if (result === 'SUCCESS') {
109
- return;
75
+ process.exit(0);
110
76
  } else {
111
- throw new Error('Jenkins job failed');
77
+ process.exit(1);
112
78
  }
113
79
  }
114
80
 
@@ -117,6 +83,6 @@ export async function callJenkinsJob({
117
83
  }
118
84
  } catch (error) {
119
85
  console.error('Error:', error.message);
120
- throw error;
86
+ process.exit(1);
121
87
  }
122
88
  }