Skip to content

Commit

Permalink
Print a link to Pipeline Executions in Datadog in the build logs
Browse files Browse the repository at this point in the history
  • Loading branch information
nikita-tkachenko-datadog committed Feb 1, 2024
1 parent 860021a commit 8506ed5
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,51 +27,52 @@ of this software and associated documentation files (the "Software"), to deal

import static hudson.Util.fixEmptyAndTrim;

import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Item;
import hudson.security.ACL;
import hudson.util.FormValidation;
import hudson.util.Secret;
import hudson.util.FormValidation.Kind;
import hudson.util.ListBoxModel;
import hudson.model.Item;
import hudson.security.ACL;

import hudson.util.Secret;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.management.InvalidAttributeValueException;
import javax.servlet.ServletException;
import jenkins.model.GlobalConfiguration;
import jenkins.model.Jenkins;

import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import org.datadog.jenkins.plugins.datadog.clients.HttpClient;
import org.datadog.jenkins.plugins.datadog.traces.write.TraceWriterFactory;
import org.jenkinsci.plugins.plaincredentials.StringCredentials;

import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.datadog.jenkins.plugins.datadog.clients.ClientFactory;
import org.datadog.jenkins.plugins.datadog.clients.DatadogApiClient;
import org.datadog.jenkins.plugins.datadog.clients.DatadogAgentClient;
import org.datadog.jenkins.plugins.datadog.clients.DatadogApiClient;
import org.datadog.jenkins.plugins.datadog.clients.HttpClient;
import org.datadog.jenkins.plugins.datadog.traces.write.TraceWriterFactory;
import org.datadog.jenkins.plugins.datadog.util.SuppressFBWarnings;
import org.datadog.jenkins.plugins.datadog.util.config.DatadogAgentConfiguration;
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.kohsuke.stapler.AncestorInPath;

import javax.management.InvalidAttributeValueException;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;

@Extension
public class DatadogGlobalConfiguration extends GlobalConfiguration {
Expand Down Expand Up @@ -833,7 +834,7 @@ public boolean configure(final StaplerRequest req, final JSONObject formData) th
this.setUsedApiKey(apiKeySecret);

//When form is saved....
DatadogClient client = ClientFactory.getClient(DatadogClient.ClientType.valueOf(this.getReportWith()), this.getTargetApiURL(),
DatadogClient client = ClientFactory.getClient(getClientType(), this.getTargetApiURL(),
this.getTargetLogIntakeURL(), this.getTargetWebhookIntakeURL(), this.getUsedApiKey(), this.getTargetHost(),
this.getTargetPort(), this.getTargetLogCollectionPort(), this.getTargetTraceCollectionPort(), this.getCiInstanceName());
// ...reinitialize the DatadogClient
Expand Down Expand Up @@ -870,6 +871,10 @@ public String getReportWith() {
return reportWith;
}

public DatadogClient.ClientType getClientType() {
return DatadogClient.ClientType.valueOf(reportWith);
}

/**
* Setter function for the reportWith global configuration.
*
Expand Down Expand Up @@ -1553,4 +1558,34 @@ private FormValidation validateEventFilteringConfig(boolean emitSecurityEvents,

return FormValidation.ok("Your filtering configuration looks good!");
}

@Nullable
public String getDatadogSite() {
DatadogClient.ClientType clientType = getClientType();
if (clientType == DatadogClient.ClientType.HTTP) {
String targetApiURL = getTargetApiURL();
return getDatadogSite(targetApiURL);
} else {
// plugin is configured to report to Datadog agent,
// and we have no way of knowing which Datadog site the agent connects to
return null;
}
}

// taget API URL looks like "https://api.datadoghq.com/api/"
private static String getDatadogSite(String apiUrl) {
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public static DatadogClient getClient() {
return null;
}

String reportWith = descriptor.getReportWith();
DatadogClient.ClientType clientType = descriptor.getClientType();
String targetApiURL = descriptor.getTargetApiURL();
String targetLogIntakeURL = descriptor.getTargetLogIntakeURL();
String targetWebhookIntakeURL = descriptor.getTargetWebhookIntakeURL();
Expand All @@ -78,7 +78,7 @@ public static DatadogClient getClient() {
Integer targetLogCollectionPort = descriptor.getTargetLogCollectionPort();
Integer targetTraceCollectionPort = descriptor.getTargetTraceCollectionPort();
String ciInstanceName = descriptor.getCiInstanceName();
return ClientFactory.getClient(DatadogClient.ClientType.valueOf(reportWith), targetApiURL, targetLogIntakeURL, targetWebhookIntakeURL,
return ClientFactory.getClient(clientType, targetApiURL, targetLogIntakeURL, targetWebhookIntakeURL,
targetApiKey, targetHost, targetPort, targetLogCollectionPort, targetTraceCollectionPort, ciInstanceName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ of this software and associated documentation files (the "Software"), to deal
import hudson.model.TaskListener;
import hudson.model.listeners.RunListener;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -343,12 +344,39 @@ public void onCompleted(Run run, @Nonnull TaskListener listener) {
}
}

// print a link to pipelines in Datadog to the build's log
printPipelineExecutionsLink(buildData, listener);

logger.fine("End DatadogBuildListener#onCompleted");
} catch (Exception e) {

Check warning on line 351 in src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java

View check run for this annotation

ci.jenkins.io / SpotBugs

REC_CATCH_EXCEPTION

NORMAL: Exception is caught when Exception is not thrown in org.datadog.jenkins.plugins.datadog.listeners.DatadogBuildListener.onCompleted(Run, TaskListener)
Raw output
<p> This method uses a try-catch block that catches Exception objects, but Exception is not thrown within the try block, and RuntimeException is not explicitly caught. It is a common bug pattern to say try { ... } catch (Exception e) { something } as a shorthand for catching a number of types of exception each of whose catch blocks is identical, but this construct also accidentally catches RuntimeException as well, masking potential bugs. </p> <p>A better approach is to either explicitly catch the specific exceptions that are thrown, or to explicitly catch RuntimeException exception, rethrow it, and then catch all non-Runtime Exceptions, as shown below:</p> <pre><code>try { ... } catch (RuntimeException e) { throw e; } catch (Exception e) { ... deal with all non-runtime exceptions ... } </code></pre>
DatadogUtilities.severe(logger, e, "Failed to process build completion");
}
}

private static void printPipelineExecutionsLink(BuildData buildData, TaskListener listener) throws IOException {
listener.getLogger().print("This pipeline is tracked with the Datadog Jenkins plugin");

DatadogGlobalConfiguration datadogConfiguration = DatadogUtilities.getDatadogGlobalDescriptor();
if (datadogConfiguration == null || !datadogConfiguration.getEnableCiVisibility()) {
listener.getLogger().println(".");
return;
}

String datadogSite = datadogConfiguration.getDatadogSite();
if (datadogSite == null) {
// Cannot detect Datadog site.
// Either URL is not set, or the plugin is configured to send data to Datadog Agent
listener.getLogger().println(".");
return;
}

String query = String.format("ci_level:pipeline @ci.pipeline.name:%s", buildData.getJobName());
String urlEncodedQuery = URLEncoder.encode(query, "UTF-8");
String link = String.format("https://app.%s/ci/pipeline-executions?query=%s", datadogSite, urlEncodedQuery);
listener.getLogger().print(" (");
listener.hyperlink(link, "view Pipeline Executions");
listener.getLogger().println(").");
}

/**
* Called when a build is finalized.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
import hudson.model.Run;
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;
Expand Down Expand Up @@ -111,11 +107,11 @@ 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());
DatadogClient.ClientType clientType = datadogConfig.getClientType();
switch (clientType) {
case HTTP:
variables.put("DD_CIVISIBILITY_AGENTLESS_ENABLED", "true");
variables.put("DD_SITE", getSite(datadogConfig.getTargetApiURL()));
variables.put("DD_SITE", datadogConfig.getDatadogSite());
variables.put("DD_API_KEY", Secret.toString(datadogConfig.getUsedApiKey()));
break;
case DSD:
Expand All @@ -134,24 +130,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");
Expand Down

0 comments on commit 8506ed5

Please sign in to comment.