@jupiterone/integration-sdk-cli 12.8.1 → 13.0.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.
- package/dist/src/bocchi/actions/steps.d.ts +4 -0
- package/dist/src/bocchi/actions/steps.js +20 -0
- package/dist/src/bocchi/actions/steps.js.map +1 -0
- package/dist/src/bocchi/bocchi.d.ts +1 -0
- package/dist/src/bocchi/bocchi.js +260 -0
- package/dist/src/bocchi/bocchi.js.map +1 -0
- package/dist/src/bocchi/templates/partials/directRelationships.hbs +26 -0
- package/dist/src/bocchi/templates/partials/mappedRelationships.hbs +36 -0
- package/dist/src/bocchi/templates/partials/stepMap.hbs +47 -0
- package/dist/src/bocchi/templates/steps/child-singleton.ts.hbs +47 -0
- package/dist/src/bocchi/templates/steps/fetch-child-entities.ts.hbs +58 -0
- package/dist/src/bocchi/templates/steps/fetch-entities.ts.hbs +47 -0
- package/dist/src/bocchi/templates/steps/index.test.ts.hbs +41 -0
- package/dist/src/bocchi/templates/steps/singleton.ts.hbs +45 -0
- package/dist/src/bocchi/templates/steps/spec.ts.hbs +73 -0
- package/dist/src/bocchi/templates/top-level/.env.example.hbs +3 -0
- package/dist/src/bocchi/templates/top-level/.eslintignore.hbs +1 -0
- package/dist/src/bocchi/templates/top-level/.eslintrc.hbs +6 -0
- package/dist/src/bocchi/templates/top-level/.github/workflows/build.yml.hbs +29 -0
- package/dist/src/bocchi/templates/top-level/.github/workflows/questions.yml.hbs +40 -0
- package/dist/src/bocchi/templates/top-level/.gitignore.hbs +8 -0
- package/dist/src/bocchi/templates/top-level/.node-version.hbs +1 -0
- package/dist/src/bocchi/templates/top-level/.prettierignore.hbs +6 -0
- package/dist/src/bocchi/templates/top-level/CHANGELOG.md.hbs +9 -0
- package/dist/src/bocchi/templates/top-level/CODEOWNERS.hbs +3 -0
- package/dist/src/bocchi/templates/top-level/Dockerfile.hbs +25 -0
- package/dist/src/bocchi/templates/top-level/LICENSE.hbs +373 -0
- package/dist/src/bocchi/templates/top-level/README.md.hbs +114 -0
- package/dist/src/bocchi/templates/top-level/docs/development.md.hbs +28 -0
- package/dist/src/bocchi/templates/top-level/docs/jupiterone.md.hbs +1 -0
- package/dist/src/bocchi/templates/top-level/docs/spec/index.ts.hbs +14 -0
- package/dist/src/bocchi/templates/top-level/husky.config.js.hbs +1 -0
- package/dist/src/bocchi/templates/top-level/jest.config.js.hbs +1 -0
- package/dist/src/bocchi/templates/top-level/jupiterone/questions/questions.yaml.hbs +16 -0
- package/dist/src/bocchi/templates/top-level/lint-staged.config.js.hbs +1 -0
- package/dist/src/bocchi/templates/top-level/package.json.hbs +49 -0
- package/dist/src/bocchi/templates/top-level/prettier.config.js.hbs +1 -0
- package/dist/src/bocchi/templates/top-level/src/client.ts.hbs +116 -0
- package/dist/src/bocchi/templates/top-level/src/config.ts.hbs +41 -0
- package/dist/src/bocchi/templates/top-level/src/index.test.ts.hbs +6 -0
- package/dist/src/bocchi/templates/top-level/src/index.ts.hbs +14 -0
- package/dist/src/bocchi/templates/top-level/src/steps/constants.ts.hbs +60 -0
- package/dist/src/bocchi/templates/top-level/src/steps/converters.ts.hbs +37 -0
- package/dist/src/bocchi/templates/top-level/src/steps/index.ts.hbs +13 -0
- package/dist/src/bocchi/templates/top-level/src/steps/types.ts.hbs +6 -0
- package/dist/src/bocchi/templates/top-level/test/README.md.hbs +5 -0
- package/dist/src/bocchi/templates/top-level/test/config.ts.hbs +30 -0
- package/dist/src/bocchi/templates/top-level/test/recording.ts.hbs +74 -0
- package/dist/src/bocchi/templates/top-level/tsconfig.dist.json.hbs +13 -0
- package/dist/src/bocchi/templates/top-level/tsconfig.json.hbs +7 -0
- package/dist/src/bocchi/utils/types.d.ts +98 -0
- package/dist/src/bocchi/utils/types.js +10 -0
- package/dist/src/bocchi/utils/types.js.map +1 -0
- package/dist/src/commands/bocchi.d.ts +1 -0
- package/dist/src/commands/bocchi.js +29 -0
- package/dist/src/commands/bocchi.js.map +1 -0
- package/dist/src/commands/collect.js.map +1 -1
- package/dist/src/commands/diff.js.map +1 -1
- package/dist/src/commands/document.js.map +1 -1
- package/dist/src/commands/generate-ingestion-sources-config.js.map +1 -1
- package/dist/src/commands/generate-integration-graph-schema.js.map +1 -1
- package/dist/src/commands/index.d.ts +1 -0
- package/dist/src/commands/index.js +1 -0
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/options.js.map +1 -1
- package/dist/src/commands/run.js.map +1 -1
- package/dist/src/commands/validate-question-file.js.map +1 -1
- package/dist/src/commands/visualize-types.js.map +1 -1
- package/dist/src/config.js.map +1 -1
- package/dist/src/generator/actions.d.ts +2 -1
- package/dist/src/generator/actions.js +5 -1
- package/dist/src/generator/actions.js.map +1 -1
- package/dist/src/generator/entitiesFlow.js.map +1 -1
- package/dist/src/generator/newIntegration.js.map +1 -1
- package/dist/src/generator/relationshipsFlow.js.map +1 -1
- package/dist/src/generator/stepsFlow.js.map +1 -1
- package/dist/src/generator/util.js.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/log.js.map +1 -1
- package/dist/src/neo4j/neo4jGraphStore.js.map +1 -1
- package/dist/src/neo4j/neo4jUtilities.js.map +1 -1
- package/dist/src/neo4j/uploadToNeo4j.js.map +1 -1
- package/dist/src/neo4j/wipeNeo4j.js.map +1 -1
- package/dist/src/questions/managedQuestionFileValidator.js.map +1 -1
- package/dist/src/services/queryLanguage.js.map +1 -1
- package/dist/src/troubleshoot/utils.js.map +1 -1
- package/dist/src/utils/generateVisHTML.js.map +1 -1
- package/dist/src/utils/getSortedJupiterOneTypes.js.map +1 -1
- package/dist/src/visualization/createMappedRelationshipNodesAndEdges.js.map +1 -1
- package/dist/src/visualization/generateDependencyVisualization.js.map +1 -1
- package/dist/src/visualization/generateVisualization.js.map +1 -1
- package/dist/src/visualization/retrieveIntegrationData.js.map +1 -1
- package/dist/src/visualization/utils.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/package.json +7 -6
- package/src/bocchi/README.md +95 -0
- package/src/bocchi/actions/steps.ts +17 -0
- package/src/bocchi/bocchi.ts +311 -0
- package/src/bocchi/docs/template/README.md +140 -0
- package/src/bocchi/docs/template/authentication.md +100 -0
- package/src/bocchi/docs/template/examples/example.json +127 -0
- package/src/bocchi/docs/template/examples/signalSciences.json +128 -0
- package/src/bocchi/docs/template/steps.md +656 -0
- package/src/bocchi/templates/partials/directRelationships.hbs +26 -0
- package/src/bocchi/templates/partials/mappedRelationships.hbs +36 -0
- package/src/bocchi/templates/partials/stepMap.hbs +47 -0
- package/src/bocchi/templates/steps/child-singleton.ts.hbs +47 -0
- package/src/bocchi/templates/steps/fetch-child-entities.ts.hbs +58 -0
- package/src/bocchi/templates/steps/fetch-entities.ts.hbs +47 -0
- package/src/bocchi/templates/steps/index.test.ts.hbs +41 -0
- package/src/bocchi/templates/steps/singleton.ts.hbs +45 -0
- package/src/bocchi/templates/steps/spec.ts.hbs +73 -0
- package/src/bocchi/templates/top-level/.env.example.hbs +3 -0
- package/src/bocchi/templates/top-level/.eslintignore.hbs +1 -0
- package/src/bocchi/templates/top-level/.eslintrc.hbs +6 -0
- package/src/bocchi/templates/top-level/.github/workflows/build.yml.hbs +29 -0
- package/src/bocchi/templates/top-level/.github/workflows/questions.yml.hbs +40 -0
- package/src/bocchi/templates/top-level/.gitignore.hbs +8 -0
- package/src/bocchi/templates/top-level/.node-version.hbs +1 -0
- package/src/bocchi/templates/top-level/.prettierignore.hbs +6 -0
- package/src/bocchi/templates/top-level/CHANGELOG.md.hbs +9 -0
- package/src/bocchi/templates/top-level/CODEOWNERS.hbs +3 -0
- package/src/bocchi/templates/top-level/Dockerfile.hbs +25 -0
- package/src/bocchi/templates/top-level/LICENSE.hbs +373 -0
- package/src/bocchi/templates/top-level/README.md.hbs +114 -0
- package/src/bocchi/templates/top-level/docs/development.md.hbs +28 -0
- package/src/bocchi/templates/top-level/docs/jupiterone.md.hbs +1 -0
- package/src/bocchi/templates/top-level/docs/spec/index.ts.hbs +14 -0
- package/src/bocchi/templates/top-level/husky.config.js.hbs +1 -0
- package/src/bocchi/templates/top-level/jest.config.js.hbs +1 -0
- package/src/bocchi/templates/top-level/jupiterone/questions/questions.yaml.hbs +16 -0
- package/src/bocchi/templates/top-level/lint-staged.config.js.hbs +1 -0
- package/src/bocchi/templates/top-level/package.json.hbs +49 -0
- package/src/bocchi/templates/top-level/prettier.config.js.hbs +1 -0
- package/src/bocchi/templates/top-level/src/client.ts.hbs +116 -0
- package/src/bocchi/templates/top-level/src/config.ts.hbs +41 -0
- package/src/bocchi/templates/top-level/src/index.test.ts.hbs +6 -0
- package/src/bocchi/templates/top-level/src/index.ts.hbs +14 -0
- package/src/bocchi/templates/top-level/src/steps/constants.ts.hbs +60 -0
- package/src/bocchi/templates/top-level/src/steps/converters.ts.hbs +37 -0
- package/src/bocchi/templates/top-level/src/steps/index.ts.hbs +13 -0
- package/src/bocchi/templates/top-level/src/steps/types.ts.hbs +6 -0
- package/src/bocchi/templates/top-level/test/README.md.hbs +5 -0
- package/src/bocchi/templates/top-level/test/config.ts.hbs +30 -0
- package/src/bocchi/templates/top-level/test/recording.ts.hbs +74 -0
- package/src/bocchi/templates/top-level/tsconfig.dist.json.hbs +13 -0
- package/src/bocchi/templates/top-level/tsconfig.json.hbs +7 -0
- package/src/bocchi/utils/types.ts +106 -0
- package/src/commands/bocchi.ts +28 -0
- package/src/commands/generate-ingestion-sources-config.ts +4 -5
- package/src/commands/index.ts +1 -0
- package/src/generator/actions.ts +13 -1
- package/src/index.ts +3 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { BaseAPIClient } from '@jupiterone/integration-sdk-http-client';
|
|
2
|
+
import { IntegrationConfig } from './config';
|
|
3
|
+
import {
|
|
4
|
+
{{#each template.steps}}
|
|
5
|
+
{{pascalCase entity.name}},
|
|
6
|
+
{{/each}}
|
|
7
|
+
} from './steps/types';
|
|
8
|
+
import { Entity, IntegrationLogger } from '@jupiterone/integration-sdk-core';
|
|
9
|
+
|
|
10
|
+
export type ResourceIteratee<T> = (each: T) => Promise<void> | void;
|
|
11
|
+
|
|
12
|
+
export class APIClient extends BaseAPIClient {
|
|
13
|
+
private authHeaders: Record<string, string>;
|
|
14
|
+
|
|
15
|
+
constructor(readonly config: IntegrationConfig, logger: IntegrationLogger) {
|
|
16
|
+
super({
|
|
17
|
+
baseUrl: '{{template.baseUrl}}',
|
|
18
|
+
logger,
|
|
19
|
+
{{#if template.tokenBucket}}
|
|
20
|
+
tokenBucket: {
|
|
21
|
+
maximumCapacity: {{template.tokenBucket.maximumCapacity}},
|
|
22
|
+
refillRate: {{template.tokenBucket.refillRate}}
|
|
23
|
+
}
|
|
24
|
+
{{/if}}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
{{#if (isNotEndpointAuth template.authentication.strategy)}}
|
|
29
|
+
protected getAuthorizationHeaders(): Record<string, string> {
|
|
30
|
+
return { {{#each (sanitizeAuthObject template.authentication.authHeaders)}}'{{@key}}': `{{this}}`,{{/each}} };
|
|
31
|
+
}
|
|
32
|
+
{{else}}
|
|
33
|
+
protected async getAuthorizationHeaders(): Promise<Record<string, string>> {
|
|
34
|
+
const response = await (await this.retryableRequest(
|
|
35
|
+
`${this.baseUrl}{{template.authentication.params.path}}`,
|
|
36
|
+
{
|
|
37
|
+
authorize: false, // needed only for authentication call
|
|
38
|
+
headers: { {{#each (sanitizeAuthObject template.authentication.params.headers)}}'{{@key}}': `{{this}}`,{{/each}} },
|
|
39
|
+
method: '{{sanitizeHttpMethod template.authentication.params.method}}',
|
|
40
|
+
{{#if request.params}}
|
|
41
|
+
body: { {{#each (sanitizeAuthObject template.authentication.params.body)}}{{@key}}: {{this}},{{/each}} }
|
|
42
|
+
{{/if}}
|
|
43
|
+
}
|
|
44
|
+
)).json();
|
|
45
|
+
return { {{#each (sanitizeAuthObject template.authentication.authHeaders)}}'{{@key}}': `{{this}}`,{{/each}} }
|
|
46
|
+
}
|
|
47
|
+
{{/if}}
|
|
48
|
+
|
|
49
|
+
public async verifyAuthentication(): Promise<void> {
|
|
50
|
+
{{#if (isNotEndpointAuth template.authentication.strategy)}}
|
|
51
|
+
// TODO: implement
|
|
52
|
+
await new Promise(() => { throw new Error('Unimplemented: Pick an API call to use for validation!') });
|
|
53
|
+
{{else}}
|
|
54
|
+
await this.getAuthorizationHeaders();
|
|
55
|
+
{{/if}}
|
|
56
|
+
}
|
|
57
|
+
{{#each template.steps}}
|
|
58
|
+
|
|
59
|
+
{{#if (isSingletonRequest response.responseType)}}
|
|
60
|
+
public async get{{pascalCase entity.name}}({{#if parentAssociation}}parentEntity: Entity{{/if}}): Promise<{{pascalCase entity.name}}> {
|
|
61
|
+
const url = `${this.baseUrl}{{{sanitizeUrlPath request.urlTemplate}}}`;
|
|
62
|
+
const response = await (await this.retryableRequest(
|
|
63
|
+
url,
|
|
64
|
+
{
|
|
65
|
+
method: '{{sanitizeHttpMethod request.method}}',
|
|
66
|
+
{{#if request.params}}
|
|
67
|
+
body: { {{#each (sanitizeHttpBody request.params)}}'{{@key}}': `{{this}}`,{{/each}} }
|
|
68
|
+
{{/if}}
|
|
69
|
+
}
|
|
70
|
+
)).json();
|
|
71
|
+
return response.{{response.dataPath}} as {{pascalCase entity.name}};
|
|
72
|
+
}
|
|
73
|
+
{{else}}
|
|
74
|
+
public async iterate{{pascalCase entity.name}}s(
|
|
75
|
+
{{#if parentAssociation}}
|
|
76
|
+
parentEntity: Entity,
|
|
77
|
+
{{/if}}
|
|
78
|
+
iteratee: ResourceIteratee<{{pascalCase entity.name}}>
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
{{#if response.nextTokenPath}}
|
|
81
|
+
let nextToken: string | undefined;
|
|
82
|
+
do {
|
|
83
|
+
{{/if}}
|
|
84
|
+
const url = `${this.baseUrl}{{{sanitizeUrlPath request.urlTemplate}}}`;
|
|
85
|
+
const response = await (await this.retryableRequest(
|
|
86
|
+
url,
|
|
87
|
+
{
|
|
88
|
+
method: '{{sanitizeHttpMethod request.method}}',
|
|
89
|
+
{{#if request.params}}
|
|
90
|
+
body: { {{#each (sanitizeHttpBody request.params)}}'{{@key}}': `{{this}}`,{{/each}} }
|
|
91
|
+
{{/if}}
|
|
92
|
+
}
|
|
93
|
+
)).json();
|
|
94
|
+
const resources = response.{{response.dataPath}} as {{pascalCase entity.name}}[];
|
|
95
|
+
for (const resource of resources) {
|
|
96
|
+
await iteratee(resource);
|
|
97
|
+
}
|
|
98
|
+
{{#if response.nextTokenPath}}
|
|
99
|
+
nextToken = response.{{response.nextTokenPath}};
|
|
100
|
+
} while (nextToken);
|
|
101
|
+
{{/if}}
|
|
102
|
+
}
|
|
103
|
+
{{/if}}
|
|
104
|
+
{{/each}}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let client: APIClient | undefined;
|
|
108
|
+
|
|
109
|
+
export function createAPIClient(
|
|
110
|
+
config: IntegrationConfig,
|
|
111
|
+
logger: IntegrationLogger
|
|
112
|
+
): APIClient {
|
|
113
|
+
return client
|
|
114
|
+
? client
|
|
115
|
+
: new APIClient(config, logger);
|
|
116
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{{#with template}}
|
|
2
|
+
import {
|
|
3
|
+
IntegrationExecutionContext,
|
|
4
|
+
IntegrationValidationError,
|
|
5
|
+
IntegrationInstanceConfigFieldMap,
|
|
6
|
+
} from '@jupiterone/integration-sdk-core';
|
|
7
|
+
import { createAPIClient } from './client';
|
|
8
|
+
|
|
9
|
+
export const instanceConfigFields = {
|
|
10
|
+
{{#each instanceConfigFields}}
|
|
11
|
+
{{@key}}: {
|
|
12
|
+
{{#if type}}type: '{{type}}',{{/if}}
|
|
13
|
+
{{#if mask}}mask: {{mask}},{{/if}}
|
|
14
|
+
{{#if optional}}optional: {{optional}},{{/if}}
|
|
15
|
+
},
|
|
16
|
+
{{/each}}
|
|
17
|
+
} satisfies IntegrationInstanceConfigFieldMap;
|
|
18
|
+
|
|
19
|
+
export interface IntegrationConfig {
|
|
20
|
+
{{#each instanceConfigFields}}
|
|
21
|
+
{{@key}}: {{configTypeToType type}};
|
|
22
|
+
{{/each}}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function validateInvocation(
|
|
26
|
+
context: IntegrationExecutionContext<IntegrationConfig>,
|
|
27
|
+
) {
|
|
28
|
+
const { config } = context.instance;
|
|
29
|
+
|
|
30
|
+
{{#with (requiredConfig instanceConfigFields)}}
|
|
31
|
+
{{#if this.length}}
|
|
32
|
+
if ({{#each this}}!config.{{this}}{{#unless @last}} || {{/unless}}{{/each}}) {
|
|
33
|
+
throw new IntegrationValidationError('Config requires all of{{#each this}} {{this}}{{/each}}');
|
|
34
|
+
}
|
|
35
|
+
{{/if}}
|
|
36
|
+
{{/with}}
|
|
37
|
+
|
|
38
|
+
const apiClient = createAPIClient(config);
|
|
39
|
+
await apiClient.verifyAuthentication();
|
|
40
|
+
}
|
|
41
|
+
{{/with}}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { invocationConfig as implementedConfig } from '.';
|
|
2
|
+
import { invocationConfig as specConfig } from '../docs/spec';
|
|
3
|
+
|
|
4
|
+
test('implemented integration should match spec', () => {
|
|
5
|
+
expect(implementedConfig).toImplementSpec(specConfig, { requireSpec: true });
|
|
6
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { IntegrationInvocationConfig } from '@jupiterone/integration-sdk-core';
|
|
2
|
+
import { integrationSteps } from './steps';
|
|
3
|
+
import {
|
|
4
|
+
validateInvocation,
|
|
5
|
+
IntegrationConfig,
|
|
6
|
+
instanceConfigFields,
|
|
7
|
+
} from './config';
|
|
8
|
+
|
|
9
|
+
export const invocationConfig: IntegrationInvocationConfig<IntegrationConfig> =
|
|
10
|
+
{
|
|
11
|
+
instanceConfigFields,
|
|
12
|
+
validateInvocation,
|
|
13
|
+
integrationSteps,
|
|
14
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{{#with template}}
|
|
2
|
+
import {
|
|
3
|
+
RelationshipClass,
|
|
4
|
+
StepEntityMetadata,
|
|
5
|
+
StepRelationshipMetadata,
|
|
6
|
+
} from '@jupiterone/integration-sdk-core';
|
|
7
|
+
|
|
8
|
+
export const Steps = {
|
|
9
|
+
{{#each steps}}
|
|
10
|
+
{{constantCase id}}: '{{id}}',
|
|
11
|
+
{{#if directRelationships}}
|
|
12
|
+
{{#with (getDirectRelationships this)}}
|
|
13
|
+
{{#each this}}
|
|
14
|
+
BUILD_{{constantCase sourceStep.entity.name}}_{{constantCase targetStep.entity.name}}_RELATIONSHIPS: 'build-{{kebabCase sourceStep.entity.name}}-{{kebabCase targetStep.entity.name}}-relationships',
|
|
15
|
+
{{/each}}
|
|
16
|
+
{{/with}}
|
|
17
|
+
{{/if}}
|
|
18
|
+
{{#if mappedRelationships}}
|
|
19
|
+
BUILD_{{constantCase entity.name}}_MAPPED_RELATIONSHIPS: 'build-{{kebabCase entity.name}}-mapped-relationships',
|
|
20
|
+
{{/if}}
|
|
21
|
+
{{/each}}
|
|
22
|
+
} satisfies Record<string, string>;
|
|
23
|
+
|
|
24
|
+
export const Entities = {
|
|
25
|
+
{{#each steps}}
|
|
26
|
+
{{#with entity}}
|
|
27
|
+
{{constantCase name}}: {
|
|
28
|
+
resourceName: '{{name}}',
|
|
29
|
+
_type: '{{_type}}',
|
|
30
|
+
_class: ['{{_class}}'],
|
|
31
|
+
},
|
|
32
|
+
{{/with}}
|
|
33
|
+
{{/each}}
|
|
34
|
+
} satisfies Record<string, StepEntityMetadata>;
|
|
35
|
+
|
|
36
|
+
export const Relationships = {
|
|
37
|
+
{{#each steps}}
|
|
38
|
+
{{#if parentAssociation}}
|
|
39
|
+
{{#with (getStepByType parentAssociation.parentEntityType)}}{{constantCase entity.name}}{{/with}}_{{constantCase parentAssociation.relationshipClass}}_{{constantCase entity.name}}: {
|
|
40
|
+
sourceType: Entities.{{#with (getStepByType parentAssociation.parentEntityType)}}{{constantCase entity.name}}{{/with}}._type,
|
|
41
|
+
_class: RelationshipClass.{{constantCase parentAssociation.relationshipClass}},
|
|
42
|
+
targetType: Entities.{{constantCase entity.name}}._type,
|
|
43
|
+
_type: '{{getRelationshipType parentAssociation.relationshipClass parentAssociation.parentEntityType entity._type}}',
|
|
44
|
+
},
|
|
45
|
+
{{/if}}
|
|
46
|
+
{{#if directRelationships}}
|
|
47
|
+
{{#with (getDirectRelationships this)}}
|
|
48
|
+
{{#each this}}
|
|
49
|
+
{{constantCase sourceStep.entity.name}}_{{constantCase relationshipClass}}_{{constantCase targetStep.entity.name}}: {
|
|
50
|
+
sourceType: Entities.{{constantCase sourceStep.entity.name}}._type,
|
|
51
|
+
_class: RelationshipClass.{{constantCase relationshipClass}},
|
|
52
|
+
targetType: Entities.{{constantCase targetStep.entity.name}}._type,
|
|
53
|
+
_type: '{{getRelationshipType relationshipClass sourceStep.entity._type targetStep.entity._type}}',
|
|
54
|
+
},
|
|
55
|
+
{{/each}}
|
|
56
|
+
{{/with}}
|
|
57
|
+
{{/if}}
|
|
58
|
+
{{/each}}
|
|
59
|
+
} satisfies Record<string, StepRelationshipMetadata>;
|
|
60
|
+
{{/with}}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{{#with template}}
|
|
2
|
+
import { createIntegrationEntity } from '@jupiterone/integration-sdk-core';
|
|
3
|
+
import { Entities } from './constants';
|
|
4
|
+
import {
|
|
5
|
+
{{#each steps}}
|
|
6
|
+
{{pascalCase entity.name}},
|
|
7
|
+
{{/each}}
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
{{#each steps}}
|
|
11
|
+
{{#with entity}}
|
|
12
|
+
export function create{{pascalCase name}}Entity(data: {{pascalCase name}}) {
|
|
13
|
+
return createIntegrationEntity({
|
|
14
|
+
entityData: {
|
|
15
|
+
source: data,
|
|
16
|
+
assign: {
|
|
17
|
+
_key: data.{{_keyPath}},
|
|
18
|
+
_type: Entities.{{constantCase name}}._type,
|
|
19
|
+
_class: Entities.{{constantCase name}}._class,
|
|
20
|
+
{{#if staticFields}}
|
|
21
|
+
{{#each staticFields}}
|
|
22
|
+
{{@key}}: {{{escape this}}},
|
|
23
|
+
{{/each}}
|
|
24
|
+
{{/if}}
|
|
25
|
+
{{#if fieldMappings}}
|
|
26
|
+
{{#each fieldMappings}}
|
|
27
|
+
{{@key}}: data.{{this}},
|
|
28
|
+
{{/each}}
|
|
29
|
+
{{/if}}
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
{{/with}}
|
|
35
|
+
|
|
36
|
+
{{/each}}
|
|
37
|
+
{{/with}}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{{#with template}}
|
|
2
|
+
{{#each steps}}
|
|
3
|
+
import { {{camelCase entity.name}}Steps } from './{{id}}';
|
|
4
|
+
{{/each}}
|
|
5
|
+
|
|
6
|
+
const integrationSteps = [
|
|
7
|
+
{{#each steps}}
|
|
8
|
+
...{{camelCase entity.name}}Steps,
|
|
9
|
+
{{/each}}
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export { integrationSteps };
|
|
13
|
+
{{/with}}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { IntegrationInvocationConfig } from '@jupiterone/integration-sdk-core';
|
|
2
|
+
import { StepTestConfig } from '@jupiterone/integration-sdk-testing';
|
|
3
|
+
import * as dotenv from 'dotenv';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { invocationConfig } from '../src';
|
|
6
|
+
import { IntegrationConfig } from '../src/config';
|
|
7
|
+
|
|
8
|
+
if (process.env.LOAD_ENV) {
|
|
9
|
+
dotenv.config({
|
|
10
|
+
path: path.join(__dirname, '../.env'),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
{{#each template.instanceConfigFields}}
|
|
15
|
+
const DEFAULT_{{constantCase @key}} = '';
|
|
16
|
+
{{/each}}
|
|
17
|
+
|
|
18
|
+
export const integrationConfig: IntegrationConfig = {
|
|
19
|
+
{{#each template.instanceConfigFields}}
|
|
20
|
+
{{camelCase @key}}: process.env.{{constantCase @key}} ?? DEFAULT_{{constantCase @key}},
|
|
21
|
+
{{/each}}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function buildStepTestConfigForStep(stepId: string): StepTestConfig {
|
|
25
|
+
return {
|
|
26
|
+
stepId,
|
|
27
|
+
instanceConfig: integrationConfig,
|
|
28
|
+
invocationConfig: invocationConfig as IntegrationInvocationConfig,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
setupRecording,
|
|
3
|
+
Recording,
|
|
4
|
+
SetupRecordingInput,
|
|
5
|
+
mutations,
|
|
6
|
+
} from '@jupiterone/integration-sdk-testing';
|
|
7
|
+
|
|
8
|
+
export { Recording };
|
|
9
|
+
|
|
10
|
+
export function setupProjectRecording(
|
|
11
|
+
input: Omit<SetupRecordingInput, 'mutateEntry'>,
|
|
12
|
+
): Recording {
|
|
13
|
+
return setupRecording({
|
|
14
|
+
...input,
|
|
15
|
+
redactedRequestHeaders: ['Authorization'],
|
|
16
|
+
redactedResponseHeaders: ['set-cookie'],
|
|
17
|
+
mutateEntry: mutations.unzipGzippedRecordingEntry,
|
|
18
|
+
/*mutateEntry: (entry) => {
|
|
19
|
+
redact(entry);
|
|
20
|
+
},*/
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// a more sophisticated redaction example below:
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
function getRedactedOAuthResponse() {
|
|
28
|
+
return {
|
|
29
|
+
access_token: '[REDACTED]',
|
|
30
|
+
expires_in: 9999,
|
|
31
|
+
token_type: 'Bearer',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function redact(entry): void {
|
|
36
|
+
if (entry.request.postData) {
|
|
37
|
+
entry.request.postData.text = '[REDACTED]';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!entry.response.content.text) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//let's unzip the entry so we can modify it
|
|
45
|
+
mutations.unzipGzippedRecordingEntry(entry);
|
|
46
|
+
|
|
47
|
+
//we can just get rid of all response content if this was the token call
|
|
48
|
+
const requestUrl = entry.request.url;
|
|
49
|
+
if (requestUrl.match(/oauth\/token/)) {
|
|
50
|
+
entry.response.content.text = JSON.stringify(getRedactedOAuthResponse());
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//if it wasn't a token call, parse the response text, removing any carriage returns or newlines
|
|
55
|
+
const responseText = entry.response.content.text;
|
|
56
|
+
const parsedResponseText = JSON.parse(responseText.replace(/\r?\n|\r/g, ''));
|
|
57
|
+
|
|
58
|
+
//now we can modify the returned object as desired
|
|
59
|
+
//in this example, if the return text is an array of objects that have the 'tenant' property...
|
|
60
|
+
if (parsedResponseText[0]?.tenant) {
|
|
61
|
+
for (let i = 0; i < parsedResponseText.length; i++) {
|
|
62
|
+
parsedResponseText[i].client_secret = '[REDACTED]';
|
|
63
|
+
parsedResponseText[i].jwt_configuration = '[REDACTED]';
|
|
64
|
+
parsedResponseText[i].signing_keys = '[REDACTED]';
|
|
65
|
+
parsedResponseText[i].encryption_key = '[REDACTED]';
|
|
66
|
+
parsedResponseText[i].addons = '[REDACTED]';
|
|
67
|
+
parsedResponseText[i].client_metadata = '[REDACTED]';
|
|
68
|
+
parsedResponseText[i].mobile = '[REDACTED]';
|
|
69
|
+
parsedResponseText[i].native_social_login = '[REDACTED]';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
entry.response.content.text = JSON.stringify(parsedResponseText);
|
|
74
|
+
} */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"include": ["src", "package.json"],
|
|
4
|
+
"exclude": [
|
|
5
|
+
"dist",
|
|
6
|
+
"**/*.test.ts",
|
|
7
|
+
"**/*.test.js",
|
|
8
|
+
"**/*/__tests__/**/*.ts",
|
|
9
|
+
"**/*/__tests__/**/*.js",
|
|
10
|
+
"**/*/__mocks__/**/*.ts",
|
|
11
|
+
"**/*/__mocks__/**/*.js"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { RelationshipClass } from '@jupiterone/integration-sdk-core';
|
|
2
|
+
|
|
3
|
+
export const StepType = {
|
|
4
|
+
SINGLETON: 'singleton',
|
|
5
|
+
CHILD_SINGLETON: 'child-singleton',
|
|
6
|
+
FETCH_ENTITIES: 'fetch-entities',
|
|
7
|
+
FETCH_CHILD_ENTITIES: 'fetch-child-entities',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export interface Template {
|
|
11
|
+
instanceConfigFields: {
|
|
12
|
+
[propertyName: string]: IntegrationConfigProperty;
|
|
13
|
+
};
|
|
14
|
+
authentication: EndpointAuthentication | ConfigFieldAuthentication;
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
tokenBucket?: {
|
|
17
|
+
maximumCapacity: number;
|
|
18
|
+
refillRate: number;
|
|
19
|
+
};
|
|
20
|
+
steps: Step[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface IntegrationConfigProperty {
|
|
24
|
+
type?: 'string' | 'json' | 'boolean';
|
|
25
|
+
mask?: Boolean;
|
|
26
|
+
optional?: Boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface EndpointAuthentication {
|
|
30
|
+
strategy: 'endpoint';
|
|
31
|
+
params: {
|
|
32
|
+
path: string;
|
|
33
|
+
method: 'GET' | 'POST';
|
|
34
|
+
body?: Record<string, any>;
|
|
35
|
+
headers?: Record<string, string>;
|
|
36
|
+
};
|
|
37
|
+
authHeaders: AuthenticationHeaders;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Authentication headers to be used in API requests.
|
|
42
|
+
* The `Authorization` header is required, and is typically
|
|
43
|
+
* a bearer token or API key.
|
|
44
|
+
*/
|
|
45
|
+
interface AuthenticationHeaders {
|
|
46
|
+
Authorization: string;
|
|
47
|
+
[headerName: string]: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface ConfigFieldAuthentication {
|
|
51
|
+
strategy: 'configField';
|
|
52
|
+
authHeaders: {
|
|
53
|
+
[headerName: string]: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface Step {
|
|
58
|
+
id: string;
|
|
59
|
+
name: string;
|
|
60
|
+
entity: {
|
|
61
|
+
name: string;
|
|
62
|
+
_type: string;
|
|
63
|
+
_class: string | Array<string>;
|
|
64
|
+
_keyPath: string;
|
|
65
|
+
fieldMappings?: {
|
|
66
|
+
[entityProperty: string]: string;
|
|
67
|
+
};
|
|
68
|
+
staticFields?: {
|
|
69
|
+
[entityProperty: string]: string;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
parentAssociation?: {
|
|
73
|
+
parentEntityType: string;
|
|
74
|
+
relationshipClass: string;
|
|
75
|
+
};
|
|
76
|
+
request: {
|
|
77
|
+
urlTemplate: string;
|
|
78
|
+
method?: 'GET' | 'POST';
|
|
79
|
+
params?: Record<string, any>;
|
|
80
|
+
};
|
|
81
|
+
response: {
|
|
82
|
+
dataPath: string;
|
|
83
|
+
responseType: 'SINGLETON' | 'LIST';
|
|
84
|
+
nextTokenPath?: string;
|
|
85
|
+
};
|
|
86
|
+
directRelationships?: {
|
|
87
|
+
targetKey: string;
|
|
88
|
+
targetType: string;
|
|
89
|
+
_class: RelationshipClass;
|
|
90
|
+
direction: 'FORWARD' | 'REVERSE';
|
|
91
|
+
}[];
|
|
92
|
+
mappedRelationships?: {
|
|
93
|
+
_class: RelationshipClass;
|
|
94
|
+
direction: 'FORWARD' | 'REVERSE';
|
|
95
|
+
mappings:
|
|
96
|
+
| {
|
|
97
|
+
sourceProperty: string;
|
|
98
|
+
targetProperty: string;
|
|
99
|
+
}
|
|
100
|
+
| {
|
|
101
|
+
targetValue: string;
|
|
102
|
+
targetProperty: string;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
dependsOn?: string[];
|
|
106
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { createCommand } from 'commander';
|
|
3
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
4
|
+
|
|
5
|
+
export function bocchi() {
|
|
6
|
+
return createCommand('bocchi')
|
|
7
|
+
.description('New graph project generator')
|
|
8
|
+
.action(async (cmdOpts) => {
|
|
9
|
+
const Plop = await dynamicImport('plop');
|
|
10
|
+
const configPath = path.resolve(
|
|
11
|
+
path.join(__dirname, '../bocchi/bocchi.js'),
|
|
12
|
+
);
|
|
13
|
+
Plop.Plop.prepare(
|
|
14
|
+
{
|
|
15
|
+
cwd: process.cwd(),
|
|
16
|
+
configPath,
|
|
17
|
+
},
|
|
18
|
+
(env) =>
|
|
19
|
+
Plop.Plop.execute(env, (env) => {
|
|
20
|
+
const options = {
|
|
21
|
+
...env,
|
|
22
|
+
dest: process.cwd(),
|
|
23
|
+
};
|
|
24
|
+
return Plop.run(options, undefined, true);
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -102,11 +102,10 @@ export function generateIngestionSourcesConfig<
|
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
104
|
// Get the dependent steps for the given matchedIntegrationStepIds
|
|
105
|
-
const childIngestionSources = integrationSteps.filter(
|
|
106
|
-
(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
),
|
|
105
|
+
const childIngestionSources = integrationSteps.filter((step) =>
|
|
106
|
+
step.dependsOn?.some((value) =>
|
|
107
|
+
matchedIntegrationStepIds.includes(value),
|
|
108
|
+
),
|
|
110
109
|
);
|
|
111
110
|
// Generate ingestionConfig with the childIngestionSources
|
|
112
111
|
newIngestionConfig[key] = {
|
package/src/commands/index.ts
CHANGED
package/src/generator/actions.ts
CHANGED
|
@@ -37,6 +37,10 @@ async function yarnLint(_answers, config, _plop) {
|
|
|
37
37
|
|
|
38
38
|
// NPM commands
|
|
39
39
|
|
|
40
|
+
async function j1Document(_answers, config, _plop) {
|
|
41
|
+
return spawnCommand(config, 'npm exec', ['j1-integration', 'document']);
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
async function npmInstall(_answers, config, _plop) {
|
|
41
45
|
return spawnCommand(config, 'npm', ['install']);
|
|
42
46
|
}
|
|
@@ -49,4 +53,12 @@ async function npmLint(_answers, config, _plop) {
|
|
|
49
53
|
return spawnCommand(config, 'npm', ['run lint']);
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
export {
|
|
56
|
+
export {
|
|
57
|
+
yarnInstall,
|
|
58
|
+
yarnFormat,
|
|
59
|
+
yarnLint,
|
|
60
|
+
npmInstall,
|
|
61
|
+
npmFormat,
|
|
62
|
+
npmLint,
|
|
63
|
+
j1Document,
|
|
64
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
generateIngestionSourcesConfigCommand,
|
|
16
16
|
troubleshootLocalExecution,
|
|
17
17
|
generate,
|
|
18
|
+
bocchi,
|
|
18
19
|
} from './commands';
|
|
19
20
|
|
|
20
21
|
export function createCli() {
|
|
@@ -32,5 +33,6 @@ export function createCli() {
|
|
|
32
33
|
.addCommand(generateIntegrationGraphSchemaCommand())
|
|
33
34
|
.addCommand(generateIngestionSourcesConfigCommand())
|
|
34
35
|
.addCommand(troubleshootLocalExecution())
|
|
35
|
-
.addCommand(generate())
|
|
36
|
+
.addCommand(generate())
|
|
37
|
+
.addCommand(bocchi());
|
|
36
38
|
}
|