@jupiterone/integration-sdk-cli 8.23.0 → 8.24.0

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.
@@ -623,9 +623,15 @@ describe('document', () => {
623
623
  });
624
624
 
625
625
  describe('generate-integration-graph-schema', () => {
626
- test('should generate integration graph schema', async () => {
626
+ test('should generate integration graph schema with TypeScript integration', async () => {
627
627
  await generateIntegrationGraphSchemaTest(
628
628
  'generate-integration-graph-schema_all-graph-data-types',
629
629
  );
630
630
  });
631
+
632
+ test('should generate integration graph schema with integration JavaScript dist', async () => {
633
+ await generateIntegrationGraphSchemaTest(
634
+ 'generate-integration-graph-schema_all-graph-data-types-dist',
635
+ );
636
+ });
631
637
  });
@@ -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(): SynchronizationJob {
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
- integrationJobId: 'test-job-id',
79
- integrationInstanceId: 'test-instance-id',
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,
@@ -8,7 +8,7 @@ import {
8
8
  } from '@jupiterone/integration-sdk-core';
9
9
  import { createCommand } from 'commander';
10
10
  import path from 'path';
11
- import { loadConfig } from '../config';
11
+ import { IntegrationInvocationConfigLoadError, loadConfig } from '../config';
12
12
  import { promises as fs } from 'fs';
13
13
  import * as log from '../log';
14
14
 
@@ -32,7 +32,7 @@ export function generateIntegrationGraphSchemaCommand() {
32
32
  log.info(
33
33
  `Generating integration graph schema (projectPath=${projectPath}, outputFile=${outputFile})`,
34
34
  );
35
- const config = await loadConfig(path.join(projectPath, 'src'));
35
+ const config = await loadConfigFromTarget(projectPath);
36
36
 
37
37
  const integrationGraphSchema = generateIntegrationGraphSchema(
38
38
  config.integrationSteps,
@@ -50,6 +50,48 @@ export function generateIntegrationGraphSchemaCommand() {
50
50
  });
51
51
  }
52
52
 
53
+ function loadConfigFromSrc(projectPath: string) {
54
+ return loadConfig(path.join(projectPath, 'src'));
55
+ }
56
+
57
+ function loadConfigFromDist(projectPath: string) {
58
+ return loadConfig(path.join(projectPath, 'dist'));
59
+ }
60
+
61
+ /**
62
+ * The way that integration npm packages are distributed has changed over time.
63
+ * This function handles different cases where the invocation config has
64
+ * traditionally lived to support backwards compatibility and make adoption
65
+ * easier.
66
+ */
67
+ async function loadConfigFromTarget(projectPath: string) {
68
+ let configFromSrcErr: Error | undefined;
69
+ let configFromDistErr: Error | undefined;
70
+
71
+ try {
72
+ const configFromSrc = await loadConfigFromSrc(projectPath);
73
+ return configFromSrc;
74
+ } catch (err) {
75
+ configFromSrcErr = err;
76
+ }
77
+
78
+ try {
79
+ const configFromDist = await loadConfigFromDist(projectPath);
80
+ return configFromDist;
81
+ } catch (err) {
82
+ configFromDistErr = err;
83
+ }
84
+
85
+ const combinedError = configFromDistErr
86
+ ? configFromSrcErr + ', ' + configFromDistErr
87
+ : configFromSrcErr;
88
+
89
+ throw new IntegrationInvocationConfigLoadError(
90
+ 'Error loading integration invocation configuration. Ensure "invocationConfig" is exported from src/index or dist/index. Additional details: ' +
91
+ combinedError,
92
+ );
93
+ }
94
+
53
95
  type IntegrationGraphSchemaEntityMetadata = {
54
96
  resourceName: string;
55
97
  _class: string | string[];
@@ -0,0 +1,73 @@
1
+ import { getApiBaseUrl } from '@jupiterone/integration-sdk-runtime';
2
+
3
+ /**
4
+ * Determines the JupiterOne `apiBaseUrl` based on these mutually-exclusive `options`:
5
+ * - --api-base-url - a specific URL to use
6
+ * - --development - use the development API
7
+ *
8
+ * @throws Error if both --api-base-url and --development are specified
9
+ */
10
+ export function getApiBaseUrlOption(options: any): string {
11
+ let apiBaseUrl: string;
12
+ if (options.apiBaseUrl) {
13
+ if (options.development) {
14
+ throw new Error(
15
+ 'Invalid configuration supplied. Cannot specify both --api-base-url and --development(-d) flags.',
16
+ );
17
+ }
18
+ apiBaseUrl = options.apiBaseUrl;
19
+ } else {
20
+ apiBaseUrl = getApiBaseUrl({
21
+ dev: options.development,
22
+ });
23
+ }
24
+ return apiBaseUrl;
25
+ }
26
+
27
+ interface SynchronizationJobSourceOptions {
28
+ source: 'integration-managed' | 'integration-external' | 'api';
29
+ scope?: string;
30
+ integrationInstanceId?: string;
31
+ }
32
+
33
+ /**
34
+ * Determines the `source` configuration for the synchronization job based on these `options`:
35
+ * - --source - a specific source to use
36
+ * - --scope - a specific scope to use, required when --source is "api"
37
+ * - --integrationInstanceId - a specific integrationInstanceId to use, required when --source is not "api"
38
+ *
39
+ * @throws Error if --source is "api" and --scope is not specified
40
+ * @throws Error if --source is "api" and --integrationInstanceId is specified
41
+ * @throws Error if one of --integrationInstanceId or --source is not specified
42
+ */
43
+ export function getSynchronizationJobSourceOptions(
44
+ options: any,
45
+ ): SynchronizationJobSourceOptions {
46
+ const synchronizeOptions: SynchronizationJobSourceOptions = {
47
+ source: options.source,
48
+ };
49
+ if (options.source === 'api') {
50
+ if (options.integrationInstanceId)
51
+ throw new Error(
52
+ 'Invalid configuration supplied. Cannot specify both --source api and --integrationInstanceId flags.',
53
+ );
54
+ if (!options.scope)
55
+ throw new Error(
56
+ 'Invalid configuration supplied. --source api requires --scope flag.',
57
+ );
58
+ synchronizeOptions.scope = options.scope;
59
+ } else if (
60
+ !['integration-managed', 'integration-external'].includes(options.source)
61
+ ) {
62
+ throw new Error(
63
+ `Invalid configuration supplied. --source must be one of: integration-managed, integration-external, api. Received: ${options.source}.`,
64
+ );
65
+ } else if (options.integrationInstanceId) {
66
+ synchronizeOptions.integrationInstanceId = options.integrationInstanceId;
67
+ } else {
68
+ throw new Error(
69
+ 'Invalid configuration supplied. --integrationInstanceId or --source api and --scope required.',
70
+ );
71
+ }
72
+ return synchronizeOptions;
73
+ }
@@ -12,21 +12,25 @@ import {
12
12
  FileSystemGraphObjectStore,
13
13
  finalizeSynchronization,
14
14
  getAccountFromEnvironment,
15
- getApiBaseUrl,
16
15
  getApiKeyFromEnvironment,
17
16
  initiateSynchronization,
17
+ synchronizationStatus,
18
18
  } from '@jupiterone/integration-sdk-runtime';
19
19
  import { createPersisterApiStepGraphObjectDataUploader } from '@jupiterone/integration-sdk-runtime/dist/src/execution/uploader';
20
20
 
21
21
  import { loadConfig } from '../config';
22
22
  import * as log from '../log';
23
+ import {
24
+ getApiBaseUrlOption,
25
+ getSynchronizationJobSourceOptions,
26
+ } from './options';
23
27
 
24
28
  const DEFAULT_UPLOAD_CONCURRENCY = 5;
25
29
 
26
30
  export function run() {
27
31
  return createCommand('run')
28
32
  .description('collect and sync to upload entities and relationships')
29
- .requiredOption(
33
+ .option(
30
34
  '-i, --integrationInstanceId <id>',
31
35
  '_integrationInstanceId assigned to uploaded entities and relationships',
32
36
  )
@@ -40,7 +44,17 @@ export function run() {
40
44
  '"true" to target apps.dev.jupiterone.io',
41
45
  !!process.env.JUPITERONE_DEV,
42
46
  )
43
- .option('--api-base-url <url>', 'API base URL used during run operation.')
47
+ .option('--api-base-url <url>', 'API base URL used during run operation')
48
+ .option('--skip-finalize', 'skip synchronization finalization')
49
+ .option(
50
+ '--source <integration-managed|integration-external|api>',
51
+ 'configure the synchronization job source value',
52
+ 'integration-managed',
53
+ )
54
+ .option(
55
+ '--scope <anystring>',
56
+ 'configure the synchronization job scope value',
57
+ )
44
58
  .option('-V, --disable-schema-validation', 'disable schema validation')
45
59
  .option(
46
60
  '-u, --upload-batch-size <number>',
@@ -65,19 +79,7 @@ export function run() {
65
79
  log.debug('Loading account from JUPITERONE_ACCOUNT environment variable');
66
80
  const account = getAccountFromEnvironment();
67
81
 
68
- let apiBaseUrl: string;
69
- if (options.apiBaseUrl) {
70
- if (options.development) {
71
- throw new Error(
72
- 'Invalid configuration supplied. Cannot specify both --api-base-url and --development(-d) flags.',
73
- );
74
- }
75
- apiBaseUrl = options.apiBaseUrl;
76
- } else {
77
- apiBaseUrl = getApiBaseUrl({
78
- dev: options.development,
79
- });
80
- }
82
+ const apiBaseUrl = getApiBaseUrlOption(options);
81
83
  log.debug(`Configuring client to access "${apiBaseUrl}"`);
82
84
 
83
85
  const startTime = Date.now();
@@ -88,7 +90,8 @@ export function run() {
88
90
  account,
89
91
  });
90
92
 
91
- const { integrationInstanceId } = options;
93
+ const synchronizationJobSourceOptions =
94
+ getSynchronizationJobSourceOptions(options);
92
95
 
93
96
  let logger = createIntegrationLogger({
94
97
  name: 'local',
@@ -98,7 +101,7 @@ export function run() {
98
101
  const synchronizationContext = await initiateSynchronization({
99
102
  logger,
100
103
  apiClient,
101
- integrationInstanceId,
104
+ ...synchronizationJobSourceOptions,
102
105
  });
103
106
 
104
107
  logger = synchronizationContext.logger;
@@ -150,12 +153,19 @@ export function run() {
150
153
 
151
154
  log.displayExecutionResults(executionResults);
152
155
 
153
- const synchronizationResult = await finalizeSynchronization({
154
- ...synchronizationContext,
155
- partialDatasets: executionResults.metadata.partialDatasets,
156
- });
157
-
158
- log.displaySynchronizationResults(synchronizationResult);
156
+ if (!options.skipFinalize) {
157
+ const synchronizationResult = await finalizeSynchronization({
158
+ ...synchronizationContext,
159
+ partialDatasets: executionResults.metadata.partialDatasets,
160
+ });
161
+ log.displaySynchronizationResults(synchronizationResult);
162
+ } else {
163
+ log.info(
164
+ 'Skipping synchronization finalization. Job will remain in "AWAITING_UPLOADS" state.',
165
+ );
166
+ const jobStatus = await synchronizationStatus(synchronizationContext);
167
+ log.displaySynchronizationResults(jobStatus);
168
+ }
159
169
  } catch (err) {
160
170
  await eventPublishingQueue.onIdle();
161
171
  if (!logger.isHandledError(err)) {
@@ -5,19 +5,22 @@ import {
5
5
  createApiClient,
6
6
  createIntegrationLogger,
7
7
  getAccountFromEnvironment,
8
- getApiBaseUrl,
9
8
  getApiKeyFromEnvironment,
10
9
  synchronizeCollectedData,
11
10
  } from '@jupiterone/integration-sdk-runtime';
12
11
 
13
12
  import * as log from '../log';
13
+ import {
14
+ getApiBaseUrlOption,
15
+ getSynchronizationJobSourceOptions,
16
+ } from './options';
14
17
 
15
18
  export function sync() {
16
19
  return createCommand('sync')
17
20
  .description(
18
21
  'sync collected data with JupiterOne, requires JUPITERONE_API_KEY, JUPITERONE_ACCOUNT',
19
22
  )
20
- .requiredOption(
23
+ .option(
21
24
  '-i, --integrationInstanceId <id>',
22
25
  '_integrationInstanceId assigned to uploaded entities and relationships',
23
26
  )
@@ -32,6 +35,15 @@ export function sync() {
32
35
  !!process.env.JUPITERONE_DEV,
33
36
  )
34
37
  .option('--api-base-url <url>', 'API base URL used during sync operation.')
38
+ .option(
39
+ '--source <integration-managed|integration-external|api>',
40
+ 'configure the synchronization job source value',
41
+ 'integration-managed',
42
+ )
43
+ .option(
44
+ '--scope <anystring>',
45
+ 'configure the synchronization job scope value',
46
+ )
35
47
  .option(
36
48
  '-u, --upload-batch-size <number>',
37
49
  'specify number of items per batch for upload (default 250)',
@@ -54,19 +66,7 @@ export function sync() {
54
66
  log.debug('Loading account from JUPITERONE_ACCOUNT environment variable');
55
67
  const account = getAccountFromEnvironment();
56
68
 
57
- let apiBaseUrl: string;
58
- if (options.apiBaseUrl) {
59
- if (options.development) {
60
- throw new Error(
61
- 'Invalid configuration supplied. Cannot specify both --api-base-url and --development(-d) flags.',
62
- );
63
- }
64
- apiBaseUrl = options.apiBaseUrl;
65
- } else {
66
- apiBaseUrl = getApiBaseUrl({
67
- dev: options.development,
68
- });
69
- }
69
+ const apiBaseUrl = getApiBaseUrlOption(options);
70
70
  log.debug(`Configuring client to access "${apiBaseUrl}"`);
71
71
 
72
72
  const apiClient = createApiClient({
@@ -75,18 +75,19 @@ export function sync() {
75
75
  accessToken,
76
76
  });
77
77
 
78
- const { integrationInstanceId } = options;
78
+ const synchronizationJobSourceOptions =
79
+ getSynchronizationJobSourceOptions(options);
79
80
 
80
81
  const logger = createIntegrationLogger({
81
82
  name: 'local',
82
83
  pretty: true,
83
84
  });
84
85
  const job = await synchronizeCollectedData({
85
- logger: logger.child({ integrationInstanceId }),
86
+ logger: logger.child(synchronizationJobSourceOptions),
86
87
  apiClient,
87
- integrationInstanceId,
88
88
  uploadBatchSize: options.uploadBatchSize,
89
89
  uploadRelationshipBatchSize: options.uploadRelationshipBatchSize,
90
+ ...synchronizationJobSourceOptions,
90
91
  });
91
92
 
92
93
  log.displaySynchronizationResults(job);