Skip to content

Commit 356837d

Browse files
authored
[Internal] Add CICD environment to User Agent (#404)
## What changes are proposed in this pull request? This PR adds CI/CD environment to the User Agent of each SDK outbound request. The implementation is similar to the one used in the Go SDK. This is useful to track how often and from which CI/CD platforms our tools are being run. ## How is this tested? Added unit tests.
1 parent c750aae commit 356837d

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

databricks-sdk-java/src/main/java/com/databricks/sdk/core/UserAgent.java

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.databricks.sdk.core;
22

3+
import com.databricks.sdk.core.utils.Environment;
4+
import java.io.File;
35
import java.util.ArrayList;
6+
import java.util.Arrays;
7+
import java.util.Collections;
48
import java.util.List;
59
import java.util.regex.Pattern;
610
import java.util.stream.Collectors;
@@ -121,6 +125,10 @@ public static String asString() {
121125
segments.add(String.format("databricks-sdk-java/%s", version));
122126
segments.add(String.format("jvm/%s", jvmVersion()));
123127
segments.add(String.format("os/%s", osName()));
128+
String cicdProvider = cicdProvider();
129+
if (!cicdProvider.isEmpty()) {
130+
segments.add(String.format("cicd/%s", cicdProvider));
131+
}
124132
// Concurrent iteration over ArrayList must be guarded with synchronized.
125133
synchronized (otherInfo) {
126134
segments.addAll(
@@ -130,4 +138,107 @@ public static String asString() {
130138
}
131139
return segments.stream().collect(Collectors.joining(" "));
132140
}
141+
142+
// List of CI/CD providers and their environment variables for detection
143+
private static List<CicdProvider> listCiCdProviders() {
144+
return Arrays.asList(
145+
new CicdProvider("github", Collections.singletonList(new EnvVar("GITHUB_ACTIONS", "true"))),
146+
new CicdProvider("gitlab", Collections.singletonList(new EnvVar("GITLAB_CI", "true"))),
147+
new CicdProvider("jenkins", Collections.singletonList(new EnvVar("JENKINS_URL", ""))),
148+
new CicdProvider("azure-devops", Collections.singletonList(new EnvVar("TF_BUILD", "True"))),
149+
new CicdProvider("circle", Collections.singletonList(new EnvVar("CIRCLECI", "true"))),
150+
new CicdProvider("travis", Collections.singletonList(new EnvVar("TRAVIS", "true"))),
151+
new CicdProvider(
152+
"bitbucket", Collections.singletonList(new EnvVar("BITBUCKET_BUILD_NUMBER", ""))),
153+
new CicdProvider(
154+
"google-cloud-build",
155+
Arrays.asList(
156+
new EnvVar("PROJECT_ID", ""),
157+
new EnvVar("BUILD_ID", ""),
158+
new EnvVar("PROJECT_NUMBER", ""),
159+
new EnvVar("LOCATION", ""))),
160+
new CicdProvider(
161+
"aws-code-build", Collections.singletonList(new EnvVar("CODEBUILD_BUILD_ARN", ""))),
162+
new CicdProvider("tf-cloud", Collections.singletonList(new EnvVar("TFC_RUN_ID", ""))));
163+
}
164+
165+
// Volatile field to ensure thread-safe lazy initialization
166+
// The 'volatile' keyword ensures that changes to these variables
167+
// are immediately visible to all threads. It prevents instruction
168+
// reordering by the compiler.
169+
protected static volatile String cicdProvider = null;
170+
171+
protected static Environment env = null;
172+
173+
// Represents an environment variable with its name and expected value
174+
private static class EnvVar {
175+
private final String name;
176+
private final String expectedValue;
177+
178+
public EnvVar(String name, String expectedValue) {
179+
this.name = name;
180+
this.expectedValue = expectedValue;
181+
}
182+
}
183+
184+
// Represents a CI/CD provider with its name and associated environment variables
185+
private static class CicdProvider {
186+
private final String name;
187+
private final List<EnvVar> envVars;
188+
189+
public CicdProvider(String name, List<EnvVar> envVars) {
190+
this.name = name;
191+
this.envVars = envVars;
192+
}
193+
194+
public boolean detect(Environment env) {
195+
for (EnvVar envVar : envVars) {
196+
String value = env.get(envVar.name);
197+
if (value == null) {
198+
return false;
199+
}
200+
if (!envVar.expectedValue.isEmpty() && !value.equals(envVar.expectedValue)) {
201+
return false;
202+
}
203+
}
204+
return true;
205+
}
206+
}
207+
208+
// Looks up the active CI/CD provider based on environment variables
209+
private static String lookupCiCdProvider(Environment env) {
210+
for (CicdProvider provider : listCiCdProviders()) {
211+
if (provider.detect(env)) {
212+
return provider.name;
213+
}
214+
}
215+
return "";
216+
}
217+
218+
// Thread-safe lazy initialization of CI/CD provider detection
219+
private static String cicdProvider() {
220+
// First check (not synchronized) to avoid unnecessary synchronization
221+
if (cicdProvider == null) {
222+
// Synchronize only if cicdProvider is null
223+
synchronized (UserAgent.class) {
224+
// Second check (synchronized) to ensure only one thread initializes
225+
// This is necessary because multiple threads might have passed the first check
226+
if (cicdProvider == null) {
227+
cicdProvider = lookupCiCdProvider(env());
228+
}
229+
}
230+
}
231+
return cicdProvider;
232+
}
233+
234+
private static Environment env() {
235+
if (env == null) {
236+
env =
237+
new Environment(
238+
System.getenv(),
239+
System.getenv("PATH").split(File.pathSeparator),
240+
System.getProperty("os.name"));
241+
}
242+
return env;
243+
}
133244
}

databricks-sdk-java/src/test/java/com/databricks/sdk/core/UserAgentTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.databricks.sdk.core;
22

3+
import com.databricks.sdk.core.utils.Environment;
4+
import java.util.ArrayList;
5+
import java.util.HashMap;
36
import org.junit.jupiter.api.Assertions;
47
import org.junit.jupiter.api.Test;
58

@@ -56,4 +59,46 @@ public void testUserAgentWithSemverValue() {
5659
String userAgent = UserAgent.asString();
5760
Assertions.assertTrue(userAgent.contains("key1/1.0.0-dev+metadata"));
5861
}
62+
63+
@Test
64+
public void testUserAgentCicdNoProvider() {
65+
UserAgent.cicdProvider = null;
66+
UserAgent.env =
67+
new Environment(new HashMap<>(), new ArrayList<>(), System.getProperty("os.name"));
68+
Assertions.assertFalse(UserAgent.asString().contains("cicd"));
69+
UserAgent.env = null;
70+
}
71+
72+
@Test
73+
public void testUserAgentCicdOneProvider() {
74+
UserAgent.cicdProvider = null;
75+
UserAgent.env =
76+
new Environment(
77+
new HashMap<String, String>() {
78+
{
79+
put("GITHUB_ACTIONS", "true");
80+
}
81+
},
82+
new ArrayList<>(),
83+
System.getProperty("os.name"));
84+
Assertions.assertTrue(UserAgent.asString().contains("cicd/github"));
85+
UserAgent.env = null;
86+
}
87+
88+
@Test
89+
public void testUserAgentCicdTwoProvider() {
90+
UserAgent.cicdProvider = null;
91+
UserAgent.env =
92+
new Environment(
93+
new HashMap<String, String>() {
94+
{
95+
put("GITLAB_CI", "true");
96+
put("JENKINS_URL", "");
97+
}
98+
},
99+
new ArrayList<>(),
100+
System.getProperty("os.name"));
101+
Assertions.assertTrue(UserAgent.asString().contains("cicd/gitlab"));
102+
UserAgent.env = null;
103+
}
59104
}

0 commit comments

Comments
 (0)