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(importCDX): Add functionality to configure release creation when importing SBOM to an existing project #2458

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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` to import new data without replacing the 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
Copy link
Member

Choose a reason for hiding this comment

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

Please add OpenAPI doc for the parameter with @Parameter annotation.

) 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