Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.databricks.sdk.core;

import com.databricks.sdk.core.utils.Environment;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -121,6 +125,10 @@ public static String asString() {
segments.add(String.format("databricks-sdk-java/%s", version));
segments.add(String.format("jvm/%s", jvmVersion()));
segments.add(String.format("os/%s", osName()));
String cicdProvider = cicdProvider();
if (!cicdProvider.isEmpty()) {
segments.add(String.format("cicd/%s", cicdProvider));
}
// Concurrent iteration over ArrayList must be guarded with synchronized.
synchronized (otherInfo) {
segments.addAll(
Expand All @@ -130,4 +138,107 @@ public static String asString() {
}
return segments.stream().collect(Collectors.joining(" "));
}

// List of CI/CD providers and their environment variables for detection
private static List<CicdProvider> listCiCdProviders() {
return Arrays.asList(
new CicdProvider("github", Collections.singletonList(new EnvVar("GITHUB_ACTIONS", "true"))),
new CicdProvider("gitlab", Collections.singletonList(new EnvVar("GITLAB_CI", "true"))),
new CicdProvider("jenkins", Collections.singletonList(new EnvVar("JENKINS_URL", ""))),
new CicdProvider("azure-devops", Collections.singletonList(new EnvVar("TF_BUILD", "True"))),
new CicdProvider("circle", Collections.singletonList(new EnvVar("CIRCLECI", "true"))),
new CicdProvider("travis", Collections.singletonList(new EnvVar("TRAVIS", "true"))),
new CicdProvider(
"bitbucket", Collections.singletonList(new EnvVar("BITBUCKET_BUILD_NUMBER", ""))),
new CicdProvider(
"google-cloud-build",
Arrays.asList(
new EnvVar("PROJECT_ID", ""),
new EnvVar("BUILD_ID", ""),
new EnvVar("PROJECT_NUMBER", ""),
new EnvVar("LOCATION", ""))),
new CicdProvider(
"aws-code-build", Collections.singletonList(new EnvVar("CODEBUILD_BUILD_ARN", ""))),
new CicdProvider("tf-cloud", Collections.singletonList(new EnvVar("TFC_RUN_ID", ""))));
}

// Volatile field to ensure thread-safe lazy initialization
// The 'volatile' keyword ensures that changes to these variables
// are immediately visible to all threads. It prevents instruction
// reordering by the compiler.
protected static volatile String cicdProvider = null;

protected static Environment env = null;

// Represents an environment variable with its name and expected value
private static class EnvVar {
private final String name;
private final String expectedValue;

public EnvVar(String name, String expectedValue) {
this.name = name;
this.expectedValue = expectedValue;
}
}

// Represents a CI/CD provider with its name and associated environment variables
private static class CicdProvider {
private final String name;
private final List<EnvVar> envVars;

public CicdProvider(String name, List<EnvVar> envVars) {
this.name = name;
this.envVars = envVars;
}

public boolean detect(Environment env) {
for (EnvVar envVar : envVars) {
String value = env.get(envVar.name);
if (value == null) {
return false;
}
if (!envVar.expectedValue.isEmpty() && !value.equals(envVar.expectedValue)) {
return false;
}
}
return true;
}
}

// Looks up the active CI/CD provider based on environment variables
private static String lookupCiCdProvider(Environment env) {
for (CicdProvider provider : listCiCdProviders()) {
if (provider.detect(env)) {
return provider.name;
}
}
return "";
}

// Thread-safe lazy initialization of CI/CD provider detection
private static String cicdProvider() {
// First check (not synchronized) to avoid unnecessary synchronization
if (cicdProvider == null) {
// Synchronize only if cicdProvider is null
synchronized (UserAgent.class) {
// Second check (synchronized) to ensure only one thread initializes
// This is necessary because multiple threads might have passed the first check
if (cicdProvider == null) {
cicdProvider = lookupCiCdProvider(env());
}
}
}
return cicdProvider;
}

private static Environment env() {
if (env == null) {
env =
new Environment(
System.getenv(),
System.getenv("PATH").split(File.pathSeparator),
System.getProperty("os.name"));
}
return env;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.databricks.sdk.core;

import com.databricks.sdk.core.utils.Environment;
import java.util.ArrayList;
import java.util.HashMap;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -56,4 +59,46 @@ public void testUserAgentWithSemverValue() {
String userAgent = UserAgent.asString();
Assertions.assertTrue(userAgent.contains("key1/1.0.0-dev+metadata"));
}

@Test
public void testUserAgentCicdNoProvider() {
UserAgent.cicdProvider = null;
UserAgent.env =
new Environment(new HashMap<>(), new ArrayList<>(), System.getProperty("os.name"));
Assertions.assertFalse(UserAgent.asString().contains("cicd"));
UserAgent.env = null;
}

@Test
public void testUserAgentCicdOneProvider() {
UserAgent.cicdProvider = null;
UserAgent.env =
new Environment(
new HashMap<String, String>() {
{
put("GITHUB_ACTIONS", "true");
}
},
new ArrayList<>(),
System.getProperty("os.name"));
Assertions.assertTrue(UserAgent.asString().contains("cicd/github"));
UserAgent.env = null;
}

@Test
public void testUserAgentCicdTwoProvider() {
UserAgent.cicdProvider = null;
UserAgent.env =
new Environment(
new HashMap<String, String>() {
{
put("GITLAB_CI", "true");
put("JENKINS_URL", "");
}
},
new ArrayList<>(),
System.getProperty("os.name"));
Assertions.assertTrue(UserAgent.asString().contains("cicd/gitlab"));
UserAgent.env = null;
}
}
Loading