From c33547abd3b7d4ac3f704feee530327bdd3a16e8 Mon Sep 17 00:00:00 2001 From: Brian Phillips Date: Wed, 23 Oct 2024 09:45:43 -0400 Subject: [PATCH] JAVA-3738 add InstallAgent task to download and install agent --- README.md | 1 + gradle-plugin/README.md | 73 ++++++ gradle-plugin/build.gradle | 14 ++ .../ContrastConfigurationExtension.java | 95 ++++++++ .../gradle/plugin/ContrastGradlePlugin.java | 11 +- .../gradle/plugin/EmptyTask.java | 12 - .../gradle/plugin/InstallAgentTask.java | 214 ++++++++++++++++++ .../gradle/plugin/EnvironmentUtils.java | 25 ++ .../gradle/plugin/FunctionalTests.java | 62 ----- .../gradle/plugin/GradleRunnerTest.java | 35 +++ .../gradle/plugin/InstallAgentTests.java | 40 ++++ .../gradle/plugin/RegistrationTests.java | 13 +- .../gradle/plugin/e2e/EndToEndTests.java | 114 ++++++++++ 13 files changed, 620 insertions(+), 89 deletions(-) create mode 100644 gradle-plugin/README.md create mode 100644 gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastConfigurationExtension.java delete mode 100644 gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/EmptyTask.java create mode 100644 gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/InstallAgentTask.java create mode 100644 gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/EnvironmentUtils.java delete mode 100644 gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/FunctionalTests.java create mode 100644 gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/GradleRunnerTest.java create mode 100644 gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/InstallAgentTests.java create mode 100644 gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/e2e/EndToEndTests.java diff --git a/README.md b/README.md index 63985f4..b1ef58f 100644 --- a/README.md +++ b/README.md @@ -9,5 +9,6 @@ Each sub-project is a standalone build, with their own maven/gradle builds. [Maven Plugin](maven-plugin/README.md) +[Gradle Plugin](gradle-plugin/README.md) diff --git a/gradle-plugin/README.md b/gradle-plugin/README.md new file mode 100644 index 0000000..47ab6ea --- /dev/null +++ b/gradle-plugin/README.md @@ -0,0 +1,73 @@ +# Contrast Gradle Plugin + +Gradle plugin for including the Contrast Security analysis in Java web applications + +Requires gradle version 8.3+ + +## Building + +Use `./gradlew build` to build the plugin + + +```shell +./gradlew publishToMavenLocal +``` + + +## Tasks +The `installAgent` task takes in your configuration as defined by the `contrastConfiguration` block and attaches the java agent to all Test tasks for your project. +If no Agent is provided, the plugin will attempt to download the current Java Agent available on TeamServer, at the endpoint provided in the configuration. + + +## Configuration +This plugin is configured via the `contrastConfiguration` block in your projects `gradle.build` script +```shell +contrastConfiguration{ + username = '' + apiKey = '' + serviceKey = '' + apiUrl = '' + orgUuid = '' + appName = '' + serverName = '' + appVersion = '' + jarPath = "" +} +``` + +### AppName +If no app name is configured the plugin will use the gradle project's name instead + +### AppVersion +TODO: If no version is provided, the plugin will generate one based on the current Travis build number + +Attaching the Java agent with this plugin relies on your API credentials being set in the following env variables: + +### Running with your tests +The plugin will add jvm arguments for your run tests, but only if `installAgent` is run as a dependency for the test task. +To have your tests run with the agent add the following configuration to your project's `build.gradle` file +```shell +tasks.named("test").configure { + dependsOn("installAgent") +} +``` +TODO auto attach to tests + +## Developement +### Publishing to MavenLocal +To publish this plugin to your mavenLocal apply the `maven-publish` plugin to this project's `build.gradle` file and run: +In order to run the plugin's end-to-end tests, you must configure these variables in your environment + + +### End to End testing +```shell +export CONTRAST__API__URL=https://app.contrastsecurity.com/Contrast ##Use your standard endpoint for the org, the plugin will apply `/api` for the restapi functionality +export CONTRAST__API__USER_NAME= +export CONTRAST__API__API_KEY= +export CONTRAST__API__SERVICE_KEY= +export CONTRAST__API__ORGANIZATION_ID= +``` +To enable end-to-end testing, these variables must be present and you must use the property `e2e` +```shell +./gradkew test -Pe2e +``` diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 30df447..b3bbc26 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'java-gradle-plugin' id 'com.diffplug.spotless' version("6.10.0") + id 'maven-publish' } version = '0.1' @@ -48,10 +49,23 @@ spotless{ } dependencies { + implementation("com.contrastsecurity:contrast-sdk-java:3.4.2") testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' } +var e2eTests = "com/contrastsecurity/gradle/plugin/e2e/EndToEndTests.*" + + test { + if(!project.hasProperty("e2e")){ + exclude(e2eTests) + } + useJUnitPlatform() + environment = ["CONTRAST__API__USER_NAME" : System.getenv("CONTRAST__API__USER_NAME"), + "CONTRAST__API__URL" : System.getenv("CONTRAST__API__URL"), + "CONTRAST__API__SERVICE_KEY" : System.getenv("CONTRAST__API__SERVICE_KEY"), + "CONTRAST__API__API_KEY" : System.getenv("CONTRAST__API__API_KEY"), + "CONTRAST__API__ORGANIZATION_ID": System.getenv("CONTRAST__API__ORGANIZATION_ID")] } diff --git a/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastConfigurationExtension.java b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastConfigurationExtension.java new file mode 100644 index 0000000..c56cce1 --- /dev/null +++ b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/ContrastConfigurationExtension.java @@ -0,0 +1,95 @@ +package com.contrastsecurity.gradle.plugin; + +/** Extension for configuring TeamServer API Credentials for downloading agent */ +public class ContrastConfigurationExtension { + private String username; + private String apiKey; + private String serviceKey; + private String apiUrl; + private String orgUuid; + private String appName; + private String serverName; + private String jarPath; + private String appVersion; + private boolean attachToTests; + + public void setUsername(final String username) { + this.username = username; + } + + public void setApiKey(final String apiKey) { + this.apiKey = apiKey; + } + + public void setServiceKey(final String serviceKey) { + this.serviceKey = serviceKey; + } + + public void setApiUrl(final String apiUrl) { + this.apiUrl = apiUrl; + } + + public void setOrgUuid(final String orgUuid) { + this.orgUuid = orgUuid; + } + + public void setAppName(final String appName) { + this.appName = appName; + } + + public void setServerName(final String serverName) { + this.serverName = serverName; + } + + public void setJarPath(final String jarPath) { + this.jarPath = jarPath; + } + + public void setAppVersion(final String appVersion) { + this.appVersion = appVersion; + } + + public void setAttachToTests(final boolean attachToTests) { + this.attachToTests = attachToTests; + } + + public String getUsername() { + return username; + } + + public String getApiKey() { + return apiKey; + } + + public String getServiceKey() { + return serviceKey; + } + + public String getApiUrl() { + return apiUrl; + } + + public String getOrgUuid() { + return orgUuid; + } + + public String getAppName() { + return appName; + } + + public String getServerName() { + return serverName; + } + + public String getJarPath() { + return jarPath; + } + + public String getAppVersion() { + return appVersion; + } + + public boolean getAttachToTests() { + return attachToTests; + } +} 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 0594387..79e02c2 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 @@ -8,11 +8,14 @@ * href=https://contrast.atlassian.net/browse/JAVA-8252>JAVA-8252 */ public class ContrastGradlePlugin implements Plugin { + public void apply(final Project target) { - target - .getTasks() - .register("hello", task -> task.doLast(s -> System.out.println("HelloWorld!"))); - target.getTasks().register("empty", EmptyTask.class); + ContrastConfigurationExtension extension = + target.getExtensions().create(EXTENSION_NAME, ContrastConfigurationExtension.class); + + target.getTasks().register("installAgent", InstallAgentTask.class); } + + public static final String EXTENSION_NAME = "contrastConfiguration"; } diff --git a/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/EmptyTask.java b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/EmptyTask.java deleted file mode 100644 index a531804..0000000 --- a/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/EmptyTask.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.contrastsecurity.gradle.plugin; - -import org.gradle.api.DefaultTask; -import org.gradle.api.tasks.TaskAction; - -/** A placeholder task for demonstrating plugin registration */ -public class EmptyTask extends DefaultTask { - @TaskAction - void doNothing() { - // - } -} 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 new file mode 100644 index 0000000..a6974e5 --- /dev/null +++ b/gradle-plugin/src/main/java/com/contrastsecurity/gradle/plugin/InstallAgentTask.java @@ -0,0 +1,214 @@ +package com.contrastsecurity.gradle.plugin; + +import static com.contrastsecurity.gradle.plugin.ContrastGradlePlugin.EXTENSION_NAME; + +import com.contrastsecurity.models.AgentType; +import com.contrastsecurity.sdk.ContrastSDK; +import com.contrastsecurity.sdk.UserAgentProduct; +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.logging.Logger; +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); + + @TaskAction + void installAgent() { + + logger.debug("Running installAgentTask"); + // create sdk object for connecting to Contrast + final ContrastSDK sdk = connectToContrast(); + + 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()); + + logger.debug("preparing to attach agent"); + attachAgentToTasks(agent.toAbsolutePath()); + } + + /** + * 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; + } + + /** Create ContrastSDK for connecting to TeamServer */ + private ContrastSDK connectToContrast() { + // TODO get plugin version for this as well + final UserAgentProduct gradle = UserAgentProduct.of("contrast-gradle-plugin"); + return new ContrastSDK.Builder(config.getUsername(), config.getServiceKey(), config.getApiKey()) + .withApiUrl(config.getApiUrl() + "/api") + // TODO JAVA-8883 figure out how to define this proxy + // .withProxy(proxy) //with proxy? + .withUserAgentProduct(gradle) + .build(); + } + + private static final String AGENT_NAME = "contrast.jar"; + final Logger logger = getProject().getLogger(); +} diff --git a/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/EnvironmentUtils.java b/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/EnvironmentUtils.java new file mode 100644 index 0000000..51707f1 --- /dev/null +++ b/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/EnvironmentUtils.java @@ -0,0 +1,25 @@ +package com.contrastsecurity.gradle.plugin; + +/** Utility class for retrieving Contrast API credentials for testing from Environment vars */ +public class EnvironmentUtils { + + public static String getUsername() { + return System.getenv("CONTRAST__API__USER_NAME"); + } + + public static String getApiUrl() { + return System.getenv("CONTRAST__API__URL"); + } + + public static String getServiceKey() { + return System.getenv("CONTRAST__API__SERVICE_KEY"); + } + + public static String getApiKey() { + return System.getenv("CONTRAST__API__API_KEY"); + } + + public static String getOrgUuid() { + return System.getenv("CONTRAST__API__ORGANIZATION_ID"); + } +} diff --git a/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/FunctionalTests.java b/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/FunctionalTests.java deleted file mode 100644 index 9636166..0000000 --- a/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/FunctionalTests.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.contrastsecurity.gradle.plugin; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Writer; -import org.gradle.testkit.runner.BuildResult; -import org.gradle.testkit.runner.GradleRunner; -import org.gradle.testkit.runner.TaskOutcome; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -/** Simple placeholder tests verifying tasks in the plugin run successfully */ -public class FunctionalTests { - - @TempDir File projectDir; - - private File getBuildFile() { - return new File(projectDir, "build.gradle"); - } - - private File getSettingsFile() { - return new File(projectDir, "settings.gradle"); - } - - @Test - void canRunTasks() throws IOException { - writeString(getSettingsFile(), ""); - writeString(getBuildFile(), "plugins {" + " id('com.contrastsecurity.java')" + "}"); - - // Run the build - final GradleRunner helloRunner = GradleRunner.create(); - helloRunner.forwardOutput(); - helloRunner.withPluginClasspath(); - helloRunner.withArguments("hello"); - helloRunner.withProjectDir(projectDir); - final BuildResult result1 = helloRunner.build(); - - // Verify the result - assertTrue(result1.getOutput().contains("hello")); - - final GradleRunner emptyRunner = GradleRunner.create(); - emptyRunner.forwardOutput(); - emptyRunner.withPluginClasspath(); - emptyRunner.withArguments("empty"); - emptyRunner.withProjectDir(projectDir); - final BuildResult result2 = emptyRunner.build(); - - // Verify the result - assertTrue( - result2.tasks(TaskOutcome.SUCCESS).stream() - .anyMatch(buildTask -> buildTask.getPath().contains("empty"))); - } - - private void writeString(File file, String string) throws IOException { - try (Writer writer = new FileWriter(file)) { - writer.write(string); - } - } -} diff --git a/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/GradleRunnerTest.java b/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/GradleRunnerTest.java new file mode 100644 index 0000000..d00bdbf --- /dev/null +++ b/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/GradleRunnerTest.java @@ -0,0 +1,35 @@ +package com.contrastsecurity.gradle.plugin; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import org.junit.jupiter.api.io.TempDir; + +/** + * Super class for Tests using the {@link org.gradle.testkit.runner.GradleRunner} to provide build + * files and setting files + */ +public class GradleRunnerTest { + @TempDir public File projectDir; + + public File getBuildFile() { + return new File(projectDir, "build.gradle"); + } + + public File getSettingsFile() { + return new File(projectDir, "settings.gradle"); + } + + public void writeString(File file, String string) throws IOException { + try (Writer writer = new FileWriter(file)) { + writer.write(string); + } + } + + public String writeConfig() { + final String testConfig = "plugins { id('com.contrastsecurity.java') }\n"; + + return testConfig; + } +} 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 new file mode 100644 index 0000000..1e42a67 --- /dev/null +++ b/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/InstallAgentTests.java @@ -0,0 +1,40 @@ +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 ee3e69d..64e2d25 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 @@ -6,7 +6,7 @@ import org.gradle.testfixtures.ProjectBuilder; import org.junit.jupiter.api.Test; -/** Simple placeholder tests for proper plugin registration */ +/** Smoke tests for proper plugin registration */ class RegistrationTests { @Test void pluginRegistersATask() { @@ -15,15 +15,6 @@ void pluginRegistersATask() { project.getPlugins().apply("com.contrastsecurity.java"); // Verify the result - assertNotNull(project.getTasks().findByName("hello")); - assertNotNull(project.getTasks().findByName("empty")); - } - - @Test - public void addCorrectBlankTask() { - final Project project = ProjectBuilder.builder().build(); - project.getPlugins().apply("com.contrastsecurity.java"); - project.afterEvaluate( - s -> assertInstanceOf(EmptyTask.class, project.getTasks().getByName("empty"))); + assertNotNull(project.getTasks().findByName("installAgent")); } } 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 new file mode 100644 index 0000000..2b64dae --- /dev/null +++ b/gradle-plugin/src/test/java/com/contrastsecurity/gradle/plugin/e2e/EndToEndTests.java @@ -0,0 +1,114 @@ +package com.contrastsecurity.gradle.plugin.e2e; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.contrastsecurity.gradle.plugin.EnvironmentUtils; +import com.contrastsecurity.gradle.plugin.GradleRunnerTest; +import com.contrastsecurity.gradle.plugin.InstallAgentTask; +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; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.Test; + +/** End-To-End tests for the gradle plugin for interacting with TeamServer */ +public class EndToEndTests extends GradleRunnerTest { + + @Test + void verify_retrieval_of_agent_from_teamserver() { + + final ContrastSDK connection = + new ContrastSDK.Builder( + EnvironmentUtils.getUsername(), + EnvironmentUtils.getServiceKey(), + EnvironmentUtils.getApiKey()) + .withApiUrl(EnvironmentUtils.getApiUrl()) + .withUserAgentProduct(UserAgentProduct.of("contrast-gradle-plugin")) + .build(); + final Path agentPath = + InstallAgentTask.retrieveAgent( + connection, null, EnvironmentUtils.getOrgUuid(), ProjectBuilder.builder().build()); + assertNotNull(agentPath); + assertTrue(agentPath.endsWith("contrast.jar")); + } + + @Test + void verify_attaches_agent_to_tests() throws IOException { + writeString(getSettingsFile(), ""); + String config = writeContrastBuildFile(); + writeString(getBuildFile(), config); + + final GradleRunner testRunner = GradleRunner.create(); + testRunner.forwardOutput(); + testRunner.withPluginClasspath(); + // outputs debug logs to stdout for testing + testRunner.withArguments("installAgent", "--debug"); + testRunner.withDebug(true); + testRunner.withProjectDir(projectDir); + final BuildResult result = testRunner.build(); + + result + .getTasks() + .forEach( + buildTask -> { + assertEquals(buildTask.getOutcome(), TaskOutcome.SUCCESS); + }); + + for (final String arg : AGENT_ARGS) { + assertTrue(result.getOutput().contains(arg)); + } + } + + private static String writeContrastBuildFile() { + return "plugins { id('com.contrastsecurity.java') }\n" + + "contrastConfiguration {\n" + + " username = " + + "'" + + EnvironmentUtils.getUsername() + + "'" + + "\n" + + " apiKey = " + + "'" + + EnvironmentUtils.getApiKey() + + "'" + + "\n" + + " serviceKey = " + + "'" + + EnvironmentUtils.getServiceKey() + + "'" + + "\n" + + " apiUrl = " + + "'" + + EnvironmentUtils.getApiUrl() + + "'" + + "\n" + + " orgUuid = " + + "'" + + EnvironmentUtils.getOrgUuid() + + "'" + + "\n" + + " appName = 'gradle-end-to-end-test'\n" + + " serverName = 'server1'\n" + + " appVersion = '0.0.1'\n" + + " attachToTests = true\n" + + "}\n" + + "tasks.register('fakeTask', org.gradle.api.tasks.testing.Test) { \nSystem.out.println('test') \n}"; + } + + private static final Collection AGENT_ARGS = new HashSet<>(); + + static { + AGENT_ARGS.add("-javaagent:"); + AGENT_ARGS.add("-Dcontrast.override.appname=gradle-end-to-end-test"); + AGENT_ARGS.add("-Dcontrast.server=server"); + AGENT_ARGS.add("-Dcontrast.override.appversion=0.0.1"); + } +}