From 9993e6aa710129ea3b522155310fb9e418ca7cb4 Mon Sep 17 00:00:00 2001 From: Chris Elias Date: Mon, 3 Jan 2022 18:03:50 -0500 Subject: [PATCH] Heavy refactor of steps and client --- .env.example | 4 +- src/addigy/client.ts | 43 ++----------------- src/client.ts | 86 ++++++++++++++++++++++++++++++++++++++ src/config.ts | 4 -- src/steps/devices/index.ts | 34 ++------------- src/steps/policy/index.ts | 33 ++------------- src/steps/user/index.ts | 34 ++------------- src/validator.ts | 11 ++--- test/config.ts | 7 ++-- 9 files changed, 112 insertions(+), 144 deletions(-) create mode 100644 src/client.ts diff --git a/.env.example b/.env.example index a494398..609e376 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,4 @@ CLIENT_ID= -CLIENT_SECRET= \ No newline at end of file +CLIENT_SECRET= +ADMIN_USERNAME= +ADMIN_PASSWORD= \ No newline at end of file diff --git a/src/addigy/client.ts b/src/addigy/client.ts index b05c59d..e4f4e67 100644 --- a/src/addigy/client.ts +++ b/src/addigy/client.ts @@ -9,14 +9,9 @@ import { } from './types'; import PQueue from 'p-queue'; import { retry } from '@lifeomic/attempt'; -import { - IntegrationLogger, - IntegrationProviderAPIError, -} from '@jupiterone/integration-sdk-core'; +import { IntegrationProviderAPIError } from '@jupiterone/integration-sdk-core'; import fetch, { RequestInit, Response, FetchError } from 'node-fetch'; -export type ResourceIteratee = (each: T) => Promise | void; - const defaultApiTimeoutMs = 60000; // 1 minute interface OnApiRequestErrorParams { @@ -33,7 +28,6 @@ type RequestFunction = ( ) => Promise; interface CreateAddigyClientParams { - host: string; clientId: string; clientSecret: string; adminUsername: string; @@ -42,37 +36,6 @@ interface CreateAddigyClientParams { onApiRequestError?: (params: OnApiRequestErrorParams) => void; } -interface CreateAddigyClientHelperParams { - clientId: string; - clientSecret: string; - adminUsername: string; - adminPassword: string; - host: string; - logger: IntegrationLogger; -} - -export function createAPIClient({ - host, - clientId, - clientSecret, - adminUsername, - adminPassword, - logger, -}: CreateAddigyClientHelperParams) { - const client = new AddigyClient({ - host, - clientId, - clientSecret, - adminUsername, - adminPassword, - onApiRequestError(requestError) { - logger.info(requestError, 'Error making API requests (will retry)'); - }, - }); - - return client; -} - const noRetryStatusCodes: number[] = [400, 401, 403, 404, 413]; function isSuccessfulStatusCode(status: number) { @@ -173,8 +136,8 @@ export class AddigyClient { options.body = JSON.stringify(body); } - const domain = url ? url : 'https://prod.addigy.com/'; - const fullUrl = domain + path; //this.getResourceUrl(path); + const domain = url || 'https://prod.addigy.com/'; + const fullUrl = domain + path; // The goal here is to retry and ensure the final error includes information // about the host we could not connect to, since users define the host and diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..a3f2732 --- /dev/null +++ b/src/client.ts @@ -0,0 +1,86 @@ +import { + AddigyClient, + // createAddigyClient, +} from './addigy/client'; + +// import { IntegrationProviderAuthenticationError } from '@jupiterone/integration-sdk-core'; + +import { Device, User, Policy } from './addigy/types'; + +import { IntegrationConfig } from './config'; + +export type ResourceIteratee = (each: T) => Promise | void; + +/** + * An APIClient maintains authentication state and provides an interface to + * third party data APIs. + * + * It is recommended that integrations wrap provider data APIs to provide a + * place to handle error responses and implement common patterns for iterating + * resources. + */ +export class APIClient { + constructor(readonly config: IntegrationConfig) { + //Set up the client + this.setUpClient(); + } + + client: AddigyClient; + + /** + * Set up the connection to Salesforce with oauth info + */ + setUpClient(): void { + this.client = new AddigyClient({ + clientId: this.config.clientId, + clientSecret: this.config.clientSecret, + adminUsername: this.config.adminUsername, + adminPassword: this.config.adminPassword, + // logger, + }); + } + + // public async verifyAuthentication(): Promise { + + // } + + /** + * Iterates each user resource in the provider. + * + * @param iteratee receives each resource to produce entities/relationships + */ + public async iterateUsers(iteratee: ResourceIteratee): Promise { + const authObject = await this.client.getAuthObject(); + const users = await this.client.fetchUsers(authObject); + for (const user of users) { + await iteratee(user); + } + } + + /** + * Iterates each device resource in the provider. + * + * @param iteratee receives each resource to produce entities/relationships + */ + public async iterateDevices( + iteratee: ResourceIteratee, + ): Promise { + const devices = await this.client.fetchDevices(); + for (const device of devices) { + await iteratee(device); + } + } + + public async iteratePolicies( + iteratee: ResourceIteratee, + ): Promise { + const policies = await this.client.fetchPolicies(); + for (const policy of policies) { + await iteratee(policy); + } + } +} + +export function createAPIClient(config: IntegrationConfig): APIClient { + return new APIClient(config); +} diff --git a/src/config.ts b/src/config.ts index 049b38a..18283a6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -18,9 +18,6 @@ export const instanceConfigFields: IntegrationInstanceConfigFieldMap = { type: 'string', mask: true, }, - host: { - type: 'string', - }, }; /** @@ -35,5 +32,4 @@ export interface IntegrationConfig extends IntegrationInstanceConfig { */ adminUsername: string; adminPassword: string; - host: string; } diff --git a/src/steps/devices/index.ts b/src/steps/devices/index.ts index 1d80dd4..dcaf870 100644 --- a/src/steps/devices/index.ts +++ b/src/steps/devices/index.ts @@ -1,49 +1,24 @@ import { createDirectRelationship, - IntegrationLogger, IntegrationStep, IntegrationStepExecutionContext, RelationshipClass, } from '@jupiterone/integration-sdk-core'; -import { - AddigyClient, - createAPIClient, - ResourceIteratee, -} from '../../addigy/client'; -import { Device } from '../../addigy/types'; + import { IntegrationConfig } from '../../config'; import { Entities, Relationships, Steps } from '../constants'; import { createDeviceEntity } from './converter'; - -async function iterateDevices( - client: AddigyClient, - logger: IntegrationLogger, - iteratee: ResourceIteratee, -): Promise { - const devices = await client.fetchDevices(); - for (const device of devices) { - await iteratee(device); - } -} +import { createAPIClient } from '../../client'; export async function fetchDevices({ instance, jobState, logger, }: IntegrationStepExecutionContext) { - const { config } = instance; - - const client = createAPIClient({ - host: config.host, - clientId: config.clientId, - clientSecret: config.clientSecret, - adminUsername: config.adminUsername, - adminPassword: config.adminPassword, - logger, - }); + const apiClient = createAPIClient(instance.config); - await iterateDevices(client, logger, async (device) => { + await apiClient.iterateDevices(async (device) => { const deviceEntity = await jobState.addEntity(createDeviceEntity(device)); const policyEntity = await jobState.findEntity(device['policy_id']); @@ -57,7 +32,6 @@ export async function fetchDevices({ ); } }); - await client.fetchDevices(); } export const devicesSteps: IntegrationStep[] = [ diff --git a/src/steps/policy/index.ts b/src/steps/policy/index.ts index b101089..3db7990 100644 --- a/src/steps/policy/index.ts +++ b/src/steps/policy/index.ts @@ -1,45 +1,20 @@ import { - IntegrationLogger, IntegrationStep, IntegrationStepExecutionContext, } from '@jupiterone/integration-sdk-core'; -import { - AddigyClient, - createAPIClient, - ResourceIteratee, -} from '../../addigy/client'; -import { Policy } from '../../addigy/types'; import { IntegrationConfig } from '../../config'; import { Entities, Steps } from '../constants'; import { createPolicyEntity } from './converter'; - -async function iteratePolicies( - client: AddigyClient, - logger: IntegrationLogger, - iteratee: ResourceIteratee, -): Promise { - const policies = await client.fetchPolicies(); - for (const policy of policies) { - await iteratee(policy); - } -} +import { createAPIClient } from '../../client'; export async function fetchPolicies({ instance, jobState, - logger, }: IntegrationStepExecutionContext) { - const { config } = instance; - const client = createAPIClient({ - host: config.host, - clientId: config.clientId, - clientSecret: config.clientSecret, - adminUsername: config.adminUsername, - adminPassword: config.adminPassword, - logger, - }); - await iteratePolicies(client, logger, async (policy) => { + const apiClient = createAPIClient(instance.config); + + await apiClient.iteratePolicies(async (policy) => { // to be used later: // const policyEntity = await jobState.addEntity(createPolicyEntity(policy)); await jobState.addEntity(createPolicyEntity(policy)); diff --git a/src/steps/user/index.ts b/src/steps/user/index.ts index 3c142e2..d3ed238 100644 --- a/src/steps/user/index.ts +++ b/src/steps/user/index.ts @@ -1,46 +1,20 @@ import { - IntegrationLogger, IntegrationStep, IntegrationStepExecutionContext, } from '@jupiterone/integration-sdk-core'; -import { - AddigyClient, - createAPIClient, - ResourceIteratee, -} from '../../addigy/client'; -import { IntegrationConfig } from '../../config'; -import { User } from '../../addigy/types'; +import { IntegrationConfig } from '../../config'; import { Entities, Steps } from '../constants'; import { createUserEntity } from './converter'; - -async function iterateUsers( - client: AddigyClient, - logger: IntegrationLogger, - iteratee: ResourceIteratee, -): Promise { - const authObject = await client.getAuthObject(); - const users = await client.fetchUsers(authObject); - for (const user of users) { - await iteratee(user); - } -} +import { createAPIClient } from '../../client'; export async function fetchUsers({ instance, jobState, logger, }: IntegrationStepExecutionContext) { - const { config } = instance; - const client = createAPIClient({ - host: config.host, - clientId: config.clientId, - clientSecret: config.clientSecret, - adminUsername: config.adminUsername, - adminPassword: config.adminPassword, - logger, - }); - await iterateUsers(client, logger, async (user) => { + const apiClient = createAPIClient(instance.config); + await apiClient.iterateUsers(async (user) => { // to be used later: // const userEntity = await jobState.addEntity(createUserEntity(user)); await jobState.addEntity(createUserEntity(user)); diff --git a/src/validator.ts b/src/validator.ts index 4a0fae3..91b2d47 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -2,7 +2,7 @@ import { IntegrationExecutionContext, IntegrationValidationError, } from '@jupiterone/integration-sdk-core'; -import { createAPIClient } from './addigy/client'; +import { AddigyClient } from './addigy/client'; import { IntegrationConfig } from './config'; export async function validateInvocation( @@ -15,21 +15,18 @@ export async function validateInvocation( !config.adminPassword || !config.adminUsername || !config.clientId || - !config.clientSecret || - !config.host + !config.clientSecret ) { throw new IntegrationValidationError( - 'Config requires all of {adminPassword, adminUsername, clientId, clientSecret, host}', + 'Config requires all of {adminPassword, adminUsername, clientId, clientSecret}', ); } - const client = createAPIClient({ - host: config.host, + const client = new AddigyClient({ clientId: config.clientId, clientSecret: config.clientSecret, adminUsername: config.adminUsername, adminPassword: config.adminPassword, - logger, }); try { diff --git a/test/config.ts b/test/config.ts index c4daa34..2b7d338 100644 --- a/test/config.ts +++ b/test/config.ts @@ -9,11 +9,12 @@ if (process.env.LOAD_ENV) { } const DEFAULT_CLIENT_ID = 'dummy-acme-client-id'; const DEFAULT_CLIENT_SECRET = 'dummy-acme-client-secret'; +const DEFAULT_ADMIN_USERNAME = 'dummy-username'; +const DEFAULT_ADMIN_PASSWORD = 'dummy-password'; export const integrationConfig: IntegrationConfig = { clientId: process.env.CLIENT_ID || DEFAULT_CLIENT_ID, clientSecret: process.env.CLIENT_SECRET || DEFAULT_CLIENT_SECRET, - adminUsername: '', - adminPassword: '', - host: '', + adminUsername: process.env.ADMIN_USERNAME || DEFAULT_ADMIN_USERNAME, + adminPassword: process.env.ADMIN_PASSWORD || DEFAULT_ADMIN_PASSWORD, };