@jupiterone/integration-sdk-cli 12.8.1 → 12.8.3
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupiterone/integration-sdk-cli",
|
|
3
|
-
"version": "12.8.
|
|
3
|
+
"version": "12.8.3",
|
|
4
4
|
"description": "The SDK for developing JupiterOne integrations",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -21,15 +21,16 @@
|
|
|
21
21
|
"prebuild:dist": "rm -rf dist && mkdir dist",
|
|
22
22
|
"build:dist": "tsc -p tsconfig.dist.json --declaration && npm run copy-files",
|
|
23
23
|
"prepack": "npm run build:dist",
|
|
24
|
-
"copy-files": "cp -r src/generator/template dist/src/generator/template && cp -r src/generator/stepTemplate dist/src/generator/stepTemplate",
|
|
24
|
+
"copy-files": "cp -r src/generator/template dist/src/generator/template && cp -r src/generator/stepTemplate dist/src/generator/stepTemplate && cp -r src/bocchi/templates dist/src/bocchi/templates",
|
|
25
25
|
"plop": "plop --plopfile dist/src/generator/newIntegration.js"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@jupiterone/data-model": "^0.61.3",
|
|
29
|
-
"@jupiterone/integration-sdk-core": "^12.8.
|
|
30
|
-
"@jupiterone/integration-sdk-runtime": "^12.8.
|
|
29
|
+
"@jupiterone/integration-sdk-core": "^12.8.3",
|
|
30
|
+
"@jupiterone/integration-sdk-runtime": "^12.8.3",
|
|
31
31
|
"chalk": "^4",
|
|
32
32
|
"commander": "^9.4.0",
|
|
33
|
+
"ejs": "^3.1.9",
|
|
33
34
|
"fs-extra": "^10.1.0",
|
|
34
35
|
"globby": "^11.0.0",
|
|
35
36
|
"inquirer-checkbox-plus-prompt": "^1.4.2",
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"url-exists": "^1.0.3"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
|
-
"@jupiterone/integration-sdk-private-test-utils": "^12.8.
|
|
48
|
+
"@jupiterone/integration-sdk-private-test-utils": "^12.8.3",
|
|
48
49
|
"@pollyjs/adapter-node-http": "^6.0.5",
|
|
49
50
|
"@pollyjs/core": "^6.0.5",
|
|
50
51
|
"@pollyjs/persister-fs": "^6.0.5",
|
|
@@ -57,5 +58,5 @@
|
|
|
57
58
|
"neo-forgery": "^2.0.0",
|
|
58
59
|
"vis": "^4.21.0-EOL"
|
|
59
60
|
},
|
|
60
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "aa9dfabc973f1b046424b742b34cafc91261c3f5"
|
|
61
62
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Bocchi - a JupiterOne graph project generator
|
|
2
|
+
|
|
3
|
+
Developing a graph project from scratch for a new JupiterOne integration can be
|
|
4
|
+
time consuming, and typically involves writing a lot of boilerplate code that is
|
|
5
|
+
similar across all integrations. This is where Bocchi comes in. It's purpose is
|
|
6
|
+
to eliminate the need to write boilerplate code, as well as standardize much of
|
|
7
|
+
the logic and practices our graph projects use. Moreover, Bocchi allows us to
|
|
8
|
+
more quickly expand JupiterOne’s integration roster without spending a ton of
|
|
9
|
+
resources and development time.
|
|
10
|
+
|
|
11
|
+
Bocchi is a one-time use tool to be used when creating a new graph project. To
|
|
12
|
+
use Bocchi, a developer provides a JSON template that contains key components of
|
|
13
|
+
the new integration, such as instance configuration fields, the authentication
|
|
14
|
+
method, and the steps the new integration should run. Bocchi will interpret this
|
|
15
|
+
JSON template and generate a fully-functioning graph project - things like an
|
|
16
|
+
http-service client, step handlers, authentication functions, etc. will all be
|
|
17
|
+
written for the developer. After using Bocchi to generate a new graph project,
|
|
18
|
+
the only code the developer (may) need to write is the more involved,
|
|
19
|
+
vendor-specific logic.
|
|
20
|
+
|
|
21
|
+
To try Bocchi out, you can use the example template
|
|
22
|
+
[here](./docs/template/examples/signalSciences.json).
|
|
23
|
+
|
|
24
|
+
## The Template
|
|
25
|
+
|
|
26
|
+
To create a new graph project using Bocchi, a developer needs to provide a JSON
|
|
27
|
+
template that Bocchi will interpret to generate graph code. The JSON template
|
|
28
|
+
defines key components of the integration, such as instance configuration
|
|
29
|
+
fields, the authentication method, as well as what steps the integration should
|
|
30
|
+
have. Moreoever, the steps outlined in the template contain information on what
|
|
31
|
+
API endpoints should be used for the step, as well as what entities and
|
|
32
|
+
relationships should be ingested.
|
|
33
|
+
|
|
34
|
+
<b>Currently, Bocchi only supports creating graph projects that ingest data from
|
|
35
|
+
REST APIs!</b>
|
|
36
|
+
|
|
37
|
+
Documentation for the JSON template can be found
|
|
38
|
+
[here](./docs/template/README.md).
|
|
39
|
+
|
|
40
|
+
Additionally, example template files can be found
|
|
41
|
+
[here](./docs/template/examples/).
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
yarn j1-integration bocchi
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Additional Notes
|
|
50
|
+
|
|
51
|
+
### Authentication
|
|
52
|
+
|
|
53
|
+
Bocchi currently supports two different methods of authentication for
|
|
54
|
+
integrations: Endpoint authentication and Config Value authentication.
|
|
55
|
+
|
|
56
|
+
Endpoint authentication is used when the product we're integrating with requires
|
|
57
|
+
an API request to a special endpoint to receive authentication headers that will
|
|
58
|
+
be used in all future requests. However, if the product does not require a
|
|
59
|
+
special authentication endpoint to be hit and instead something like a pre-made
|
|
60
|
+
API token can be sent on all API requests, then Config Value authentication can
|
|
61
|
+
be used.
|
|
62
|
+
|
|
63
|
+
Note that if Config Value authentication is used for the integration - that is,
|
|
64
|
+
authentication information is provided in the instance config - then the graph
|
|
65
|
+
project Bocchi generates will have an incomplete `verifyAuthentication()`
|
|
66
|
+
function in the client. This is because the graph-project generator does not
|
|
67
|
+
know what endpoint to use for verifying authentication.
|
|
68
|
+
|
|
69
|
+
<b>The developer will need to manually update the generated
|
|
70
|
+
verifyAuthentication() function and choose what endpoint should be used for
|
|
71
|
+
verifying authentication.</b>
|
|
72
|
+
|
|
73
|
+
### Types
|
|
74
|
+
|
|
75
|
+
After running Bocchi, the generated graph project will have a `types.ts` file
|
|
76
|
+
that contains TS types for the entities the integration will produce. The types
|
|
77
|
+
however will be `any`. This is because Bocchi cannot guess what properties will
|
|
78
|
+
be on the different entities the integration will produce. So for prosperity and
|
|
79
|
+
to follow best coding practices, <b>The types should be updated manually by the
|
|
80
|
+
developer.</b>
|
|
81
|
+
|
|
82
|
+
### Mapped Relationships
|
|
83
|
+
|
|
84
|
+
If a developer defines a mapped relationship(s) in the template, then they may
|
|
85
|
+
notice the following warning when running the integration locally:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
The following types were encountered but are not declared in the step's "types" field:
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
If a step produces a mapped relationship(s), the developer will need to manually
|
|
92
|
+
add the `mappedRelationships` field in the generated stepMap for that step. The
|
|
93
|
+
`mappedRelationships` field will need to contain a valid
|
|
94
|
+
`StepMappedRelationshipMetadata` object (from
|
|
95
|
+
`@jupiterone/integration-sdk-core`).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Step, StepType } from '../utils/types';
|
|
2
|
+
|
|
3
|
+
export function stepTemplateHelper(step: Step) {
|
|
4
|
+
return {
|
|
5
|
+
stepTemplateFile: `${determineTypeOfStep(step)}.ts.hbs`,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function determineTypeOfStep(step: Step): string {
|
|
10
|
+
return step.response.responseType === 'SINGLETON'
|
|
11
|
+
? step.parentAssociation
|
|
12
|
+
? StepType.CHILD_SINGLETON
|
|
13
|
+
: StepType.SINGLETON
|
|
14
|
+
: step.parentAssociation
|
|
15
|
+
? StepType.FETCH_CHILD_ENTITIES
|
|
16
|
+
: StepType.FETCH_ENTITIES;
|
|
17
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { NodePlopAPI } from 'plop';
|
|
2
|
+
import checkboxPlus from 'inquirer-checkbox-plus-prompt';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { kebabCase } from 'lodash';
|
|
5
|
+
import {
|
|
6
|
+
yarnFormat,
|
|
7
|
+
yarnInstall,
|
|
8
|
+
yarnLint,
|
|
9
|
+
j1Document,
|
|
10
|
+
} from '../generator/actions';
|
|
11
|
+
import { Template } from './utils/types';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import { stepTemplateHelper } from './actions/steps';
|
|
14
|
+
import {
|
|
15
|
+
IntegrationInstanceConfigFieldMap,
|
|
16
|
+
RelationshipDirection,
|
|
17
|
+
generateRelationshipType,
|
|
18
|
+
} from '@jupiterone/integration-sdk-core';
|
|
19
|
+
|
|
20
|
+
function bocchi(plop: NodePlopAPI) {
|
|
21
|
+
plop.setActionType('yarnFormat', yarnFormat);
|
|
22
|
+
plop.setActionType('yarnInstall', yarnInstall);
|
|
23
|
+
plop.setActionType('yarnLint', yarnLint);
|
|
24
|
+
plop.setActionType('j1Document', j1Document);
|
|
25
|
+
plop.setPrompt('checkbox-plus', checkboxPlus);
|
|
26
|
+
plop.setHelper('getDirectRelationships', (step, options) => {
|
|
27
|
+
return step.directRelationships.map((relationship) => {
|
|
28
|
+
const step2 = options.data.root.template.steps.find(
|
|
29
|
+
(s) => s.entity._type === relationship.targetType,
|
|
30
|
+
);
|
|
31
|
+
return {
|
|
32
|
+
step,
|
|
33
|
+
sourceStep:
|
|
34
|
+
relationship.direction === RelationshipDirection.FORWARD
|
|
35
|
+
? step
|
|
36
|
+
: step2,
|
|
37
|
+
targetStep:
|
|
38
|
+
relationship.direction === RelationshipDirection.FORWARD
|
|
39
|
+
? step2
|
|
40
|
+
: step,
|
|
41
|
+
relationshipClass: relationship._class,
|
|
42
|
+
targetKey: relationship.targetKey,
|
|
43
|
+
forward: relationship.direction === RelationshipDirection.FORWARD,
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
plop.setHelper('getStepByType', (type, options) => {
|
|
48
|
+
return options.data.root.template.steps.find(
|
|
49
|
+
(s) => s.entity._type === type,
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* (_class, fromType, toType) => string
|
|
54
|
+
*/
|
|
55
|
+
plop.setHelper('getRelationshipType', generateRelationshipType);
|
|
56
|
+
plop.setHelper('sanitizeUrlPath', (urlTemplate: string): string => {
|
|
57
|
+
const regex = /%parent\.(.+?)%/g;
|
|
58
|
+
for (const match of urlTemplate.matchAll(regex)) {
|
|
59
|
+
urlTemplate = urlTemplate.replace(
|
|
60
|
+
match[0],
|
|
61
|
+
'${parentEntity.' + match[1] + '}',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
// looks at the urlTemplate provided and replaces "%nextToken%" with "${nextToken}",
|
|
65
|
+
// which is the string the client file will use to reference the nextToken
|
|
66
|
+
return urlTemplate.replace('%nextToken%', '${nextToken}');
|
|
67
|
+
});
|
|
68
|
+
plop.setHelper('escape', (data) => {
|
|
69
|
+
if (typeof data === 'string') {
|
|
70
|
+
return "'data'";
|
|
71
|
+
} else if (Array.isArray(data)) {
|
|
72
|
+
return '[' + data.toString() + ']';
|
|
73
|
+
} else {
|
|
74
|
+
return data;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
plop.setHelper('sanitizeHttpMethod', (method: string = 'GET'): string => {
|
|
78
|
+
// if the user puts nothing in request.method, we need to default to GET
|
|
79
|
+
return method.toUpperCase();
|
|
80
|
+
});
|
|
81
|
+
plop.setHelper(
|
|
82
|
+
'sanitizeHttpBody',
|
|
83
|
+
(body: Record<string, any>): Record<string, any> => {
|
|
84
|
+
// Also, we need to check if the body contains "%nextToken%" and replace it with
|
|
85
|
+
// "nextToken" since that is the var in the client file that references the nextToken.
|
|
86
|
+
for (const key in body) {
|
|
87
|
+
if (typeof body[key] === 'string') {
|
|
88
|
+
body[key] = body[key].replace('%nextToken%', '${nextToken}');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return body;
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
plop.setHelper('isSingletonRequest', (responseType: string): boolean => {
|
|
95
|
+
return responseType === 'SINGLETON';
|
|
96
|
+
});
|
|
97
|
+
plop.setHelper('configTypeToType', (type: string) => {
|
|
98
|
+
if (type.toLowerCase() === 'json') {
|
|
99
|
+
return 'Record<string, any>';
|
|
100
|
+
}
|
|
101
|
+
return type;
|
|
102
|
+
});
|
|
103
|
+
plop.setHelper(
|
|
104
|
+
'requiredConfig',
|
|
105
|
+
(configFields: IntegrationInstanceConfigFieldMap) => {
|
|
106
|
+
return Object.entries(configFields)
|
|
107
|
+
.filter(([_, value]) => !value.optional)
|
|
108
|
+
.map(([key, _]) => key);
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
plop.setHelper('isNotEndpointAuth', (authStrategy: string): boolean => {
|
|
112
|
+
return authStrategy !== 'endpoint';
|
|
113
|
+
});
|
|
114
|
+
plop.setHelper(
|
|
115
|
+
'sanitizeAuthObject',
|
|
116
|
+
(authObj: Record<string, any>): Record<string, any> => {
|
|
117
|
+
const regex = /%(response|config)\.(.+?)%/g;
|
|
118
|
+
for (const key in authObj) {
|
|
119
|
+
if (typeof authObj[key] === 'string') {
|
|
120
|
+
for (const match of authObj[key].matchAll(regex)) {
|
|
121
|
+
authObj[key] = authObj[key].replace(
|
|
122
|
+
match[0],
|
|
123
|
+
'${' +
|
|
124
|
+
(match[1] === 'config' ? 'this.' : '') +
|
|
125
|
+
match[1] +
|
|
126
|
+
'.' +
|
|
127
|
+
match[2] +
|
|
128
|
+
'}',
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return authObj;
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
for (const partial of fs.readdirSync(
|
|
138
|
+
path.join(__dirname, 'templates/partials'),
|
|
139
|
+
)) {
|
|
140
|
+
plop.setPartial(
|
|
141
|
+
partial.replace('.hbs', ''),
|
|
142
|
+
fs.readFileSync(
|
|
143
|
+
path.join(__dirname, 'templates/partials', partial),
|
|
144
|
+
'utf8',
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
plop.setGenerator('bocchi', {
|
|
150
|
+
description: 'Create a new integration graph project',
|
|
151
|
+
prompts: async function (inquirer) {
|
|
152
|
+
const { vendorName } = await inquirer.prompt({
|
|
153
|
+
type: 'input',
|
|
154
|
+
name: 'vendorName',
|
|
155
|
+
message:
|
|
156
|
+
'The integration vendor name (ex. AWS, Google Workspace, CrowdStrike)',
|
|
157
|
+
validate(input) {
|
|
158
|
+
if (!input) {
|
|
159
|
+
return 'Cannot be an empty string';
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const { packageName } = await inquirer.prompt({
|
|
166
|
+
type: 'input',
|
|
167
|
+
name: 'packageName',
|
|
168
|
+
default: `@jupiterone/graph-${kebabCase(vendorName)}`,
|
|
169
|
+
message: 'The npm package name',
|
|
170
|
+
validate(input) {
|
|
171
|
+
if (!input) {
|
|
172
|
+
return 'Cannot be an empty string';
|
|
173
|
+
}
|
|
174
|
+
return true;
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const { packageDescription } = await inquirer.prompt({
|
|
179
|
+
type: 'input',
|
|
180
|
+
name: 'packageDescription',
|
|
181
|
+
message: 'A description for package',
|
|
182
|
+
default: `An integration and graph conversion project for ${vendorName}`,
|
|
183
|
+
validate(input) {
|
|
184
|
+
if (!input) {
|
|
185
|
+
return 'Cannot be an empty string';
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const { templateFile } = await inquirer.prompt({
|
|
192
|
+
type: 'input',
|
|
193
|
+
name: 'templateFile',
|
|
194
|
+
message: 'Template file for the integration',
|
|
195
|
+
validate(input) {
|
|
196
|
+
if (!input) {
|
|
197
|
+
return 'Cannot be an empty string';
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
vendorName,
|
|
205
|
+
packageName,
|
|
206
|
+
packageDescription,
|
|
207
|
+
templateFile,
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
actions: function (data) {
|
|
211
|
+
if (!data) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const { templateFile } = data;
|
|
216
|
+
|
|
217
|
+
// @jupiterone/graph-foo -> graph-foo
|
|
218
|
+
// graph-foo -> graph-foo
|
|
219
|
+
const directoryName = path.join(
|
|
220
|
+
process.cwd(),
|
|
221
|
+
path.basename(data.packageName),
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const template: Template = JSON.parse(
|
|
225
|
+
fs.readFileSync(templateFile, 'utf8'),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const actions: any[] = [];
|
|
229
|
+
|
|
230
|
+
actions.push({
|
|
231
|
+
type: 'addMany',
|
|
232
|
+
destination: directoryName,
|
|
233
|
+
base: path.join(__dirname, '/templates/top-level'),
|
|
234
|
+
templateFiles: path.join(__dirname + '/templates/top-level/**'),
|
|
235
|
+
globOptions: { dot: true },
|
|
236
|
+
force: true,
|
|
237
|
+
data: { ...data, template },
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
for (const step of template.steps) {
|
|
241
|
+
const { stepTemplateFile } = stepTemplateHelper(step);
|
|
242
|
+
actions.push({
|
|
243
|
+
type: 'add',
|
|
244
|
+
path: path.join(
|
|
245
|
+
directoryName,
|
|
246
|
+
path.normalize(`src/steps/${kebabCase(step.id)}/index.ts`),
|
|
247
|
+
),
|
|
248
|
+
templateFile: path.join(
|
|
249
|
+
__dirname,
|
|
250
|
+
`templates/steps/${stepTemplateFile}`,
|
|
251
|
+
),
|
|
252
|
+
data: { template, step },
|
|
253
|
+
force: true,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
actions.push({
|
|
257
|
+
type: 'add',
|
|
258
|
+
path: path.join(
|
|
259
|
+
directoryName,
|
|
260
|
+
path.normalize(`src/steps/${kebabCase(step.id)}/index.test.ts`),
|
|
261
|
+
),
|
|
262
|
+
templateFile: path.join(
|
|
263
|
+
__dirname,
|
|
264
|
+
`templates/steps/index.test.ts.hbs`,
|
|
265
|
+
),
|
|
266
|
+
data: { template, step },
|
|
267
|
+
force: true,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
actions.push({
|
|
271
|
+
type: 'add',
|
|
272
|
+
path: path.join(
|
|
273
|
+
directoryName,
|
|
274
|
+
path.normalize(`docs/spec/${kebabCase(step.id)}/index.ts`),
|
|
275
|
+
),
|
|
276
|
+
templateFile: path.join(__dirname, `templates/steps/spec.ts.hbs`),
|
|
277
|
+
data: { template, step },
|
|
278
|
+
force: true,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
actions.push({
|
|
283
|
+
type: 'yarnInstall',
|
|
284
|
+
path: directoryName,
|
|
285
|
+
verbose: true,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
actions.push({
|
|
289
|
+
type: 'j1Document',
|
|
290
|
+
path: directoryName,
|
|
291
|
+
verbose: true,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
actions.push({
|
|
295
|
+
type: 'yarnFormat',
|
|
296
|
+
path: directoryName,
|
|
297
|
+
verbose: true,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
actions.push({
|
|
301
|
+
type: 'yarnLint',
|
|
302
|
+
path: directoryName,
|
|
303
|
+
verbose: true,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return actions;
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = bocchi;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# The Template File
|
|
2
|
+
|
|
3
|
+
In order to generate the graph project code, Bocchi interprets a
|
|
4
|
+
developer-provided JSON template file. This JSON template defines key components
|
|
5
|
+
of the integration, such as instance configuration fields, the authentication
|
|
6
|
+
method, as well as what steps the integration should have.
|
|
7
|
+
|
|
8
|
+
Contents:
|
|
9
|
+
|
|
10
|
+
- [Format](#format)
|
|
11
|
+
- [Fields](#fields)
|
|
12
|
+
- [Instance Config Fields](#instance-config-fields)
|
|
13
|
+
- [Base URL](#base-url)
|
|
14
|
+
- [Token Bucket](#token-bucket)
|
|
15
|
+
- [Authentication](#authentication)
|
|
16
|
+
- [Steps](#steps)
|
|
17
|
+
|
|
18
|
+
## Format
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
{
|
|
22
|
+
instanceConfigFields: Object,
|
|
23
|
+
baseUrl: string,
|
|
24
|
+
tokenBucket: Object
|
|
25
|
+
authentication: Object,
|
|
26
|
+
steps: Array<Object>
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Fields
|
|
31
|
+
|
|
32
|
+
### Instance Config Fields
|
|
33
|
+
|
|
34
|
+
Lists out the config fields used by this integration.
|
|
35
|
+
|
|
36
|
+
Schema:
|
|
37
|
+
|
|
38
|
+
| | |
|
|
39
|
+
| -------- | ---------------------- |
|
|
40
|
+
| Property | `instanceConfigFields` |
|
|
41
|
+
| Type | `Object` |
|
|
42
|
+
| Required | `true` |
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
{
|
|
46
|
+
[configFieldName]: {
|
|
47
|
+
type?: 'string' | 'json' | 'boolean',
|
|
48
|
+
mask?: Boolean,
|
|
49
|
+
optional?: Boolean
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"accessKey": {
|
|
59
|
+
"type": "string"
|
|
60
|
+
},
|
|
61
|
+
"accessKeyId": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"optional": false
|
|
64
|
+
},
|
|
65
|
+
"example": {
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"mask": true,
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Base URL
|
|
74
|
+
|
|
75
|
+
Template for the base URL of the API.
|
|
76
|
+
|
|
77
|
+
Schema:
|
|
78
|
+
|
|
79
|
+
| | |
|
|
80
|
+
| ------------------- | --------- |
|
|
81
|
+
| Property | `baseUrl` |
|
|
82
|
+
| Type | `string` |
|
|
83
|
+
| Required | `true` |
|
|
84
|
+
| Available templates | `config` |
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
|
|
88
|
+
`https://api.github.com/octocat`
|
|
89
|
+
|
|
90
|
+
`https://%config.tenant%.example.com`
|
|
91
|
+
|
|
92
|
+
### Token Bucket
|
|
93
|
+
|
|
94
|
+
Allows you to define a rate limit across all steps. Useful for an API where the
|
|
95
|
+
rate limit is per API key rather than per endpoint.
|
|
96
|
+
|
|
97
|
+
Can be used in combination with step-level token buckets.
|
|
98
|
+
|
|
99
|
+
Schema:
|
|
100
|
+
|
|
101
|
+
| | |
|
|
102
|
+
| -------- | ------------- |
|
|
103
|
+
| Property | `tokenBucket` |
|
|
104
|
+
| Type | `Object` |
|
|
105
|
+
| Required | `false` |
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
{
|
|
109
|
+
maximumCapacity: number,
|
|
110
|
+
refillRate: number
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Examples:
|
|
115
|
+
|
|
116
|
+
10 API calls / second
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"maximumCapacity": 10,
|
|
121
|
+
"refillRate": 10
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
10 API calls / second, 50 Burst
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"maximumCapacity": 50,
|
|
130
|
+
"refillRate": 10
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Authentication
|
|
135
|
+
|
|
136
|
+
[Authentication](./authentication.md)
|
|
137
|
+
|
|
138
|
+
### Steps
|
|
139
|
+
|
|
140
|
+
[Steps](./steps.md)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Authentication
|
|
2
|
+
|
|
3
|
+
Defines the authentication method to use for the API. The template currently
|
|
4
|
+
supports two options for Authentication.
|
|
5
|
+
|
|
6
|
+
## Option 1 - Endpoint:
|
|
7
|
+
|
|
8
|
+
Use this method if you need to hit an endpoint to get authentication headers.
|
|
9
|
+
|
|
10
|
+
| | |
|
|
11
|
+
| ------------------- | ------------------- |
|
|
12
|
+
| Property | `authentication` |
|
|
13
|
+
| Type | `Object` |
|
|
14
|
+
| Required | `true` |
|
|
15
|
+
| Available templates | `config` `response` |
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
{
|
|
19
|
+
strategy: 'endpoint',
|
|
20
|
+
params: {
|
|
21
|
+
path: String,
|
|
22
|
+
method: 'GET' | 'POST',
|
|
23
|
+
body?: Object,
|
|
24
|
+
headers?: Object
|
|
25
|
+
}
|
|
26
|
+
authHeaders: Object
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Note: `authHeaders` represents the authentication headers that will be present
|
|
31
|
+
on all API requests the client makes, aside from the initial
|
|
32
|
+
`verifyAuthentication` request. The `authHeaders` object should contain at least
|
|
33
|
+
the `Authorization` property, which is typically a bearer token or API key.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"strategy": "endpoint",
|
|
40
|
+
"params": {
|
|
41
|
+
"path": "/auth",
|
|
42
|
+
"method": "POST",
|
|
43
|
+
"headers": {
|
|
44
|
+
"user-agent": "JupiterOne",
|
|
45
|
+
"tenant": "%config.tenant%"
|
|
46
|
+
},
|
|
47
|
+
"body": {
|
|
48
|
+
"exampleBody": "%config.exampleConfigValue%"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"authHeaders": {
|
|
52
|
+
"user-agent": "JupiterOne",
|
|
53
|
+
"tenant": "%config.tenant%",
|
|
54
|
+
"Authorization": "bearer %response.auth.token%"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Using this authentication schema will result in a verifyAuthentication()
|
|
60
|
+
function in the generated client that is ready-to-use; that is, the function
|
|
61
|
+
requires no manual updates, and when your integration runs, it will hit the
|
|
62
|
+
provided endpoint to verify authentication.
|
|
63
|
+
|
|
64
|
+
## Option 2 - Config Fields:
|
|
65
|
+
|
|
66
|
+
Use this method if the authentication headers will be provided in the instance
|
|
67
|
+
config.
|
|
68
|
+
|
|
69
|
+
| | |
|
|
70
|
+
| ------------------- | ---------------- |
|
|
71
|
+
| Property | `authentication` |
|
|
72
|
+
| Type | `Object` |
|
|
73
|
+
| Required | `true` |
|
|
74
|
+
| Available templates | `config` |
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
{
|
|
78
|
+
strategy: 'configField',
|
|
79
|
+
authHeaders: Object
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Example:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"strategy": "configField",
|
|
88
|
+
"authHeaders": {
|
|
89
|
+
"x-api-user": "%config.email%",
|
|
90
|
+
"x-api-token": "%config.token%"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Using this authentication schema will result in an incomplete
|
|
96
|
+
verifyAuthentication() function in the generated client. This is because the
|
|
97
|
+
graph-project generator does not know what endpoint to use for verifying
|
|
98
|
+
authentication. <b>You will need to manually update the generated
|
|
99
|
+
verifyAuthentication() function and choose what endpoint should be used for
|
|
100
|
+
verifying authentication.</b>
|