Skip to content

Commit

Permalink
Heavy refactor of steps and client
Browse files Browse the repository at this point in the history
  • Loading branch information
ceelias committed Jan 3, 2022
1 parent 0d63163 commit 9993e6a
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 144 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
CLIENT_ID=
CLIENT_SECRET=
CLIENT_SECRET=
ADMIN_USERNAME=
ADMIN_PASSWORD=
43 changes: 3 additions & 40 deletions src/addigy/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = (each: T) => Promise<void> | void;

const defaultApiTimeoutMs = 60000; // 1 minute

interface OnApiRequestErrorParams {
Expand All @@ -33,7 +28,6 @@ type RequestFunction = (
) => Promise<Response>;

interface CreateAddigyClientParams {
host: string;
clientId: string;
clientSecret: string;
adminUsername: string;
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
86 changes: 86 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -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<T> = (each: T) => Promise<void> | 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<void> {

// }

/**
* Iterates each user resource in the provider.
*
* @param iteratee receives each resource to produce entities/relationships
*/
public async iterateUsers(iteratee: ResourceIteratee<User>): Promise<void> {
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<Device>,
): Promise<void> {
const devices = await this.client.fetchDevices();
for (const device of devices) {
await iteratee(device);
}
}

public async iteratePolicies(
iteratee: ResourceIteratee<Policy>,
): Promise<void> {
const policies = await this.client.fetchPolicies();
for (const policy of policies) {
await iteratee(policy);
}
}
}

export function createAPIClient(config: IntegrationConfig): APIClient {
return new APIClient(config);
}
4 changes: 0 additions & 4 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ export const instanceConfigFields: IntegrationInstanceConfigFieldMap = {
type: 'string',
mask: true,
},
host: {
type: 'string',
},
};

/**
Expand All @@ -35,5 +32,4 @@ export interface IntegrationConfig extends IntegrationInstanceConfig {
*/
adminUsername: string;
adminPassword: string;
host: string;
}
34 changes: 4 additions & 30 deletions src/steps/devices/index.ts
Original file line number Diff line number Diff line change
@@ -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<Device>,
): Promise<void> {
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<IntegrationConfig>) {
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']);
Expand All @@ -57,7 +32,6 @@ export async function fetchDevices({
);
}
});
await client.fetchDevices();
}

export const devicesSteps: IntegrationStep<IntegrationConfig>[] = [
Expand Down
33 changes: 4 additions & 29 deletions src/steps/policy/index.ts
Original file line number Diff line number Diff line change
@@ -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<Policy>,
): Promise<void> {
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<IntegrationConfig>) {
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));
Expand Down
34 changes: 4 additions & 30 deletions src/steps/user/index.ts
Original file line number Diff line number Diff line change
@@ -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<User>,
): Promise<void> {
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<IntegrationConfig>) {
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));
Expand Down
11 changes: 4 additions & 7 deletions src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 {
Expand Down
7 changes: 4 additions & 3 deletions test/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

0 comments on commit 9993e6a

Please sign in to comment.