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

Feature/support parent child relationships #141

Merged
merged 5 commits into from
Feb 1, 2023
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
19 changes: 14 additions & 5 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,31 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
node-version: 'lts/Hydrogen'
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build with Maven and Sonar
if: matrix.java == '11' && github.repository == 'jenkinsci/dependency-track-plugin' && !startsWith(github.head_ref, 'dependabot/')
- name: check Sonar pre-conditions
id: check_sonar
continue-on-error: true
env:
SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
SONAR_ORGANIZATION: ${{ secrets.SONARCLOUD_ORGANIZATION }}
run: test "${SONAR_ORGANIZATION}" -a "${SONAR_TOKEN}"
shell: bash
- name: Build with Sonar
id: build_sonar
if: matrix.java == '11' && steps.check_sonar.outcome == 'success' && !startsWith(github.head_ref, 'dependabot/')
env:
SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
SONAR_ORGANIZATION: ${{ secrets.SONARCLOUD_ORGANIZATION }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: mvn -B clean test verify package org.sonarsource.scanner.maven:sonar-maven-plugin:3.9.1.2184:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.projectKey=org.jenkins-ci.plugins:dependency-track -Dsonar.organization=$SONAR_ORGANIZATION -Dsonar.login=$SONAR_TOKEN
- name: Build with Maven
if: matrix.java == '11' && ( github.repository != 'jenkinsci/dependency-track-plugin' || startsWith(github.head_ref, 'dependabot/') )
- name: Build without Sonar
if: steps.build_sonar.conclusion == 'skipped'
run: mvn -B clean test verify package
- uses: actions/upload-artifact@v3
if: success()
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
## Unreleased
### ⚠ Breaking
### ⭐ New Features
- Added support for parent-child-relationships of projects with Dependency-Track v4.7 and newer (fixes [#139](https://github.com/jenkinsci/dependency-track-plugin/issues/139))

### 🐞 Bugs Fixed
- Searching on the result page was partially broken due to [a bug in bootstrap-vue 2.22+](https://github.com/bootstrap-vue/bootstrap-vue/issues/6967)

## v4.2.0 - 2022-07-04
### ⚠ Breaking
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"license": "Apache-2.0",
"description": "Dependency-Track is an intelligent Software Supply Chain Component Analysis platform that allows organizations to identify and reduce risk from the use of third-party and open source components. This plug-in can publish supported Software Bill-of-Materials (SBOM) formats to Dependency-Track.",
"dependencies": {
"bootstrap-vue": "^2.21.1",
"bootstrap-vue": "2.21.2",
"echarts": "^5.0.0",
"vue": "^2.6.12"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,11 @@ public void updateProjectProperties(@NonNull final String projectUuid, @NonNull
rawProject.elementOpt("group", properties.getGroup());
// overwrite description only if it is set (means not null)
rawProject.elementOpt("description", properties.getDescription());
// set new parent project if it is set (means not null)
if (properties.getParentId() != null) {
JSONObject newParent = new JSONObject().elementOpt("uuid", properties.getParentId());
rawProject.element("parent", newParent);
}
// 3. update project
updateProject(projectUuid, rawProject);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
*/
@Extension
@Symbol("dependencyTrackPublisher") // This indicates to Jenkins that this is an implementation of an extension point.
public final class DescriptorImpl extends BuildStepDescriptor<Publisher> implements Serializable {
public class DescriptorImpl extends BuildStepDescriptor<Publisher> implements Serializable {

private static final long serialVersionUID = -2018722914973282748L;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Project parse(final JSONObject json) {
.active(activeStr != null ? Boolean.parseBoolean(activeStr) : null)
.swidTagId(getKeyOrNull(json, "swidTagId"))
.group(getKeyOrNull(json, "group"))
.parent(json.has("parent") ? ProjectParser.parse(json.getJSONObject("parent")) : null)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,27 @@
package org.jenkinsci.plugins.DependencyTrack;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.Extension;
import hudson.RelativePath;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.model.Item;
import hudson.util.ListBoxModel;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jenkins.model.Jenkins;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.verb.POST;

import static org.jenkinsci.plugins.DependencyTrack.PluginUtil.areAllElementsOfType;

Expand All @@ -46,22 +54,32 @@ public final class ProjectProperties extends AbstractDescribableImpl<ProjectProp
/**
* Tags to set for the project
*/
@Nullable
private List<String> tags;

/**
* SWID Tag ID for the project
*/
@Nullable
private String swidTagId;

/**
* Group to set for the project
*/
@Nullable
private String group;

/**
* Description to set for the project
*/
@Nullable
private String description;

/**
* UUID of the parent project
*/
@Nullable
private String parentId;

@NonNull
public List<String> getTags() {
Expand Down Expand Up @@ -111,6 +129,11 @@ public void setDescription(final String description) {
this.description = StringUtils.trimToNull(description);
}

@DataBoundSetter
public void setParentId(final String parentId) {
this.parentId = StringUtils.trimToNull(parentId);
}

@NonNull
public String getTagsAsText() {
return StringUtils.join(getTags(), System.lineSeparator());
Expand All @@ -129,5 +152,19 @@ private List<String> normalizeTags(final Collection<String> values) {

@Extension
public static class DescriptorImpl extends Descriptor<ProjectProperties> {

/**
* Retrieve the projects to populate the dropdown.
*
* @param dependencyTrackUrl the base URL to Dependency-Track
* @param dependencyTrackApiKey the API key to use for authentication
* @param item used to lookup credentials in job config
* @return ListBoxModel
*/
@POST
public ListBoxModel doFillParentIdItems(@RelativePath("..") @QueryParameter final String dependencyTrackUrl, @RelativePath("..") @QueryParameter final String dependencyTrackApiKey, @AncestorInPath @Nullable final Item item) {
org.jenkinsci.plugins.DependencyTrack.DescriptorImpl pluginDescriptor = Jenkins.get().getDescriptorByType(org.jenkinsci.plugins.DependencyTrack.DescriptorImpl.class);
return pluginDescriptor.doFillProjectIdItems(dependencyTrackUrl, dependencyTrackApiKey, item);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ public class Project implements Serializable {
private Boolean active;
private String swidTagId;
private String group;
private Project parent;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@ limitations under the License.
<f:entry field="description" title="${%description}">
<f:textbox id="description" />
</f:entry>
<f:entry field="parentId" title="${%parentId}">
<f:select id="parentId"/>
</f:entry>

</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ tags=Tags
swidTagId=SWID Tag ID
group=Namespace / Group / Vendor
description=Description
parentId=Parent project
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ tags=Tags
swidTagId=SWID Tag ID
group=Namensraum / Gruppe / Hersteller
description=Beschreibung
parentId=\u00dcbergeordnetes Projekt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
The ID (UUID) of the parent project.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Die ID (UUID) des übergeordneten Projektes.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ public void updateProjectPropertiesTest() throws ApiClientException, Interrupted
.get(ApiClient.PROJECT_URL + "/uuid-3", (request, response) -> {
assertThat(request.requestHeaders().contains(ApiClient.API_KEY_HEADER, API_KEY, false)).isTrue();
assertThat(request.requestHeaders().contains(HttpHeaderNames.ACCEPT, HttpHeaderValues.APPLICATION_JSON, true)).isTrue();
return response.sendString(Mono.just("{\"name\":\"test-project\",\"uuid\":\"uuid-3\",\"version\":\"1.2.3\",\"tags\":[{\"name\":\"tag1\"},{\"name\":\"tag2\"}]}"));
return response.sendString(Mono.just("{\"name\":\"test-project\",\"uuid\":\"uuid-3\",\"version\":\"1.2.3\",\"tags\":[{\"name\":\"tag1\"},{\"name\":\"tag2\"}],\"parent\":{\"uuid\":\"old-parent\"}}"));
})
.post(ApiClient.PROJECT_URL, (request, response) -> {
assertThat(request.requestHeaders().contains(ApiClient.API_KEY_HEADER, API_KEY, false)).isTrue();
Expand All @@ -380,6 +380,7 @@ public void updateProjectPropertiesTest() throws ApiClientException, Interrupted
props.setSwidTagId("my swid tag id");
props.setGroup("my group");
props.setDescription("my description");
props.setParentId("parent-uuid");

assertThatCode(() -> uut.updateProjectProperties("uuid-3", props)).doesNotThrowAnyException();
completionSignal.await(5, TimeUnit.SECONDS);
Expand All @@ -388,6 +389,7 @@ public void updateProjectPropertiesTest() throws ApiClientException, Interrupted
assertThat(project.getSwidTagId()).isEqualTo(props.getSwidTagId());
assertThat(project.getGroup()).isEqualTo(props.getGroup());
assertThat(project.getDescription()).isEqualTo(props.getDescription());
assertThat(project.getParent()).hasFieldOrPropertyWithValue("uuid", props.getParentId());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.util.Secret;
import io.jenkins.plugins.casc.misc.JenkinsConfiguredRule;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
Expand All @@ -44,6 +43,7 @@
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.jvnet.hudson.test.JenkinsRule;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner.StrictStubs;

Expand All @@ -70,7 +70,7 @@ public class DependencyTrackPublisherTest {
public TemporaryFolder tmpDir = new TemporaryFolder();

@Rule
public JenkinsConfiguredRule r = new JenkinsConfiguredRule();
public JenkinsRule r = new JenkinsRule();

@Mock
private Run build;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import hudson.util.ListBoxModel;
import hudson.util.Secret;
import hudson.util.VersionNumber;
import io.jenkins.plugins.casc.misc.JenkinsConfiguredRule;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -43,6 +42,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;
import org.kohsuke.stapler.StaplerRequest;
import org.mockito.Mock;
Expand All @@ -65,7 +65,7 @@
public class DescriptorImplTest {

@Rule
public JenkinsConfiguredRule r = new JenkinsConfiguredRule();
public JenkinsRule r = new JenkinsRule();

@Mock
private ApiClient client;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import hudson.security.ACLContext;
import hudson.security.AccessDeniedException3;
import hudson.util.RunList;
import io.jenkins.plugins.casc.misc.JenkinsConfiguredRule;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -34,6 +33,7 @@
import org.jenkinsci.plugins.DependencyTrack.model.SeverityDistribution;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -49,7 +49,7 @@
public class JobActionTest {

@Rule
public JenkinsConfiguredRule j = new JenkinsConfiguredRule();
public JenkinsRule j = new JenkinsRule();

@Test
public void isTrendVisible() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,22 @@
*/
package org.jenkinsci.plugins.DependencyTrack;

import hudson.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jenkins.model.Jenkins;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
*
Expand Down Expand Up @@ -70,8 +77,32 @@ void verifyEmptyStringsShallBeNull() {
uut.setDescription("");
uut.setGroup("\t");
uut.setSwidTagId(System.lineSeparator());
uut.setParentId(" ");
assertThat(uut.getDescription()).isNull();
assertThat(uut.getGroup()).isNull();
assertThat(uut.getSwidTagId()).isNull();
assertThat(uut.getParentId()).isNull();
}

@Nested
class DescriptorImplTest {

@Test
void doFillParentIdItemsTest() throws Exception {
Field instanceField = ReflectionUtils.findField(Jenkins.class, "theInstance", Jenkins.class);
ReflectionUtils.makeAccessible(instanceField);
Jenkins origJenkins = (Jenkins) instanceField.get(null);
Jenkins mockJenkins = mock(Jenkins.class);
ReflectionUtils.setField(instanceField, null, mockJenkins);
org.jenkinsci.plugins.DependencyTrack.DescriptorImpl descriptorMock = mock(org.jenkinsci.plugins.DependencyTrack.DescriptorImpl.class);
when(mockJenkins.getDescriptorByType(org.jenkinsci.plugins.DependencyTrack.DescriptorImpl.class)).thenReturn(descriptorMock);
ProjectProperties.DescriptorImpl uut = new ProjectProperties.DescriptorImpl();

uut.doFillParentIdItems("url", "key", null);

ReflectionUtils.setField(instanceField, null, origJenkins);
verify(mockJenkins).getDescriptorByType(org.jenkinsci.plugins.DependencyTrack.DescriptorImpl.class);
verify(descriptorMock).doFillProjectIdItems("url", "key", null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import hudson.security.ACL;
import hudson.security.ACLContext;
import hudson.security.AccessDeniedException3;
import io.jenkins.plugins.casc.misc.JenkinsConfiguredRule;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
Expand All @@ -35,6 +34,7 @@
import org.jenkinsci.plugins.DependencyTrack.model.SeverityDistribution;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -48,7 +48,7 @@
public class ResultActionTest {

@Rule
public JenkinsConfiguredRule j = new JenkinsConfiguredRule();
public JenkinsRule j = new JenkinsRule();

private List<Finding> getTestFindings() {
File findings = new File("src/test/resources/findings.json");
Expand Down