Skip to content

Commit

Permalink
feat(importCDX): Add functionality to configure release creation when…
Browse files Browse the repository at this point in the history
… importing SBOM to an existing project

Signed-off-by: sameed.ahmad <[email protected]>
  • Loading branch information
sameed20 committed Oct 28, 2024
1 parent 5bdef6d commit 695ce84
Show file tree
Hide file tree
Showing 8 changed files with 48 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ private Map<String, List<org.cyclonedx.model.Component>> getVcsToComponentMap(Li
}

@SuppressWarnings("unchecked")
public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent attachmentContent, String projectId, User user) {
public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent attachmentContent, String projectId, User user, boolean doNotReplacePackageAndRelease) {
RequestSummary requestSummary = new RequestSummary();
Map<String, String> messageMap = new HashMap<>();
requestSummary.setRequestStatus(RequestStatus.FAILURE);
Expand Down Expand Up @@ -182,15 +182,14 @@ public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent a

if (!IS_PACKAGE_PORTLET_ENABLED) {
vcsToComponentMap.put("", components);
requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent);
requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent, doNotReplacePackageAndRelease);
} else {
vcsToComponentMap = getVcsToComponentMap(components);
if (componentsCount == vcsCount) {

requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent);
requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent, doNotReplacePackageAndRelease);
} else if (componentsCount > vcsCount) {

requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent);
requestSummary = importSbomAsProject(compMetadata, vcsToComponentMap, projectId, attachmentContent, doNotReplacePackageAndRelease);

if (requestSummary.requestStatus.equals(RequestStatus.SUCCESS)) {

Expand Down Expand Up @@ -355,7 +354,7 @@ public RequestSummary importFromBOM(InputStream inputStream, AttachmentContent a
}

public RequestSummary importSbomAsProject(org.cyclonedx.model.Component compMetadata,
Map<String, List<org.cyclonedx.model.Component>> vcsToComponentMap, String projectId, AttachmentContent attachmentContent)
Map<String, List<org.cyclonedx.model.Component>> vcsToComponentMap, String projectId, AttachmentContent attachmentContent, boolean doNotReplacePackageAndRelease)
throws SW360Exception {
final RequestSummary summary = new RequestSummary();
summary.setRequestStatus(RequestStatus.FAILURE);
Expand Down Expand Up @@ -408,7 +407,7 @@ public RequestSummary importSbomAsProject(org.cyclonedx.model.Component compMeta
}

if (IS_PACKAGE_PORTLET_ENABLED) {
messageMap = importAllComponentsAsPackages(vcsToComponentMap, project);
messageMap = importAllComponentsAsPackages(vcsToComponentMap, project, doNotReplacePackageAndRelease);
} else {
messageMap = importAllComponentsAsReleases(vcsToComponentMap, project);
}
Expand Down Expand Up @@ -538,19 +537,25 @@ private Map<String, String> importAllComponentsAsReleases(Map<String, List<org.c
return messageMap;
}

private Map<String, String> importAllComponentsAsPackages(Map<String, List<org.cyclonedx.model.Component>> vcsToComponentMap, Project project) {

private Map<String, String> importAllComponentsAsPackages(Map<String, List<org.cyclonedx.model.Component>> vcsToComponentMap, Project project, boolean doNotReplacePackageAndRelease) throws SW360Exception {
final var countMap = new HashMap<String, Integer>();
final Set<String> duplicateComponents = new HashSet<>();
final Set<String> duplicateReleases = new HashSet<>();
final Set<String> duplicatePackages = new HashSet<>();
final Set<String> invalidReleases = new HashSet<>();
final Set<String> invalidPackages = new HashSet<>();
final Map<String, ProjectReleaseRelationship> releaseRelationMap = CommonUtils.isNullOrEmptyMap(project.getReleaseIdToUsage()) ? new HashMap<>() : project.getReleaseIdToUsage();
final Set<String> projectPkgIds = CommonUtils.isNullOrEmptyCollection(project.getPackageIds()) ? new HashSet<>() : project.getPackageIds();
countMap.put(REL_CREATION_COUNT_KEY, 0); countMap.put(REL_REUSE_COUNT_KEY, 0);
countMap.put(PKG_CREATION_COUNT_KEY, 0); countMap.put(PKG_REUSE_COUNT_KEY, 0);
int relCreationCount = 0, relReuseCount = 0, pkgCreationCount = 0, pkgReuseCount = 0;

if (!doNotReplacePackageAndRelease) {
releaseRelationMap.clear();
projectPkgIds.clear();
log.info("Cleared existing releases and packages for project: " + project.getName());
}

for (Map.Entry<String, List<org.cyclonedx.model.Component>> entry : vcsToComponentMap.entrySet()) {
Component comp = createComponent(entry.getKey());
List<org.cyclonedx.model.Component> componentsFromBom = entry.getValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1848,6 +1848,10 @@ public RequestSummary importBomFromAttachmentContent(User user, String attachmen
}

public RequestSummary importCycloneDxFromAttachmentContent(User user, String attachmentContentId, String projectId) throws SW360Exception {
return importCycloneDxFromAttachmentContent(user, attachmentContentId, projectId, false);
}

public RequestSummary importCycloneDxFromAttachmentContent(User user, String attachmentContentId, String projectId, boolean doNotReplacePackageAndRelease) throws SW360Exception {
final AttachmentContent attachmentContent = attachmentConnector.getAttachmentContent(attachmentContentId);
final Duration timeout = Duration.durationOf(30, TimeUnit.SECONDS);
try {
Expand All @@ -1856,7 +1860,7 @@ public RequestSummary importCycloneDxFromAttachmentContent(User user, String att
.unsafeGetAttachmentStream(attachmentContent)) {
final CycloneDxBOMImporter cycloneDxBOMImporter = new CycloneDxBOMImporter(this,
componentDatabaseHandler, packageDatabaseHandler, attachmentConnector, user);
return cycloneDxBOMImporter.importFromBOM(inputStream, attachmentContent, projectId, user);
return cycloneDxBOMImporter.importFromBOM(inputStream, attachmentContent, projectId, user, doNotReplacePackageAndRelease);
}
} catch (IOException e) {
log.error("Error while importing / parsing CycloneDX SBOM! ", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,13 @@ public RequestSummary importCycloneDxFromAttachmentContent(User user, String att
return handler.importCycloneDxFromAttachmentContent(user, attachmentContentId, projectId);
}

@Override
public RequestSummary importCycloneDxFromAttachmentContentWithReplaceReleaseAndPackageFlag(User user, String attachmentContentId, String projectId, boolean doNotReplacePackageAndRelease) throws SW360Exception {
assertId(attachmentContentId);
assertUser(user);
return handler.importCycloneDxFromAttachmentContent(user, attachmentContentId, projectId, doNotReplacePackageAndRelease);
}

@Override
public RequestSummary exportCycloneDxSbom(String projectId, String bomType, boolean includeSubProjReleases, User user) throws SW360Exception {
assertId(projectId);
Expand Down
6 changes: 6 additions & 0 deletions libraries/datahandler/src/main/thrift/projects.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,12 @@ service ProjectService {
*/
RequestSummary importCycloneDxFromAttachmentContent(1: User user, 2: string attachmentContentId, 3: string projectId) throws (1: SW360Exception exp);

/**
* Parse a CycloneDx SBoM file (XML or JSON) during re-import on a project and write the information to SW360 as Project / Component / Release / Package
* with replaceReleaseAndPackageFlag
*/
RequestSummary importCycloneDxFromAttachmentContentWithReplaceReleaseAndPackageFlag(1: User user, 2: string attachmentContentId, 3: string projectId, 4: bool doNotReplacePackageAndRelease) throws (1: SW360Exception exp);

/**
* Export a CycloneDx SBoM file (XML or JSON) for a Project
*/
Expand Down
8 changes: 8 additions & 0 deletions rest/resource-server/src/docs/asciidoc/projects.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,14 @@ include::{snippets}/should_document_import_cyclonedx/http-response.adoc[]

A `POST` request is used to import a SBOM on a project. Currently only CycloneDX(.xml/.json) files are supported.

[red]#Request parameter#
|===
|Parameter |Description

|doNotReplacePackageAndRelease
|By default (when set to false), existing releases and packages in the project are replaced with the latest data from the SBOM. When set to true, the latest SBOM data is added to the existing releases and packages without replacing them.
|===

[red]#Request body#
|===
Type |Description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2017,7 +2017,7 @@ public ResponseEntity<?> importSBOM(
}
projectId = requestSummary.getMessage();
} else {
requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), "");
requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), "", true);

if (requestSummary.getRequestStatus() == RequestStatus.FAILURE) {
return new ResponseEntity<String>(requestSummary.getMessage(), HttpStatus.BAD_REQUEST);
Expand Down Expand Up @@ -2071,7 +2071,8 @@ public ResponseEntity<?> importSBOMonProject(
@Parameter(description = "Project ID", example = "376576")
@PathVariable(value = "id", required = true) String id,
@Parameter(description = "SBOM file")
@RequestBody MultipartFile file
@RequestBody MultipartFile file,
@RequestParam(value = "doNotReplacePackageAndRelease", required = false) boolean doNotReplacePackageAndRelease
) throws TException {
final User sw360User = restControllerHelper.getSw360UserFromAuthentication();
Attachment attachment = null;
Expand All @@ -2086,7 +2087,7 @@ public ResponseEntity<?> importSBOMonProject(
throw new RuntimeException("failed to upload attachment", e);
}

requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), id);
requestSummary = projectService.importCycloneDX(sw360User, attachment.getAttachmentContentId(), id, doNotReplacePackageAndRelease);

if (requestSummary.getRequestStatus() == RequestStatus.FAILURE) {
return new ResponseEntity<String>(requestSummary.getMessage(), HttpStatus.BAD_REQUEST);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1103,9 +1103,9 @@ public RequestSummary importSPDX(User user, String attachmentContentId) throws T
* @return RequestSummary
* @throws TException
*/
public RequestSummary importCycloneDX(User user, String attachmentContentId, String projectId) throws TException {
public RequestSummary importCycloneDX(User user, String attachmentContentId, String projectId, boolean doNotReplacePackageAndRelease) throws TException {
ProjectService.Iface sw360ProjectClient = getThriftProjectClient();
return sw360ProjectClient.importCycloneDxFromAttachmentContent(user, attachmentContentId, CommonUtils.nullToEmptyString(projectId));
return sw360ProjectClient.importCycloneDxFromAttachmentContentWithReplaceReleaseAndPackageFlag(user, attachmentContentId, CommonUtils.nullToEmptyString(projectId), doNotReplacePackageAndRelease);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ public void before() throws TException, IOException {
given(this.projectServiceMock.loadPreferredClearingDateLimit()).willReturn(Integer.valueOf(7));

given(this.projectServiceMock.importSPDX(any(),any())).willReturn(requestSummaryForSPDX);
given(this.projectServiceMock.importCycloneDX(any(),any(),any())).willReturn(requestSummaryForCycloneDX);
given(this.projectServiceMock.importCycloneDX(any(),any(),any(),anyBoolean())).willReturn(requestSummaryForCycloneDX);
given(this.sw360ReportServiceMock.getDocumentName(any(), any())).willReturn(projectName);
given(this.sw360ReportServiceMock.getProjectBuffer(any(),anyBoolean(),any())).willReturn(ByteBuffer.allocate(10000));
given(this.projectServiceMock.getProjectsForUser(any(), any())).willReturn(projectList);
Expand Down Expand Up @@ -2456,6 +2456,7 @@ public void should_document_import_cyclonedx_on_project() throws Exception {
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/api/projects/"+project.getId()+"/import/SBOM")
.content(file.getBytes())
.contentType(MediaType.MULTIPART_FORM_DATA)
.queryParam("doNotReplacePackageAndRelease", "false")
.header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword));
this.mockMvc.perform(builder).andExpect(status().isOk()).andDo(this.documentationHandler.document());
}
Expand Down

0 comments on commit 695ce84

Please sign in to comment.