diff --git a/pom.xml b/pom.xml index 2dc3028..e835bcf 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ - 6.9.2 + 7.2.10 ${bamboo.version} ${basedir}/src/test/resources/generated-test-resources-692-license-723.zip @@ -126,6 +126,12 @@ 2.8.9 + + com.squareup.okhttp3 + okhttp + 4.12.0 + + org.junit.jupiter diff --git a/src/main/i18n/develocity-bamboo-plugin.properties b/src/main/i18n/develocity-bamboo-plugin.properties index e506fec..3ce7bc5 100644 --- a/src/main/i18n/develocity-bamboo-plugin.properties +++ b/src/main/i18n/develocity-bamboo-plugin.properties @@ -34,3 +34,5 @@ develocity.config.enforce-url.description=Whether to enforce the Develocity serv develocity.config.general.title=General settings develocity.config.general.vcs-repository-filter=Auto-injection Git VCS repository filters develocity.config.general.vcs-repository-filter.info=Newline-delimited set of rules in the form of +|-:repository_matching_keyword, for which to enable/disable Develocity Gradle plugin/Maven extension auto-injection.
By default, all Git VCS repositories have auto-injection enabled. +develocity.config.general.short-lived-token-expiry=Develocity short-lived access token expiry +develocity.config.general.short-lived-token-expiry.description=The short-lived access tokens expiry in hours. Defaults to 2 hours. For more information, please refer to the documentation. diff --git a/src/main/java/com/gradle/develocity/bamboo/DevelocityAccessCredential.java b/src/main/java/com/gradle/develocity/bamboo/DevelocityAccessCredential.java new file mode 100644 index 0000000..52d3f6f --- /dev/null +++ b/src/main/java/com/gradle/develocity/bamboo/DevelocityAccessCredential.java @@ -0,0 +1,86 @@ +package com.gradle.develocity.bamboo; + +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; + +public final class DevelocityAccessCredential { + + private final String hostname; + private final String key; + + private DevelocityAccessCredential(String hostname, String key) { + this.hostname = hostname; + this.key = key; + } + + public static DevelocityAccessCredential of(String hostname, String key) { + return new DevelocityAccessCredential(hostname, key); + } + + public static Optional parse(String rawAccessKey, String host) { + return Arrays.stream(rawAccessKey.split(";")) + .map(k -> k.split("=")) + .filter(hostKey -> hostKey[0].equals(host)) + .map(hostKey -> new DevelocityAccessCredential(hostKey[0], hostKey[1])) + .findFirst(); + } + + public static boolean isValid(String value) { + if (StringUtils.isBlank(value)) { + return false; + } + + String[] entries = value.split(";"); + + for (String entry : entries) { + String[] parts = entry.split("=", 2); + if (parts.length < 2) { + return false; + } + + String servers = parts[0]; + String accessKey = parts[1]; + + if (StringUtils.isBlank(servers) || StringUtils.isBlank(accessKey)) { + return false; + } + + for (String server : servers.split(",")) { + if (StringUtils.isBlank(server)) { + return false; + } + } + } + + return true; + } + + public String getRawAccessKey() { + return hostname + "=" + key; + } + + public String getHostname() { + return hostname; + } + + public String getKey() { + return key; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DevelocityAccessCredential that = (DevelocityAccessCredential) o; + return Objects.equals(hostname, that.hostname) && Objects.equals(key, that.key); + } + + @Override + public int hashCode() { + return Objects.hash(hostname, key); + } + +} diff --git a/src/main/java/com/gradle/develocity/bamboo/DevelocityPreJobAction.java b/src/main/java/com/gradle/develocity/bamboo/DevelocityPreJobAction.java index 1588e70..6ba7d48 100644 --- a/src/main/java/com/gradle/develocity/bamboo/DevelocityPreJobAction.java +++ b/src/main/java/com/gradle/develocity/bamboo/DevelocityPreJobAction.java @@ -13,6 +13,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.net.MalformedURLException; +import java.net.URL; import java.util.List; public class DevelocityPreJobAction implements PreJobAction { @@ -22,13 +24,18 @@ public class DevelocityPreJobAction implements PreJobAction { private final PersistentConfigurationManager configurationManager; private final UsernameAndPasswordCredentialsProvider credentialsProvider; private final List> injectors; + private final ShortLivedTokenClient shortLivedTokenClient; - public DevelocityPreJobAction(PersistentConfigurationManager configurationManager, - UsernameAndPasswordCredentialsProvider credentialsProvider, - List> injectors) { + public DevelocityPreJobAction( + PersistentConfigurationManager configurationManager, + UsernameAndPasswordCredentialsProvider credentialsProvider, + List> injectors, + ShortLivedTokenClient shortLivedTokenClient + ) { this.configurationManager = configurationManager; this.credentialsProvider = credentialsProvider; this.injectors = injectors; + this.shortLivedTokenClient = shortLivedTokenClient; } @Override @@ -46,8 +53,8 @@ public void execute(@NotNull StageExecution stageExecution, @NotNull BuildContex UsernameAndPassword credentials = credentialsProvider.findByName(sharedCredentialName).orElse(null); if (credentials == null) { LOGGER.warn( - "Shared credentials with the name {} are not found. Environment variable {} will not be set", - sharedCredentialName, Constants.DEVELOCITY_ACCESS_KEY + "Shared credentials with the name {} are not found. Environment variable {} will not be set", + sharedCredentialName, Constants.DEVELOCITY_ACCESS_KEY ); return; } @@ -56,20 +63,27 @@ public void execute(@NotNull StageExecution stageExecution, @NotNull BuildContex String accessKey = credentials.getPassword(); if (StringUtils.isBlank(accessKey)) { LOGGER.warn( - "Shared credentials with the name {} do not have password set. Environment variable {} will not be set", - sharedCredentialName, Constants.DEVELOCITY_ACCESS_KEY + "Shared credentials with the name {} do not have password set. Environment variable {} will not be set", + sharedCredentialName, Constants.DEVELOCITY_ACCESS_KEY ); return; } - injectors.stream() - .filter(i -> i.hasSupportedTasks(buildContext)) - .map(i -> i.buildToolConfiguration(configuration)) - .filter(BuildToolConfiguration::isEnabled) - .findFirst() - .ifPresent(__ -> - buildContext - .getVariableContext() - .addLocalVariable(Constants.ACCESS_KEY, accessKey)); + DevelocityAccessCredential.parse(accessKey, getHostnameFromServerUrl(configuration.getServer())) + .flatMap(parsedKey -> injectors.stream() + .filter(i -> i.hasSupportedTasks(buildContext)) + .map(i -> i.buildToolConfiguration(configuration)) + .filter(BuildToolConfiguration::isEnabled) + .findFirst() + .flatMap(__ -> shortLivedTokenClient.get(configuration.getServer(), parsedKey, configuration.getShortLivedTokenExpiry()))) + .ifPresent(shortLivedToken -> buildContext.getVariableContext().addLocalVariable(Constants.ACCESS_KEY, shortLivedToken.getRawAccessKey())); + } + + private static String getHostnameFromServerUrl(String serverUrl) { + try { + return new URL(serverUrl).getHost(); + } catch (MalformedURLException e) { + return null; + } } } diff --git a/src/main/java/com/gradle/develocity/bamboo/ShortLivedTokenClient.java b/src/main/java/com/gradle/develocity/bamboo/ShortLivedTokenClient.java new file mode 100644 index 0000000..6c6c577 --- /dev/null +++ b/src/main/java/com/gradle/develocity/bamboo/ShortLivedTokenClient.java @@ -0,0 +1,78 @@ +package com.gradle.develocity.bamboo; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +@Component +public class ShortLivedTokenClient { + + private static final Logger LOGGER = LoggerFactory.getLogger(ShortLivedTokenClient.class); + + private static final RequestBody EMPTY_BODY = RequestBody.create(new byte[]{}); + + private static final int MAX_RETRIES = 3; + private static final Duration RETRY_INTERVAL = Duration.ofSeconds(1); + + private final OkHttpClient httpClient; + + public ShortLivedTokenClient() { + this.httpClient = new OkHttpClient().newBuilder() + .callTimeout(10, TimeUnit.SECONDS) + .build(); + } + + public Optional get(String server, DevelocityAccessCredential accessKey, String expiryInHours) { + String url = normalize(server) + "api/auth/token"; + if (StringUtils.isNotBlank(expiryInHours)) { + url += "?expiresInHours=" + expiryInHours; + } + + Request request = new Request.Builder() + .url(url) + .addHeader("Authorization", "Bearer " + accessKey.getKey()) + .addHeader("Content-Type", "application/json") + .post(EMPTY_BODY) + .build(); + + int tryCount = 0; + Integer errorCode = null; + while (tryCount < MAX_RETRIES) { + try (Response response = httpClient.newCall(request).execute()) { + if (response.code() == 200 && response.body() != null) { + return Optional.of(DevelocityAccessCredential.of(accessKey.getHostname(), response.body().string())); + } else if (response.code() == 401) { + LOGGER.warn("Short lived token request failed {} with status code 401", url); + return Optional.empty(); + } else { + tryCount++; + errorCode = response.code(); + Thread.sleep(RETRY_INTERVAL.toMillis()); + } + } catch (IOException e) { + LOGGER.warn("Short lived token request failed {}", url, e); + return Optional.empty(); + } catch (InterruptedException e) { + // Ignore sleep exception as + } + } + + LOGGER.warn("Develocity short lived token request failed {} with status code {}", url, errorCode); + return Optional.empty(); + } + + private static String normalize(String server) { + return server.endsWith("/") ? server : server + "/"; + } + +} diff --git a/src/main/java/com/gradle/develocity/bamboo/admin/AccessKeyValidator.java b/src/main/java/com/gradle/develocity/bamboo/admin/AccessKeyValidator.java deleted file mode 100644 index eb3e1dd..0000000 --- a/src/main/java/com/gradle/develocity/bamboo/admin/AccessKeyValidator.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.gradle.develocity.bamboo.admin; - -import org.apache.commons.lang3.StringUtils; - -final class AccessKeyValidator { - - private AccessKeyValidator() { - } - - public static boolean isValid(String value) { - if (StringUtils.isBlank(value)) { - return false; - } - - String[] entries = value.split(";"); - - for (String entry : entries) { - String[] parts = entry.split("=", 2); - if (parts.length < 2) { - return false; - } - - String servers = parts[0]; - String accessKey = parts[1]; - - if (StringUtils.isBlank(servers) || StringUtils.isBlank(accessKey)) { - return false; - } - - for (String server : servers.split(",")) { - if (StringUtils.isBlank(server)) { - return false; - } - } - } - - return true; - } -} diff --git a/src/main/java/com/gradle/develocity/bamboo/admin/BuildScansConfigAction.java b/src/main/java/com/gradle/develocity/bamboo/admin/BuildScansConfigAction.java index 740d74b..98ce50e 100644 --- a/src/main/java/com/gradle/develocity/bamboo/admin/BuildScansConfigAction.java +++ b/src/main/java/com/gradle/develocity/bamboo/admin/BuildScansConfigAction.java @@ -2,6 +2,7 @@ import com.atlassian.bamboo.configuration.GlobalAdminAction; import com.atlassian.bamboo.repository.NameValuePair; +import com.gradle.develocity.bamboo.DevelocityAccessCredential; import com.gradle.develocity.bamboo.MavenCoordinates; import com.gradle.develocity.bamboo.VcsRepositoryFilter; import com.gradle.develocity.bamboo.config.PersistentConfiguration; @@ -19,6 +20,7 @@ public class BuildScansConfigAction extends GlobalAdminAction { private static final Pattern VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+(\\.\\d+)?(-[-\\w]+)?$"); + private static final Pattern SHORT_LIVED_TOKEN_EXPIRY_PATTERN = Pattern.compile("^(?:[1-9]|1\\d?|2[0-4]?)$"); /* Common parameters for all build systems */ private String server; @@ -46,6 +48,7 @@ public class BuildScansConfigAction extends GlobalAdminAction { /* General settings */ private String vcsRepositoryFilter; + private String shortLivedTokenExpiry; public BuildScansConfigAction(UsernameAndPasswordCredentialsProvider credentialsProvider, PersistentConfigurationManager configurationManager) { @@ -71,6 +74,7 @@ public String input() { vcsRepositoryFilter = config.getVcsRepositoryFilter(); gradleCaptureFileFingerprints = config.isGradleCaptureFileFingerprints(); mavenCaptureFileFingerprints = config.isMavenCaptureFileFingerprints(); + shortLivedTokenExpiry = config.getShortLivedTokenExpiry(); }); return INPUT; @@ -90,17 +94,17 @@ public void validate() { addFieldError("sharedCredentialName", "Please specify the name of the existing shared credential of type 'Username and password'."); } else { String accessKey = credentials.getPassword(); - if (!AccessKeyValidator.isValid(accessKey)) { + if (!DevelocityAccessCredential.isValid(accessKey)) { addFieldError("sharedCredentialName", "Shared credential contains an invalid access key."); } } } - if (!isBlankOrValidVersion(develocityPluginVersion)) { + if (!isBlankOrValid(VERSION_PATTERN, develocityPluginVersion)) { addFieldError("develocityPluginVersion", "Please specify a valid version of the Develocity Gradle plugin."); } - if (!isBlankOrValidVersion(ccudPluginVersion)) { + if (!isBlankOrValid(VERSION_PATTERN, ccudPluginVersion)) { addFieldError("ccudPluginVersion", "Please specify a valid version of the Common Custom User Data Gradle plugin."); } @@ -127,6 +131,9 @@ public void validate() { addFieldError("vcsRepositoryFilter", "Please specify a valid vcs filter, ie lines of: +|-:repository_matching_keyword"); } + if (!isBlankOrValid(SHORT_LIVED_TOKEN_EXPIRY_PATTERN, shortLivedTokenExpiry)) { + addFieldError("shortLivedTokenExpiry", "Please specify a valid short-lived token expiry in hours between 1 and 24, i.e. 6"); + } } private boolean isBlankOrValidVcsFilter(String vcsRepositoryFilter) { @@ -166,11 +173,11 @@ private static boolean isBlankOrValidUrl(String url) { } } - private static boolean isBlankOrValidVersion(String version) { - if (StringUtils.isBlank(version)) { + private static boolean isBlankOrValid(Pattern pattern, String value) { + if (StringUtils.isBlank(value)) { return true; } - return VERSION_PATTERN.matcher(version).matches(); + return pattern.matcher(value).matches(); } public String save() { @@ -191,6 +198,7 @@ public String save() { .setVcsRepositoryFilter(vcsRepositoryFilter) .setGradleCaptureFileFingerprints(gradleCaptureFileFingerprints) .setMavenCaptureFileFingerprints(mavenCaptureFileFingerprints) + .setShortLivedTokenExpiry(shortLivedTokenExpiry) ); return SUCCESS; @@ -316,4 +324,11 @@ public void setMavenCaptureFileFingerprints(boolean mavenCaptureFileFingerprints this.mavenCaptureFileFingerprints = mavenCaptureFileFingerprints; } + public String getShortLivedTokenExpiry() { + return shortLivedTokenExpiry; + } + + public void setShortLivedTokenExpiry(String shortLivedTokenExpiry) { + this.shortLivedTokenExpiry = shortLivedTokenExpiry; + } } diff --git a/src/main/java/com/gradle/develocity/bamboo/config/PersistentConfiguration.java b/src/main/java/com/gradle/develocity/bamboo/config/PersistentConfiguration.java index 635fd25..df6427c 100644 --- a/src/main/java/com/gradle/develocity/bamboo/config/PersistentConfiguration.java +++ b/src/main/java/com/gradle/develocity/bamboo/config/PersistentConfiguration.java @@ -36,6 +36,9 @@ public class PersistentConfiguration { @Nullable private String vcsRepositoryFilter; + @Nullable + private String shortLivedTokenExpiry; + private boolean gradleCaptureFileFingerprints; private boolean mavenCaptureFileFingerprints; @@ -183,6 +186,16 @@ public PersistentConfiguration setMavenCaptureFileFingerprints(boolean mavenCapt return this; } + @Nullable + public String getShortLivedTokenExpiry() { + return shortLivedTokenExpiry; + } + + public PersistentConfiguration setShortLivedTokenExpiry(String shortLivedTokenExpiry) { + this.shortLivedTokenExpiry = shortLivedTokenExpiry; + return this; + } + @Override public String toString() { return new ToStringBuilder(this) @@ -201,6 +214,7 @@ public String toString() { .append("vcsRepositoryFilter", vcsRepositoryFilter) .append("gradleCaptureFileFingerprints", gradleCaptureFileFingerprints) .append("mavenCaptureFileFingerprints", mavenCaptureFileFingerprints) + .append("shortLivedTokenExpiry", shortLivedTokenExpiry) .toString(); } @@ -223,14 +237,15 @@ public boolean equals(Object o) { Objects.equals(ccudExtensionCustomCoordinates, that.ccudExtensionCustomCoordinates) && Objects.equals(vcsRepositoryFilter, that.vcsRepositoryFilter) && Objects.equals(gradleCaptureFileFingerprints, that.gradleCaptureFileFingerprints) && - Objects.equals(mavenCaptureFileFingerprints, that.mavenCaptureFileFingerprints); + Objects.equals(mavenCaptureFileFingerprints, that.mavenCaptureFileFingerprints) && + Objects.equals(shortLivedTokenExpiry, that.shortLivedTokenExpiry); } @Override public int hashCode() { return Objects.hash(server, allowUntrustedServer, sharedCredentialName, enforceUrl, develocityPluginVersion, ccudPluginVersion, pluginRepository, pluginRepositoryCredentialName, injectMavenExtension, injectCcudExtension, mavenExtensionCustomCoordinates, - ccudExtensionCustomCoordinates, vcsRepositoryFilter, gradleCaptureFileFingerprints, mavenCaptureFileFingerprints + ccudExtensionCustomCoordinates, vcsRepositoryFilter, gradleCaptureFileFingerprints, mavenCaptureFileFingerprints, shortLivedTokenExpiry ); } diff --git a/src/main/resources/templates/views/admin/buildScansConfig.ftl b/src/main/resources/templates/views/admin/buildScansConfig.ftl index 41801fb..c424a61 100644 --- a/src/main/resources/templates/views/admin/buildScansConfig.ftl +++ b/src/main/resources/templates/views/admin/buildScansConfig.ftl @@ -33,6 +33,7 @@ [/@ui.bambooSection] [@ui.bambooSection titleKey="develocity.config.general.title" headerWeight="h2"] + [@ww.textfield labelKey="develocity.config.general.short-lived-token-expiry" name="shortLivedTokenExpiry"/] [@ww.textarea labelKey='develocity.config.general.vcs-repository-filter' name='vcsRepositoryFilter' /]
diff --git a/src/test/java/com/gradle/develocity/bamboo/admin/AccessKeyValidatorTest.java b/src/test/java/com/gradle/develocity/bamboo/DevelocityAccessCredentialTest.java similarity index 61% rename from src/test/java/com/gradle/develocity/bamboo/admin/AccessKeyValidatorTest.java rename to src/test/java/com/gradle/develocity/bamboo/DevelocityAccessCredentialTest.java index 078c418..9fd608a 100644 --- a/src/test/java/com/gradle/develocity/bamboo/admin/AccessKeyValidatorTest.java +++ b/src/test/java/com/gradle/develocity/bamboo/DevelocityAccessCredentialTest.java @@ -1,13 +1,14 @@ -package com.gradle.develocity.bamboo.admin; +package com.gradle.develocity.bamboo; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -class AccessKeyValidatorTest { +class DevelocityAccessCredentialTest { @ParameterizedTest @ValueSource(strings = { @@ -23,7 +24,17 @@ class AccessKeyValidatorTest { " server1= secret1; server2 , sever3 = secret2 ;" }) void validAccessKeys(String accessKey) { - assertThat(AccessKeyValidator.isValid(accessKey), is(true)); + assertThat(DevelocityAccessCredential.isValid(accessKey), is(true)); + } + + @ParameterizedTest + @CsvSource({ + "host1=secret,true", + "host1=secret;host2=secret,true", + "host2=secret;host3=secret,false", + }) + void canParseAccessKeys(String accessKey, boolean isPresent) { + assertThat(DevelocityAccessCredential.parse(accessKey, "host1").isPresent(), is(isPresent)); } @ParameterizedTest @@ -38,6 +49,6 @@ void validAccessKeys(String accessKey) { "server1, server2,, server3 = secret " }) void invalidAccessKeys(String accessKey) { - assertThat(AccessKeyValidator.isValid(accessKey), is(false)); + assertThat(DevelocityAccessCredential.isValid(accessKey), is(false)); } } diff --git a/src/test/java/com/gradle/develocity/bamboo/DevelocityPreJobActionTest.java b/src/test/java/com/gradle/develocity/bamboo/DevelocityPreJobActionTest.java index 4a63d17..e0627ec 100644 --- a/src/test/java/com/gradle/develocity/bamboo/DevelocityPreJobActionTest.java +++ b/src/test/java/com/gradle/develocity/bamboo/DevelocityPreJobActionTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.params.provider.ValueSource; import java.util.Collections; +import java.util.Optional; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -38,15 +39,18 @@ class DevelocityPreJobActionTest { private final BuildContext buildContext = mock(BuildContext.class); private final VariableContext variableContext = mock(VariableContext.class); + private final ShortLivedTokenClient mockShortLivedTokenClient = mock(ShortLivedTokenClient.class); + private final GradleBuildScanInjector gradleBuildScanInjector = - new GradleBuildScanInjector(null, null, null, null, null); + new GradleBuildScanInjector(null, null, null, null, null); private final DevelocityPreJobAction develocityPreJobAction = - new DevelocityPreJobAction( - new PersistentConfigurationManager(bandanaManager), - new UsernameAndPasswordCredentialsProvider(credentialsAccessor), - Collections.singletonList(gradleBuildScanInjector) - ); + new DevelocityPreJobAction( + new PersistentConfigurationManager(bandanaManager), + new UsernameAndPasswordCredentialsProvider(credentialsAccessor), + Collections.singletonList(gradleBuildScanInjector), + mockShortLivedTokenClient + ); @Test void doesNothingIfNoConfiguration() { @@ -62,7 +66,7 @@ void doesNothingIfNoConfiguration() { void doesNothingIfNoSharedCredentials() { // given when(bandanaManager.getValue(any(BandanaContext.class), anyString())) - .thenReturn("{}"); + .thenReturn("{}"); // when develocityPreJobAction.execute(stageExecution, buildContext); @@ -77,7 +81,7 @@ void doesNothingIfCredentialsNotFound() { // given String credentialsName = RandomStringUtils.randomAlphanumeric(10); when(bandanaManager.getValue(any(BandanaContext.class), anyString())) - .thenReturn("{\"sharedCredentialName\":\"" + credentialsName + "\"}"); + .thenReturn("{\"sharedCredentialName\":\"" + credentialsName + "\"}"); // when develocityPreJobAction.execute(stageExecution, buildContext); @@ -96,9 +100,9 @@ void doesNothingIfCredentialsWithoutPassword() { String credentialsName = RandomStringUtils.randomAlphanumeric(10); when(bandanaManager.getValue(any(BandanaContext.class), anyString())) - .thenReturn("{\"sharedCredentialName\":\"" + credentialsName + "\"}"); + .thenReturn("{\"sharedCredentialName\":\"" + credentialsName + "\"}"); when(credentialsAccessor.getCredentialsByName(credentialsName)) - .thenReturn(credentialsData); + .thenReturn(credentialsData); // when develocityPreJobAction.execute(stageExecution, buildContext); @@ -118,11 +122,11 @@ void doesNothingIfNoSupportedTasks() { String credentialsName = RandomStringUtils.randomAlphanumeric(10); when(bandanaManager.getValue(any(BandanaContext.class), anyString())) - .thenReturn("{\"server\":\"https://scans.gradle.com\",\"sharedCredentialName\":\"" + credentialsName + "\", " + - "\"develocityPluginVersion\": \"3.12\"}"); + .thenReturn("{\"server\":\"https://scans.gradle.com\",\"sharedCredentialName\":\"" + credentialsName + "\", " + + "\"develocityPluginVersion\": \"3.12\"}"); when(credentialsAccessor.getCredentialsByName(credentialsName)) - .thenReturn(credentialsData); + .thenReturn(credentialsData); RuntimeTaskDefinition runtimeTaskDefinition = mock(RuntimeTaskDefinition.class); when(runtimeTaskDefinition.isEnabled()).thenReturn(true); @@ -147,9 +151,37 @@ void doesNothingIfInjectionDisabled() { String credentialsName = RandomStringUtils.randomAlphanumeric(10); when(bandanaManager.getValue(any(BandanaContext.class), anyString())) - .thenReturn("{\"server\":\"https://scans.gradle.com\",\"sharedCredentialName\":\"" + credentialsName + "\"}"); + .thenReturn("{\"server\":\"https://scans.gradle.com\",\"sharedCredentialName\":\"" + credentialsName + "\"}"); + when(credentialsAccessor.getCredentialsByName(credentialsName)) + .thenReturn(credentialsData); + + RuntimeTaskDefinition runtimeTaskDefinition = mock(RuntimeTaskDefinition.class); + when(runtimeTaskDefinition.isEnabled()).thenReturn(true); + when(runtimeTaskDefinition.getPluginKey()).thenReturn(GradleBuildScanInjector.SCRIPT_PLUGIN_KEY); + when(buildContext.getRuntimeTaskDefinitions()).thenReturn(Collections.singletonList(runtimeTaskDefinition)); + + // when + develocityPreJobAction.execute(stageExecution, buildContext); + + // then + assertThat(gradleBuildScanInjector.hasSupportedTasks(buildContext), is(true)); + verify(buildContext, never()).getVariableContext(); + } + + @Test + void doesNothingIfNoShortLivedTokenRetrieved() { + // given + String accessKey = String.format("scans.gradle.com=%s", RandomStringUtils.randomAlphanumeric(10)); + CredentialsData credentialsData = mock(CredentialsData.class); + when(credentialsData.getPluginKey()).thenReturn(UsernameAndPassword.SHARED_USERNAME_PASSWORD_PLUGIN_KEY); + when(credentialsData.getConfiguration()).thenReturn(Collections.singletonMap(UsernameAndPassword.PASSWORD, accessKey)); + when(mockShortLivedTokenClient.get(anyString(), any(), anyString())).thenReturn(Optional.empty()); + + String credentialsName = RandomStringUtils.randomAlphanumeric(10); + when(bandanaManager.getValue(any(BandanaContext.class), anyString())) + .thenReturn("{\"server\":\"https://scans.gradle.com\",\"sharedCredentialName\":\"" + credentialsName + "\"}"); when(credentialsAccessor.getCredentialsByName(credentialsName)) - .thenReturn(credentialsData); + .thenReturn(credentialsData); RuntimeTaskDefinition runtimeTaskDefinition = mock(RuntimeTaskDefinition.class); when(runtimeTaskDefinition.isEnabled()).thenReturn(true); @@ -162,31 +194,35 @@ void doesNothingIfInjectionDisabled() { // then assertThat(gradleBuildScanInjector.hasSupportedTasks(buildContext), is(true)); verify(buildContext, never()).getVariableContext(); + verify(variableContext, never()).addLocalVariable(anyString(), anyString()); } @ParameterizedTest @ValueSource(strings = { - GradleBuildScanInjector.BOB_SWIFT_GROOVY_TASKS_PLUGIN_GRADLE_KEY, - GradleBuildScanInjector.BOB_SWIFT_GROOVY_TASKS_PLUGIN_GRADLE_WRAPPER_KEY, - GradleBuildScanInjector.BOB_SWIFT_GROOVY_TASKS_PLUGIN_GRADLEW_KEY, - GradleBuildScanInjector.SCRIPT_PLUGIN_KEY, - GradleBuildScanInjector.COMMAND_PLUGIN_KEY, - "org.jfrog.bamboo." + GradleBuildScanInjector.ARTIFACTORY_GRADLE_TASK_KEY_SUFFIX + GradleBuildScanInjector.BOB_SWIFT_GROOVY_TASKS_PLUGIN_GRADLE_KEY, + GradleBuildScanInjector.BOB_SWIFT_GROOVY_TASKS_PLUGIN_GRADLE_WRAPPER_KEY, + GradleBuildScanInjector.BOB_SWIFT_GROOVY_TASKS_PLUGIN_GRADLEW_KEY, + GradleBuildScanInjector.SCRIPT_PLUGIN_KEY, + GradleBuildScanInjector.COMMAND_PLUGIN_KEY, + "org.jfrog.bamboo." + GradleBuildScanInjector.ARTIFACTORY_GRADLE_TASK_KEY_SUFFIX }) void addsAccessKeyToContext(String pluginKey) { // given String accessKey = String.format("scans.gradle.com=%s", RandomStringUtils.randomAlphanumeric(10)); + String shortLivedToken = String.format("scans.gradle.com=%s", RandomStringUtils.randomAlphanumeric(10)); + CredentialsData credentialsData = mock(CredentialsData.class); when(credentialsData.getPluginKey()).thenReturn(UsernameAndPassword.SHARED_USERNAME_PASSWORD_PLUGIN_KEY); when(credentialsData.getConfiguration()).thenReturn(Collections.singletonMap(UsernameAndPassword.PASSWORD, accessKey)); + when(mockShortLivedTokenClient.get(anyString(), any(), any())).thenReturn(Optional.of(DevelocityAccessCredential.parse(shortLivedToken, "scans.gradle.com").get())); String credentialsName = RandomStringUtils.randomAlphanumeric(10); when(bandanaManager.getValue(any(BandanaContext.class), anyString())) - .thenReturn("{\"server\":\"https://scans.gradle.com\",\"sharedCredentialName\":\"" + credentialsName + "\", " + - "\"develocityPluginVersion\": \"3.12\"}"); + .thenReturn("{\"server\":\"https://scans.gradle.com\",\"sharedCredentialName\":\"" + credentialsName + "\", " + + "\"develocityPluginVersion\": \"3.12\"}"); when(credentialsAccessor.getCredentialsByName(credentialsName)) - .thenReturn(credentialsData); + .thenReturn(credentialsData); RuntimeTaskDefinition runtimeTaskDefinition = mock(RuntimeTaskDefinition.class); when(runtimeTaskDefinition.isEnabled()).thenReturn(true); @@ -198,6 +234,6 @@ void addsAccessKeyToContext(String pluginKey) { develocityPreJobAction.execute(stageExecution, buildContext); // then - verify(variableContext, times(1)).addLocalVariable(Constants.ACCESS_KEY, accessKey); + verify(variableContext, times(1)).addLocalVariable(Constants.ACCESS_KEY, shortLivedToken); } } diff --git a/src/test/java/com/gradle/develocity/bamboo/TestFixtures.java b/src/test/java/com/gradle/develocity/bamboo/TestFixtures.java index 532cb33..67c5278 100644 --- a/src/test/java/com/gradle/develocity/bamboo/TestFixtures.java +++ b/src/test/java/com/gradle/develocity/bamboo/TestFixtures.java @@ -1,5 +1,6 @@ package com.gradle.develocity.bamboo; +import com.atlassian.bamboo.bandana.BambooBandanaContextImpl; import com.atlassian.bamboo.build.BuildOutputLogEntry; import com.atlassian.bamboo.build.DefaultBuildDefinition; import com.atlassian.bamboo.chains.ChainStorageTag; @@ -45,13 +46,14 @@ public static BuildContext getBuildContext() { null, null, Collections.emptyMap(), - Collections.singleton(RandomUtils.nextLong()), + Collections.emptySet(), Collections.singletonMap(RandomUtils.nextLong(), RandomStringUtils.randomAscii(10)), false, false, false, - null, - Collections.singletonList(new CredentialsDataEntity("key", "name", Collections.singletonMap("key", "value"), null)), + false, + new BambooBandanaContextImpl(null, null), + Collections.singletonList(new CredentialsDataEntity("key", "name", Collections.singletonMap("key", "value"), null, null)), Collections.singletonMap(PlanKeys.getPlanKey("SOME-KEY"), new ChainStorageTag("tag")), new BuildKey() ); diff --git a/src/test/java/com/gradle/develocity/bamboo/config/ConfigurationMigratorTest.java b/src/test/java/com/gradle/develocity/bamboo/config/ConfigurationMigratorTest.java index 018f68e..ef3d2b7 100644 --- a/src/test/java/com/gradle/develocity/bamboo/config/ConfigurationMigratorTest.java +++ b/src/test/java/com/gradle/develocity/bamboo/config/ConfigurationMigratorTest.java @@ -34,7 +34,7 @@ void runsMigrateConfigV0ToV1() { new ConfigurationMigrator(bandanaManager).onPluginEnabled(pluginEnabledEvent); verify(bandanaManager, times(1)).setValue(any(BandanaContext.class), eq("com.gradle.bamboo.plugins.develocity.config.v1"), - eq("{\"server\":\"https://mycomp\",\"allowUntrustedServer\":false,\"sharedCredentialName\":null,\"enforceUrl\":false,\"develocityPluginVersion\":null,\"ccudPluginVersion\":null,\"pluginRepository\":null,\"pluginRepositoryCredentialName\":null,\"injectMavenExtension\":false,\"injectCcudExtension\":false,\"mavenExtensionCustomCoordinates\":null,\"ccudExtensionCustomCoordinates\":null,\"vcsRepositoryFilter\":null,\"gradleCaptureFileFingerprints\":false,\"mavenCaptureFileFingerprints\":false}")); + eq("{\"server\":\"https://mycomp\",\"allowUntrustedServer\":false,\"sharedCredentialName\":null,\"enforceUrl\":false,\"develocityPluginVersion\":null,\"ccudPluginVersion\":null,\"pluginRepository\":null,\"pluginRepositoryCredentialName\":null,\"injectMavenExtension\":false,\"injectCcudExtension\":false,\"mavenExtensionCustomCoordinates\":null,\"ccudExtensionCustomCoordinates\":null,\"vcsRepositoryFilter\":null,\"shortLivedTokenExpiry\":null,\"gradleCaptureFileFingerprints\":false,\"mavenCaptureFileFingerprints\":false}")); verify(bandanaManager, times(1)).removeValue(any(BandanaContext.class), eq("com.gradle.bamboo.plugins.develocity.config")); } diff --git a/src/test/java/com/gradle/develocity/bamboo/config/JsonConfigurationConverterTest.java b/src/test/java/com/gradle/develocity/bamboo/config/JsonConfigurationConverterTest.java index 5efc213..f08ac07 100644 --- a/src/test/java/com/gradle/develocity/bamboo/config/JsonConfigurationConverterTest.java +++ b/src/test/java/com/gradle/develocity/bamboo/config/JsonConfigurationConverterTest.java @@ -31,7 +31,7 @@ class JsonConfigurationConverterTest { "\"sharedCredentialName\":\"develocity-creds\",\"enforceUrl\":false,\"develocityPluginVersion\":\"3.11\",\"ccudPluginVersion\":\"1.11\"," + "\"pluginRepository\":\"https://plugins.mycompany.com\",\"pluginRepositoryCredentialName\":\"plugin-creds\",\"injectMavenExtension\":true," + "\"injectCcudExtension\":true," + - "\"mavenExtensionCustomCoordinates\":\"foo:bar\",\"ccudExtensionCustomCoordinates\":\"foo:ccud-bar\",\"vcsRepositoryFilter\":null,\"gradleCaptureFileFingerprints\":true,\"mavenCaptureFileFingerprints\":true}"; + "\"mavenExtensionCustomCoordinates\":\"foo:bar\",\"ccudExtensionCustomCoordinates\":\"foo:ccud-bar\",\"vcsRepositoryFilter\":null,\"shortLivedTokenExpiry\":null,\"gradleCaptureFileFingerprints\":true,\"mavenCaptureFileFingerprints\":true}"; @Test void toJson() throws JsonProcessingException { diff --git a/src/test/java/it/com/gradle/develocity/bamboo/BuildScansConfigurationForm.java b/src/test/java/it/com/gradle/develocity/bamboo/BuildScansConfigurationForm.java index 75a4fbc..73e8725 100644 --- a/src/test/java/it/com/gradle/develocity/bamboo/BuildScansConfigurationForm.java +++ b/src/test/java/it/com/gradle/develocity/bamboo/BuildScansConfigurationForm.java @@ -107,6 +107,11 @@ public BuildScansConfigurationForm setVcsRepositoryFilter(String filter) { return this; } + public BuildScansConfigurationForm setShortLivedTokenExpiry(String expiry) { + getShortLivedTokenExpiry().fill(expiry); + return this; + } + private Locator getVcsRepositoryFilterLocator() { return page.getByLabel("Auto-injection Git VCS repository filters"); } @@ -158,4 +163,9 @@ public Locator getMavenExtensionCustomCoordinatesLocator() { public Locator getEnforceUrlLocator() { return page.getByText("Enforce Develocity server URL"); } + + public Locator getShortLivedTokenExpiry() { + return page.getByText("Develocity short-lived access token expiry"); + } + } diff --git a/src/test/java/it/com/gradle/develocity/bamboo/injection/MockDevelocityServer.java b/src/test/java/it/com/gradle/develocity/bamboo/MockDevelocityServer.java similarity index 63% rename from src/test/java/it/com/gradle/develocity/bamboo/injection/MockDevelocityServer.java rename to src/test/java/it/com/gradle/develocity/bamboo/MockDevelocityServer.java index ce4d5c4..cea5ddd 100644 --- a/src/test/java/it/com/gradle/develocity/bamboo/injection/MockDevelocityServer.java +++ b/src/test/java/it/com/gradle/develocity/bamboo/MockDevelocityServer.java @@ -1,4 +1,4 @@ -package it.com.gradle.develocity.bamboo.injection; +package it.com.gradle.develocity.bamboo; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.type.TypeReference; @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; -final class MockDevelocityServer implements BeforeEachCallback, AfterEachCallback { +public final class MockDevelocityServer implements BeforeEachCallback, AfterEachCallback { private static final long TEN_MEGABYTES_IN_BYTES = 1024 * 1024 * 10; @@ -30,8 +30,8 @@ final class MockDevelocityServer implements BeforeEachCallback, AfterEachCallbac private static final ObjectWriter JSON_WRITER = JSON_OBJECT_MAPPER.writer(); private static final TypeReference> MAP_TYPE_REFERENCE = - new TypeReference>() { - }; + new TypeReference>() { + }; private static final String PUBLIC_BUILD_SCAN_ID = "z7o6hj5ag6bpc"; private static final String DEFAULT_SCAN_UPLOAD_TOKEN = "scan-upload-token"; @@ -43,11 +43,14 @@ final class MockDevelocityServer implements BeforeEachCallback, AfterEachCallbac @Override public void beforeEach(ExtensionContext context) { - mockDevelocityServer = EmbeddedApp.fromHandlers(c -> c - .prefix("scans/publish", c1 -> c1 - .post("gradle/:pluginVersion/token", this::handleToken) - .post("gradle/:pluginVersion/upload", this::handleUpload) - .notFound())); + mockDevelocityServer = EmbeddedApp.fromHandlers( + c -> c + .prefix("scans/publish", c1 -> c1 + .post("gradle/:pluginVersion/token", this::handleToken) + .post("gradle/:pluginVersion/upload", this::handleUpload) + .notFound() + ) + ); } @Override @@ -58,43 +61,43 @@ public void afterEach(ExtensionContext context) { private void handleToken(Context ctx) { ctx.getRequest().getBody(TEN_MEGABYTES_IN_BYTES).then(request -> { Map requestBody = - JSON_OBJECT_MAPPER.readValue(request.getText(), MAP_TYPE_REFERENCE); + JSON_OBJECT_MAPPER.readValue(request.getText(), MAP_TYPE_REFERENCE); scanTokenRequests.add( - new ScanTokenRequest( - (String) requestBody.get("buildToolType"), - (String) requestBody.get("buildToolVersion"), - (String) requestBody.get("buildAgentVersion") - )); + new ScanTokenRequest( + (String) requestBody.get("buildToolType"), + (String) requestBody.get("buildToolVersion"), + (String) requestBody.get("buildAgentVersion") + )); Map responseBody = - ImmutableMap.of( - "id", PUBLIC_BUILD_SCAN_ID, - "scanUrl", publicBuildScanId(), - "scanUploadUrl", scanUploadUrl(ctx), - "scanUploadToken", DEFAULT_SCAN_UPLOAD_TOKEN - ); + ImmutableMap.of( + "id", PUBLIC_BUILD_SCAN_ID, + "scanUrl", publicBuildScanId(), + "scanUploadUrl", scanUploadUrl(ctx), + "scanUploadToken", DEFAULT_SCAN_UPLOAD_TOKEN + ); ctx.getResponse() - .contentType("application/vnd.gradle.scan-ack+json") - .send(JSON_WRITER.writeValueAsBytes(responseBody)); + .contentType("application/vnd.gradle.scan-ack+json") + .send(JSON_WRITER.writeValueAsBytes(responseBody)); }); } private void handleUpload(Context ctx) { ctx.getRequest().getBody(TEN_MEGABYTES_IN_BYTES) - .then(__ -> { - Response response = ctx.getResponse(); - if (rejectUpload) { - response - .status(Status.BAD_GATEWAY) - .send(); - } else { - response - .contentType("application/vnd.gradle.scan-upload-ack+json") - .send("{}"); - } - }); + .then(__ -> { + Response response = ctx.getResponse(); + if (rejectUpload) { + response + .status(Status.BAD_GATEWAY) + .send(); + } else { + response + .contentType("application/vnd.gradle.scan-upload-ack+json") + .send("{}"); + } + }); } private String scanUploadUrl(Context ctx) { diff --git a/src/test/java/it/com/gradle/develocity/bamboo/ShortLivedTokenClientTest.java b/src/test/java/it/com/gradle/develocity/bamboo/ShortLivedTokenClientTest.java new file mode 100644 index 0000000..1f30bb9 --- /dev/null +++ b/src/test/java/it/com/gradle/develocity/bamboo/ShortLivedTokenClientTest.java @@ -0,0 +1,110 @@ +package it.com.gradle.develocity.bamboo; + +import com.gradle.develocity.bamboo.DevelocityAccessCredential; +import com.gradle.develocity.bamboo.ShortLivedTokenClient; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; +import ratpack.test.embed.EmbeddedApp; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; + +public class ShortLivedTokenClientTest implements AfterEachCallback { + + private final ShortLivedTokenClient shortLivedTokenClient = new ShortLivedTokenClient(); + + private EmbeddedApp mockDevelocityServer; + + @Override + public void afterEach(ExtensionContext context) { + if (mockDevelocityServer != null) { + mockDevelocityServer.close(); + } + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {"2"}) + void shortLivedTokenIsRetrieved(String expiryInHours) { + String shortLivedToken = RandomStringUtils.randomAlphanumeric(50); + mockDevelocityServer = EmbeddedApp.fromHandlers( + c -> c.post("api/auth/token", ctx -> ctx.getResponse().status(200).send(shortLivedToken)) + ); + + DevelocityAccessCredential develocityAccessCredential = shortLivedTokenClient.get( + mockDevelocityServer.getAddress().toString(), + DevelocityAccessCredential.parse("localhost=" + RandomStringUtils.randomAlphanumeric(30), "localhost").get(), + expiryInHours + ).orElseThrow(() -> new IllegalStateException("Short lived token value is expected")); + + assertThat(develocityAccessCredential.getHostname(), equalTo("localhost")); + assertThat(develocityAccessCredential.getKey(), equalTo(shortLivedToken)); + } + + @Test + void shortLivedTokenIsNotRetrievedIfResponseIsNotSuccessful() { + AtomicInteger requestCount = new AtomicInteger(0); + mockDevelocityServer = EmbeddedApp.fromHandlers( + c -> c.post("api/auth/token", ctx -> { + requestCount.incrementAndGet(); + ctx.getResponse().status(503); + }) + ); + + Optional develocityAccessKey = shortLivedTokenClient.get( + mockDevelocityServer.getAddress().toString(), + DevelocityAccessCredential.parse("localhost=" + RandomStringUtils.randomAlphanumeric(30), "localhost").get(), + null + ); + + assertThat(develocityAccessKey.isPresent(), is(false)); + assertThat(requestCount.get(), equalTo(3)); + } + + @Test + void shortLivedTokenRetrievalFailsWithException() { + Optional develocityAccessKey = shortLivedTokenClient.get( + "http://localhost:8888", + DevelocityAccessCredential.parse("localhost=" + RandomStringUtils.randomAlphanumeric(30), "localhost").get(), + null + ); + + assertThat(develocityAccessKey.isPresent(), is(false)); + } + + @Test + void shortLivedTokenRetrievalSuccedsAfterRetry() { + AtomicBoolean firstRequest = new AtomicBoolean(true); + mockDevelocityServer = EmbeddedApp.fromHandlers( + c -> c.post("api/auth/token", ctx -> { + if (firstRequest.get()) { + firstRequest.set(false); + ctx.getResponse().status(503); + } else { + ctx.getResponse().status(200).send(RandomStringUtils.randomAlphanumeric(50)); + } + }) + ); + DevelocityAccessCredential develocityAccessCredential = shortLivedTokenClient.get( + mockDevelocityServer.getAddress().toString(), + DevelocityAccessCredential.parse("localhost=" + RandomStringUtils.randomAlphanumeric(30), "localhost").get(), + null + ).orElseThrow(() -> new IllegalStateException("Short lived token value is expected")); + + assertThat(develocityAccessCredential.getHostname(), equalTo("localhost")); + assertThat(develocityAccessCredential.getKey(), not(isEmptyOrNullString())); + } + +} diff --git a/src/test/java/it/com/gradle/develocity/bamboo/browser/PluginConfigurationBrowserTest.java b/src/test/java/it/com/gradle/develocity/bamboo/browser/PluginConfigurationBrowserTest.java index fa99706..ea1ef2d 100644 --- a/src/test/java/it/com/gradle/develocity/bamboo/browser/PluginConfigurationBrowserTest.java +++ b/src/test/java/it/com/gradle/develocity/bamboo/browser/PluginConfigurationBrowserTest.java @@ -38,6 +38,7 @@ void shouldConfigureAllFields() { .setPluginRepositoryCredentialName(pluginRepositoryCredentialName) .allowUntrustedServer() .enforceUrl() + .setShortLivedTokenExpiry("6") .enableDevelocityExtensionAutoInjection() .enableCcudExtensionAutoInjection(), @@ -48,6 +49,7 @@ void shouldConfigureAllFields() { assertThat(form.getCcudPluginVersionLocator()).hasValue("1.8.2"); assertThat(form.getPluginRepositoryLocator()).hasValue("https://plugins.gradle.org"); assertThat(form.getPluginRepositoryCredentialNameLocator()).hasValue(pluginRepositoryCredentialName); + assertThat(form.getShortLivedTokenExpiry()).hasValue("6"); assertThat(form.getAllowUntrustedServerLocator()).isChecked(); assertThat(form.getEnforceUrlLocator()).isChecked(); @@ -115,6 +117,15 @@ void invalidGradlePluginRepository() { ); } + @Test + void invalidShortLivedTokenExpiry() { + assertInvalidInput( + form -> form.setShortLivedTokenExpiry(randomString()), + "#fieldArea_saveBuildScansConfig_shortLivedTokenExpiry > div.error.control-form-error", + "Please specify a valid short-lived token expiry in hours between 1 and 24, i.e. 6" + ); + } + @Test void showsEmbeddedDevelocityExtensionVersion() { assertPluginConfiguration( diff --git a/src/test/java/it/com/gradle/develocity/bamboo/injection/GradleInjectionTest.java b/src/test/java/it/com/gradle/develocity/bamboo/injection/GradleInjectionTest.java index e1f9db2..f36bc94 100644 --- a/src/test/java/it/com/gradle/develocity/bamboo/injection/GradleInjectionTest.java +++ b/src/test/java/it/com/gradle/develocity/bamboo/injection/GradleInjectionTest.java @@ -6,6 +6,7 @@ import com.google.common.collect.Iterables; import com.gradle.develocity.bamboo.RemoteAgentProcess; import com.gradle.develocity.bamboo.model.JobKey; +import it.com.gradle.develocity.bamboo.MockDevelocityServer; import org.apache.commons.lang3.SystemUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll;