Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use api v3 get all projects endpoint #342

Merged
merged 1 commit into from
Jul 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"p-map": "4.0.0",
"parse-link-header": "2.0.0",
"sleep-promise": "8.0.1",
"snyk-request-manager": "1.6.0",
"snyk-request-manager": "1.7.1",
"source-map-support": "^0.5.16",
"split": "1.0.1",
"yargs": "16.2.0"
Expand Down
124 changes: 107 additions & 17 deletions src/lib/api/org/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { requestsManager } from 'snyk-request-manager';
import * as debugLib from 'debug';
import { getApiToken } from '../../get-api-token';
import { getSnykHost } from '../../get-snyk-host';
import { SnykProject } from '../../types';
import { SnykProject, v3ProjectData } from '../../types';
import { StringNullableChain } from 'lodash';

const debug = debugLib('snyk:api-group');
Expand Down Expand Up @@ -136,19 +136,35 @@ export async function deleteOrg(

interface ProjectsResponse {
org: {
name: string;
id: string;
};
projects: SnykProject[];
}


interface v3ProjectsResponse {
data: v3ProjectData[];
jsonapi: {
version: string;
};
links: {
first: string;
last: string;
next: string;
prev: string;
related: string;
self: string;
};
}

interface ProjectsFilters {
name?: StringNullableChain; // If supplied, only projects that have a name that starts with this value will be returned
origin?: string; //If supplied, only projects that exactly match this origin will be returned
type?: string; //If supplied, only projects that exactly match this type will be returned
isMonitored?: boolean; // If set to true, only include projects which are monitored, if set to false, only include projects which are not monitored
}


export async function listProjects(
requestManager: requestsManager,
orgId: string,
Expand All @@ -165,23 +181,97 @@ export async function listProjects(
);
}

try {
const projects = await listAllProjects(requestManager, orgId, filters)

const snykProjectData: ProjectsResponse = {
org: {
id: orgId,
},
projects: projects
}

return snykProjectData;
}

async function listAllProjects(requestManager: requestsManager,
orgId: string,
filters?: ProjectsFilters,
): Promise<SnykProject[]> {

let lastPage = false;
let projectsList: SnykProject[] = [];
let pageCount = 1;
let nextPageLink: string | undefined = undefined;
while (!lastPage) {
try {
const {
projects,
next,
}: {
projects: SnykProject[];
next?: string;
} = await getProject(requestManager, orgId, filters, nextPageLink);

projectsList = projectsList.concat(projects);
next
? ((lastPage = false), (nextPageLink = next))
: ((lastPage = true), (nextPageLink = ''));
pageCount++;
} catch (e) {
debug('Failed to update notification settings for ', orgId, e);
throw e;
}
}
return projectsList
}

async function getProject(requestManager: requestsManager,
orgId: string,
filters?: ProjectsFilters,
nextPageLink?: string,
): Promise< { projects: SnykProject[], next?: string } > {

const url = nextPageLink ? nextPageLink : `/orgs/${orgId.trim()}/projects?version=2022-06-08~beta`
const res = await requestManager.request({
verb: 'post',
url: `/org/${orgId.trim()}/projects`,
body: JSON.stringify(filters),
});
verb: 'get',
url: url,
body: JSON.stringify(filters),
useRESTApi: true,
});

const statusCode = res.statusCode || res.status;
if (!statusCode || statusCode !== 200) {
throw new Error(
'Expected a 200 response, instead received: ' +
JSON.stringify({ data: res.data, status: statusCode }),
);
const statusCode = res.statusCode || res.status;
if (!statusCode || statusCode !== 200) {
throw new Error(
'Expected a 200 response, instead received: ' +
JSON.stringify({ data: res.data, status: statusCode }),
);
}

const v3responseData = res.data as v3ProjectsResponse

const projects = convertToSnykProject(v3responseData.data)
const next = v3responseData.links.next

return { projects, next}

}

function convertToSnykProject(projectData: v3ProjectData[]) : SnykProject[] {

const projects: SnykProject[] = [];

for (const project of projectData) {
const projectTmp: SnykProject = {
id : project.id,
branch : project.attributes.targetReference ,
created : project.attributes.created,
origin : project.attributes.origin,
name : project.attributes.name,
type: project.attributes.type,
}
return res.data || {};
} catch (e) {
debug('Failed to update notification settings for ', orgId, e);
throw e;
projects.push(projectTmp)
}

return projects
}

71 changes: 44 additions & 27 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,35 +117,15 @@ export interface CommandResult {
}

export interface SnykProject {
name: string;
id: string;
created: string;
origin: string;
type: string;
readOnly: boolean;
testFrequency: string;
isMonitored: boolean;
totalDependencies: number;
issueCountsBySeverity: {
low: number;
high: number;
medium: number;
citical: number;
};
remoteRepoUrl: string; // URL of the repo
lastTestedDate: string;
browseUrl: string;
owner: string | null;
importingUser: ImportingUser;
tags: unknown[];
attributes: {
criticality: unknown[];
lifecycle: unknown[];
environment: unknown[];
};
branch: string | null;
name: string;
id: string;
created: string;
origin: string;
type: string;
branch: string | null;
}


export interface Org {
name: string;
id: string;
Expand All @@ -156,3 +136,40 @@ export interface Org {
id: string;
};
}


export interface v3ProjectData {
attributes: v3ProjectsAttributes;
id: string;
relationships: v3ProjectsRelationships;
type: string
}

export interface v3ProjectsAttributes {
businessCriticality: string;
created: string;
environment: string;
lifecycle: string;
name: string;
origin: string;
status: string;
tags: unknown;
targetReference: string | null;
type: string;
}

export interface v3ProjectsRelationships {
importingUser: v3ProjectsRelashionshipData;
org: v3ProjectsRelashionshipData;
owner: v3ProjectsRelashionshipData;
target: v3ProjectsRelashionshipData;
}

export interface v3ProjectsRelashionshipData {
data: {
id: string;
type: string;
}
links: unknown;
meta: unknown;
}
29 changes: 15 additions & 14 deletions src/scripts/generate-imported-targets-from-snyk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,20 +130,21 @@ export async function generateSnykImportedTargets(
]);
const { projects } = resProjects;
const scmTargets = projects
.filter((p) =>
integrationTypes.includes(
p.origin as SupportedIntegrationTypesToListSnykTargets,
),
)
.map((p) => {
const target = targetGenerators[
p.origin as SupportedIntegrationTypesToListSnykTargets
](p);
return {
target,
integrationId: resIntegrations[p.origin],
};
});
.filter((p) =>
integrationTypes.includes(
p.origin as SupportedIntegrationTypesToListSnykTargets,
),
)
.map((p) => {
const target = targetGenerators[
p.origin as SupportedIntegrationTypesToListSnykTargets
](p);
return {
target,
integrationId: resIntegrations[p.origin],
};
});

const uniqueTargets: Set<string> = new Set();
const orgTargets: Target[] = [];
if (!scmTargets.length || scmTargets.length === 0) {
Expand Down
1 change: 0 additions & 1 deletion test/lib/org.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ describe('listProjects', () => {
expect(res).toMatchObject({
org: {
id: ORG_ID,
name: expect.any(String),
},
projects: expect.any(Array),
});
Expand Down