From 0709458dd3dc85c91f7417a7ff398742fa7f293e Mon Sep 17 00:00:00 2001 From: Antoine Arlaud Date: Sat, 2 Sep 2023 22:36:56 +0200 Subject: [PATCH 1/5] chore: push some test fixtures --- ...multiple-targets-project-id-and-exclusion.yaml | 15 +++++++++++++++ test/fixtures/catalog-multiple-targets.yaml | 12 ++++++++++++ test/fixtures/catalog-project-ids.yaml | 12 ++++++++++++ test/fixtures/catalog-project-slug-goof.yaml | 12 ++++++++++++ test/fixtures/catalog-target-id-goof.yaml | 12 ++++++++++++ test/fixtures/catalog-target-name-java-goof.yaml | 12 ++++++++++++ 6 files changed, 75 insertions(+) create mode 100644 test/fixtures/catalog-multiple-targets-project-id-and-exclusion.yaml create mode 100644 test/fixtures/catalog-multiple-targets.yaml create mode 100644 test/fixtures/catalog-project-ids.yaml create mode 100644 test/fixtures/catalog-project-slug-goof.yaml create mode 100644 test/fixtures/catalog-target-id-goof.yaml create mode 100644 test/fixtures/catalog-target-name-java-goof.yaml diff --git a/test/fixtures/catalog-multiple-targets-project-id-and-exclusion.yaml b/test/fixtures/catalog-multiple-targets-project-id-and-exclusion.yaml new file mode 100644 index 0000000..60b3d9f --- /dev/null +++ b/test/fixtures/catalog-multiple-targets-project-id-and-exclusion.yaml @@ -0,0 +1,15 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: goof + description: Goof + annotations: + snyk.io/org-id: 361fd3c0-41d4-4ea4-ba77-09bb17890967 + snyk.io/targets: Snyk Demo/java-goof,508d2263-ea8a-4e42-bc9d-844de21f4172 + snyk.io/target-id: aarlaud-playground/goof + snyk.io/project-ids: 0cc9d21b-4ac7-4f27-8c6a-b32b4cdf5c68,38e02916-0cf7-4927-ba98-06afae9fef36,0cc9d21b-4ac7-4f27-8c6a-b32b4cdf5c68 + snyk.io/exclude-project-ids: 4737fc9c-3894-40ba-9dc5-aa8ae658c9f6,38e02916-0cf7-4927-ba98-06afae9fef36 +spec: + type: service + lifecycle: production + owner: guest \ No newline at end of file diff --git a/test/fixtures/catalog-multiple-targets.yaml b/test/fixtures/catalog-multiple-targets.yaml new file mode 100644 index 0000000..53b68ec --- /dev/null +++ b/test/fixtures/catalog-multiple-targets.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: goof + description: Goof + annotations: + snyk.io/org-id: 361fd3c0-41d4-4ea4-ba77-09bb17890967 + snyk.io/targets: Snyk Demo/java-goof,508d2263-ea8a-4e42-bc9d-844de21f4172 +spec: + type: service + lifecycle: production + owner: guest \ No newline at end of file diff --git a/test/fixtures/catalog-project-ids.yaml b/test/fixtures/catalog-project-ids.yaml new file mode 100644 index 0000000..2930bf1 --- /dev/null +++ b/test/fixtures/catalog-project-ids.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: goof + description: Goof + annotations: + snyk.io/org-id: 361fd3c0-41d4-4ea4-ba77-09bb17890967 + snyk.io/project-ids: 0cc9d21b-4ac7-4f27-8c6a-b32b4cdf5c68,38e02916-0cf7-4927-ba98-06afae9fef36,019e3f03-5230-4d4f-9326-2fdc7161016f,4737fc9c-3894-40ba-9dc5-aa8ae658c9f6 +spec: + type: service + lifecycle: production + owner: guest \ No newline at end of file diff --git a/test/fixtures/catalog-project-slug-goof.yaml b/test/fixtures/catalog-project-slug-goof.yaml new file mode 100644 index 0000000..02eb7f2 --- /dev/null +++ b/test/fixtures/catalog-project-slug-goof.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: goof + description: Goof + annotations: + snyk.io/org-id: 361fd3c0-41d4-4ea4-ba77-09bb17890967 + github.com/project-slug: Snyk Demo/java-goof +spec: + type: service + lifecycle: production + owner: guest \ No newline at end of file diff --git a/test/fixtures/catalog-target-id-goof.yaml b/test/fixtures/catalog-target-id-goof.yaml new file mode 100644 index 0000000..cb8f539 --- /dev/null +++ b/test/fixtures/catalog-target-id-goof.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: goof + description: Goof + annotations: + snyk.io/org-id: 361fd3c0-41d4-4ea4-ba77-09bb17890967 + snyk.io/target-id: 3dfebab7-c6d3-4343-a712-54bc3b02b8d2 +spec: + type: service + lifecycle: production + owner: guest \ No newline at end of file diff --git a/test/fixtures/catalog-target-name-java-goof.yaml b/test/fixtures/catalog-target-name-java-goof.yaml new file mode 100644 index 0000000..7fed572 --- /dev/null +++ b/test/fixtures/catalog-target-name-java-goof.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: goof + description: Goof + annotations: + snyk.io/org-id: 361fd3c0-41d4-4ea4-ba77-09bb17890967 + snyk.io/target-id: Snyk Demo/java-goof +spec: + type: service + lifecycle: production + owner: guest \ No newline at end of file From dc0fe2bda23bda05f6038c6aa21aae7b6d3cdaa8 Mon Sep 17 00:00:00 2001 From: aarlaud Date: Sat, 2 Sep 2023 23:25:46 +0200 Subject: [PATCH 2/5] Update catalog-multiple-targets-project-id-and-exclusion.yaml --- .../catalog-multiple-targets-project-id-and-exclusion.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fixtures/catalog-multiple-targets-project-id-and-exclusion.yaml b/test/fixtures/catalog-multiple-targets-project-id-and-exclusion.yaml index 60b3d9f..ed2372c 100644 --- a/test/fixtures/catalog-multiple-targets-project-id-and-exclusion.yaml +++ b/test/fixtures/catalog-multiple-targets-project-id-and-exclusion.yaml @@ -6,10 +6,10 @@ metadata: annotations: snyk.io/org-id: 361fd3c0-41d4-4ea4-ba77-09bb17890967 snyk.io/targets: Snyk Demo/java-goof,508d2263-ea8a-4e42-bc9d-844de21f4172 - snyk.io/target-id: aarlaud-playground/goof + snyk.io/target-id: aarlaud-snyk/github-stats snyk.io/project-ids: 0cc9d21b-4ac7-4f27-8c6a-b32b4cdf5c68,38e02916-0cf7-4927-ba98-06afae9fef36,0cc9d21b-4ac7-4f27-8c6a-b32b4cdf5c68 snyk.io/exclude-project-ids: 4737fc9c-3894-40ba-9dc5-aa8ae658c9f6,38e02916-0cf7-4927-ba98-06afae9fef36 spec: type: service lifecycle: production - owner: guest \ No newline at end of file + owner: guest From 6de882c83983359c4b85b21a02899b87bbded041 Mon Sep 17 00:00:00 2001 From: aarlaud Date: Sun, 3 Sep 2023 00:49:56 +0200 Subject: [PATCH 3/5] Update catalog-multiple-targets-project-id-and-exclusion.yaml --- .../catalog-multiple-targets-project-id-and-exclusion.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/catalog-multiple-targets-project-id-and-exclusion.yaml b/test/fixtures/catalog-multiple-targets-project-id-and-exclusion.yaml index ed2372c..76946ea 100644 --- a/test/fixtures/catalog-multiple-targets-project-id-and-exclusion.yaml +++ b/test/fixtures/catalog-multiple-targets-project-id-and-exclusion.yaml @@ -7,7 +7,7 @@ metadata: snyk.io/org-id: 361fd3c0-41d4-4ea4-ba77-09bb17890967 snyk.io/targets: Snyk Demo/java-goof,508d2263-ea8a-4e42-bc9d-844de21f4172 snyk.io/target-id: aarlaud-snyk/github-stats - snyk.io/project-ids: 0cc9d21b-4ac7-4f27-8c6a-b32b4cdf5c68,38e02916-0cf7-4927-ba98-06afae9fef36,0cc9d21b-4ac7-4f27-8c6a-b32b4cdf5c68 + snyk.io/project-ids: 7439e322-f9c1-4c42-8367-002b33b9d946,db066cb9-b373-46da-b918-b49b541e0d63 snyk.io/exclude-project-ids: 4737fc9c-3894-40ba-9dc5-aa8ae658c9f6,38e02916-0cf7-4927-ba98-06afae9fef36 spec: type: service From 6cc2667d7d5872365f3934c03c315c24ed7bd27c Mon Sep 17 00:00:00 2001 From: Antoine Arlaud Date: Sun, 3 Sep 2023 11:17:43 +0200 Subject: [PATCH 4/5] feat: multitargets support & project ids import and exclusion --- README.md | 23 ++-- src/api/index.ts | 120 ++++++++++++++---- .../SnykEntityComponent.tsx | 99 ++++++++++----- .../SnykOverviewComponent.tsx | 48 ++++--- src/components/SnykEntityComponent/index.ts | 16 ++- .../SnykEntityComponent/snykTab.tsx | 8 +- src/config.ts | 3 + 7 files changed, 228 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index aad04b3..0bccd6b 100644 --- a/README.md +++ b/README.md @@ -102,35 +102,40 @@ snyk: export SNYK_TOKEN="123-123-123-123" ``` -6. Add the following annotations to your entities. +6. Add this following annotation to your entities. - `snyk.io/org-name` is the Snyk organization name where your project is. Use the slug (like in url, or in the org settings page), not the display name + +7. Then add one or more than one of the following annotations to your entities. +- `snyk.io/target-id` specify a single target by name or ID. Target ID will avoid an API call and be therefore faster. Use this [API endpoint](https://apidocs.snyk.io/?version=2023-06-19%7Ebeta#get-/orgs/-org_id-/targets) to get the Target IDs. +- `snyk.io/targets` specify one or more targets, by name or ID. Target ID will avoid an API call and be therefore faster. Use this [API endpoint](https://apidocs.snyk.io/?version=2023-06-19%7Ebeta#get-/orgs/-org_id-/targets) to get the Target IDs. - `snyk.io/project-ids` are the project ID (see slug in url or ID in project settings) If multiple projects (like multiple package.json or pom files, add them with increasing number), add them comma separated +- `snyk.io/exclude-project-ids` to exclude specific projects you might not want. .... - Example: ```yaml apiVersion: backstage.io/v1alpha1 kind: Component metadata: - name: Java-goof - description: Java Goof - .... + name: goof + description: Goof annotations: - snyk.io/org-name: snyk-demo-org - snyk.io/project-ids: 12345678-1234-1234-1234-123456789012,12345678-1234-1234-1234-123456789013,12345678-1234-1234-1234-123456789014 - ... + snyk.io/org-id: 361fd3c0-41d4-4ea4-ba77-09bb17890967 + snyk.io/targets: Snyk Demo/java-goof,508d2263-ea8a-4e42-bc9d-844de21f4172 + snyk.io/target-id: aarlaud-snyk/github-stats + snyk.io/project-ids: 7439e322-f9c1-4c42-8367-002b33b9d946,db066cb9-b373-46da-b918-b49b541e0d63 + snyk.io/exclude-project-ids: 4737fc9c-3894-40ba-9dc5-aa8ae658c9f6,38e02916-0cf7-4927-ba98-06afae9fef36 spec: type: service lifecycle: production owner: guest .... ``` +Some more examples can be found in [here](https://github.com/snyk-tech-services/backstage-plugin-snyk/tree/develop/test/fixtures) ## Migration steps from version 1.x to 2.x - Update the proxy target to not contain /v1 -- snyk.io/project-ids annotations are no longer in use, instead replaced by targets designated by github.com/project-slug or snyk.io/target-id. ## Troubleshooting diff --git a/src/api/index.ts b/src/api/index.ts index f4f6656..0e62e2c 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -22,7 +22,14 @@ import { } from "@backstage/core-plugin-api"; import { TargetData } from "../types/targetsTypes"; import { OrgData } from "../types/orgsTypes"; - +import { ProjectsData } from "../types/projectsTypes"; +import { + SNYK_ANNOTATION_TARGETID, + SNYK_ANNOTATION_TARGETNAME, + SNYK_ANNOTATION_TARGETS, + SNYK_ANNOTATION_PROJECTIDS, + SNYK_ANNOTATION_EXCLUDE_PROJECTIDS +} from "../config"; const DEFAULT_PROXY_PATH_BASE = ""; type Options = { @@ -41,7 +48,7 @@ export const snykApiRef: ApiRef = createApiRef({ export interface SnykApi { ListAllAggregatedIssues(orgName: string, projectId: string): Promise; ProjectDetails(orgName: string, projectId: string): Promise; - ProjectsList(orgName: string, repoName:string): Promise; + GetCompleteProjectsListFromAnnotations(orgId: string, annotations: Record):Promise; GetDependencyGraph(orgName: string, projectId: string): Promise; GetSnykAppHost(): string; GetOrgSlug(orgId: string): Promise; @@ -129,8 +136,87 @@ export class SnykApiClient implements SnykApi { return orgData.attributes.slug } - async ProjectsList(orgId: string, repoName: string) { - if(repoName == ''){ + async GetCompleteProjectsListFromAnnotations(orgId: string, annotations: Record):Promise { + let completeProjectsList: ProjectsData[] = [] + const targetsArray = annotations?.[SNYK_ANNOTATION_TARGETS] ? annotations?.[SNYK_ANNOTATION_TARGETS].split(',') : [] + + if(annotations?.[SNYK_ANNOTATION_TARGETNAME] ){ + targetsArray.push(annotations?.[SNYK_ANNOTATION_TARGETNAME]) + } else if(annotations?.[SNYK_ANNOTATION_TARGETID]){ + targetsArray.push(annotations?.[SNYK_ANNOTATION_TARGETID]) + } + if(targetsArray.length>0){ + const fullProjectByTargetList = await this.ProjectsListByTargets( + orgId, + Array.isArray(targetsArray)? targetsArray: [...targetsArray] + ); + completeProjectsList.push(...fullProjectByTargetList) + } + + if(annotations?.[SNYK_ANNOTATION_PROJECTIDS]){ + const fullProjectByIdList = await this.ProjectsListByProjectIds( + orgId, + annotations?.[SNYK_ANNOTATION_PROJECTIDS].split(',') + ); + completeProjectsList.push(...fullProjectByIdList) + } + + if(annotations?.[SNYK_ANNOTATION_EXCLUDE_PROJECTIDS]){ + let idsToExclude = annotations?.[SNYK_ANNOTATION_EXCLUDE_PROJECTIDS].split(',') + idsToExclude = idsToExclude.filter(id => /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/.test(id)) + completeProjectsList = completeProjectsList.filter((project) => { + return !idsToExclude.includes(project.id) + }) + } + return completeProjectsList + } + + async ProjectsListByTargets(orgId: string, repoName: string[]):Promise { + const TargetIdsArray:string[] = [] + for(let i=0;i= 400 && response.status < 600) { + throw new Error( + `Error ${response.status} - Failed fetching Projects list snyk data` + ); + } + const jsonResponse = await response.json(); + return jsonResponse.data as ProjectsData[]; + } + + async ProjectsListByProjectIds(orgId: string, projectIdsArray: string[]):Promise { + const backendBaseUrl = await this.getApiUrl(); + let v3Headers = this.headers; + v3Headers["Content-Type"] = "application/vnd.api+json"; + + const projectsForTargetUrl = `${backendBaseUrl}/rest/orgs/${orgId}/projects?ids=${projectIdsArray.join('%2C')}&limit=100&version=2023-06-19~beta`; + const response = await fetch(projectsForTargetUrl, { + method: "GET", + headers: v3Headers, + }); + + if (response.status >= 400 && response.status < 600) { + throw new Error( + `Error ${response.status} - Failed fetching Projects list snyk data` + ); + } + const jsonResponse = await response.json(); + return jsonResponse.data as ProjectsData[]; + } + + private async GetTargetId(orgId:string,targetIdentifier:string): Promise { + if(targetIdentifier == ''){ throw new Error( `Error - Unable to find repo name. Please add github.com/project-slug or snyk.io/target-id annotation` ); @@ -139,10 +225,10 @@ export class SnykApiClient implements SnykApi { let v3Headers = this.headers; v3Headers["Content-Type"] = "application/vnd.api+json"; let targetId - if(/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/.test(repoName)){ - targetId = repoName + if(/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/.test(targetIdentifier)){ + targetId = targetIdentifier } else { - const targetsAPIUrl = `${backendBaseUrl}/rest/orgs/${orgId}/targets?displayName=${encodeURIComponent(repoName)}&version=2023-06-19~beta`; + const targetsAPIUrl = `${backendBaseUrl}/rest/orgs/${orgId}/targets?displayName=${encodeURIComponent(targetIdentifier)}&version=2023-06-19~beta`; const targetResponse = await fetch(`${targetsAPIUrl}`, { method: "GET", headers: v3Headers, @@ -155,28 +241,16 @@ export class SnykApiClient implements SnykApi { const targetsList = await targetResponse.json() const targetsListData = targetsList.data as TargetData[] targetId = targetsListData.find(target => { - return target.attributes.displayName == repoName + return target.attributes.displayName == targetIdentifier })?.id if(!targetId){ throw new Error( - `Error - Failed finding Target snyk data for repo ${repoName}` + `Error - Failed finding Target snyk data for repo ${targetIdentifier}` ); } } + return targetId - const projectsForTargetUrl = `${backendBaseUrl}/rest/orgs/${orgId}/projects?target_id=${targetId}&limit=100&version=2023-06-19~beta`; - const response = await fetch(`${projectsForTargetUrl}`, { - method: "GET", - headers: v3Headers, - }); - - if (response.status >= 400 && response.status < 600) { - throw new Error( - `Error ${response.status} - Failed fetching Projects list snyk data` - ); - } - const jsonResponse = await response.json(); - return jsonResponse.data; } async GetDependencyGraph(orgName: string, projectId: string) { @@ -192,6 +266,6 @@ export class SnykApiClient implements SnykApi { ); } const jsonResponse = await response.json(); - return jsonResponse; + return jsonResponse as ProjectsData[]; } } diff --git a/src/components/SnykEntityComponent/SnykEntityComponent.tsx b/src/components/SnykEntityComponent/SnykEntityComponent.tsx index 04ae6e1..c3f165e 100644 --- a/src/components/SnykEntityComponent/SnykEntityComponent.tsx +++ b/src/components/SnykEntityComponent/SnykEntityComponent.tsx @@ -4,11 +4,13 @@ import { Progress, Content, TabbedLayout, - Link - + Link, } from "@backstage/core-components"; import { useApi } from "@backstage/core-plugin-api"; -import { MissingAnnotationEmptyState, InfoCard } from "@backstage/core-components"; +import { + MissingAnnotationEmptyState, + InfoCard, +} from "@backstage/core-components"; import { snykApiRef } from "../../api"; import { useAsync } from "react-use"; import { Alert } from "@material-ui/lab"; @@ -25,11 +27,18 @@ import { } from "./svgs"; import { useEntity } from "@backstage/plugin-catalog-react"; import { ProjectsData } from "../../types/projectsTypes"; -import { SNYK_ANNOTATION_ORG, SNYK_ANNOTATION_TARGETID, SNYK_ANNOTATION_TARGETNAME } from "../../config"; +import { + SNYK_ANNOTATION_ORG, + SNYK_ANNOTATION_TARGETID, + SNYK_ANNOTATION_TARGETNAME, + SNYK_ANNOTATION_PROJECTIDS, + SNYK_ANNOTATION_TARGETS, +} from "../../config"; type SnykTab = { name: string; icon: any; + projectId: string, tabContent: any; type: string; }; @@ -67,28 +76,45 @@ const getIconForProjectType = (projectOrigin: string) => { } }; - export const SnykEntityComponent = () => { const { entity } = useEntity(); if (!entity || !entity?.metadata.name) { return <>No Snyk org/project-ids listed; } - const containerStyle = { width: '60%', padding: '20px' }; + const containerStyle = { width: "60%", padding: "20px" }; if ( !entity || !entity?.metadata.annotations || !entity?.metadata.annotations[SNYK_ANNOTATION_ORG] || - !(entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETNAME] || entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETID]) + !( + entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETNAME] || + entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETID] || + entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETS] || + entity.metadata.annotations?.[SNYK_ANNOTATION_PROJECTIDS] + ) ) { return (
- +
- or alternatively using the target ID you can retrieve using the Targets endpoint endpoint. + or alternatively using the target name or ID (you can retrieve using the{" "} + + Targets endpoint) + {" "} + endpoint.
- +
+ Other combinations are possible, please checkout the README.
); } @@ -99,12 +125,9 @@ export const SnykEntityComponent = () => { const orgId = entity?.metadata.annotations?.[SNYK_ANNOTATION_ORG] || "null"; const { value, loading, error } = useAsync(async () => { - const fullProjectList = await snykApi.ProjectsList( - orgId, - entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETNAME] || entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETID] || '' - ); - const orgSlug = await snykApi.GetOrgSlug(orgId) - return { fullProjectList, orgSlug }; + const completeProjectsList: ProjectsData[] = entity?.metadata.annotations ? await snykApi.GetCompleteProjectsListFromAnnotations(orgId,entity?.metadata.annotations): [] + const orgSlug = await snykApi.GetOrgSlug(orgId); + return { completeProjectsList, orgSlug }; }); if (loading) { return ( @@ -117,30 +140,42 @@ export const SnykEntityComponent = () => { return {error.message}; } - const projectList = value?.fullProjectList as ProjectsData[]; - const orgSlug = value?.orgSlug || '' + const projectList = value?.completeProjectsList as ProjectsData[]; + const orgSlug = value?.orgSlug || ""; projectList.forEach((project) => { tabs.push({ - name: utils.extractTargetShortname(project.attributes.name || "unknown"), + name: `${utils.extractTargetShortname(project.attributes.name || "unknown")}`, icon: getIconForProjectType(project.attributes.origin || ""), - tabContent: generateSnykTabForProject(snykApi, orgId, orgSlug, project.id,entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETNAME] || entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETID] || ''), + projectId: project.id, + tabContent: generateSnykTabForProject( + snykApi, + orgId, + orgSlug, + project.id + ), type: project.attributes.type, }); }); - const infoCardTitle = `${tabs.length} Project${tabs.length>1? 's' : '' }` + const infoCardTitle = `${tabs.length} Project${tabs.length > 1 ? "s" : ""}`; return ( - <> - - - {tabs.map(tab => ( - 15 ? '...':''}`}> - - - ))} - - - + <> + + + {tabs.map((tab) => ( + + + + + + ))} + + + ); }; diff --git a/src/components/SnykEntityComponent/SnykOverviewComponent.tsx b/src/components/SnykEntityComponent/SnykOverviewComponent.tsx index 0aff5de..d44576b 100644 --- a/src/components/SnykEntityComponent/SnykOverviewComponent.tsx +++ b/src/components/SnykEntityComponent/SnykOverviewComponent.tsx @@ -11,14 +11,19 @@ import { snykApiRef } from "../../api"; import { useAsync } from "react-use"; import { Alert } from "@material-ui/lab"; import * as utils from "../../utils/utils"; -import { ProjectsData } from "../../types/projectsTypes"; import { Grid } from "@material-ui/core"; import { SnykCircularCounter } from "./components/SnykCircularCountersComponent"; import { issuesCount } from "../../types/types"; import { useEntity } from "@backstage/plugin-catalog-react"; import { UnifiedIssues } from "../../types/unifiedIssuesTypes"; -import { SNYK_ANNOTATION_ORG, SNYK_ANNOTATION_TARGETID, SNYK_ANNOTATION_TARGETNAME } from "../../config"; +import { + SNYK_ANNOTATION_ORG, + SNYK_ANNOTATION_PROJECTIDS, + SNYK_ANNOTATION_TARGETID, + SNYK_ANNOTATION_TARGETNAME, + SNYK_ANNOTATION_TARGETS, +} from "../../config"; export const SnykOverviewComponent = ({ entity }: { entity: Entity }) => { if (!entity || !entity?.metadata.name) { @@ -28,7 +33,12 @@ export const SnykOverviewComponent = ({ entity }: { entity: Entity }) => { if ( !entity.metadata.annotations || !entity.metadata.annotations?.[SNYK_ANNOTATION_ORG] || - !(entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETNAME] || entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETID]) + !( + entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETNAME] || + entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETID] || + entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETS] || + entity.metadata.annotations?.[SNYK_ANNOTATION_PROJECTIDS] + ) ) { return ( { medium: 0, low: 0, }; - const fullProjectList = await snykApi.ProjectsList(orgId, entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETNAME] || entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETID] || ''); - const projectList = fullProjectList as ProjectsData[]; - + const projectList = entity?.metadata.annotations + ? await snykApi.GetCompleteProjectsListFromAnnotations( + orgId, + entity.metadata.annotations + ) + : []; + let projectsCount = 0; - const projectIds = projectList.map(project => project.id) + const projectIds = projectList.map((project) => project.id); for (let i = 0; i < projectIds.length; i++) { if ( projectList?.some( @@ -84,17 +98,15 @@ export const SnykOverviewComponent = ({ entity }: { entity: Entity }) => { ) { projectsCount++; - const vulnsIssues: UnifiedIssues = await snykApi.ListAllAggregatedIssues( - orgId, - projectIds[i] - ); - const currentProjectIssuesCount = utils.getIssuesCount(vulnsIssues.data); - aggregatedIssuesCount.critical += currentProjectIssuesCount.critical; - aggregatedIssuesCount.high += currentProjectIssuesCount.high; - aggregatedIssuesCount.medium += currentProjectIssuesCount.medium; - aggregatedIssuesCount.low += currentProjectIssuesCount.low; - - + const vulnsIssues: UnifiedIssues = + await snykApi.ListAllAggregatedIssues(orgId, projectIds[i]); + const currentProjectIssuesCount = utils.getIssuesCount( + vulnsIssues.data + ); + aggregatedIssuesCount.critical += currentProjectIssuesCount.critical; + aggregatedIssuesCount.high += currentProjectIssuesCount.high; + aggregatedIssuesCount.medium += currentProjectIssuesCount.medium; + aggregatedIssuesCount.low += currentProjectIssuesCount.low; } } return { aggregatedIssuesCount, projectsCount }; diff --git a/src/components/SnykEntityComponent/index.ts b/src/components/SnykEntityComponent/index.ts index dbd4890..55e1942 100644 --- a/src/components/SnykEntityComponent/index.ts +++ b/src/components/SnykEntityComponent/index.ts @@ -1,8 +1,18 @@ -import { Entity } from '@backstage/catalog-model'; -import { SNYK_ANNOTATION_ORG, SNYK_ANNOTATION_TARGETID, SNYK_ANNOTATION_TARGETNAME } from '../../config'; +import { Entity } from "@backstage/catalog-model"; +import { + SNYK_ANNOTATION_ORG, + SNYK_ANNOTATION_TARGETID, + SNYK_ANNOTATION_TARGETNAME, + SNYK_ANNOTATION_TARGETS, + SNYK_ANNOTATION_PROJECTIDS +} from "../../config"; export { SnykEntityComponent } from "./SnykEntityComponent"; export { SnykOverview } from "./SnykOverviewComponent"; export const isPluginApplicableToEntity = (entity: Entity) => - Boolean(entity.metadata.annotations?.[SNYK_ANNOTATION_ORG]) && (Boolean(entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETNAME]) || Boolean(entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETID])); \ No newline at end of file + Boolean(entity.metadata.annotations?.[SNYK_ANNOTATION_ORG]) && + (Boolean(entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETNAME]) || + Boolean(entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETID]) || + Boolean(entity.metadata.annotations?.[SNYK_ANNOTATION_TARGETS]) || + Boolean(entity.metadata.annotations?.[SNYK_ANNOTATION_PROJECTIDS])); diff --git a/src/components/SnykEntityComponent/snykTab.tsx b/src/components/SnykEntityComponent/snykTab.tsx index 2ab62b0..847460c 100644 --- a/src/components/SnykEntityComponent/snykTab.tsx +++ b/src/components/SnykEntityComponent/snykTab.tsx @@ -5,7 +5,7 @@ import { InfoCard, Content, TabbedCard, - CardTab, + CardTab } from "@backstage/core-components"; import { Grid } from "@material-ui/core"; import { SnykApi } from "../../api/index"; @@ -26,8 +26,7 @@ export const generateSnykTabForProject = ( snykApi: SnykApi, orgId: string, orgSlug: string, - projectId: string, - orgDisplayName?: string + projectId: string ) => { const genericIssuesTypeArray = Object.values(TypeDef).filter(type => type != 'license') return ({}) => { @@ -71,8 +70,9 @@ export const generateSnykTabForProject = ( const metadata = { origin: value.projectDetails.origin, type: value.projectDetails.type, + "created": value.projectDetails.created, "last tested": value.projectDetails.lastTestedDate, - repository: value.projectDetails.remoteRepoUrl || orgDisplayName, + "Project ID": `${value.projectDetails.id}` }; const linkInfo = { title: "More details", diff --git a/src/config.ts b/src/config.ts index 076cecf..97e5575 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,3 +1,6 @@ export const SNYK_ANNOTATION_ORG = "snyk.io/org-id"; export const SNYK_ANNOTATION_TARGETNAME = "github.com/project-slug"; export const SNYK_ANNOTATION_TARGETID = "snyk.io/target-id"; +export const SNYK_ANNOTATION_TARGETS = "snyk.io/targets"; +export const SNYK_ANNOTATION_PROJECTIDS = "snyk.io/project-ids"; +export const SNYK_ANNOTATION_EXCLUDE_PROJECTIDS = "snyk.io/exclude-project-ids"; From 186c3574fa4677ef43553421f2c20bb79251b511 Mon Sep 17 00:00:00 2001 From: Antoine Arlaud Date: Sun, 3 Sep 2023 22:33:44 +0200 Subject: [PATCH 5/5] chore: add more fixtures --- test/fixtures/catalog-empty-goof.yaml | 11 +++++++++++ test/fixtures/catalog-notfound-goof.yaml | 12 ++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 test/fixtures/catalog-empty-goof.yaml create mode 100644 test/fixtures/catalog-notfound-goof.yaml diff --git a/test/fixtures/catalog-empty-goof.yaml b/test/fixtures/catalog-empty-goof.yaml new file mode 100644 index 0000000..f865ccf --- /dev/null +++ b/test/fixtures/catalog-empty-goof.yaml @@ -0,0 +1,11 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: goof + description: Goof + annotations: + snyk.io/org-id: 361fd3c0-41d4-4ea4-ba77-09bb17890967 +spec: + type: service + lifecycle: production + owner: guest \ No newline at end of file diff --git a/test/fixtures/catalog-notfound-goof.yaml b/test/fixtures/catalog-notfound-goof.yaml new file mode 100644 index 0000000..9038f10 --- /dev/null +++ b/test/fixtures/catalog-notfound-goof.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: goof + description: Goof + annotations: + snyk.io/org-id: 361fd3c0-41d4-4ea4-ba77-09bb17890967 + snyk.io/target-id: abc123 +spec: + type: service + lifecycle: production + owner: guest \ No newline at end of file