Skip to content

Commit

Permalink
Refactor global configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
nikita-tkachenko-datadog committed Oct 11, 2024
1 parent 815812a commit d20b98e
Show file tree
Hide file tree
Showing 81 changed files with 3,649 additions and 1,715 deletions.
8 changes: 7 additions & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,16 @@ To spin up a development environment for the *jenkins-datadog* plugin repository
- You can set Global Tag. For example `.*, owner:$1, release_env:$2, optional:Tag3`.

To test Configuration as Code update `docker/docker-compose.yaml`, uncommenting `CASC_JENKINS_CONFIG`.
The applied configuration is stored in `docker/controller-node/jenkins-casc.yaml` (note that it is placed inside the container at image build time).
The applied configuration is stored in `docker/controller-node/jenkins-casc.yaml` (note that it is placed inside the container at image build time).
You can find some examples of the plugin's configuration with CasC in `src/test/resources/org/datadog/jenkins/plugins/datadog`.

Jenkins controller container exposes port 5055 for remote debugging via JDWP.

#### Known errors

If docker-compose fails with a message that looks like `error mounting ".../datadog-plugin/target/datadog.hpi" to rootfs at "/var/jenkins_home/plugins/datadog.hpi"`,
make sure that you have properly set the `JENKINS_PLUGIN` env var and that you have built the plugin by running `mvn clean package -DskipTests`.

#### Manual Testing without an Agent

Alternatively, you can manually test the plugin by running the command `mvn hpi:run`, which will spin up a local development environment without the agent. This allows you to test using the HTTP client without needing docker. See the [jenkins documentation](https://jenkinsci.github.io/maven-hpi-plugin/run-mojo.html) for more details and options.
Expand Down
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-lambda</artifactId>
<version>1.2.1</version>
<scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,6 @@ of this software and associated documentation files (the "Software"), to deal

public interface DatadogClient {

enum ClientType {
HTTP,
DSD;

ClientType() { }
}

enum Status {
OK(0),
WARNING(1),
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,17 @@
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.TopLevelItem;
import hudson.util.Secret;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.datadog.jenkins.plugins.datadog.DatadogClient;
import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration;
import org.datadog.jenkins.plugins.datadog.DatadogUtilities;
import org.datadog.jenkins.plugins.datadog.model.DatadogPluginAction;
import org.datadog.jenkins.plugins.datadog.configuration.DatadogClientConfiguration;

public class DatadogTracerConfigurator {

Expand Down Expand Up @@ -125,20 +120,9 @@ private static Map<String, String> getCommonEnvVariables(DatadogGlobalConfigurat
variables.put("DD_ENV", "ci");
variables.put("DD_SERVICE", tracerConfig.getServiceName());

DatadogClient.ClientType clientType = DatadogClient.ClientType.valueOf(datadogConfig.getReportWith());
switch (clientType) {
case HTTP:
variables.put("DD_CIVISIBILITY_AGENTLESS_ENABLED", "true");
variables.put("DD_SITE", getSite(datadogConfig.getTargetApiURL()));
variables.put("DD_API_KEY", Secret.toString(datadogConfig.getUsedApiKey()));
break;
case DSD:
variables.put("DD_AGENT_HOST", datadogConfig.getTargetHost());
variables.put("DD_TRACE_AGENT_PORT", getAgentPort(datadogConfig.getTargetTraceCollectionPort()));
break;
default:
throw new IllegalArgumentException("Unexpected client type: " + clientType);
}
DatadogClientConfiguration clientConfiguration = datadogConfig.getDatadogClientConfiguration();
Map<String, String> clientEnvironmentVariables = clientConfiguration.toEnvironmentVariables();
variables.putAll(clientEnvironmentVariables);

Map<String, String> additionalVariables = tracerConfig.getAdditionalVariables();
if (additionalVariables != null) {
Expand All @@ -148,31 +132,6 @@ private static Map<String, String> getCommonEnvVariables(DatadogGlobalConfigurat
return variables;
}

private static String getSite(String apiUrl) {
// what users configure for Pipelines looks like "https://api.datadoghq.com/api/"
// while what the tracer needs "datadoghq.com"
try {
URI uri = new URL(apiUrl).toURI();
String host = uri.getHost();
if (host == null) {
throw new IllegalArgumentException("Cannot find host in Datadog API URL: " + uri);
}

String[] parts = host.split("\\.");
return (parts.length >= 2 ? parts[parts.length - 2] + "." : "") + parts[parts.length - 1];

} catch (MalformedURLException | URISyntaxException e) {
throw new IllegalArgumentException("Cannot parse Datadog API URL", e);
}
}

private static String getAgentPort(Integer traceCollectionPort) {
if (traceCollectionPort == null) {
throw new IllegalArgumentException("Traces collection port is not set");
} else {
return traceCollectionPort.toString();
}
}

private static final class ConfigureTracerAction extends DatadogPluginAction {
private final String nodeHostname;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ of this software and associated documentation files (the "Software"), to deal
import com.timgroup.statsd.NonBlockingStatsDClient;
import com.timgroup.statsd.ServiceCheck;
import com.timgroup.statsd.StatsDClient;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import org.datadog.jenkins.plugins.datadog.DatadogClient;
import org.datadog.jenkins.plugins.datadog.DatadogEvent;
import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration;
Expand All @@ -43,18 +61,6 @@ of this software and associated documentation files (the "Software"), to deal
import org.json.JSONArray;
import org.json.JSONObject;

import javax.annotation.concurrent.GuardedBy;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.logging.Logger;

/**
* This class is used to collect all methods that has to do with transmitting
* data to Datadog.
Expand Down Expand Up @@ -90,37 +96,41 @@ public DatadogAgentClient(String hostname, Integer port, Integer logCollectionPo
}

public DatadogAgentClient(String hostname, Integer port, Integer logCollectionPort, Integer traceCollectionPort, long evpProxyTimeoutMillis) {
validate(hostname, port, logCollectionPort, traceCollectionPort);

this.hostname = hostname;
this.port = port;
this.logCollectionPort = logCollectionPort;
this.traceCollectionPort = traceCollectionPort;
this.client = new HttpClient(evpProxyTimeoutMillis);
}

private static void validate(String hostname, Integer port, Integer logCollectionPort, Integer traceCollectionPort) {
if (hostname == null || hostname.isEmpty()) {
throw new IllegalArgumentException("Datadog Target URL is not set properly");
}
if (port == null) {
throw new IllegalArgumentException("Datadog Target Port is not set properly");
}
if (DatadogUtilities.getDatadogGlobalDescriptor().isCollectBuildLogs() && logCollectionPort == null) {
logger.warning("Datadog Log Collection Port is not set properly");
}
/**
* Fetches the supported endpoints from the Trace Agent /info API
*
* @return a set of endpoints (if /info wasn't available, it will be empty)
*/
@SuppressFBWarnings("REC_CATCH_EXCEPTION")
Set<String> fetchAgentSupportedEndpoints() {
logger.fine("Fetching Agent info");

if (DatadogUtilities.getDatadogGlobalDescriptor().getEnableCiVisibility() && traceCollectionPort == null) {
logger.warning("Datadog Trace Collection Port is not set properly");
}
}
String url = String.format("http://%s:%d/info", hostname, traceCollectionPort);
try {
return client.get(url, Collections.emptyMap(), s -> {
JSONObject jsonResponse = new JSONObject(s);
JSONArray jsonEndpoints = jsonResponse.getJSONArray("endpoints");

public static ConnectivityResult checkConnectivity(final String host, final int port) {
try(Socket ignored = new Socket(host, port)) {
return ConnectivityResult.SUCCESS;
} catch (Exception ex) {
DatadogUtilities.severe(logger, ex, "Failed to create socket to host: " + host + ", port: " +port + ". Error: " + ex);
return new ConnectivityResult(true, ex.toString());
Set<String> endpoints = new HashSet<>();
for (int i = 0; i < jsonEndpoints.length(); i++) {
endpoints.add(jsonEndpoints.getString(i));
}
return endpoints;
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
DatadogUtilities.severe(logger, e, "Could not get the list of agent endpoints");
return Collections.emptySet();
} catch (Exception e) {
DatadogUtilities.severe(logger, e, "Could not get the list of agent endpoints");
return Collections.emptySet();
}
}

Expand Down Expand Up @@ -379,37 +389,6 @@ boolean isEvpProxySupported() {
return supportedAgentEndpoints.contains("/evp_proxy/v3/");
}

/**
* Fetches the supported endpoints from the Trace Agent /info API
*
* @return a set of endpoints (if /info wasn't available, it will be empty)
*/
@SuppressFBWarnings("REC_CATCH_EXCEPTION")
Set<String> fetchAgentSupportedEndpoints() {
logger.fine("Fetching Agent info");

String url = String.format("http://%s:%d/info", hostname, traceCollectionPort);
try {
return client.get(url, Collections.emptyMap(), s -> {
JSONObject jsonResponse = new JSONObject(s);
JSONArray jsonEndpoints = jsonResponse.getJSONArray("endpoints");

Set<String> endpoints = new HashSet<>();
for (int i = 0; i < jsonEndpoints.length(); i++) {
endpoints.add(jsonEndpoints.getString(i));
}
return endpoints;
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
DatadogUtilities.severe(logger, e, "Could not get the list of agent endpoints");
return Collections.emptySet();
} catch (Exception e) {
DatadogUtilities.severe(logger, e, "Could not get the list of agent endpoints");
return Collections.emptySet();
}
}

/**
* Posts a given payload to the Agent EVP Proxy, so it is forwarded to the Webhook Intake.
*/
Expand Down
Loading

0 comments on commit d20b98e

Please sign in to comment.