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 30, 2024
1 parent 9452b2b commit aaf0ed3
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 17 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 @@ -1853,6 +1853,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 @@ -1861,7 +1865,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 importCycloneDxFromAttachmentContentWithReplacePackageAndReleaseFlag(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 importCycloneDxFromAttachmentContentWithReplacePackageAndReleaseFlag(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
10 changes: 9 additions & 1 deletion rest/resource-server/src/docs/asciidoc/projects.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,15 @@ include::{snippets}/should_document_import_cyclonedx/http-response.adoc[]
[[resources-import-sbom-on-project]]
==== Import SBOM on project

A `POST` request is used to import a SBOM on a project. Currently only CycloneDX(.xml/.json) files are supported.
A `POST` request is used to import a SBOM on a project, the project’s releases and packages will be replaced with the latest data from the SBOM. Currently only CycloneDX(.xml/.json) files are supported.

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

|doNotReplacePackageAndRelease
|When importing an SBOM into an existing project, the project’s releases and packages will be replaced with the latest data from the SBOM. Use the parameter `doNotReplacePackageAndRelease=true` if you want to import new data without replacing existing releases and packages.
|===

[red]#Request body#
|===
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2010,7 +2010,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 @@ -2064,7 +2064,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 @@ -2079,7 +2080,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.importCycloneDxFromAttachmentContentWithReplacePackageAndReleaseFlag(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 aaf0ed3

Please sign in to comment.