@jupiterone/integration-sdk-cli 8.23.1 → 8.24.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/dist/src/commands/collect.js +5 -5
- package/dist/src/commands/collect.js.map +1 -1
- package/dist/src/commands/diff.d.ts +1 -1
- package/dist/src/commands/diff.js +1 -0
- package/dist/src/commands/diff.js.map +1 -1
- package/dist/src/commands/generate-integration-graph-schema.js +1 -0
- package/dist/src/commands/generate-integration-graph-schema.js.map +1 -1
- package/dist/src/commands/options.d.ts +50 -0
- package/dist/src/commands/options.js +109 -0
- package/dist/src/commands/options.js.map +1 -0
- package/dist/src/commands/run.d.ts +2 -1
- package/dist/src/commands/run.js +28 -43
- package/dist/src/commands/run.js.map +1 -1
- package/dist/src/commands/sync.d.ts +2 -1
- package/dist/src/commands/sync.js +18 -42
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/log.js +1 -0
- package/dist/src/log.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/__tests__/cli-run.test.ts +33 -51
- package/src/__tests__/cli-sync.test.ts +18 -62
- package/src/__tests__/options.test.ts +223 -0
- package/src/__tests__/util/synchronization.ts +17 -3
- package/src/commands/collect.ts +7 -13
- package/src/commands/diff.ts +1 -0
- package/src/commands/generate-integration-graph-schema.ts +1 -0
- package/src/commands/options.ts +190 -0
- package/src/commands/run.ts +47 -74
- package/src/commands/sync.ts +38 -70
- package/src/log.ts +1 -0
|
@@ -64,16 +64,10 @@ test('uploads data to the synchronization api and displays the results', async (
|
|
|
64
64
|
});
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
test('
|
|
68
|
-
process.env.JUPITERONE_DEV = 'true';
|
|
69
|
-
|
|
67
|
+
test('skips finalization with skip-finalize', async () => {
|
|
70
68
|
const job = generateSynchronizationJob();
|
|
71
69
|
|
|
72
|
-
setupSynchronizerApi({
|
|
73
|
-
polly,
|
|
74
|
-
job,
|
|
75
|
-
baseUrl: 'https://api.dev.jupiterone.io',
|
|
76
|
-
});
|
|
70
|
+
setupSynchronizerApi({ polly, job, baseUrl: 'https://api.us.jupiterone.io' });
|
|
77
71
|
|
|
78
72
|
await createCli().parseAsync([
|
|
79
73
|
'node',
|
|
@@ -81,12 +75,13 @@ test('hits dev urls if JUPITERONE_DEV environment variable is set', async () =>
|
|
|
81
75
|
'sync',
|
|
82
76
|
'--integrationInstanceId',
|
|
83
77
|
'test',
|
|
78
|
+
'--skip-finalize',
|
|
84
79
|
]);
|
|
85
80
|
|
|
86
81
|
expect(log.displaySynchronizationResults).toHaveBeenCalledTimes(1);
|
|
87
82
|
expect(log.displaySynchronizationResults).toHaveBeenCalledWith({
|
|
88
83
|
...job,
|
|
89
|
-
status: SynchronizationJobStatus.
|
|
84
|
+
status: SynchronizationJobStatus.AWAITING_UPLOADS,
|
|
90
85
|
// We arrive at these numbers because of what
|
|
91
86
|
// was written to disk in the 'synchronization' project fixture
|
|
92
87
|
numEntitiesUploaded: 6,
|
|
@@ -94,71 +89,32 @@ test('hits dev urls if JUPITERONE_DEV environment variable is set', async () =>
|
|
|
94
89
|
});
|
|
95
90
|
});
|
|
96
91
|
|
|
97
|
-
test('
|
|
98
|
-
const job = generateSynchronizationJob();
|
|
92
|
+
test('does not publish events for source "api" since there is no integrationJobId', async () => {
|
|
93
|
+
const job = generateSynchronizationJob({ source: 'api', scope: 'test' });
|
|
99
94
|
|
|
100
95
|
setupSynchronizerApi({
|
|
101
96
|
polly,
|
|
102
97
|
job,
|
|
103
|
-
baseUrl: 'https://api.
|
|
98
|
+
baseUrl: 'https://api.us.jupiterone.io',
|
|
104
99
|
});
|
|
105
100
|
|
|
101
|
+
let eventsPublished = false;
|
|
102
|
+
polly.server
|
|
103
|
+
.post(`https://example.com/persister/synchronization/jobs/${job.id}/events`)
|
|
104
|
+
.intercept((req, res) => {
|
|
105
|
+
eventsPublished = true;
|
|
106
|
+
});
|
|
107
|
+
|
|
106
108
|
await createCli().parseAsync([
|
|
107
109
|
'node',
|
|
108
110
|
'j1-integration',
|
|
109
111
|
'sync',
|
|
110
|
-
'--
|
|
112
|
+
'--source',
|
|
113
|
+
'api',
|
|
114
|
+
'--scope',
|
|
111
115
|
'test',
|
|
112
|
-
'--api-base-url',
|
|
113
|
-
'https://api.TEST.jupiterone.io',
|
|
114
116
|
]);
|
|
115
117
|
|
|
118
|
+
expect(eventsPublished).toBe(false);
|
|
116
119
|
expect(log.displaySynchronizationResults).toHaveBeenCalledTimes(1);
|
|
117
|
-
expect(log.displaySynchronizationResults).toHaveBeenCalledWith({
|
|
118
|
-
...job,
|
|
119
|
-
status: SynchronizationJobStatus.FINALIZE_PENDING,
|
|
120
|
-
// We arrive at these numbers because of what
|
|
121
|
-
// was written to disk in the 'synchronization' project fixture
|
|
122
|
-
numEntitiesUploaded: 6,
|
|
123
|
-
numRelationshipsUploaded: 3,
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('throws an error if --api-base-url is set with --development', async () => {
|
|
128
|
-
const job = generateSynchronizationJob();
|
|
129
|
-
|
|
130
|
-
setupSynchronizerApi({
|
|
131
|
-
polly,
|
|
132
|
-
job,
|
|
133
|
-
baseUrl: 'https://api.TEST.jupiterone.io',
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
await expect(
|
|
137
|
-
createCli().parseAsync([
|
|
138
|
-
'node',
|
|
139
|
-
'j1-integration',
|
|
140
|
-
'sync',
|
|
141
|
-
'--integrationInstanceId',
|
|
142
|
-
'test',
|
|
143
|
-
'--development',
|
|
144
|
-
'--api-base-url',
|
|
145
|
-
'https://api.TEST.jupiterone.io',
|
|
146
|
-
]),
|
|
147
|
-
).rejects.toThrow(
|
|
148
|
-
'Invalid configuration supplied. Cannot specify both --api-base-url and --development(-d) flags.',
|
|
149
|
-
);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test('throws if JUPITERONE_API_KEY is not set', async () => {
|
|
153
|
-
delete process.env.JUPITERONE_API_KEY;
|
|
154
|
-
|
|
155
|
-
await expect(
|
|
156
|
-
createCli().parseAsync([
|
|
157
|
-
'node',
|
|
158
|
-
'j1-integration',
|
|
159
|
-
'sync',
|
|
160
|
-
'--integrationInstanceId',
|
|
161
|
-
'test',
|
|
162
|
-
]),
|
|
163
|
-
).rejects.toThrow('JUPITERONE_API_KEY environment variable must be set');
|
|
164
120
|
});
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { createCommand } from 'commander';
|
|
2
|
+
import {
|
|
3
|
+
addApiClientOptionsToCommand,
|
|
4
|
+
addSyncOptionsToCommand,
|
|
5
|
+
ApiClientOptions,
|
|
6
|
+
getApiClientOptions,
|
|
7
|
+
SyncOptions,
|
|
8
|
+
validateApiClientOptions,
|
|
9
|
+
validateSyncOptions,
|
|
10
|
+
} from '../commands/options';
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_UPLOAD_BATCH_SIZE,
|
|
13
|
+
JUPITERONE_DEV_API_BASE_URL,
|
|
14
|
+
JUPITERONE_PROD_API_BASE_URL,
|
|
15
|
+
} from '@jupiterone/integration-sdk-runtime';
|
|
16
|
+
|
|
17
|
+
const syncOptions = (values?: Partial<SyncOptions>): SyncOptions => ({
|
|
18
|
+
source: 'integration-managed',
|
|
19
|
+
uploadBatchSize: DEFAULT_UPLOAD_BATCH_SIZE,
|
|
20
|
+
uploadRelationshipBatchSize: DEFAULT_UPLOAD_BATCH_SIZE,
|
|
21
|
+
skipFinalize: false,
|
|
22
|
+
...values,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const apiClientOptions = (
|
|
26
|
+
values?: Partial<ApiClientOptions>,
|
|
27
|
+
): ApiClientOptions => ({
|
|
28
|
+
apiBaseUrl: JUPITERONE_PROD_API_BASE_URL,
|
|
29
|
+
development: false,
|
|
30
|
+
...values,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('addSyncOptionsToCommand', () => {
|
|
34
|
+
test('default values', () => {
|
|
35
|
+
expect(
|
|
36
|
+
addSyncOptionsToCommand(createCommand())
|
|
37
|
+
.parse(['node', 'command'])
|
|
38
|
+
.opts<SyncOptions>(),
|
|
39
|
+
).toEqual(syncOptions());
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('--integrationInstanceId', () => {
|
|
43
|
+
expect(
|
|
44
|
+
addSyncOptionsToCommand(createCommand())
|
|
45
|
+
.parse(['node', 'command', '--integrationInstanceId', 'test'])
|
|
46
|
+
.opts(),
|
|
47
|
+
).toEqual(syncOptions({ integrationInstanceId: 'test' }));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('--source', () => {
|
|
51
|
+
expect(
|
|
52
|
+
addSyncOptionsToCommand(createCommand())
|
|
53
|
+
.parse(['node', 'command', '--source', 'api'])
|
|
54
|
+
.opts(),
|
|
55
|
+
).toEqual(syncOptions({ source: 'api' }));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('--source unknown', () => {
|
|
59
|
+
expect(() => {
|
|
60
|
+
addSyncOptionsToCommand(createCommand())
|
|
61
|
+
.parse(['node', 'command', '--source', 'unknown'])
|
|
62
|
+
.opts();
|
|
63
|
+
}).toThrowError(/must be/);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('validateSyncOptions', () => {
|
|
68
|
+
test('valid', () => {
|
|
69
|
+
expect(() =>
|
|
70
|
+
validateSyncOptions(syncOptions({ integrationInstanceId: 'test' })),
|
|
71
|
+
).not.toThrow();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('source: api and integrationInstanceId', () => {
|
|
75
|
+
expect(() =>
|
|
76
|
+
validateSyncOptions(
|
|
77
|
+
syncOptions({ integrationInstanceId: 'test', source: 'api' }),
|
|
78
|
+
),
|
|
79
|
+
).toThrowError(/both --source api and --integrationInstanceId/);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('source: api without scope', () => {
|
|
83
|
+
expect(() =>
|
|
84
|
+
validateSyncOptions(syncOptions({ source: 'api' })),
|
|
85
|
+
).toThrowError(/--source api requires --scope/);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('defaults with no integrationInstanceId', () => {
|
|
89
|
+
expect(() => validateSyncOptions(syncOptions())).toThrowError(
|
|
90
|
+
/--integrationInstanceId or --source api/,
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('addApiClientOptionsToCommand', () => {
|
|
96
|
+
test('default values', () => {
|
|
97
|
+
expect(
|
|
98
|
+
addApiClientOptionsToCommand(createCommand())
|
|
99
|
+
.parse(['node', 'command'])
|
|
100
|
+
.opts<SyncOptions>(),
|
|
101
|
+
).toEqual(apiClientOptions());
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('--development with default --api-base-url', () => {
|
|
105
|
+
expect(
|
|
106
|
+
addApiClientOptionsToCommand(createCommand())
|
|
107
|
+
.parse(['node', 'command', '--development'])
|
|
108
|
+
.opts(),
|
|
109
|
+
).toEqual(
|
|
110
|
+
apiClientOptions({
|
|
111
|
+
development: true,
|
|
112
|
+
apiBaseUrl: JUPITERONE_PROD_API_BASE_URL,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('validApiClientOptions', () => {
|
|
119
|
+
test('valid', () => {
|
|
120
|
+
expect(() => validateApiClientOptions(apiClientOptions())).not.toThrow();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('--development with default --api-base-url', () => {
|
|
124
|
+
expect(() =>
|
|
125
|
+
validateApiClientOptions(
|
|
126
|
+
apiClientOptions({
|
|
127
|
+
development: true,
|
|
128
|
+
apiBaseUrl: JUPITERONE_PROD_API_BASE_URL,
|
|
129
|
+
}),
|
|
130
|
+
),
|
|
131
|
+
).not.toThrow();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('--development with --api-base-url JUPITERONE_DEV_API_BASE_URL', () => {
|
|
135
|
+
expect(() =>
|
|
136
|
+
validateApiClientOptions(
|
|
137
|
+
apiClientOptions({
|
|
138
|
+
development: true,
|
|
139
|
+
apiBaseUrl: JUPITERONE_DEV_API_BASE_URL,
|
|
140
|
+
}),
|
|
141
|
+
),
|
|
142
|
+
).not.toThrow();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('--development with --api-base-url', () => {
|
|
146
|
+
expect(() =>
|
|
147
|
+
validateApiClientOptions(
|
|
148
|
+
apiClientOptions({
|
|
149
|
+
development: true,
|
|
150
|
+
apiBaseUrl: 'https://example.com',
|
|
151
|
+
}),
|
|
152
|
+
),
|
|
153
|
+
).toThrow(/both --development and --api-base-url/);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('getApiClientOptions', () => {
|
|
158
|
+
beforeEach(() => {
|
|
159
|
+
process.env.JUPITERONE_ACCOUNT = 'test account';
|
|
160
|
+
process.env.JUPITERONE_API_KEY = 'test api key';
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
afterEach(() => {
|
|
164
|
+
delete process.env.JUPITERONE_ACCOUNT;
|
|
165
|
+
delete process.env.JUPITERONE_API_KEY;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('defaults with valid environment', () => {
|
|
169
|
+
expect(getApiClientOptions(apiClientOptions())).toEqual({
|
|
170
|
+
apiBaseUrl: JUPITERONE_PROD_API_BASE_URL,
|
|
171
|
+
accessToken: 'test api key',
|
|
172
|
+
account: 'test account',
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('defaults with no JUPITERONE_API_KEY', () => {
|
|
177
|
+
delete process.env.JUPITERONE_API_KEY;
|
|
178
|
+
expect(() => getApiClientOptions(apiClientOptions())).toThrowError(
|
|
179
|
+
/JUPITERONE_API_KEY/,
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('defaults with no JUPITERONE_ACCOUNT', () => {
|
|
184
|
+
delete process.env.JUPITERONE_ACCOUNT;
|
|
185
|
+
expect(() => getApiClientOptions(apiClientOptions())).toThrowError(
|
|
186
|
+
/JUPITERONE_ACCOUNT/,
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('--development', () => {
|
|
191
|
+
expect(
|
|
192
|
+
getApiClientOptions(apiClientOptions({ development: true })),
|
|
193
|
+
).toEqual({
|
|
194
|
+
apiBaseUrl: JUPITERONE_DEV_API_BASE_URL,
|
|
195
|
+
accessToken: 'test api key',
|
|
196
|
+
account: 'test account',
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test(`--api-base-url ${JUPITERONE_DEV_API_BASE_URL}`, () => {
|
|
201
|
+
expect(
|
|
202
|
+
getApiClientOptions(
|
|
203
|
+
apiClientOptions({ apiBaseUrl: JUPITERONE_DEV_API_BASE_URL }),
|
|
204
|
+
),
|
|
205
|
+
).toEqual({
|
|
206
|
+
apiBaseUrl: JUPITERONE_DEV_API_BASE_URL,
|
|
207
|
+
accessToken: 'test api key',
|
|
208
|
+
account: 'test account',
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test(`--api-base-url ${JUPITERONE_PROD_API_BASE_URL}`, () => {
|
|
213
|
+
expect(
|
|
214
|
+
getApiClientOptions(
|
|
215
|
+
apiClientOptions({ apiBaseUrl: JUPITERONE_PROD_API_BASE_URL }),
|
|
216
|
+
),
|
|
217
|
+
).toEqual({
|
|
218
|
+
apiBaseUrl: JUPITERONE_PROD_API_BASE_URL,
|
|
219
|
+
accessToken: 'test api key',
|
|
220
|
+
account: 'test account',
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
@@ -18,6 +18,13 @@ export function setupSynchronizerApi({ polly, job, baseUrl }: SetupOptions) {
|
|
|
18
18
|
res.status(200).json({ job });
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
polly.server
|
|
22
|
+
.get(`${baseUrl}/persister/synchronization/jobs/${job.id}`)
|
|
23
|
+
.intercept((req, res) => {
|
|
24
|
+
allowCrossOrigin(req, res);
|
|
25
|
+
res.status(200).json({ job });
|
|
26
|
+
});
|
|
27
|
+
|
|
21
28
|
polly.server
|
|
22
29
|
.post(`${baseUrl}/persister/synchronization/jobs/${job.id}/entities`)
|
|
23
30
|
.intercept((req, res) => {
|
|
@@ -72,11 +79,18 @@ function allowCrossOrigin(req, res) {
|
|
|
72
79
|
});
|
|
73
80
|
}
|
|
74
81
|
|
|
75
|
-
export function generateSynchronizationJob(
|
|
82
|
+
export function generateSynchronizationJob(
|
|
83
|
+
options?: Pick<
|
|
84
|
+
SynchronizationJob,
|
|
85
|
+
'source' | 'scope' | 'integrationInstanceId' | 'integrationJobId'
|
|
86
|
+
>,
|
|
87
|
+
): SynchronizationJob {
|
|
76
88
|
return {
|
|
77
89
|
id: 'test',
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
source: options?.source || 'integration-managed',
|
|
91
|
+
scope: options?.scope,
|
|
92
|
+
integrationJobId: options?.integrationJobId || 'test-job-id',
|
|
93
|
+
integrationInstanceId: options?.integrationInstanceId || 'test-instance-id',
|
|
80
94
|
status: SynchronizationJobStatus.AWAITING_UPLOADS,
|
|
81
95
|
startTimestamp: Date.now(),
|
|
82
96
|
numEntitiesUploaded: 0,
|
package/src/commands/collect.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
|
|
12
12
|
import { loadConfig } from '../config';
|
|
13
13
|
import * as log from '../log';
|
|
14
|
+
import { addPathOptionsToCommand, configureRuntimeFilesystem } from './options';
|
|
14
15
|
|
|
15
16
|
// coercion function to collect multiple values for a flag
|
|
16
17
|
const collector = (value: string, arr: string[]) => {
|
|
@@ -19,13 +20,11 @@ const collector = (value: string, arr: string[]) => {
|
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
export function collect() {
|
|
22
|
-
|
|
23
|
+
const command = createCommand('collect');
|
|
24
|
+
addPathOptionsToCommand(command);
|
|
25
|
+
|
|
26
|
+
return command
|
|
23
27
|
.description('collect data and store entities and relationships to disk')
|
|
24
|
-
.option(
|
|
25
|
-
'-p, --project-path <directory>',
|
|
26
|
-
'path to integration project directory',
|
|
27
|
-
process.cwd(),
|
|
28
|
-
)
|
|
29
28
|
.option(
|
|
30
29
|
'-s, --step <steps>',
|
|
31
30
|
'step(s) to run, comma separated. Utilizes available caches to speed up dependent steps.',
|
|
@@ -42,6 +41,8 @@ export function collect() {
|
|
|
42
41
|
)
|
|
43
42
|
.option('-V, --disable-schema-validation', 'disable schema validation')
|
|
44
43
|
.action(async (options) => {
|
|
44
|
+
configureRuntimeFilesystem(options);
|
|
45
|
+
|
|
45
46
|
if (!options.cache && options.step.length === 0) {
|
|
46
47
|
throw new Error(
|
|
47
48
|
'Invalid option: Option --no-cache requires option --step to also be specified.',
|
|
@@ -53,13 +54,6 @@ export function collect() {
|
|
|
53
54
|
);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
// Point `fileSystem.ts` functions to expected location relative to
|
|
57
|
-
// integration project path.
|
|
58
|
-
process.env.JUPITERONE_INTEGRATION_STORAGE_DIRECTORY = path.resolve(
|
|
59
|
-
options.projectPath,
|
|
60
|
-
'.j1-integration',
|
|
61
|
-
);
|
|
62
|
-
|
|
63
57
|
if (options.step.length > 0 && options.cache && !options.cachePath) {
|
|
64
58
|
// Step option was used, cache is wanted, and no cache path was provided
|
|
65
59
|
// therefore, copy .j1-integration into .j1-integration-cache
|
package/src/commands/diff.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { IntegrationInvocationConfigLoadError, loadConfig } from '../config';
|
|
|
12
12
|
import { promises as fs } from 'fs';
|
|
13
13
|
import * as log from '../log';
|
|
14
14
|
|
|
15
|
+
/* eslint-disable no-console */
|
|
15
16
|
export function generateIntegrationGraphSchemaCommand() {
|
|
16
17
|
return createCommand('generate-integration-graph-schema')
|
|
17
18
|
.description(
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_UPLOAD_BATCH_SIZE,
|
|
3
|
+
getAccountFromEnvironment,
|
|
4
|
+
getApiKeyFromEnvironment,
|
|
5
|
+
JUPITERONE_DEV_API_BASE_URL,
|
|
6
|
+
JUPITERONE_PROD_API_BASE_URL,
|
|
7
|
+
} from '@jupiterone/integration-sdk-runtime';
|
|
8
|
+
import { Command, OptionValues } from 'commander';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
export interface PathOptions {
|
|
12
|
+
projectPath: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function addPathOptionsToCommand(command: Command): Command {
|
|
16
|
+
return command.option(
|
|
17
|
+
'-p, --project-path <directory>',
|
|
18
|
+
'path to integration project directory',
|
|
19
|
+
process.cwd(),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configure the filesystem interaction code to function against `${options.projectPath}/.j1-integration by setting the
|
|
25
|
+
* global `process.env.JUPITERONE_INTEGRATION_STORAGE_DIRECTORY` variable.
|
|
26
|
+
*/
|
|
27
|
+
export function configureRuntimeFilesystem(options: PathOptions): void {
|
|
28
|
+
process.env.JUPITERONE_INTEGRATION_STORAGE_DIRECTORY = path.resolve(
|
|
29
|
+
options.projectPath,
|
|
30
|
+
'.j1-integration',
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SyncOptions {
|
|
35
|
+
source: 'integration-managed' | 'integration-external' | 'api';
|
|
36
|
+
scope?: string | undefined;
|
|
37
|
+
integrationInstanceId?: string | undefined;
|
|
38
|
+
uploadBatchSize: number;
|
|
39
|
+
uploadRelationshipBatchSize: number;
|
|
40
|
+
skipFinalize: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function addSyncOptionsToCommand(command: Command): Command {
|
|
44
|
+
return command
|
|
45
|
+
.option(
|
|
46
|
+
'-i, --integrationInstanceId <id>',
|
|
47
|
+
'_integrationInstanceId assigned to uploaded entities and relationships',
|
|
48
|
+
)
|
|
49
|
+
.option(
|
|
50
|
+
'--source <integration-managed|integration-external|api>',
|
|
51
|
+
'specify synchronization job source value',
|
|
52
|
+
(value: string) => {
|
|
53
|
+
if (
|
|
54
|
+
value !== 'integration-managed' &&
|
|
55
|
+
value !== 'integration-external' &&
|
|
56
|
+
value !== 'api'
|
|
57
|
+
) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`--source must be one of "integration-managed", "integration-external", or "api"`,
|
|
60
|
+
);
|
|
61
|
+
} else {
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
'integration-managed',
|
|
66
|
+
)
|
|
67
|
+
.option('--scope <anystring>', 'specify synchronization job scope value')
|
|
68
|
+
.option(
|
|
69
|
+
'-u, --upload-batch-size <number>',
|
|
70
|
+
'specify number of entities and relationships per upload batch',
|
|
71
|
+
(value, _previous: Number) => Number(value),
|
|
72
|
+
DEFAULT_UPLOAD_BATCH_SIZE,
|
|
73
|
+
)
|
|
74
|
+
.option(
|
|
75
|
+
'-ur, --upload-relationship-batch-size <number>',
|
|
76
|
+
'specify number of relationships per upload batch, overrides --upload-batch-size',
|
|
77
|
+
(value, _previous: Number) => Number(value),
|
|
78
|
+
DEFAULT_UPLOAD_BATCH_SIZE,
|
|
79
|
+
)
|
|
80
|
+
.option(
|
|
81
|
+
'--skip-finalize',
|
|
82
|
+
'skip synchronization finalization to leave job open for additional uploads',
|
|
83
|
+
false,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Validates options for the synchronization job.
|
|
89
|
+
*
|
|
90
|
+
* @throws {Error} if --source is "api" and --scope is not specified
|
|
91
|
+
* @throws {Error} if --source is "api" and --integrationInstanceId is specified
|
|
92
|
+
* @throws {Error} if one of --integrationInstanceId or --source is not specified
|
|
93
|
+
*/
|
|
94
|
+
export function validateSyncOptions(options: SyncOptions): SyncOptions {
|
|
95
|
+
if (options.source === 'api') {
|
|
96
|
+
if (options.integrationInstanceId)
|
|
97
|
+
throw new Error(
|
|
98
|
+
'Invalid configuration supplied. Cannot specify both --source api and --integrationInstanceId flags.',
|
|
99
|
+
);
|
|
100
|
+
if (!options.scope)
|
|
101
|
+
throw new Error(
|
|
102
|
+
'Invalid configuration supplied. --source api requires --scope flag.',
|
|
103
|
+
);
|
|
104
|
+
} else if (
|
|
105
|
+
!['integration-managed', 'integration-external'].includes(options.source)
|
|
106
|
+
) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Invalid configuration supplied. --source must be one of: integration-managed, integration-external, api. Received: ${options.source}.`,
|
|
109
|
+
);
|
|
110
|
+
} else if (!options.integrationInstanceId) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
'Invalid configuration supplied. --integrationInstanceId or --source api and --scope required.',
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return options;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Returns an object containing only `SyncOptions` properties. This helps to ensure other properties of `OptionValues` are not passed to the `SynchronizationJob`.
|
|
120
|
+
*/
|
|
121
|
+
export function getSyncOptions(options: OptionValues): SyncOptions {
|
|
122
|
+
return {
|
|
123
|
+
source: options.source,
|
|
124
|
+
scope: options.scope,
|
|
125
|
+
integrationInstanceId: options.integrationInstanceId,
|
|
126
|
+
uploadBatchSize: options.uploadBatchSize,
|
|
127
|
+
uploadRelationshipBatchSize: options.uploadRelationshipBatchSize,
|
|
128
|
+
skipFinalize: options.skipFinalize,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface ApiClientOptions {
|
|
133
|
+
development: boolean;
|
|
134
|
+
apiBaseUrl: string;
|
|
135
|
+
apiKey?: string;
|
|
136
|
+
account?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function addApiClientOptionsToCommand(command: Command): Command {
|
|
140
|
+
return command
|
|
141
|
+
.option(
|
|
142
|
+
'--api-base-url <url>',
|
|
143
|
+
'specify synchronization API base URL',
|
|
144
|
+
JUPITERONE_PROD_API_BASE_URL,
|
|
145
|
+
)
|
|
146
|
+
.option(
|
|
147
|
+
'-d, --development',
|
|
148
|
+
'"true" to target apps.dev.jupiterone.io (JUPITERONE_DEV environment variable)',
|
|
149
|
+
!!process.env.JUPITERONE_DEV,
|
|
150
|
+
)
|
|
151
|
+
.option(
|
|
152
|
+
'--account <account>',
|
|
153
|
+
'JupiterOne account ID (JUPITERONE_ACCOUNT environment variable)',
|
|
154
|
+
process.env.JUPITERONE_ACCOUNT,
|
|
155
|
+
)
|
|
156
|
+
.option(
|
|
157
|
+
'--api-key <key>',
|
|
158
|
+
'JupiterOne API key (JUPITERONE_API_KEY environment variable)',
|
|
159
|
+
process.env.JUPITERONE_API_KEY?.replace(/./, '*'),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function validateApiClientOptions(options: ApiClientOptions) {
|
|
164
|
+
if (
|
|
165
|
+
options.development &&
|
|
166
|
+
![JUPITERONE_PROD_API_BASE_URL, JUPITERONE_DEV_API_BASE_URL].includes(
|
|
167
|
+
options.apiBaseUrl,
|
|
168
|
+
)
|
|
169
|
+
) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`Invalid configuration supplied. Cannot specify both --development and --api-base-url flags.`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Builds a set of options for the 'createApiClient' function.
|
|
178
|
+
*
|
|
179
|
+
* @throws IntegrationApiKeyRequiredError
|
|
180
|
+
* @throws IntegrationAccountRequiredError
|
|
181
|
+
*/
|
|
182
|
+
export function getApiClientOptions(options: ApiClientOptions) {
|
|
183
|
+
return {
|
|
184
|
+
apiBaseUrl: options.development
|
|
185
|
+
? JUPITERONE_DEV_API_BASE_URL
|
|
186
|
+
: options.apiBaseUrl,
|
|
187
|
+
accessToken: options.apiKey || getApiKeyFromEnvironment(),
|
|
188
|
+
account: options.account || getAccountFromEnvironment(),
|
|
189
|
+
};
|
|
190
|
+
}
|