From 2e34d5bcde27ab411d362db3bd8da2d99b7660dd Mon Sep 17 00:00:00 2001 From: Brian Phillips Date: Fri, 10 Jan 2025 14:41:16 -0500 Subject: [PATCH] JAVA-8886 restructure plugin to be more inline with what gradle expects --- .../gradle/plugin/ContrastGradlePlugin.java | 136 +++++++++-- .../gradle/plugin/ContrastVerifyTestTask.java | 14 ++ .../gradle/plugin/InstallAgentTask.java | 216 ------------------ .../gradle/plugin/ResolveAgentTask.java | 21 +- .../{ => extensions}/ContrastSDKService.java | 3 +- .../gradle/plugin/InstallAgentTests.java | 40 ---- .../gradle/plugin/RegistrationTests.java | 2 +- .../gradle/plugin/e2e/EndToEndTests.java | 11 +- 8 files changed, 156 insertions(+), 287 deletions(-) create mode 100644 gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastVerifyTestTask.java delete mode 100644 gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/InstallAgentTask.java rename gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/{ => extensions}/ContrastSDKService.java (91%) delete mode 100644 gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/InstallAgentTests.java diff --git a/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastGradlePlugin.java b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastGradlePlugin.java index 29a7724..ceb4c13 100644 --- a/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastGradlePlugin.java +++ b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastGradlePlugin.java @@ -2,9 +2,16 @@ import com.contrastsecurity.gradle.plugin.extensions.ContrastConfigurationExtension; import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedList; import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.Task; import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.testing.Test; +import org.jetbrains.annotations.VisibleForTesting; /** * Gradle plugin for contrast utilities. The goals for this plugin are defined here resolveAgentTask = - target - .getTasks() - .register( - "resolveAgent", - ResolveAgentTask.class, - task -> { - task.setAgentFile(new File(target.getProjectDir() + "/agent.jar")); - }); + target.getTasks().register("resolveAgent", ResolveAgentTask.class); + + resolveAgentTask.configure( + task -> { + task.setFileString("contrast.jar"); + task.setAgent( + new File( + target.getLayout().getBuildDirectory().getAsFile().get().getPath(), + "contrast.jar")); + }); + + // lifecycle task for grouping verification tasks + TaskProvider contrastCheck = target.getTasks().register("contrastCheck"); + + // Create contrast Args + final Collection contrastArgs = + createContrastArgs( + target.getName(), + resolveAgentTask.get().getOutputs().getFiles().getAsPath(), + extension.getAppName(), + extension.getServerName(), + extension.getAppVersion()); + + // for each test task... target .getTasks() - .register("installAgent", InstallAgentTask.class) - .configure( - task -> { - // Ensure resolve agent task runs before attempting to install the agent - task.dependsOn(resolveAgentTask); - // set input for the agent file as the output from the resolve agent task - task.getInputFile().set(resolveAgentTask.flatMap(ResolveAgentTask::getAgentFile)); + .withType(Test.class) + .forEach( + testTask -> { + // tests depend on get Agent + testTask.dependsOn(resolveAgentTask); + + // attach agent args + testTask.jvmArgs(contrastArgs); + + // generate ContrastVerifyTestTask for each test + TaskProvider verifyTask = + target + .getTasks() + .register( + "contrastVerifyTest" + testTask.getName(), ContrastVerifyTestTask.class); + verifyTask.configure( + v -> { + v.dependsOn(testTask); + // set Input as testTask junit Output. I don't know how to do this yet. You can + // call testTask.getOutputs but then what? + // v.setjUnitOutput(?); + }); + + // configure lifecycle task to depend on this test's corresponding verifyTask + contrastCheck.configure(task -> task.dependsOn(verifyTask)); }); } + /** + * Creates the jvmArgs for running the java agent against JavaExec tasks + * + * @param projectName name of the current Gradle Project + * @param agentPath preconfigured path to an agent defined by the ContrastConfigurationExtension + * @param appName the name of the application on TS defined by the ContrastConfigurationExtension + * @param serverName the name of the server on TS defined by the ContrastConfigurationExtension + * @param appVersion the app version, if not supplied by the ContrastConfigurationExtension, then + * it is generated in {@link InstallAgentTask#computeAppVersion(String)} + * @return Set of arguments to be attached to specified tasks + */ + @VisibleForTesting + public static Collection createContrastArgs( + final String projectName, + final String agentPath, + final String appName, + final String serverName, + String appVersion) { + + // List to preserve ordering of arguments + final Collection args = new LinkedList<>(); + + args.add("-javaagent:" + agentPath); + + // Use gradle project name in the even the appName is not set + if ("null".equals(appName) || appName == null) { + args.add("-Dcontrast.override.appname=" + projectName); + } else { + args.add("-Dcontrast.override.appname=" + appName); + } + + args.add("-Dcontrast.server=" + serverName); + + if (appVersion == null) { + appVersion = computeAppVersion(appName); + } + + args.add("-Dcontrast.override.appversion=" + appVersion); + + return args; + } + + /** + * Shamelessly stolen from the maven plugin TODO check if we still want to do this based on travis + * or circle build number + * + * @return computed AppVersion + */ + @VisibleForTesting + public static String computeAppVersion(final String appName) { + final Date currentDate = new Date(); + final String travisBuildNumber = System.getenv("TRAVIS_BUILD_NUMBER"); + final String circleBuildNum = System.getenv("CIRCLE_BUILD_NUM"); + + final String appVersionQualifier; + if (travisBuildNumber != null) { + appVersionQualifier = travisBuildNumber; + } else if (circleBuildNum != null) { + appVersionQualifier = circleBuildNum; + } else { + appVersionQualifier = new SimpleDateFormat("yyyyMMddHHmmss").format(currentDate); + } + return appName + "-" + appVersionQualifier; + } + public static final String EXTENSION_NAME = "contrastConfiguration"; } diff --git a/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastVerifyTestTask.java b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastVerifyTestTask.java new file mode 100644 index 0000000..6d1ca99 --- /dev/null +++ b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastVerifyTestTask.java @@ -0,0 +1,14 @@ +package com.contrastsecurity.gradle.plugin; + +import java.io.File; +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.InputFile; + +public class ContrastVerifyTestTask extends DefaultTask { + + @InputFile private File jUnitResults; + + public void setJUnitOutput(final File jUnitResults) { + this.jUnitResults = jUnitResults; + } +} diff --git a/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/InstallAgentTask.java b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/InstallAgentTask.java deleted file mode 100644 index 912aaa6..0000000 --- a/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/InstallAgentTask.java +++ /dev/null @@ -1,216 +0,0 @@ -package com.contrastsecurity.gradle.plugin; - -import static com.contrastsecurity.gradle.plugin.ContrastGradlePlugin.EXTENSION_NAME; - -import com.contrastsecurity.gradle.plugin.extensions.ContrastConfigurationExtension; -import com.contrastsecurity.models.AgentType; -import com.contrastsecurity.sdk.ContrastSDK; -import java.io.IOException; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.text.SimpleDateFormat; -import java.util.Collection; -import java.util.Date; -import java.util.LinkedList; -import org.gradle.api.DefaultTask; -import org.gradle.api.GradleException; -import org.gradle.api.Project; -import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.logging.Logger; -import org.gradle.api.tasks.InputFile; -import org.gradle.api.tasks.TaskAction; -import org.gradle.api.tasks.testing.Test; -import org.jetbrains.annotations.VisibleForTesting; - -/** - * Downloads the current java agent from TeamServer using Credentials provided by the - * AgentCredentialsExtension - */ -public class InstallAgentTask extends DefaultTask { - - final ContrastConfigurationExtension config = - (ContrastConfigurationExtension) getProject().getExtensions().getByName(EXTENSION_NAME); - - @InputFile final RegularFileProperty agent = getProject().getObjects().fileProperty(); - - public RegularFileProperty getInputFile() { - return agent; - } - - @TaskAction - void installAgent() { - - logger.debug("Running installAgentTask"); - // create sdk object for connecting to Contrast - final ContrastSDK sdk = ContrastSDKService.getInstance(config); - - logger.debug("Connected to Contrast at: " + sdk.getRestApiURL()); - - // get agent, either from configured jar path or from TS - // final Path agent = retrieveAgent(sdk, config.getJarPath(), config.getOrgUuid(), - // getProject()); - - Path agentPath = getInputFile().get().getAsFile().toPath(); - if (!Files.exists(agentPath)) { - throw new GradleException("Java Agent not found for installAgent task"); - } - logger.debug("preparing to attach agent"); - // attachAgentToTasks(agent.toAbsolutePath()); - attachAgentToTasks(agentPath); - } - - /** - * Configures tasks to run with the agent attached Should be configurable via the plugin - * configurations to determine which tasks we attach to. For now, this configuration is just a - * boolean and only attaches to Test tasks. - */ - private void attachAgentToTasks(final Path agentPath) { - if (config.getAttachToTests()) { - Collection arguments = - createContrastArgs( - getProject().getName(), - agentPath, - config.getAppName(), - config.getServerName(), - config.getAppVersion()); - getProject() - .getTasks() - .withType(Test.class) - .configureEach( - task -> { - task.jvmArgs(arguments); - }); - - getProject() - .getTasks() - .withType(Test.class) - .forEach(s -> logger.debug(s.getAllJvmArgs().toString())); - } - } - - /** - * Creates the jvmArgs for running the java agent against JavaExec tasks - * - * @param projectName name of the current Gradle Project - * @param agentPath preconfigured path to an agent defined by the ContrastConfigurationExtension - * @param appName the name of the application on TS defined by the ContrastConfigurationExtension - * @param serverName the name of the server on TS defined by the ContrastConfigurationExtension - * @param appVersion the app version, if not supplied by the ContrastConfigurationExtension, then - * it is generated in {@link InstallAgentTask#computeAppVersion(String)} - * @return Set of arguments to be attached to specified tasks - */ - @VisibleForTesting - public static Collection createContrastArgs( - final String projectName, - final Path agentPath, - final String appName, - final String serverName, - String appVersion) { - - // List to preserve ordering of arguments - final Collection args = new LinkedList<>(); - - args.add("-javaagent:" + agentPath.toAbsolutePath()); - - // Use gradle project name in the even the appName is not set - if ("null".equals(appName) || appName == null) { - args.add("-Dcontrast.override.appname=" + projectName); - } else { - args.add("-Dcontrast.override.appname=" + appName); - } - - args.add("-Dcontrast.server=" + serverName); - - if (appVersion == null) { - appVersion = computeAppVersion(appName); - } - - args.add("-Dcontrast.override.appversion=" + appVersion); - - return args; - } - - /** - * Shamelessly stolen from the maven plugin TODO check if we still want to do this based on travis - * or circle build number - * - * @return computed AppVersion - */ - @VisibleForTesting - public static String computeAppVersion(final String appName) { - final Date currentDate = new Date(); - final String travisBuildNumber = System.getenv("TRAVIS_BUILD_NUMBER"); - final String circleBuildNum = System.getenv("CIRCLE_BUILD_NUM"); - - final String appVersionQualifier; - if (travisBuildNumber != null) { - appVersionQualifier = travisBuildNumber; - } else if (circleBuildNum != null) { - appVersionQualifier = circleBuildNum; - } else { - appVersionQualifier = new SimpleDateFormat("yyyyMMddHHmmss").format(currentDate); - } - return appName + "-" + appVersionQualifier; - } - - /** Use ContrastSDK to download agent and return the path where agent jar is stored */ - @VisibleForTesting - public static Path retrieveAgent( - final ContrastSDK connection, - final String jarPath, - final String uuid, - final Project project) { - - final Logger logger = project.getLogger(); - // Initially attempt to get agent from the previously configured location - if (jarPath != null) { - final Path agent = Paths.get(jarPath); - if (!Files.exists(agent)) { - throw new GradleException("Unable to find java agent at " + jarPath); - } - logger.debug("Agent provided via configuration retrieved"); - return agent; - } - - logger.debug("No agent path provided, checking for cached agent"); - - final Path agent = Paths.get(project.getProjectDir().getPath()).resolve(AGENT_NAME); - if (Files.exists(agent)) { - System.out.println("Agent jar found at " + project.getProjectDir().getPath()); - return agent; - } - - logger.debug("Attempting to retrieve agent from TeamServer"); - // If no jar is provided, and no jarpath configured, attempt to retrieve the agent from TS - final byte[] bytes; - Path downloadedAgent; - try { - bytes = connection.getAgent(AgentType.JAVA, uuid); - - // Save the jar to the 'target' directory - final Path target = Paths.get(project.getProjectDir().getPath()); - - try { - Files.createFile(target); - } catch (FileAlreadyExistsException e) { - logger.debug("Project dir already exists"); - } - - downloadedAgent = target.resolve(AGENT_NAME); - - Files.write(downloadedAgent, bytes, StandardOpenOption.CREATE, StandardOpenOption.WRITE); - - } catch (RuntimeException | IOException e) { - throw new GradleException("Failed to download java agent from the Contrast api: " + e); - } - - logger.debug("Agent retrieved from TeamServer"); - return downloadedAgent; - } - - private static final String AGENT_NAME = "contrast.jar"; - final Logger logger = getProject().getLogger(); -} diff --git a/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ResolveAgentTask.java b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ResolveAgentTask.java index c041856..d795740 100644 --- a/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ResolveAgentTask.java +++ b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ResolveAgentTask.java @@ -3,6 +3,7 @@ import static com.contrastsecurity.gradle.plugin.ContrastGradlePlugin.EXTENSION_NAME; import com.contrastsecurity.gradle.plugin.extensions.ContrastConfigurationExtension; +import com.contrastsecurity.gradle.plugin.extensions.ContrastSDKService; import com.contrastsecurity.models.AgentType; import com.contrastsecurity.sdk.ContrastSDK; import java.io.File; @@ -15,8 +16,8 @@ import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.Project; -import org.gradle.api.file.RegularFileProperty; import org.gradle.api.logging.Logger; +import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.jetbrains.annotations.VisibleForTesting; @@ -26,20 +27,26 @@ public class ResolveAgentTask extends DefaultTask { final ContrastConfigurationExtension config = (ContrastConfigurationExtension) getProject().getExtensions().getByName(EXTENSION_NAME); - @OutputFile RegularFileProperty agentFile = getProject().getObjects().fileProperty(); + @Input private String fileString; - public RegularFileProperty getAgentFile() { - return agentFile; + @OutputFile private File agent; + + public File getAgentFile() { + return agent; + } + + public void setFileString(String fileString) { + this.fileString = fileString; } - public void setAgentFile(final File file) { - this.agentFile.set(file); + public void setAgent(File agent) { + this.agent = agent; } @TaskAction void resolveAgent() { final ContrastSDK sdk = ContrastSDKService.getInstance(config); - setAgentFile(retrieveAgent(sdk, config.getJarPath(), config.getOrgUuid(), getProject())); + setAgent(retrieveAgent(sdk, config.getJarPath(), config.getOrgUuid(), getProject())); } /** Use ContrastSDK to download agent and return the path where agent jar is stored */ diff --git a/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastSDKService.java b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/extensions/ContrastSDKService.java similarity index 91% rename from gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastSDKService.java rename to gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/extensions/ContrastSDKService.java index 46233f8..f07f74c 100644 --- a/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastSDKService.java +++ b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/extensions/ContrastSDKService.java @@ -1,6 +1,5 @@ -package com.contrastsecurity.gradle.plugin; +package com.contrastsecurity.gradle.plugin.extensions; -import com.contrastsecurity.gradle.plugin.extensions.ContrastConfigurationExtension; import com.contrastsecurity.sdk.ContrastSDK; import com.contrastsecurity.sdk.UserAgentProduct; diff --git a/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/InstallAgentTests.java b/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/InstallAgentTests.java deleted file mode 100644 index 1e42a67..0000000 --- a/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/InstallAgentTests.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.contrastsecurity.gradle.plugin; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.contrastsecurity.sdk.ContrastSDK; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; -import java.util.Set; -import org.gradle.testfixtures.ProjectBuilder; -import org.junit.jupiter.api.Test; - -/** Unit tests for verifying logic in {@link InstallAgentTask} works properly */ -public class InstallAgentTests { - - @Test - void verify_correct_contrast_args() { - final Collection expectedArgs = - Set.of( - "-javaagent:/test/path", - "-Dcontrast.override.appname=foo", - "-Dcontrast.server=bar", - "-Dcontrast.override.appversion=0.0.1"); - final Collection actualArgs = - InstallAgentTask.createContrastArgs("name", Path.of("/test/path"), "foo", "bar", "0.0.1"); - assertTrue(actualArgs.containsAll(expectedArgs)); - } - - @Test - void verify_agent_retrieval_given_real_path() throws IOException { - final Path tempAgent = Files.createTempFile("agent", "jar"); - final ContrastSDK connection = new ContrastSDK.Builder("user", "serviceKey", "apiKey").build(); - final Path agentPath = - InstallAgentTask.retrieveAgent( - connection, tempAgent.toString(), "uuid", ProjectBuilder.builder().build()); - assertEquals(tempAgent.toString(), agentPath.toString()); - } -} diff --git a/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/RegistrationTests.java b/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/RegistrationTests.java index a7c9253..fe6daaf 100644 --- a/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/RegistrationTests.java +++ b/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/RegistrationTests.java @@ -15,7 +15,7 @@ void pluginRegistersATask() { project.getPlugins().apply("com.contrastsecurity.java"); // Verify the result - assertNotNull(project.getTasks().findByName("installAgent")); + assertNotNull(project.getTasks().findByName("contrastCheck")); assertNotNull(project.getTasks().findByName("resolveAgent")); } } diff --git a/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/e2e/EndToEndTests.java b/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/e2e/EndToEndTests.java index 45a8816..e234644 100644 --- a/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/e2e/EndToEndTests.java +++ b/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/e2e/EndToEndTests.java @@ -6,11 +6,10 @@ import com.contrastsecurity.gradle.plugin.EnvironmentUtils; import com.contrastsecurity.gradle.plugin.GradleRunnerTest; -import com.contrastsecurity.gradle.plugin.InstallAgentTask; +import com.contrastsecurity.gradle.plugin.ResolveAgentTask; import com.contrastsecurity.sdk.ContrastSDK; import com.contrastsecurity.sdk.UserAgentProduct; import java.io.IOException; -import java.nio.file.Path; import java.util.Collection; import java.util.HashSet; import org.gradle.testfixtures.ProjectBuilder; @@ -33,9 +32,10 @@ void verify_retrieval_of_agent_from_teamserver() { .withApiUrl(EnvironmentUtils.getApiUrl()) .withUserAgentProduct(UserAgentProduct.of("contrast-gradle-plugin")) .build(); - final Path agentPath = - InstallAgentTask.retrieveAgent( - connection, null, EnvironmentUtils.getOrgUuid(), ProjectBuilder.builder().build()); + final String agentPath = + ResolveAgentTask.retrieveAgent( + connection, null, EnvironmentUtils.getOrgUuid(), ProjectBuilder.builder().build()) + .getPath(); assertNotNull(agentPath); assertTrue(agentPath.endsWith("contrast.jar")); } @@ -50,7 +50,6 @@ void verify_attaches_agent_to_tests() throws IOException { testRunner.forwardOutput(); testRunner.withPluginClasspath(); // outputs debug logs to stdout for testing - testRunner.withArguments("installAgent"); testRunner.withDebug(true); testRunner.withProjectDir(projectDir); final BuildResult result = testRunner.build();