Developing an integration
Getting Started
In this development guide, we will walk you through the initial steps of getting your integration up and running. Along the way, we will provide tips and tricks to ensure your success. JupiterOne has many open-source projects that provide an easy-to-use framework for creating a new integration, including the code found in this SDK project.
Requirements
To get started, you'll need the following:
Node.js
- yarn
npm install --global yarn
Setup
With our tooling squared away, we'll need to setup our project:
Through the GitHub CLI
gh repo create graph-$INTEGRATION_NAME --public \
--clone \
--template=https://github.com/jupiterone/integration-template
cd graph-$INTEGRATION_NAME
yarn install
Through the GitHub UI
- Use the integration-template to create a new repository
- Clone your repository and run
yarn install
git clone https://github.com/$USERNAME/$REPO_NAME`
cd $REPO_NAME
yarn install
That's it! Your integration project is ready for development!
Developing the integration
In this guide, we will create a small integration with DigitalOcean using examples which you can apply to the integration you are building.
Integration configuration
Every integration builds and exports an InvocationConfig
that is used to
execute the integration.
In the new integration that you created, you can see the InvocationConfig
exported in
src/index.ts
export const invocationConfig: IntegrationInvocationConfig<IntegrationConfig> =
{
instanceConfigFields,
validateInvocation,
integrationSteps,
};
Let's work from the top to bottom. We'll start by defining
instanceConfigFields
, next we'll implement validateInvocation
, and finally
define our integrationSteps
.
1. Creating InstanceConfigFields
The first object in our InvocationConfig
is instanceConfigFields
with type
IntegrationInstanceConfigFieldsMap
.
You'll find this defined in your project in
src/config.ts
.
/**
* A type describing the configuration fields required to execute the
* integration for a specific account in the data provider.
*
* When executing the integration in a development environment, these values may
* be provided in a `.env` file with environment variables. For example:
*
* - `CLIENT_ID=123` becomes `instance.config.clientId = '123'`
* - `CLIENT_SECRET=abc` becomes `instance.config.clientSecret = 'abc'`
*
* Environment variables are NOT used when the integration is executing in a
* managed environment. For example, in JupiterOne, users configure
* `instance.config` in a UI.
*/
export const instanceConfigFields: IntegrationInstanceConfigFieldMap = {
clientId: {
type: 'string',
},
clientSecret: {
type: 'string',
mask: true,
},
};
The instanceConfigFields
object lets us control how the integration will
execute. A common use is to provide credentials to authenticate requests. For
example, DigitalOcean requires a Personal Access Token
(see below). Other
common config values include a Client ID
, API Key
, or API URL
. Any outside
information the integration needs at runtime can be defined here.
DigitalOcean requires a Person Access Token
, so we'll edit the fields to reflect
that.
export const instanceConfigFields: IntegrationInstanceConfigFieldMap = {
- clientId: {
- type: 'string',
- },
- clientSecret: {
- type: 'string',
- mask: true,
- },
+ accessToken: {
+ type: 'string',
+ mask: true,
+ }
};
The mask
property should be set to true any time a property is secret or sensitive.
We should also edit
IntegrationConfig
in the same file to match instanceConfigFields
. IntegrationConfig
is used to
add and provide type information throughout the project.
📁 src/config.ts
export interface IntegrationConfig extends IntegrationInstanceConfig {
- /**
- * The provider API client ID used to authenticate requests.
- */
- clientId: string;
-
- /**
- * The provider API client secret used to authenticate requests.
- */
- clientSecret: string;
+ /**
+ * The accessToken to use when authenticating with the API.
+ */
+ accessToken: string;
}
Lastly, we will want to create a .env file with our configuration. Let's
edit .env.example
to match our project:
- CLIENT_ID=
- CLIENT_SECRET=
+ ACCESS_TOKEN=<access token goes here>
cp .env.example .env
In the .env file, we can put our ACCESS_TOKEN
. Make sure not to put real
secrets in the .env.example!
The .env file should NEVER be committed. The integration-template
has .env
in the .gitignore, but always be sure not to add and commit it.
Awesome! We have created our instanceConfigFields
and IntegrationConfig
.
Let's go to the next step.
2. Creating ValidateInvocation
Next, we will create our validateInvocation
function. The basic contract for
validateInvocation
is as follows:
- The function receives the execution context and configuration we set in
instanceConfigFields
. - The function will validate that all configuration is present and valid or throw an error otherwise.
Let's create a validateInvocation
for DigitalOcean.
Running the integration
We've now:
✅ Created a new integration project
✅ Installed dependencies with yarn install
✅ Created our instanceConfigFields
✅ Setup a .env file
✅ Created our validateInvocation
✅ Added our API Client and authenticated request
✅ Created our first IntegrationStep
Looks like we're ready to run our integration! We can collect data using:
yarn start
You should now see the collected data in the .j1-integration and you can visualize the results with yarn graph.