Skip to content

Commit

Permalink
CLM-19069 New index task to save a module descriptor for Nexus IQ. (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
guillermo-varela authored Sep 15, 2021
1 parent 7046461 commit 26d7201
Show file tree
Hide file tree
Showing 14 changed files with 368 additions and 26 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Gradle can be used to build projects developed in various programming languages.
- Edit its `build.gradle` file adding this:
```
plugins {
id 'org.sonatype.gradle.plugins.scan' version '2.0.10' // Update the version as needed
id 'org.sonatype.gradle.plugins.scan' version '2.2.0' // Update the version as needed
}
```

Expand Down Expand Up @@ -96,7 +96,7 @@ ossIndexAudit {
- Open Terminal on the project's root and run `./gradlew ossIndexAudit`
- You should see the audit result on Terminal.

### Nexus IQ Server
### Nexus IQ Server Scan and Evaluate
- Start a local instance of IQ Server, or get the URL and credentials of a remote one.
- Configure IQ Server settings inside the `nexusIQScan` configuration on the file `build.gradle` e.g.
```
Expand All @@ -116,6 +116,14 @@ nexusIQScan {
- Open Terminal on the project's root and run `./gradlew nexusIQScan`
- You should see the scan report URL report on Terminal.

### Nexus IQ Index
Allows you to save information about the dependencies of a project into module information (module.xml) files that Sonatype CI tools can use to include these dependencies in a scan.
```
nexusIQIndex {
modulesExcluded = ['module-1', 'module-2'] // Optional. For multi-module projects, the names of the sub-modules to exclude from indexing.
}
```

### Sensitive Data
Sometimes it's not desirable to keep sensitive data stored on `build.gradle`. For such cases it's possible to use project
properties (-P arguments) or environment variables (-D arguments or injected from a tool) from command line when running
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#

group=org.sonatype.gradle.plugins
version=2.1.1-SNAPSHOT
version=2.2.0-SNAPSHOT
release.useAutomaticVersion=true

nexusPlatformApiVersion=3.37
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.gradle.testkit.runner.TaskOutcome.FAILED;
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS;
import static org.sonatype.gradle.plugins.scan.nexus.iq.index.NexusIqIndexTask.MODULE_XML_FILE;
import static org.sonatype.gradle.plugins.scan.nexus.iq.scan.NexusIqPluginScanExtension.SONATYPE_CLM_FOLDER;

@RunWith(Parameterized.class)
public abstract class ScanPluginIntegrationTestBase
Expand Down Expand Up @@ -167,6 +169,23 @@ public void testScanTask_AndroidMultipleFlavors_NexusIQ() {
assertThat(result.task(":nexusIQScan").getOutcome()).isEqualTo(SUCCESS);
}

@Test
public void testIndexTask_NexusIQ() throws IOException {
writeFile(buildFile, "control_default.gradle");

final BuildResult result = GradleRunner.create()
.withGradleVersion(gradleVersion)
.withProjectDir(testProjectDir.getRoot())
.withPluginClasspath()
.withArguments("nexusIQIndex", "--info")
.build();

String resultOutput = result.getOutput();
assertThat(resultOutput).contains("Saved module information to");
assertThat(resultOutput).contains(SONATYPE_CLM_FOLDER + File.separator + MODULE_XML_FILE);
assertThat(result.task(":nexusIQIndex").getOutcome()).isEqualTo(SUCCESS);
}

@Test
public void testAuditTask_NoVulnerabilities_OssIndex_Default_Empty() throws IOException {
writeFile(buildFile, "control_default_not_all.gradle");
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/org/sonatype/gradle/plugins/scan/ScanPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package org.sonatype.gradle.plugins.scan;

import org.sonatype.gradle.plugins.scan.nexus.iq.NexusIqPluginExtension;
import org.sonatype.gradle.plugins.scan.nexus.iq.NexusIqScanTask;
import org.sonatype.gradle.plugins.scan.nexus.iq.index.NexusIqIndexTask;
import org.sonatype.gradle.plugins.scan.nexus.iq.index.NexusIqPluginIndexExtension;
import org.sonatype.gradle.plugins.scan.nexus.iq.scan.NexusIqPluginScanExtension;
import org.sonatype.gradle.plugins.scan.nexus.iq.scan.NexusIqScanTask;
import org.sonatype.gradle.plugins.scan.ossindex.OssIndexAuditTask;
import org.sonatype.gradle.plugins.scan.ossindex.OssIndexPluginExtension;

Expand All @@ -29,11 +31,17 @@ public class ScanPlugin implements Plugin<Project>

@Override
public void apply(Project project) {
project.getExtensions().create("nexusIQScan", NexusIqPluginExtension.class, project);
project.getExtensions().create("nexusIQScan", NexusIqPluginScanExtension.class, project);
NexusIqScanTask nexusIqScanTask = project.getTasks().create("nexusIQScan", NexusIqScanTask.class);
nexusIqScanTask.setGroup(SONATYPE_GROUP);
nexusIqScanTask.setDescription("Scan and evaluate the dependencies of the project using Nexus IQ Server.");

project.getExtensions().create("nexusIQIndex", NexusIqPluginIndexExtension.class, project);
NexusIqIndexTask nexusIqIndexTask = project.getTasks().create("nexusIQIndex", NexusIqIndexTask.class);
nexusIqIndexTask.setGroup(SONATYPE_GROUP);
nexusIqIndexTask.setDescription("Saves information about the dependencies of a project into module information "
+ "(module.xml) files that Sonatype CI tools can use to include these dependencies in a scan.");

project.getExtensions().create("ossIndexAudit", OssIndexPluginExtension.class, project);
OssIndexAuditTask ossIndexAuditTask = project.getTasks().create("ossIndexAudit", OssIndexAuditTask.class);
ossIndexAuditTask.setGroup(SONATYPE_GROUP);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -71,13 +74,12 @@ public class DependenciesFinder

private static final String ATTRIBUTES_SUPPORTED_GRADLE_VERSION = "4.0";

public Set<ResolvedDependency> findResolvedDependencies(Project rootProject, boolean allConfigurations) {
return new LinkedHashSet<>(rootProject.getAllprojects()).stream()
.flatMap(project -> new LinkedHashSet<>(project.getConfigurations()).stream())
public Set<ResolvedDependency> findResolvedDependencies(Project project, boolean allConfigurations) {
return new LinkedHashSet<>(new LinkedHashSet<>(project.getConfigurations()).stream()
.filter(configuration -> isAcceptableConfiguration(configuration, allConfigurations))
.flatMap(configuration -> getDependencies(rootProject, configuration,
.flatMap(configuration -> getDependencies(project, configuration,
resolvedConfiguration -> resolvedConfiguration.getFirstLevelModuleDependencies().stream()))
.collect(Collectors.toCollection(LinkedHashSet::new));
.collect(collectResolvedDependencies()).values());
}

public Set<ResolvedArtifact> findResolvedArtifacts(Project project, boolean allConfigurations) {
Expand Down Expand Up @@ -119,7 +121,11 @@ public List<Module> findModules(Project rootProject, boolean allConfigurations,

@VisibleForTesting
Module buildModule(Project project) {
Module module = new Module().setIdKind("gradle").setPathname(project.getProjectDir());
Module module = new Module()
.setIdKind("gradle")
.setPathname(project.getProjectDir())
.setBuilderInfo(Module.BI_CLM_TOOL, "gradle")
.setBuilderInfo(Module.BI_CLM_VERSION, PluginVersionUtils.getPluginVersion());

StringBuilder idBuilder = new StringBuilder();
if (StringUtils.isNotBlank(project.getGroup().toString())) {
Expand Down Expand Up @@ -241,4 +247,13 @@ private boolean isAcceptableConfiguration(Configuration configuration, boolean a
|| StringUtils.endsWithIgnoreCase(configuration.getName(), RELEASE_COMPILE_CONFIGURATION_NAME)
|| StringUtils.endsWithIgnoreCase(configuration.getName(), RELEASE_RUNTIME_CONFIGURATION_NAME));
}

private Collector<ResolvedDependency, ?, LinkedHashMap<String, ResolvedDependency>> collectResolvedDependencies() {
return Collectors.toMap(ResolvedDependency::getName, Function.identity(), (existing, replacement) -> {
if (StringUtils.containsAny(replacement.getConfiguration().toLowerCase(Locale.ROOT), "runtime", "release")) {
return replacement;
}
return existing;
}, LinkedHashMap::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2020-present Sonatype, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sonatype.gradle.plugins.scan.nexus.iq.index;

import java.io.File;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import com.sonatype.insight.scan.module.model.Module;
import com.sonatype.insight.scan.module.model.io.ModuleIoManager;

import org.sonatype.gradle.plugins.scan.common.DependenciesFinder;

import org.apache.commons.lang3.StringUtils;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;
import org.gradle.internal.impldep.com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.sonatype.gradle.plugins.scan.nexus.iq.scan.NexusIqPluginScanExtension.SONATYPE_CLM_FOLDER;

public class NexusIqIndexTask
extends DefaultTask
{
public static final String MODULE_XML_FILE = "module.xml";

private final Logger log = LoggerFactory.getLogger(NexusIqIndexTask.class);

private final NexusIqPluginIndexExtension extension;

private DependenciesFinder dependenciesFinder;

private ModuleIoManager moduleIoManager;

public NexusIqIndexTask() {
extension = getProject().getExtensions().getByType(NexusIqPluginIndexExtension.class);
dependenciesFinder = new DependenciesFinder();
moduleIoManager = new ModuleIoManager(log);
}

@TaskAction
public void saveModule() {
try {
List<Module> modules =
dependenciesFinder.findModules(getProject(), extension.isAllConfigurations(), extension.getModulesExcluded());
List<File> files = new ArrayList<>(modules.size());

for (Module module : modules) {
File file = Paths.get(module.getPathname(), "build", SONATYPE_CLM_FOLDER, MODULE_XML_FILE).toFile();
moduleIoManager.writeModule(file, module);
files.add(file);
}

log.info("Saved module information to {}", StringUtils.join(files, ", "));
}
catch (Exception e) {
throw new GradleException("Could not save the module information for the project: " + e.getMessage(), e);
}
}

@VisibleForTesting
void setDependenciesFinder(DependenciesFinder dependenciesFinder) {
this.dependenciesFinder = dependenciesFinder;
}

@VisibleForTesting
void setModuleIoManager(ModuleIoManager moduleIoManager) {
this.moduleIoManager = moduleIoManager;
}

@Input
public boolean isAllConfigurations() {
return extension.isAllConfigurations();
}

@Input
public Set<String> getModulesExcluded() {
return extension.getModulesExcluded();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2020-present Sonatype, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sonatype.gradle.plugins.scan.nexus.iq.index;

import java.util.Collections;
import java.util.Set;

import org.gradle.api.Project;

public class NexusIqPluginIndexExtension
{
private boolean allConfigurations;

private Set<String> modulesExcluded;

public NexusIqPluginIndexExtension(Project project) {
modulesExcluded = Collections.emptySet();
}

public boolean isAllConfigurations() {
return allConfigurations;
}

public void setAllConfigurations(boolean allConfigurations) {
this.allConfigurations = allConfigurations;
}

public Set<String> getModulesExcluded() {
return modulesExcluded;
}

public void setModulesExcluded(Set<String> modulesExcluded) {
this.modulesExcluded = modulesExcluded;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sonatype.gradle.plugins.scan.nexus.iq;
package org.sonatype.gradle.plugins.scan.nexus.iq.scan;

import java.io.File;
import java.util.Collections;
import java.util.Set;

Expand All @@ -23,8 +24,10 @@

import org.gradle.api.Project;

public class NexusIqPluginExtension
public class NexusIqPluginScanExtension
{
public static final String SONATYPE_CLM_FOLDER = "sonatype-clm";

private String stage;

private String scanFolderPath;
Expand All @@ -51,11 +54,11 @@ public class NexusIqPluginExtension

private String dirExcludes;

public NexusIqPluginExtension(Project project) {
public NexusIqPluginScanExtension(Project project) {
stage = Stage.ID_BUILD;
simulationEnabled = false;
simulatedPolicyActionId = PolicyAction.NONE.toString();
scanFolderPath = project.getBuildDir() + "/sonatype-clm/";
scanFolderPath = project.getBuildDir() + File.separator + SONATYPE_CLM_FOLDER + File.separator;
modulesExcluded = Collections.emptySet();
dirIncludes = "";
dirExcludes = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sonatype.gradle.plugins.scan.nexus.iq;
package org.sonatype.gradle.plugins.scan.nexus.iq.scan;

import java.io.File;
import java.net.URI;
Expand Down Expand Up @@ -58,12 +58,12 @@ public class NexusIqScanTask

private static final String USER_AGENT_NAME = "Sonatype_Nexus_Gradle";

private final NexusIqPluginExtension extension;
private final NexusIqPluginScanExtension extension;

private DependenciesFinder dependenciesFinder;

public NexusIqScanTask() {
extension = getProject().getExtensions().getByType(NexusIqPluginExtension.class);
extension = getProject().getExtensions().getByType(NexusIqPluginScanExtension.class);
dependenciesFinder = new DependenciesFinder();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.sonatype.goodies.packageurl.PackageUrl;
import org.sonatype.goodies.packageurl.PackageUrlBuilder;
Expand Down Expand Up @@ -70,8 +72,10 @@ public void audit() {
boolean hasVulnerabilities;

try (OssindexClient ossIndexClient = buildOssIndexClient()) {
Set<ResolvedDependency> dependencies =
dependenciesFinder.findResolvedDependencies(getProject(), extension.isAllConfigurations());
Set<ResolvedDependency> dependencies = getProject().getAllprojects().stream()
.flatMap(
project -> dependenciesFinder.findResolvedDependencies(project, extension.isAllConfigurations()).stream())
.collect(Collectors.toCollection(LinkedHashSet::new));
BiMap<ResolvedDependency, PackageUrl> dependenciesMap = HashBiMap.create();

dependencies.forEach(dependency -> buildDependenciesMap(dependency, dependenciesMap));
Expand Down
Loading

0 comments on commit 26d7201

Please sign in to comment.