Skip to content

Commit e36aaff

Browse files
authored
Support conventional server URL variable (#468)
As follow-up to #455 / #338, support the conventional server URL variable that's already set up for official Develocity tooling, such as - any Gradle project using [`common-custom-user-data-gradle-plugin`][1] with a set [Develocity URL override][2] - the [Develocity Python agent][3] `Config.apiUrl` and `DEVELOCITY_API_URL` are renamed to `Config.server` and `DEVELOCITY_URL` for consistency with official Develocity tooling nomenclature. `Config.server` is strongly typed as a URI and validation is improved. [1]: https://github.com/gradle/common-custom-user-data-gradle-plugin [2]: https://github.com/gradle/common-custom-user-data-gradle-plugin/tree/main?tab=readme-ov-file#configuration-overrides [3]: https://docs.gradle.com/develocity/python-agent/#environment_variables
1 parent a512da1 commit e36aaff

File tree

12 files changed

+108
-85
lines changed

12 files changed

+108
-85
lines changed

.github/workflows/pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
kotlin-tests:
1313
runs-on: ubuntu-latest
1414
env:
15-
DEVELOCITY_API_URL: "${{ vars.DEVELOCITY_API_URL }}"
15+
DEVELOCITY_URL: "${{ vars.DEVELOCITY_URL }}"
1616
DEVELOCITY_ACCESS_KEY: "${{ secrets.DEVELOCITY_ACCESS_KEY }}"
1717
DEVELOCITY_API_CACHE_ENABLED: "false"
1818
steps:

.github/workflows/publish-library.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
build-and-publish:
2626
runs-on: ubuntu-latest
2727
env:
28-
DEVELOCITY_API_URL: "${{ vars.DEVELOCITY_API_URL }}"
28+
DEVELOCITY_URL: "${{ vars.DEVELOCITY_URL }}"
2929
DEVELOCITY_ACCESS_KEY: "${{ secrets.DEVELOCITY_ACCESS_KEY }}"
3030
steps:
3131
- name: Checkout

library/api/library.api

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,25 +78,25 @@ public final class com/gabrielfeo/develocity/api/BuildsApi$DefaultImpls {
7878

7979
public final class com/gabrielfeo/develocity/api/Config {
8080
public fun <init> ()V
81-
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lokhttp3/OkHttpClient$Builder;Ljava/lang/Integer;JLcom/gabrielfeo/develocity/api/Config$CacheConfig;)V
82-
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lokhttp3/OkHttpClient$Builder;Ljava/lang/Integer;JLcom/gabrielfeo/develocity/api/Config$CacheConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
81+
public fun <init> (Ljava/lang/String;Ljava/net/URI;Lkotlin/jvm/functions/Function0;Lokhttp3/OkHttpClient$Builder;Ljava/lang/Integer;JLcom/gabrielfeo/develocity/api/Config$CacheConfig;)V
82+
public synthetic fun <init> (Ljava/lang/String;Ljava/net/URI;Lkotlin/jvm/functions/Function0;Lokhttp3/OkHttpClient$Builder;Ljava/lang/Integer;JLcom/gabrielfeo/develocity/api/Config$CacheConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
8383
public final fun component1 ()Ljava/lang/String;
84-
public final fun component2 ()Ljava/lang/String;
84+
public final fun component2 ()Ljava/net/URI;
8585
public final fun component3 ()Lkotlin/jvm/functions/Function0;
8686
public final fun component4 ()Lokhttp3/OkHttpClient$Builder;
8787
public final fun component5 ()Ljava/lang/Integer;
8888
public final fun component6 ()J
8989
public final fun component7 ()Lcom/gabrielfeo/develocity/api/Config$CacheConfig;
90-
public final fun copy (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lokhttp3/OkHttpClient$Builder;Ljava/lang/Integer;JLcom/gabrielfeo/develocity/api/Config$CacheConfig;)Lcom/gabrielfeo/develocity/api/Config;
91-
public static synthetic fun copy$default (Lcom/gabrielfeo/develocity/api/Config;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lokhttp3/OkHttpClient$Builder;Ljava/lang/Integer;JLcom/gabrielfeo/develocity/api/Config$CacheConfig;ILjava/lang/Object;)Lcom/gabrielfeo/develocity/api/Config;
90+
public final fun copy (Ljava/lang/String;Ljava/net/URI;Lkotlin/jvm/functions/Function0;Lokhttp3/OkHttpClient$Builder;Ljava/lang/Integer;JLcom/gabrielfeo/develocity/api/Config$CacheConfig;)Lcom/gabrielfeo/develocity/api/Config;
91+
public static synthetic fun copy$default (Lcom/gabrielfeo/develocity/api/Config;Ljava/lang/String;Ljava/net/URI;Lkotlin/jvm/functions/Function0;Lokhttp3/OkHttpClient$Builder;Ljava/lang/Integer;JLcom/gabrielfeo/develocity/api/Config$CacheConfig;ILjava/lang/Object;)Lcom/gabrielfeo/develocity/api/Config;
9292
public fun equals (Ljava/lang/Object;)Z
9393
public final fun getAccessKey ()Lkotlin/jvm/functions/Function0;
94-
public final fun getApiUrl ()Ljava/lang/String;
9594
public final fun getCacheConfig ()Lcom/gabrielfeo/develocity/api/Config$CacheConfig;
9695
public final fun getClientBuilder ()Lokhttp3/OkHttpClient$Builder;
9796
public final fun getLogLevel ()Ljava/lang/String;
9897
public final fun getMaxConcurrentRequests ()Ljava/lang/Integer;
9998
public final fun getReadTimeoutMillis ()J
99+
public final fun getServer ()Ljava/net/URI;
100100
public fun hashCode ()I
101101
public fun toString ()Ljava/lang/String;
102102
}

library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/DevelocityApiIntegrationTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package com.gabrielfeo.develocity.api
33
import com.gabrielfeo.develocity.api.internal.*
44
import com.google.common.reflect.ClassPath
55
import kotlinx.coroutines.test.runTest
6-
import okhttp3.OkHttpClient
76
import org.junit.jupiter.api.assertDoesNotThrow
7+
import java.net.URI
88
import kotlin.reflect.KVisibility.PUBLIC
99
import kotlin.reflect.full.memberProperties
1010
import kotlin.reflect.javaType
@@ -35,8 +35,8 @@ class DevelocityApiIntegrationTest {
3535
env = FakeEnv()
3636
assertDoesNotThrow {
3737
val config = Config(
38-
apiUrl = "https://google.com/api/",
39-
accessKey = { "" },
38+
server = URI("https://example.com/"),
39+
accessKey = { "example.com=example-token" }
4040
)
4141
DevelocityApi.newInstance(config)
4242
}

library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package com.gabrielfeo.develocity.api
22

3-
import com.gabrielfeo.develocity.api.internal.auth.accessKeyResolver
4-
import com.gabrielfeo.develocity.api.internal.basicOkHttpClient
5-
import com.gabrielfeo.develocity.api.internal.env
6-
import com.gabrielfeo.develocity.api.internal.systemProperties
3+
import com.gabrielfeo.develocity.api.internal.*
4+
import com.gabrielfeo.develocity.api.internal.auth.*
75
import okhttp3.Dispatcher
6+
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
87
import okhttp3.OkHttpClient
98
import java.io.File
109
import java.net.URI
@@ -44,25 +43,28 @@ data class Config(
4443
?: "off",
4544

4645
/**
47-
* Provides the URL of a Develocity API instance REST API. By default, uses
48-
* environment variable `DEVELOCITY_API_URL`. Must end with `/api/`.
46+
* Provides the URL of a Develocity server to use in API requests. By default, uses environment
47+
* variable `DEVELOCITY_URL`. Must be a valid URL with no path segments (trailing slash OK) or
48+
* query parameters.
49+
*
50+
* Example value: `https://develocity.example.com/`
4951
*/
50-
val apiUrl: String =
51-
env["DEVELOCITY_API_URL"]
52-
?.also { requireValidUrl(it) }
53-
?: error(ERROR_NULL_API_URL),
52+
val server: URI =
53+
requireNotNull(env["DEVELOCITY_URL"]?.let(::URI)) { ERROR_NULL_DEVELOCITY_URL },
5454

5555
/**
56-
* Provides the access key for a Develocity API instance. By default, resolves to the first
57-
* key from these sources that matches the host of [apiUrl]:
56+
* Provides the access key for the Develocity server. By default, resolves to the first key from
57+
* these sources that matches the host of [server]:
5858
*
5959
* - variable `DEVELOCITY_ACCESS_KEY`
6060
* - variable `GRADLE_ENTERPRISE_ACCESS_KEY`
6161
* - file `$GRADLE_USER_HOME/.gradle/develocity/keys.properties` or, if `GRADLE_USER_HOME` is
6262
* not set, `~/.gradle/develocity/keys.properties`
6363
* - file `~/.m2/.develocity/keys.properties`
6464
*
65-
* Refer to Develocity documentation for details on the format of such variables and files:
65+
* Example value: `develocity.example.com=abcdefg1234567`
66+
*
67+
* Refer to Develocity documentation for more details on the format of such variables and files:
6668
*
6769
* - [Develocity Gradle Plugin User Manual][1]
6870
* - [Develocity Maven Extension User Manual][2]
@@ -73,8 +75,7 @@ data class Config(
7375
* @throws IllegalArgumentException if no matching key is found.
7476
*/
7577
val accessKey: () -> String = {
76-
val host = URI(apiUrl).host
77-
requireNotNull(accessKeyResolver.resolve(host)) { ERROR_NULL_ACCESS_KEY }
78+
requireNotNull(accessKeyResolver.resolve(server.host)) { ERROR_NULL_ACCESS_KEY }
7879
},
7980

8081
/**
@@ -118,6 +119,10 @@ data class Config(
118119
CacheConfig(),
119120
) {
120121

122+
init {
123+
requireValidBaseUrl(server)
124+
}
125+
121126
/**
122127
* HTTP cache is off by default, but can speed up requests significantly. The Develocity
123128
* API disallows HTTP caching, but this library forcefully enables it by overwriting
@@ -236,14 +241,17 @@ data class Config(
236241
)
237242
}
238243

239-
private fun requireValidUrl(string: String) {
240-
requireNotNull(runCatching { URI(string) }.getOrNull()) {
241-
ERROR_MALFORMED_API_URL.format(string)
242-
}
244+
245+
private fun requireValidBaseUrl(url: URI) {
246+
require(url.scheme == "http" || url.scheme == "https") { ERROR_MALFORMED_DEVELOCITY_URL.format(url) }
247+
require(url.path.isNullOrEmpty() || url.path == "/") { ERROR_MALFORMED_DEVELOCITY_URL.format(url) }
248+
require(url.query == null) { ERROR_MALFORMED_DEVELOCITY_URL.format(url) }
249+
requireNotNull(url.toHttpUrlOrNull()) { ERROR_MALFORMED_DEVELOCITY_URL.format(url) }
243250
}
244251

245-
private const val ERROR_NULL_API_URL = "DEVELOCITY_API_URL is required"
246-
private const val ERROR_MALFORMED_API_URL = "DEVELOCITY_API_URL contains a malformed URL: %s"
252+
private const val ERROR_NULL_DEVELOCITY_URL = "DEVELOCITY_URL is required"
253+
private const val ERROR_MALFORMED_DEVELOCITY_URL = "DEVELOCITY_URL must be a valid HTTP or HTTPS " +
254+
"URL to a Develocity server, with no path or query parameters: %s"
247255
private const val ERROR_NULL_ACCESS_KEY = "Develocity access key not found. " +
248256
"Please set DEVELOCITY_ACCESS_KEY='[host]=[accessKey]' or see Config.accessKey javadoc for " +
249257
"other supported options."

library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Retrofit.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.gabrielfeo.develocity.api.internal
22

3-
import com.gabrielfeo.develocity.api.Config
3+
import com.gabrielfeo.develocity.api.*
44
import com.squareup.moshi.Moshi
55
import okhttp3.OkHttpClient
66
import retrofit2.Retrofit
@@ -12,10 +12,7 @@ internal fun buildRetrofit(
1212
client: OkHttpClient,
1313
moshi: Moshi,
1414
) = with(Retrofit.Builder()) {
15-
val url = config.apiUrl
16-
check("/api/" in url) { "A valid API URL must end in /api/" }
17-
val instanceUrl = url.substringBefore("api/")
18-
baseUrl(instanceUrl)
15+
baseUrl(config.server.resolve("/").toString())
1916
addConverterFactory(ScalarsConverterFactory.create())
2017
addConverterFactory(MoshiConverterFactory.create(moshi))
2118
client(client)

library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
package com.gabrielfeo.develocity.api
22

3-
import com.gabrielfeo.develocity.api.internal.*
43
import kotlin.test.*
54

65
class CacheConfigTest {
76

8-
@BeforeTest
9-
fun before() {
10-
env = FakeEnv("DEVELOCITY_API_URL" to "https://example.com/api/")
11-
}
12-
137
@Test
148
fun `default longTermCacheUrlPattern matches attributes URLs`() {
159
Config.CacheConfig().longTermCacheUrlPattern.assertMatches(

library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
package com.gabrielfeo.develocity.api
22

3-
import com.gabrielfeo.develocity.api.internal.FakeEnv
4-
import com.gabrielfeo.develocity.api.internal.FakeSystemProperties
5-
import com.gabrielfeo.develocity.api.internal.auth.AccessKeyResolver
6-
import com.gabrielfeo.develocity.api.internal.auth.accessKeyResolver
7-
import com.gabrielfeo.develocity.api.internal.env
8-
import com.gabrielfeo.develocity.api.internal.systemProperties
3+
import com.gabrielfeo.develocity.api.internal.*
4+
import com.gabrielfeo.develocity.api.internal.auth.*
95
import okio.Path.Companion.toPath
106
import okio.fakefilesystem.FakeFileSystem
7+
import org.junit.jupiter.api.DynamicTest.dynamicTest
8+
import org.junit.jupiter.api.TestFactory
119
import org.junit.jupiter.api.assertDoesNotThrow
12-
import kotlin.test.BeforeTest
13-
import kotlin.test.Test
14-
import kotlin.test.assertEquals
15-
import kotlin.test.assertFails
10+
import java.net.URI
11+
import kotlin.test.*
1612

1713
class ConfigTest {
1814

1915
@BeforeTest
2016
fun before() {
21-
env = FakeEnv("DEVELOCITY_API_URL" to "https://example.com/api/")
17+
env = FakeEnv("DEVELOCITY_URL" to "https://example.com/")
2218
systemProperties = FakeSystemProperties()
2319
accessKeyResolver = AccessKeyResolver(
2420
env,
@@ -30,38 +26,64 @@ class ConfigTest {
3026
@Test
3127
fun `Given no URL set in env, error`() {
3228
env = FakeEnv()
33-
assertFails {
29+
assertFailsWith<IllegalArgumentException> {
3430
Config()
3531
}
3632
}
3733

3834
@Test
39-
fun `Given URL set in env, apiUrl is env URL`() {
40-
(env as FakeEnv)["DEVELOCITY_API_URL"] = "https://example.com/api/"
41-
assertEquals("https://example.com/api/", Config().apiUrl)
35+
fun `Given server URL set in env, server is correct URL`() {
36+
(env as FakeEnv)["DEVELOCITY_URL"] = "https://example.com/"
37+
assertEquals(URI("https://example.com/"), Config().server)
38+
}
39+
40+
@Test
41+
fun `Given server URL set in code, server is correct URL`() {
42+
val config = Config(server = URI("https://custom.example.com/"))
43+
assertEquals(URI("https://custom.example.com/"), config.server)
44+
}
45+
46+
@TestFactory
47+
fun `Given malformed URL, error`() = listOf(
48+
"mailto:foo@bar.com",
49+
"file:///example/foo",
50+
"http://example.com?foo",
51+
"https://example.com?foo",
52+
"https://example.com/foo",
53+
"https://example.com/foo?bar=1",
54+
"https://example.com/foo/bar/baz",
55+
"https://example.com/foo/bar/baz?qux=1",
56+
).map { url ->
57+
dynamicTest(url) {
58+
assertFailsWith<IllegalArgumentException> {
59+
Config(server = URI(url))
60+
}
61+
}
4262
}
4363

4464
@Test
4565
fun `Given default access key function and resolvable key, accessKey is key`() {
46-
(env as FakeEnv)["DEVELOCITY_API_URL"] = "https://example.com/api/"
66+
(env as FakeEnv)["DEVELOCITY_URL"] = "https://example.com/"
4767
(env as FakeEnv)["DEVELOCITY_ACCESS_KEY"] = "example.com=foo"
4868
assertEquals("foo", Config().accessKey())
4969
}
5070

5171
@Test
52-
fun `Given default access key and no resolvable key, error`() {
53-
(env as FakeEnv)["DEVELOCITY_API_URL"] = "https://example.com/api/"
72+
fun `Given default access key function and no resolvable key, error`() {
73+
(env as FakeEnv)["DEVELOCITY_URL"] = "https://example.com/"
5474
(env as FakeEnv)["DEVELOCITY_ACCESS_KEY"] = "notexample.com=foo"
55-
assertFails {
75+
assertFailsWith<IllegalArgumentException> {
5676
Config().accessKey()
5777
}
5878
}
5979

6080
@Test
61-
fun `Given custom access key function fails, error`() {
62-
assertFails {
63-
Config(accessKey = { error("foo") }).accessKey()
81+
fun `Given custom access key function fails, uncaught and unwrapped error`() {
82+
val error = assertFails {
83+
Config(accessKey = { throw RuntimeException("foo") }).accessKey()
6484
}
85+
assertIs<RuntimeException>(error)
86+
assertEquals("foo", error.message)
6587
}
6688

6789
@Test

library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ package com.gabrielfeo.develocity.api
33
import com.gabrielfeo.develocity.api.internal.*
44
import org.junit.jupiter.api.assertDoesNotThrow
55
import org.junit.jupiter.api.assertThrows
6-
import kotlin.test.Test
7-
import kotlin.test.assertContains
6+
import kotlin.test.*
87

98
class DevelocityApiTest {
109

@@ -14,12 +13,12 @@ class DevelocityApiTest {
1413
val error = assertThrows<Exception> {
1514
DevelocityApi.newInstance(Config())
1615
}
17-
error.assertRootMessageContains("DEVELOCITY_API_URL")
16+
error.assertRootMessageContains("DEVELOCITY_URL")
1817
}
1918

2019
@Test
2120
fun `Fails lazily if no access key`() {
22-
env = FakeEnv("DEVELOCITY_API_URL" to "https://example.com/api/")
21+
env = FakeEnv("DEVELOCITY_URL" to "https://example.com/")
2322
val api = assertDoesNotThrow {
2423
DevelocityApi.newInstance(Config())
2524
}

library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ class OkHttpClientTest {
7272
val fakeEnv = FakeEnv(*envVars)
7373
if ("DEVELOCITY_ACCESS_KEY" !in fakeEnv)
7474
fakeEnv["DEVELOCITY_ACCESS_KEY"] = "example.com=example-token"
75-
if ("DEVELOCITY_API_URL" !in fakeEnv)
76-
fakeEnv["DEVELOCITY_API_URL"] = "https://example.com/api/"
75+
if ("DEVELOCITY_URL" !in fakeEnv)
76+
fakeEnv["DEVELOCITY_URL"] = "https://example.com/"
7777
env = fakeEnv
7878
systemProperties = FakeSystemProperties()
7979
accessKeyResolver = AccessKeyResolver(

0 commit comments

Comments
 (0)