Skip to content

Commit 66623ed

Browse files
committed
Authenticate with the build cache using an access token
Closes gh-73
1 parent 709fc65 commit 66623ed

File tree

6 files changed

+113
-84
lines changed

6 files changed

+113
-84
lines changed

README.md

+20-38
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,18 @@ Conventions for Gradle projects that use the Gradle Enterprise instance hosted a
77
When applied, the conventions will configure the build cache to:
88

99
- Enable local caching.
10-
- Use https://ge.spring.io/cache/ as the remote cache.
10+
- Use https://ge.spring.io as the remote cache server.
1111
- Enable pulling from the remote cache.
12-
- Enable pushing to the remote cache if the required credentials are available.
12+
- Enable pushing to the remote cache when a CI environment is detected and the required access token is available.
1313

1414
### Remote cache
1515

1616
#### URL
1717

18-
By default, https://ge.spring.io/cache/ will be used as the remote cache.
19-
The URL can be configured using the `GRADLE_ENTERPRISE_CACHE_URL` environment variable.
20-
21-
#### Credentials
22-
23-
:rotating_light: **Credentials must not be configured in environments where pull requests are built.** :rotating_light:
24-
25-
Pushing to the remote cache requires authentication.
26-
The necessary credentials can be provided using the `GRADLE_ENTERPRISE_CACHE_USERNAME` and `GRADLE_ENTERPRISE_CACHE_PASSWORD` environment variables.
27-
28-
#### Bamboo
29-
30-
The username and password environment variables should be set using `${bamboo.gradle_enterprise_cache_user}` and `${bamboo.gradle_enterprise_cache_password}` respectively.
31-
32-
#### Concourse
33-
34-
The username and password environment variables should be set using `((gradle_enterprise_cache_user.username))` and `((gradle_enterprise_cache_user.password))` from Vault respectively.
35-
36-
#### GitHub Actions
37-
38-
The username and password environment variables should be set using the `GRADLE_ENTERPRISE_CACHE_USER` and `GRADLE_ENTERPRISE_CACHE_PASSWORD` organization secrets respectively.
39-
40-
#### Jenkins
41-
42-
The username and password environment variables should be set using the `gradle_enterprise_cache_user` username with password credential.
18+
By default, https://ge.spring.io will be used as the remote cache server.
19+
The server can be configured using the `GRADLE_ENTERPRISE_CACHE_SERVER` environment variable.
20+
For backwards compatibility, `GRADLE_ENTERPRISE_CACHE_URL` is also supported for a limited time.
21+
`/cache/` is removed from the end of the URL and the remainder is used to configure the remote cache server.
4322

4423
## Build scan conventions
4524

@@ -62,15 +41,24 @@ The build scans will be customized to:
6241
- Enable capturing of file fingerprints
6342
- Upload build scans in the foreground when running on CI
6443

65-
### Build scan publishing credentials
44+
### Git branch names
6645

67-
:rotating_light: **Credentials must not be configured in environments where pull requests are built.** :rotating_light:
46+
`git rev-parse --abbrev-ref HEAD` is used to determine the name of the current branch.
47+
This does not work on Concourse as its git resource places the repository in a detached head state.
48+
To work around this, an environment variable named `BRANCH` can be set on the task to provide the name of the branch.
6849

69-
Publishing to [ge.spring.io](https://ge.spring.io) requires authentication via an access key.
70-
When running on CI, the access key should be made available via the `GRADLE_ENTERPRISE_ACCESS_KEY` environment variable.
50+
### Anonymous publication
7151

7252
When using Gradle, build scans can be published anonymously to scans.gradle.com by running the build with `--scan`.
7353

54+
## Authentication
55+
56+
:rotating_light: **Credentials must not be configured in environments where pull requests are built.** :rotating_light:
57+
58+
Publishing build scans and pushing to the remote cache requires authentication via an access key.
59+
Additionally, pushing to the remote cache also requires that a CI environment be detected.
60+
When running on CI, the access key should be made available via the `GRADLE_ENTERPRISE_ACCESS_KEY` environment variable.
61+
7462
#### Bamboo
7563

7664
The environment variable should be set to `${bamboo.gradle_enterprise_secret_access_key}`.
@@ -91,13 +79,7 @@ The environment variable should be set using the `gradle_enterprise_secret_acces
9179

9280
An access key can be provisioned by running `./gradlew provisionGradleEnterpriseAccessKey` once the project has been configured to use this plugin.
9381

94-
### Git branch names
95-
96-
`git rev-parse --abbrev-ref HEAD` is used to determine the name of the current branch.
97-
This does not work on Concourse as its git resource places the repository in a detached head state.
98-
To work around this, an environment variable named `BRANCH` can be set on the task to provide the name of the branch.
99-
100-
### Detecting CI
82+
## Detecting CI
10183

10284
Bamboo is detected by looking for an environment variable named `bamboo_resultsUrl`.
10385

gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
version=0.0.17-SNAPSHOT
22

3-
gradleEnterprisePluginVersion=3.17
3+
gradleEnterprisePluginVersion=3.17.2
44
javaFormatVersion=0.0.39

src/main/java/io/spring/ge/conventions/gradle/BuildCacheConventions.java

+32-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2022 the original author or authors.
2+
* Copyright 2020-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,24 +18,27 @@
1818

1919
import java.util.Map;
2020

21+
import com.gradle.develocity.agent.gradle.buildcache.DevelocityBuildCache;
2122
import org.gradle.caching.configuration.BuildCacheConfiguration;
22-
import org.gradle.caching.http.HttpBuildCache;
2323

2424
/**
25-
* Conventions that are applied to the build cache for Maven and Gradle builds.
25+
* Conventions that are applied to the build cache.
2626
*
2727
* @author Andy Wilkinson
2828
*/
2929
public class BuildCacheConventions {
3030

3131
private final Map<String, String> env;
3232

33-
public BuildCacheConventions() {
34-
this(System.getenv());
33+
private final Class<? extends DevelocityBuildCache> buildCacheType;
34+
35+
public BuildCacheConventions(Class<? extends DevelocityBuildCache> buildCache) {
36+
this(buildCache, System.getenv());
3537
}
3638

37-
BuildCacheConventions(Map<String, String> env) {
39+
BuildCacheConventions(Class<? extends DevelocityBuildCache> buildCacheType, Map<String, String> env) {
3840
this.env = env;
41+
this.buildCacheType = buildCacheType;
3942
}
4043

4144
/**
@@ -44,21 +47,35 @@ public BuildCacheConventions() {
4447
*/
4548
public void execute(BuildCacheConfiguration buildCache) {
4649
buildCache.local((local) -> local.setEnabled(true));
47-
buildCache.remote(HttpBuildCache.class, (remote) -> {
50+
buildCache.remote(this.buildCacheType, (remote) -> {
4851
remote.setEnabled(true);
49-
remote.setUrl(this.env.getOrDefault("GRADLE_ENTERPRISE_CACHE_URL", "https://ge.spring.io/cache/"));
50-
String username = this.env.get("GRADLE_ENTERPRISE_CACHE_USERNAME");
51-
String password = this.env.get("GRADLE_ENTERPRISE_CACHE_PASSWORD");
52-
if (hasText(username) && hasText(password)) {
52+
String cacheServer = this.env.get("GRADLE_ENTERPRISE_CACHE_SERVER");
53+
if (cacheServer == null) {
54+
cacheServer = serverOfCacheUrl(this.env.get("GRADLE_ENTERPRISE_CACHE_URL"));
55+
if (cacheServer == null) {
56+
cacheServer = "https://ge.spring.io";
57+
}
58+
}
59+
remote.setServer(cacheServer);
60+
String accessKey = this.env.get("GRADLE_ENTERPRISE_ACCESS_KEY");
61+
if (hasText(accessKey) && ContinuousIntegration.detect(this.env) != null) {
5362
remote.setPush(true);
54-
remote.credentials((credentials) -> {
55-
credentials.setUsername(username);
56-
credentials.setPassword(password);
57-
});
5863
}
5964
});
6065
}
6166

67+
private String serverOfCacheUrl(String cacheUrl) {
68+
if (cacheUrl != null) {
69+
if (cacheUrl.endsWith("/cache/")) {
70+
return cacheUrl.substring(0, cacheUrl.length() - 7);
71+
}
72+
if (cacheUrl.endsWith("/cache")) {
73+
return cacheUrl.substring(0, cacheUrl.length() - 6);
74+
}
75+
}
76+
return null;
77+
}
78+
6279
private boolean hasText(String string) {
6380
return string != null && string.length() > 0;
6481
}

src/main/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPlugin.java

+6-7
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,13 @@ public GradleEnterpriseConventionsPlugin(ProcessOperations processOperations) {
4545

4646
@Override
4747
public void apply(Settings settings) {
48-
settings.getPlugins().withType(DevelocityPlugin.class, (plugin) -> {
49-
DevelocityConfiguration extension = settings.getExtensions().getByType(DevelocityConfiguration.class);
50-
configureBuildScanConventions(extension, extension.getBuildScan(), settings.getStartParameter(),
51-
settings.getRootDir());
52-
});
48+
DevelocityConfiguration extension = settings.getExtensions().getByType(DevelocityConfiguration.class);
49+
settings.getPlugins()
50+
.withType(DevelocityPlugin.class, (plugin) -> configureBuildScanConventions(extension,
51+
extension.getBuildScan(), settings.getStartParameter(), settings.getRootDir()));
5352
if (settings.getStartParameter().isBuildCacheEnabled()) {
54-
settings
55-
.buildCache((buildCacheConfiguration) -> new BuildCacheConventions().execute(buildCacheConfiguration));
53+
settings.buildCache((buildCacheConfiguration) -> new BuildCacheConventions(extension.getBuildCache())
54+
.execute(buildCacheConfiguration));
5655
}
5756
}
5857

src/test/java/io/spring/ge/conventions/gradle/BuildCacheConventionsTests.java

+48-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2022 the original author or authors.
2+
* Copyright 2020-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,17 +16,19 @@
1616

1717
package io.spring.ge.conventions.gradle;
1818

19-
import java.net.URI;
19+
import java.util.Collections;
2020
import java.util.HashMap;
2121
import java.util.Map;
2222

23+
import com.gradle.develocity.agent.gradle.buildcache.DevelocityBuildCache;
2324
import org.gradle.api.Action;
2425
import org.gradle.caching.BuildCacheServiceFactory;
2526
import org.gradle.caching.configuration.BuildCache;
2627
import org.gradle.caching.configuration.BuildCacheConfiguration;
27-
import org.gradle.caching.http.HttpBuildCache;
2828
import org.gradle.caching.local.DirectoryBuildCache;
2929
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.params.ParameterizedTest;
31+
import org.junit.jupiter.params.provider.ValueSource;
3032

3133
import static org.assertj.core.api.Assertions.assertThat;
3234

@@ -41,44 +43,73 @@ class BuildCacheConventionsTests {
4143

4244
@Test
4345
void localCacheIsEnabled() {
44-
new BuildCacheConventions().execute(this.buildCache);
46+
new BuildCacheConventions(DevelocityBuildCache.class).execute(this.buildCache);
4547
assertThat(this.buildCache.local.isEnabled()).isTrue();
4648
}
4749

4850
@Test
4951
void remoteCacheIsEnabled() {
50-
new BuildCacheConventions().execute(this.buildCache);
52+
new BuildCacheConventions(DevelocityBuildCache.class).execute(this.buildCache);
5153
assertThat(this.buildCache.remote.isEnabled()).isTrue();
52-
assertThat(this.buildCache.remote.getUrl()).isEqualTo(URI.create("https://ge.spring.io/cache/"));
54+
assertThat(this.buildCache.remote.getServer()).isEqualTo("https://ge.spring.io");
55+
assertThat(this.buildCache.remote.isPush()).isFalse();
56+
}
57+
58+
@ParameterizedTest
59+
@ValueSource(strings = { "https://ge.example.com/cache/", "https://ge.example.com/cache" })
60+
void remoteCacheUrlCanBeConfigured(String cacheUrl) {
61+
Map<String, String> env = new HashMap<>();
62+
env.put("GRADLE_ENTERPRISE_CACHE_URL", cacheUrl);
63+
new BuildCacheConventions(DevelocityBuildCache.class, env).execute(this.buildCache);
64+
assertThat(this.buildCache.remote.isEnabled()).isTrue();
65+
assertThat(this.buildCache.remote.getServer()).isEqualTo("https://ge.example.com");
5366
assertThat(this.buildCache.remote.isPush()).isFalse();
5467
}
5568

5669
@Test
57-
void remoteCacheUrlCanBeConfigured() {
70+
void remoteCacheServerCanBeConfigured() {
5871
Map<String, String> env = new HashMap<>();
59-
env.put("GRADLE_ENTERPRISE_CACHE_URL", "https://ge.example.com/cache/");
60-
new BuildCacheConventions(env).execute(this.buildCache);
72+
env.put("GRADLE_ENTERPRISE_CACHE_SERVER", "https://ge.example.com");
73+
new BuildCacheConventions(DevelocityBuildCache.class, env).execute(this.buildCache);
6174
assertThat(this.buildCache.remote.isEnabled()).isTrue();
62-
assertThat(this.buildCache.remote.getUrl()).isEqualTo(URI.create("https://ge.example.com/cache/"));
75+
assertThat(this.buildCache.remote.getServer()).isEqualTo("https://ge.example.com");
76+
assertThat(this.buildCache.remote.isPush()).isFalse();
77+
}
78+
79+
@Test
80+
void remoteCacheServerHasPrecedenceOverRemoteCacheUrl() {
81+
Map<String, String> env = new HashMap<>();
82+
env.put("GRADLE_ENTERPRISE_CACHE_URL", "https://ge-cache.example.com/cache/");
83+
env.put("GRADLE_ENTERPRISE_CACHE_SERVER", "https://ge.example.com");
84+
new BuildCacheConventions(DevelocityBuildCache.class, env).execute(this.buildCache);
85+
assertThat(this.buildCache.remote.isEnabled()).isTrue();
86+
assertThat(this.buildCache.remote.getServer()).isEqualTo("https://ge.example.com");
87+
assertThat(this.buildCache.remote.isPush()).isFalse();
88+
}
89+
90+
@Test
91+
void whenAccessTokenIsProvidedInALocalEnvironmentThenPushingToTheRemoteCacheIsNotEnabled() {
92+
new BuildCacheConventions(DevelocityBuildCache.class,
93+
Collections.singletonMap("GRADLE_ENTERPRISE_ACCESS_KEY", "ge.example.com=a1b2c3d4"))
94+
.execute(this.buildCache);
6395
assertThat(this.buildCache.remote.isPush()).isFalse();
6496
}
6597

6698
@Test
67-
void whenCredentialsAreProvidedThenPushingToTheRemoteCacheIsEnabled() {
99+
void whenAccessTokenIsProvidedInACiEnvironmentThenPushingToTheRemoteCacheIsNotEnabled() {
68100
Map<String, String> env = new HashMap<>();
69-
env.put("GRADLE_ENTERPRISE_CACHE_USERNAME", "user");
70-
env.put("GRADLE_ENTERPRISE_CACHE_PASSWORD", "secret");
71-
new BuildCacheConventions(env).execute(this.buildCache);
101+
env.put("GRADLE_ENTERPRISE_ACCESS_KEY", "ge.example.com=a1b2c3d4");
102+
env.put("CI", "true");
103+
new BuildCacheConventions(DevelocityBuildCache.class, env).execute(this.buildCache);
72104
assertThat(this.buildCache.remote.isPush()).isTrue();
73-
assertThat(this.buildCache.remote.getCredentials().getUsername()).isEqualTo("user");
74-
assertThat(this.buildCache.remote.getCredentials().getPassword()).isEqualTo("secret");
75105
}
76106

77107
private static final class TestBuildCacheConfiguration implements BuildCacheConfiguration {
78108

79109
private final DirectoryBuildCache local = new DirectoryBuildCache();
80110

81-
private final HttpBuildCache remote = new HttpBuildCache();
111+
private final DevelocityBuildCache remote = new DevelocityBuildCache() {
112+
};
82113

83114
@Override
84115
public DirectoryBuildCache getLocal() {

src/test/java/io/spring/ge/conventions/gradle/GradleEnterpriseConventionsPluginIntegrationTests.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ void whenThePluginIsAppliedThenBuildScanConventionsAreApplied(@TempDir File proj
5454
void whenThePluginIsAppliedThenBuildCacheConventionsAreApplied(@TempDir File projectDir) {
5555
prepareProject(projectDir);
5656
BuildResult result = build(projectDir, "6.0.1", "verifyBuildCacheConfig");
57-
assertThat(result.getOutput()).contains("Build cache remote: https://ge.spring.io/cache/");
57+
assertThat(result.getOutput()).contains("Build cache server: https://ge.spring.io");
5858
}
5959

6060
@Test
@@ -95,7 +95,7 @@ void whenThePluginIsAppliedAndScanIsSpecifiedThenServerIsNotCustomized(@TempDir
9595
void whenThePluginIsAppliedAndBuildCacheIsDisabledThenBuildCacheConventionsAreNotApplied(@TempDir File projectDir) {
9696
prepareProject(projectDir);
9797
BuildResult result = build(projectDir, "6.0.1", "verifyBuildCacheConfig", "--no-build-cache");
98-
assertThat(result.getOutput()).contains("Build cache remote: null");
98+
assertThat(result.getOutput()).contains("Build cache server: null");
9999
}
100100

101101
private void prepareProject(File projectDir) {
@@ -116,8 +116,8 @@ private void prepareProject(File projectDir) {
116116
writer.println("}");
117117
writer.println("task verifyBuildCacheConfig {");
118118
writer.println(" doFirst {");
119-
writer
120-
.println(" println \"Build cache remote: ${project.ext['settings'].buildCache?.remote?.url}\"");
119+
writer.println(
120+
" println \"Build cache server: ${project.ext['settings'].buildCache?.remote?.server}\"");
121121
writer.println(" }");
122122
writer.println("}");
123123
});
@@ -142,8 +142,8 @@ private void prepareMultiModuleProject(File projectDir) {
142142
writer.println("}");
143143
writer.println("task verifyBuildCacheConfig {");
144144
writer.println(" doFirst {");
145-
writer
146-
.println(" println \"Build cache remote: ${project.ext['settings'].buildCache?.remote?.url}\"");
145+
writer.println(
146+
" println \"Build cache server: ${project.ext['settings'].buildCache?.remote?.server}\"");
147147
writer.println(" }");
148148
writer.println("}");
149149
});

0 commit comments

Comments
 (0)