Skip to content
This repository has been archived by the owner on Sep 3, 2024. It is now read-only.

fix: steps added in v2 and handle 403 errors #43

Merged
merged 2 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@jupiterone/integration-sdk-dev-tools": "^8.30.2",
"@jupiterone/integration-sdk-testing": "^8.30.2",
"@types/node": "^18.8.3",
"@types/node-fetch": "^2.6.2",
"jest-fetch-mock": "^3.0.3",
"type-fest": "^0.20.2"
},
Expand Down
83 changes: 45 additions & 38 deletions src/collector/ServicesClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ import { retry } from '@lifeomic/attempt';

import { IntegrationConfig } from '../types';
import { fatalRequestError, retryableRequestError } from './error';
import { HardwareAsset, PaginatedResponse, SnipeItUser } from './types';
import {
HardwareAsset,
PaginatedResponse,
SnipeItUser,
ResourceIteratee,
HardwareLicense,
SnipeItConsumable,
ConsumableUser,
Location,
} from './types';

/**
* Services Api
Expand All @@ -25,61 +34,59 @@ export class ServicesClient {
}

async fetchUser(username: string): Promise<SnipeItUser | undefined> {
const usersResponse = await this.fetch<PaginatedResponse>('users', {
search: username,
});
const usersResponse = await this.fetch<PaginatedResponse<SnipeItUser>>(
'users',
{
search: username,
},
);
if (usersResponse.total > 0) return usersResponse.rows[0];
}

async iterateHardware(
iteratee: (asset: HardwareAsset) => Promise<void>,
): Promise<void> {
const limit = 500;
let offset = 0;
let total = 0;

do {
const response: PaginatedResponse = await this.fetch('hardware', {
offset: offset.toString(),
});
if (!response.rows) {
break;
}
total = response.total;
offset += limit;
for (const resource of response.rows) {
await iteratee(resource);
}
} while (offset < total);
async iterateHardware(iteratee: ResourceIteratee<HardwareAsset>) {
const hardwareAssets = await this.iterateAll<HardwareAsset>('hardware');
for (const hardware of hardwareAssets) {
await iteratee(hardware);
}
}

listLocations(): Promise<object[]> {
return this.iterateAll('locations');
listLocations() {
return this.iterateAll<Location>('locations');
}

listConsumables(): Promise<object[]> {
return this.iterateAll('consumables');
listConsumables() {
return this.iterateAll<SnipeItConsumable>('consumables');
}

listHardwareLicenses(id: number): Promise<object[]> {
return this.iterateAll(`hardware/${id}/licenses`);
async iterateHardwareLicenses(
id: number,
iteratee: ResourceIteratee<HardwareLicense>,
) {
const licenses = await this.iterateAll<HardwareLicense>(
`hardware/${id}/licenses`,
);
for (const license of licenses) {
await iteratee(license);
}
}

listUsers(): Promise<object[]> {
return this.iterateAll('users');
listUsers() {
return this.iterateAll<SnipeItUser>('users');
}

listConsumableUsers(consumableId: string): Promise<object[]> {
return this.iterateAll(`consumables/view/${consumableId}/users`);
listConsumableUsers(consumableId: string) {
return this.iterateAll<ConsumableUser>(
`consumables/view/${consumableId}/users`,
);
}

async iterateAll<T = object[]>(url: string): Promise<T> {
const data: any[] = [];
async iterateAll<T = unknown>(url: string): Promise<T[]> {
const data: T[] = [];
const limit = 500;
let offset = 0;
let total = 0;
do {
const response: PaginatedResponse = await this.fetch(url, {
const response = await this.fetch<PaginatedResponse<T>>(url, {
offset: offset.toString(),
limit: limit.toString(),
});
Expand All @@ -90,7 +97,7 @@ export class ServicesClient {
offset += limit;
data.push(...response.rows);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's preferable to iterate the data and call an iteratee instead of collecting all the date first in the case that there's a failure along the way. We should address that in a different ticket.

} while (offset < total);
return (data as unknown) as T;
return data;
}

fetch<T = object>(
Expand Down
11 changes: 10 additions & 1 deletion src/collector/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,23 @@ export class RetryableError extends Error {
retryable = true;
}

export class FatalRequestError extends Error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we switch out the Error for an IntegrationProviderAPIError?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or IntegrationProviderAuthorizationError if applicable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or IntegrationProviderAuthorizationError if applicable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

public statusCode: number;
constructor(message: string, statusCode: number) {
super(message);
this.statusCode = statusCode;
}
}

export function retryableRequestError(response: Response): RetryableError {
return new RetryableError(
`Encountered retryable response from provider (status=${response.status})`,
);
}

export function fatalRequestError(response: Response): Error {
return new Error(
return new FatalRequestError(
`Encountered unexpected response from provider (status="${response.status}")`,
response.status,
);
}
Loading