@adobe/aio-cli-plugin-api-mesh 3.4.0 → 3.5.0-beta.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/oclif.manifest.json +1 -1
- package/package.json +7 -5
- package/src/commands/__fixtures__/env_invalid +2 -8
- package/src/commands/__fixtures__/sample_secrets_mesh.json +18 -0
- package/src/commands/__fixtures__/secrets_invalid.yaml +3 -0
- package/src/commands/__fixtures__/secrets_valid.yaml +2 -0
- package/src/commands/__fixtures__/secrets_with_batch_variables.yaml +4 -0
- package/src/commands/api-mesh/__tests__/create.test.js +316 -5
- package/src/commands/api-mesh/__tests__/run.test.js +146 -7
- package/src/commands/api-mesh/create.js +21 -1
- package/src/commands/api-mesh/run.js +17 -0
- package/src/commands/api-mesh/update.js +33 -7
- package/src/helpers.js +21 -0
- package/src/lib/devConsole.js +48 -0
- package/src/server.js +7 -0
- package/src/serverUtils.js +21 -0
- package/src/utils.js +149 -60
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
promptConfirm,
|
|
19
19
|
setUpTenantFiles,
|
|
20
20
|
initSdk,
|
|
21
|
+
writeSecretsFile,
|
|
21
22
|
} = require('../../../helpers');
|
|
22
23
|
const { getMeshId, getMeshArtifact } = require('../../../lib/devConsole');
|
|
23
24
|
require('@adobe-apimesh/mesh-builder');
|
|
@@ -35,12 +36,16 @@ jest.mock('../../../helpers', () => ({
|
|
|
35
36
|
importFiles: jest.fn().mockResolvedValue(),
|
|
36
37
|
promptConfirm: jest.fn().mockResolvedValue(true),
|
|
37
38
|
setUpTenantFiles: jest.fn().mockResolvedValue(),
|
|
39
|
+
writeSecretsFile: jest.fn().mockResolvedValue(),
|
|
38
40
|
}));
|
|
39
41
|
|
|
40
42
|
jest.mock('../../../lib/devConsole', () => ({
|
|
41
43
|
getMeshId: jest.fn().mockResolvedValue('mockMeshId'),
|
|
42
44
|
getMeshArtifact: jest.fn().mockResolvedValue(),
|
|
43
45
|
}));
|
|
46
|
+
jest.mock('chalk', () => ({
|
|
47
|
+
red: jest.fn(text => text), // Return the input text without any color formatting
|
|
48
|
+
}));
|
|
44
49
|
|
|
45
50
|
jest.mock('@adobe-apimesh/mesh-builder', () => {
|
|
46
51
|
return {
|
|
@@ -55,12 +60,15 @@ jest.mock('@adobe-apimesh/mesh-builder', () => {
|
|
|
55
60
|
let logSpy = null;
|
|
56
61
|
let errorLogSpy = null;
|
|
57
62
|
let parseSpy = null;
|
|
63
|
+
let platformSpy = null;
|
|
58
64
|
|
|
59
65
|
const originalEnv = {
|
|
60
66
|
API_MESH_TIER: 'NON-TI',
|
|
61
67
|
};
|
|
62
68
|
|
|
63
69
|
const defaultPort = 5000;
|
|
70
|
+
const os = require('os');
|
|
71
|
+
|
|
64
72
|
describe('run command tests', () => {
|
|
65
73
|
beforeEach(() => {
|
|
66
74
|
global.requestId = 'dummy_request_id';
|
|
@@ -68,10 +76,14 @@ describe('run command tests', () => {
|
|
|
68
76
|
logSpy = jest.spyOn(RunCommand.prototype, 'log');
|
|
69
77
|
errorLogSpy = jest.spyOn(RunCommand.prototype, 'error');
|
|
70
78
|
parseSpy = jest.spyOn(RunCommand.prototype, 'parse');
|
|
79
|
+
platformSpy = jest.spyOn(os, 'platform');
|
|
71
80
|
process.env = {
|
|
72
81
|
...originalEnv,
|
|
73
82
|
};
|
|
74
83
|
});
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
platformSpy.mockRestore();
|
|
86
|
+
});
|
|
75
87
|
|
|
76
88
|
test('snapshot run command description', () => {
|
|
77
89
|
expect(RunCommand.description).toMatchInlineSnapshot(
|
|
@@ -121,6 +133,15 @@ describe('run command tests', () => {
|
|
|
121
133
|
"parse": [Function],
|
|
122
134
|
"type": "option",
|
|
123
135
|
},
|
|
136
|
+
"secrets": {
|
|
137
|
+
"char": "s",
|
|
138
|
+
"default": false,
|
|
139
|
+
"description": "Path to secrets file",
|
|
140
|
+
"input": [],
|
|
141
|
+
"multiple": false,
|
|
142
|
+
"parse": [Function],
|
|
143
|
+
"type": "option",
|
|
144
|
+
},
|
|
124
145
|
"select": {
|
|
125
146
|
"allowNo": false,
|
|
126
147
|
"default": false,
|
|
@@ -251,17 +272,20 @@ describe('run command tests', () => {
|
|
|
251
272
|
|
|
252
273
|
await expect(runResult).rejects.toEqual(
|
|
253
274
|
new Error(
|
|
254
|
-
|
|
275
|
+
'Issue in src/commands/__fixtures__/env_invalid file - Interpolated mesh is not a valid JSON. Please check the generated json file.',
|
|
255
276
|
),
|
|
256
277
|
);
|
|
257
278
|
|
|
258
279
|
expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
|
|
259
280
|
[
|
|
260
281
|
[
|
|
261
|
-
"
|
|
282
|
+
"Interpolated mesh is not a valid JSON. Please check the generated json file.",
|
|
283
|
+
],
|
|
284
|
+
[
|
|
285
|
+
"Issue in src/commands/__fixtures__/env_invalid file - Interpolated mesh is not a valid JSON. Please check the generated json file.",
|
|
262
286
|
],
|
|
263
287
|
[
|
|
264
|
-
"Issue in src/commands/__fixtures__/env_invalid file -
|
|
288
|
+
"Issue in src/commands/__fixtures__/env_invalid file - Interpolated mesh is not a valid JSON. Please check the generated json file.",
|
|
265
289
|
],
|
|
266
290
|
]
|
|
267
291
|
`);
|
|
@@ -283,7 +307,9 @@ describe('run command tests', () => {
|
|
|
283
307
|
|
|
284
308
|
const runResult = RunCommand.run();
|
|
285
309
|
await expect(runResult).rejects.toEqual(
|
|
286
|
-
new Error(
|
|
310
|
+
new Error(
|
|
311
|
+
'Issue in src/commands/__fixtures__/env_valid file - The mesh file cannot be interpolated due to missing keys : newKey1 , newKey2',
|
|
312
|
+
),
|
|
287
313
|
);
|
|
288
314
|
|
|
289
315
|
await expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
|
|
@@ -292,7 +318,10 @@ describe('run command tests', () => {
|
|
|
292
318
|
"The mesh file cannot be interpolated due to missing keys : newKey1 , newKey2",
|
|
293
319
|
],
|
|
294
320
|
[
|
|
295
|
-
"The mesh file cannot be interpolated due to missing keys : newKey1 , newKey2",
|
|
321
|
+
"Issue in src/commands/__fixtures__/env_valid file - The mesh file cannot be interpolated due to missing keys : newKey1 , newKey2",
|
|
322
|
+
],
|
|
323
|
+
[
|
|
324
|
+
"Issue in src/commands/__fixtures__/env_valid file - The mesh file cannot be interpolated due to missing keys : newKey1 , newKey2",
|
|
296
325
|
],
|
|
297
326
|
]
|
|
298
327
|
`);
|
|
@@ -318,7 +347,9 @@ describe('run command tests', () => {
|
|
|
318
347
|
|
|
319
348
|
const runResult = RunCommand.run();
|
|
320
349
|
await expect(runResult).rejects.toEqual(
|
|
321
|
-
new Error(
|
|
350
|
+
new Error(
|
|
351
|
+
'Issue in src/commands/__fixtures__/env_valid file - Interpolated mesh is not a valid JSON. Please check the generated json file.',
|
|
352
|
+
),
|
|
322
353
|
);
|
|
323
354
|
|
|
324
355
|
await expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
|
|
@@ -327,7 +358,10 @@ describe('run command tests', () => {
|
|
|
327
358
|
"Interpolated mesh is not a valid JSON. Please check the generated json file.",
|
|
328
359
|
],
|
|
329
360
|
[
|
|
330
|
-
"Interpolated mesh is not a valid JSON. Please check the generated json file.",
|
|
361
|
+
"Issue in src/commands/__fixtures__/env_valid file - Interpolated mesh is not a valid JSON. Please check the generated json file.",
|
|
362
|
+
],
|
|
363
|
+
[
|
|
364
|
+
"Issue in src/commands/__fixtures__/env_valid file - Interpolated mesh is not a valid JSON. Please check the generated json file.",
|
|
331
365
|
],
|
|
332
366
|
]
|
|
333
367
|
`);
|
|
@@ -836,4 +870,109 @@ describe('run command tests', () => {
|
|
|
836
870
|
);
|
|
837
871
|
expect(setUpTenantFiles).toHaveBeenCalled();
|
|
838
872
|
});
|
|
873
|
+
|
|
874
|
+
test('should return error for run command if mesh has placeholders and the provided secrets file is invalid', async () => {
|
|
875
|
+
parseSpy.mockResolvedValueOnce({
|
|
876
|
+
args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
|
|
877
|
+
flags: {
|
|
878
|
+
secrets: 'src/commands/__fixtures__/secrets_invalid.yaml',
|
|
879
|
+
},
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
const runResult = RunCommand.run();
|
|
883
|
+
|
|
884
|
+
await expect(runResult).rejects.toEqual(
|
|
885
|
+
new Error('Unable to import secrets. Please check the file and try again.'),
|
|
886
|
+
);
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
test('should return error for run command if mesh has placeholders and the provided secrets file is not yaml or yml', async () => {
|
|
890
|
+
parseSpy.mockResolvedValueOnce({
|
|
891
|
+
args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
|
|
892
|
+
flags: {
|
|
893
|
+
secrets: 'src/commands/__fixtures__/.secrets_file.env',
|
|
894
|
+
},
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
const runResult = RunCommand.run();
|
|
898
|
+
|
|
899
|
+
await expect(runResult).rejects.toEqual(
|
|
900
|
+
new Error('Unable to import secrets. Please check the file and try again.'),
|
|
901
|
+
);
|
|
902
|
+
|
|
903
|
+
expect(logSpy.mock.calls).toMatchInlineSnapshot(`
|
|
904
|
+
[
|
|
905
|
+
[
|
|
906
|
+
"Invalid file format. Please provide a YAML file (.yaml or .yml).",
|
|
907
|
+
],
|
|
908
|
+
]
|
|
909
|
+
`);
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
test('should successfully run the mesh if provided secrets file is valid', async () => {
|
|
913
|
+
parseSpy.mockResolvedValueOnce({
|
|
914
|
+
args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
|
|
915
|
+
flags: {
|
|
916
|
+
secrets: 'src/commands/__fixtures__/secrets_valid.yaml',
|
|
917
|
+
debug: false,
|
|
918
|
+
},
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
await RunCommand.run();
|
|
922
|
+
expect(writeSecretsFile).toHaveBeenCalled();
|
|
923
|
+
expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
test('should return error if ran with secrets against windows platform with batch variables', async () => {
|
|
927
|
+
platformSpy.mockReturnValue('win32');
|
|
928
|
+
parseSpy.mockResolvedValueOnce({
|
|
929
|
+
args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
|
|
930
|
+
flags: {
|
|
931
|
+
secrets: 'src/commands/__fixtures__/secrets_with_batch_variables.yaml',
|
|
932
|
+
},
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
const runResult = RunCommand.run();
|
|
936
|
+
await expect(runResult).rejects.toEqual(
|
|
937
|
+
new Error('Unable to import secrets. Please check the file and try again.'),
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
expect(logSpy.mock.calls).toMatchInlineSnapshot(`
|
|
941
|
+
[
|
|
942
|
+
[
|
|
943
|
+
"Batch variables are not supported in YAML files on Windows.",
|
|
944
|
+
],
|
|
945
|
+
]
|
|
946
|
+
`);
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
test('should pass if ran with secrets against linux platform with batch variables', async () => {
|
|
950
|
+
platformSpy.mockReturnValue('linux');
|
|
951
|
+
parseSpy.mockResolvedValueOnce({
|
|
952
|
+
args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
|
|
953
|
+
flags: {
|
|
954
|
+
secrets: 'src/commands/__fixtures__/secrets_with_batch_variables.yaml',
|
|
955
|
+
debug: false,
|
|
956
|
+
},
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
await RunCommand.run();
|
|
960
|
+
expect(writeSecretsFile).toHaveBeenCalled();
|
|
961
|
+
expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
test('should pass if ran with secrets against darwin(macOS) platform with batch variables', async () => {
|
|
965
|
+
platformSpy.mockReturnValue('darwin');
|
|
966
|
+
parseSpy.mockResolvedValueOnce({
|
|
967
|
+
args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
|
|
968
|
+
flags: {
|
|
969
|
+
secrets: 'src/commands/__fixtures__/secrets_with_batch_variables.yaml',
|
|
970
|
+
debug: false,
|
|
971
|
+
},
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
await RunCommand.run();
|
|
975
|
+
expect(writeSecretsFile).toHaveBeenCalled();
|
|
976
|
+
expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
|
|
977
|
+
});
|
|
839
978
|
});
|
|
@@ -19,11 +19,15 @@ const {
|
|
|
19
19
|
jsonFlag,
|
|
20
20
|
getFilesInMeshConfig,
|
|
21
21
|
envFileFlag,
|
|
22
|
+
secretsFlag,
|
|
22
23
|
checkPlaceholders,
|
|
23
24
|
readFileContents,
|
|
24
25
|
validateAndInterpolateMesh,
|
|
26
|
+
interpolateSecrets,
|
|
27
|
+
validateSecretsFile,
|
|
28
|
+
encryptSecrets,
|
|
25
29
|
} = require('../../utils');
|
|
26
|
-
const { createMesh, getTenantFeatures } = require('../../lib/devConsole');
|
|
30
|
+
const { createMesh, getTenantFeatures, getPublicEncryptionKey } = require('../../lib/devConsole');
|
|
27
31
|
const { buildEdgeMeshUrl, buildMeshUrl } = require('../../urlBuilder');
|
|
28
32
|
|
|
29
33
|
class CreateCommand extends Command {
|
|
@@ -33,6 +37,7 @@ class CreateCommand extends Command {
|
|
|
33
37
|
autoConfirmAction: autoConfirmActionFlag,
|
|
34
38
|
json: jsonFlag,
|
|
35
39
|
env: envFileFlag,
|
|
40
|
+
secrets: secretsFlag,
|
|
36
41
|
};
|
|
37
42
|
|
|
38
43
|
static enableJsonFlag = true;
|
|
@@ -53,6 +58,7 @@ class CreateCommand extends Command {
|
|
|
53
58
|
const ignoreCache = await flags.ignoreCache;
|
|
54
59
|
const autoConfirmAction = await flags.autoConfirmAction;
|
|
55
60
|
const envFilePath = await flags.env;
|
|
61
|
+
const secretsFilePath = await flags.secrets;
|
|
56
62
|
const {
|
|
57
63
|
imsOrgId,
|
|
58
64
|
imsOrgCode,
|
|
@@ -103,6 +109,20 @@ class CreateCommand extends Command {
|
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
111
|
|
|
112
|
+
// if secrets is present, include that in data.secrets
|
|
113
|
+
if (secretsFilePath) {
|
|
114
|
+
try {
|
|
115
|
+
await validateSecretsFile(secretsFilePath);
|
|
116
|
+
const secretsData = await interpolateSecrets(secretsFilePath, this);
|
|
117
|
+
const publicKey = await getPublicEncryptionKey(imsOrgCode);
|
|
118
|
+
const encryptedSecrets = await encryptSecrets(publicKey, secretsData);
|
|
119
|
+
data.secrets = encryptedSecrets;
|
|
120
|
+
} catch (err) {
|
|
121
|
+
this.log(err.message);
|
|
122
|
+
this.error('Unable to import secrets. Please check the file and try again.');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
106
126
|
let shouldContinue = true;
|
|
107
127
|
|
|
108
128
|
if (!autoConfirmAction) {
|
|
@@ -15,11 +15,14 @@ const {
|
|
|
15
15
|
debugFlag,
|
|
16
16
|
selectFlag,
|
|
17
17
|
envFileFlag,
|
|
18
|
+
secretsFlag,
|
|
18
19
|
autoConfirmActionFlag,
|
|
19
20
|
readFileContents,
|
|
20
21
|
validateAndInterpolateMesh,
|
|
21
22
|
checkPlaceholders,
|
|
22
23
|
getFilesInMeshConfig,
|
|
24
|
+
validateSecretsFile,
|
|
25
|
+
interpolateSecrets,
|
|
23
26
|
} = require('../../utils');
|
|
24
27
|
const meshBuilder = require('@adobe-apimesh/mesh-builder');
|
|
25
28
|
const fs = require('fs');
|
|
@@ -31,6 +34,7 @@ const {
|
|
|
31
34
|
startGraphqlServer,
|
|
32
35
|
importFiles,
|
|
33
36
|
setUpTenantFiles,
|
|
37
|
+
writeSecretsFile,
|
|
34
38
|
} = require('../../helpers');
|
|
35
39
|
const logger = require('../../classes/logger');
|
|
36
40
|
const { getMeshId, getMeshArtifact } = require('../../lib/devConsole');
|
|
@@ -55,6 +59,7 @@ class RunCommand extends Command {
|
|
|
55
59
|
env: envFileFlag,
|
|
56
60
|
autoConfirmAction: autoConfirmActionFlag,
|
|
57
61
|
select: selectFlag,
|
|
62
|
+
secrets: secretsFlag,
|
|
58
63
|
};
|
|
59
64
|
|
|
60
65
|
static enableJsonFlag = true;
|
|
@@ -67,6 +72,7 @@ class RunCommand extends Command {
|
|
|
67
72
|
logger.info(`RequestId: ${global.requestId}`);
|
|
68
73
|
|
|
69
74
|
const { args, flags } = await this.parse(RunCommand);
|
|
75
|
+
const secretsFilePath = await flags.secrets;
|
|
70
76
|
|
|
71
77
|
//Initialize the meshId based on
|
|
72
78
|
let meshId = null;
|
|
@@ -165,6 +171,17 @@ class RunCommand extends Command {
|
|
|
165
171
|
}
|
|
166
172
|
|
|
167
173
|
let portNo;
|
|
174
|
+
//secrets management
|
|
175
|
+
if (secretsFilePath) {
|
|
176
|
+
try {
|
|
177
|
+
await validateSecretsFile(secretsFilePath);
|
|
178
|
+
const stringifiedSecrets = await interpolateSecrets(secretsFilePath, this);
|
|
179
|
+
await writeSecretsFile(stringifiedSecrets, meshId);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
this.log(error.message);
|
|
182
|
+
this.error('Unable to import secrets. Please check the file and try again.');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
168
185
|
|
|
169
186
|
//To set the port number using the environment file
|
|
170
187
|
if (process.env.PORT !== undefined) {
|
|
@@ -17,12 +17,16 @@ const {
|
|
|
17
17
|
ignoreCacheFlag,
|
|
18
18
|
autoConfirmActionFlag,
|
|
19
19
|
envFileFlag,
|
|
20
|
+
secretsFlag,
|
|
20
21
|
checkPlaceholders,
|
|
21
22
|
readFileContents,
|
|
22
23
|
validateAndInterpolateMesh,
|
|
23
24
|
getFilesInMeshConfig,
|
|
25
|
+
interpolateSecrets,
|
|
26
|
+
validateSecretsFile,
|
|
27
|
+
encryptSecrets,
|
|
24
28
|
} = require('../../utils');
|
|
25
|
-
const { getMeshId, updateMesh } = require('../../lib/devConsole');
|
|
29
|
+
const { getMeshId, updateMesh, getPublicEncryptionKey } = require('../../lib/devConsole');
|
|
26
30
|
|
|
27
31
|
class UpdateCommand extends Command {
|
|
28
32
|
static args = [{ name: 'file' }];
|
|
@@ -30,6 +34,7 @@ class UpdateCommand extends Command {
|
|
|
30
34
|
ignoreCache: ignoreCacheFlag,
|
|
31
35
|
autoConfirmAction: autoConfirmActionFlag,
|
|
32
36
|
env: envFileFlag,
|
|
37
|
+
secrets: secretsFlag,
|
|
33
38
|
};
|
|
34
39
|
|
|
35
40
|
async run() {
|
|
@@ -48,12 +53,19 @@ class UpdateCommand extends Command {
|
|
|
48
53
|
const ignoreCache = await flags.ignoreCache;
|
|
49
54
|
const autoConfirmAction = await flags.autoConfirmAction;
|
|
50
55
|
const envFilePath = await flags.env;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
const secretsFilePath = await flags.secrets;
|
|
57
|
+
|
|
58
|
+
const {
|
|
59
|
+
imsOrgId,
|
|
60
|
+
imsOrgCode,
|
|
61
|
+
projectId,
|
|
62
|
+
workspaceId,
|
|
63
|
+
orgName,
|
|
64
|
+
projectName,
|
|
65
|
+
workspaceName,
|
|
66
|
+
} = await initSdk({
|
|
67
|
+
ignoreCache,
|
|
68
|
+
});
|
|
57
69
|
|
|
58
70
|
//Input the mesh data from the input file
|
|
59
71
|
let inputMeshData = await readFileContents(args.file, this, 'mesh');
|
|
@@ -103,6 +115,20 @@ class UpdateCommand extends Command {
|
|
|
103
115
|
}
|
|
104
116
|
}
|
|
105
117
|
|
|
118
|
+
// if secrets is present, include that in data.secrets
|
|
119
|
+
if (secretsFilePath) {
|
|
120
|
+
try {
|
|
121
|
+
await validateSecretsFile(secretsFilePath);
|
|
122
|
+
const secretsData = await interpolateSecrets(secretsFilePath, this);
|
|
123
|
+
const publicKey = await getPublicEncryptionKey(imsOrgCode);
|
|
124
|
+
const encryptedSecrets = await encryptSecrets(publicKey, secretsData);
|
|
125
|
+
data.secrets = encryptedSecrets;
|
|
126
|
+
} catch (err) {
|
|
127
|
+
this.log(err.message);
|
|
128
|
+
this.error('Unable to import secrets. Please check the file and try again.');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
106
132
|
if (meshId) {
|
|
107
133
|
let shouldContinue = true;
|
|
108
134
|
|
package/src/helpers.js
CHANGED
|
@@ -860,6 +860,26 @@ async function setUpTenantFiles(meshId) {
|
|
|
860
860
|
}
|
|
861
861
|
}
|
|
862
862
|
|
|
863
|
+
/**
|
|
864
|
+
* This function is to create secrets.yaml in mesh-artifacts for respective meshId. Used for local development run command
|
|
865
|
+
*
|
|
866
|
+
* @secretsData secretsData
|
|
867
|
+
* @meshId meshId
|
|
868
|
+
*/
|
|
869
|
+
async function writeSecretsFile(secretsData, meshId) {
|
|
870
|
+
if (!fs.existsSync(path.resolve(process.cwd(), 'mesh-artifact', meshId))) {
|
|
871
|
+
throw new Error(`Unexpected Error: issue creating secrets file.`);
|
|
872
|
+
}
|
|
873
|
+
try {
|
|
874
|
+
const secretsFileName = 'secrets.yaml';
|
|
875
|
+
const folderPath = path.join(process.cwd(), 'mesh-artifact', meshId);
|
|
876
|
+
const filePath = path.join(folderPath, secretsFileName);
|
|
877
|
+
fs.writeFileSync(filePath, secretsData);
|
|
878
|
+
} catch (error) {
|
|
879
|
+
throw new Error(error.message);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
863
883
|
module.exports = {
|
|
864
884
|
objToString,
|
|
865
885
|
promptInput,
|
|
@@ -876,4 +896,5 @@ module.exports = {
|
|
|
876
896
|
updateFilesArray,
|
|
877
897
|
startGraphqlServer,
|
|
878
898
|
setUpTenantFiles,
|
|
899
|
+
writeSecretsFile,
|
|
879
900
|
};
|
package/src/lib/devConsole.js
CHANGED
|
@@ -10,6 +10,7 @@ const fs = require('fs');
|
|
|
10
10
|
const util = require('util');
|
|
11
11
|
const exec = util.promisify(require('child_process').exec);
|
|
12
12
|
const contentDisposition = require('content-disposition');
|
|
13
|
+
const chalk = require('chalk');
|
|
13
14
|
|
|
14
15
|
const { DEV_CONSOLE_TRANSPORTER_API_KEY, SMS_BASE_URL } = CONSTANTS;
|
|
15
16
|
|
|
@@ -968,6 +969,52 @@ const getMeshDeployments = async (organizationCode, projectId, workspaceId, mesh
|
|
|
968
969
|
}
|
|
969
970
|
};
|
|
970
971
|
|
|
972
|
+
/**
|
|
973
|
+
* Gets the public key to encrypt secrets.
|
|
974
|
+
*
|
|
975
|
+
* This request bypasses the Dev Console and is sent directly to the Schema Management Service.
|
|
976
|
+
* As a result, we provide the publicKey used for secrets encryption.
|
|
977
|
+
* The near-term goal is to stop using Dev Console as a proxy for all routes.
|
|
978
|
+
* @param organizationCode
|
|
979
|
+
* @returns string
|
|
980
|
+
*/
|
|
981
|
+
const getPublicEncryptionKey = async organizationCode => {
|
|
982
|
+
const { accessToken, apiKey } = await getDevConsoleConfig();
|
|
983
|
+
const config = {
|
|
984
|
+
method: 'get',
|
|
985
|
+
url: `${SMS_BASE_URL}/organizations/${organizationCode}/getPublicKey?API_KEY=${apiKey}`,
|
|
986
|
+
headers: {
|
|
987
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
988
|
+
'x-request-id': global.requestId,
|
|
989
|
+
},
|
|
990
|
+
};
|
|
991
|
+
logger.info(
|
|
992
|
+
'Initiating GET %s',
|
|
993
|
+
`${SMS_BASE_URL}/organizations/${organizationCode}/getPublicKey?API_KEY=${apiKey}`,
|
|
994
|
+
);
|
|
995
|
+
try {
|
|
996
|
+
const response = await axios(config);
|
|
997
|
+
|
|
998
|
+
logger.info('Response from GET %s', response.status);
|
|
999
|
+
if (response.status == 200) {
|
|
1000
|
+
let publicKey = '';
|
|
1001
|
+
logger.info(`Public key for encryption: ${objToString(response, ['data'])}`);
|
|
1002
|
+
if (response.data.publicKey) {
|
|
1003
|
+
publicKey = response.data.publicKey.replace(/\\n/g, '\n'); //correcting public key format
|
|
1004
|
+
}
|
|
1005
|
+
return publicKey;
|
|
1006
|
+
} else {
|
|
1007
|
+
let errorMessage = `Failed to load encryption keys. Please contact support.`;
|
|
1008
|
+
logger.error(`${errorMessage}. Received ${response.status}, expected 200`);
|
|
1009
|
+
throw new Error(chalk.red(errorMessage));
|
|
1010
|
+
}
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
let errorMessage = `Something went wrong while encrypting secrets. Please try again.`;
|
|
1013
|
+
logger.error(errorMessage);
|
|
1014
|
+
throw new Error(chalk.red(errorMessage));
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
|
|
971
1018
|
module.exports = {
|
|
972
1019
|
getApiKeyCredential,
|
|
973
1020
|
describeMesh,
|
|
@@ -984,4 +1031,5 @@ module.exports = {
|
|
|
984
1031
|
getMeshArtifact,
|
|
985
1032
|
getTenantFeatures,
|
|
986
1033
|
getMeshDeployments,
|
|
1034
|
+
getPublicEncryptionKey,
|
|
987
1035
|
};
|
package/src/server.js
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
removeRequestHeaders,
|
|
14
14
|
prepSourceResponseHeaders,
|
|
15
15
|
processResponseHeaders,
|
|
16
|
+
readSecretsFile,
|
|
16
17
|
} = require('./serverUtils');
|
|
17
18
|
|
|
18
19
|
let yogaServer = null;
|
|
@@ -64,6 +65,8 @@ const getYogaServer = async () => {
|
|
|
64
65
|
const tenantMesh = await getBuiltMesh();
|
|
65
66
|
const corsOptions = getCORSOptions();
|
|
66
67
|
|
|
68
|
+
const secrets = readSecretsFile(meshId);
|
|
69
|
+
|
|
67
70
|
logger.info('Creating graphQL server');
|
|
68
71
|
|
|
69
72
|
meshConfig = readMeshConfig(meshId);
|
|
@@ -73,6 +76,10 @@ const getYogaServer = async () => {
|
|
|
73
76
|
graphqlEndpoint: `/graphql`,
|
|
74
77
|
graphiql: true,
|
|
75
78
|
cors: corsOptions,
|
|
79
|
+
context: initialContext => ({
|
|
80
|
+
...initialContext,
|
|
81
|
+
secrets,
|
|
82
|
+
}),
|
|
76
83
|
});
|
|
77
84
|
|
|
78
85
|
return yogaServer;
|
package/src/serverUtils.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const LRUCache = require('lru-cache');
|
|
4
4
|
const logger = require('./classes/logger');
|
|
5
|
+
const YAML = require('yaml');
|
|
5
6
|
|
|
6
7
|
const headersCache = new LRUCache({
|
|
7
8
|
max: parseInt(process.env.CACHE_OPT_MAX || '500', 10),
|
|
@@ -315,9 +316,29 @@ function ccDirectivesToString(directives) {
|
|
|
315
316
|
return chStr.toString();
|
|
316
317
|
}
|
|
317
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Returns secrets content from artifacts
|
|
321
|
+
* @param meshId
|
|
322
|
+
* @returns
|
|
323
|
+
*/
|
|
324
|
+
function readSecretsFile(meshId) {
|
|
325
|
+
let secrets = {};
|
|
326
|
+
try {
|
|
327
|
+
const filePath = path.resolve(process.cwd(), 'mesh-artifact', `${meshId}`, 'secrets.yaml');
|
|
328
|
+
if (fs.existsSync(filePath)) {
|
|
329
|
+
secrets = YAML.parse(fs.readFileSync(filePath, 'utf8'));
|
|
330
|
+
}
|
|
331
|
+
} catch (error) {
|
|
332
|
+
logger.error('Unexpected error: unable to locate secrets file in mesh artifacts.');
|
|
333
|
+
throw new Error(error.message);
|
|
334
|
+
}
|
|
335
|
+
return secrets;
|
|
336
|
+
}
|
|
337
|
+
|
|
318
338
|
module.exports = {
|
|
319
339
|
readMeshConfig,
|
|
320
340
|
removeRequestHeaders,
|
|
321
341
|
prepSourceResponseHeaders,
|
|
322
342
|
processResponseHeaders,
|
|
343
|
+
readSecretsFile,
|
|
323
344
|
};
|