From 8506ed581027bf1929c389fcbbc1457dc289a9bf Mon Sep 17 00:00:00 2001 From: Nikita Tkachenko Date: Thu, 1 Feb 2024 23:57:56 +0100 Subject: [PATCH] Print a link to Pipeline Executions in Datadog in the build logs --- .../datadog/DatadogGlobalConfiguration.java | 89 +++++++++++++------ .../datadog/clients/ClientFactory.java | 4 +- .../listeners/DatadogBuildListener.java | 28 ++++++ .../tracer/DatadogTracerConfigurator.java | 26 +----- 4 files changed, 94 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration.java b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration.java index 54c3b3e60..c4a02f0ce 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration.java @@ -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 { @@ -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 @@ -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. * @@ -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); + } + } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/ClientFactory.java b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/ClientFactory.java index 90ec8d161..07cd0bc7a 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/ClientFactory.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/ClientFactory.java @@ -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(); @@ -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); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java index 28aed6578..9b95bbd5b 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/listeners/DatadogBuildListener.java @@ -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; @@ -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) { 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. diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/tracer/DatadogTracerConfigurator.java b/src/main/java/org/datadog/jenkins/plugins/datadog/tracer/DatadogTracerConfigurator.java index 48be9c254..b1e22b5a7 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/tracer/DatadogTracerConfigurator.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/tracer/DatadogTracerConfigurator.java @@ -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; @@ -111,11 +107,11 @@ private static Map 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: @@ -134,24 +130,6 @@ private static Map 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");