diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 55e4f93c4..6bf8deca5 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -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. diff --git a/pom.xml b/pom.xml index f901dc96c..53c96589b 100644 --- a/pom.xml +++ b/pom.xml @@ -101,6 +101,13 @@ json 20240303 + + com.github.stefanbirkner + system-lambda + 1.2.1 + test + + org.mockito diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogClient.java b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogClient.java index 33d36d7c3..8967f2bbd 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogClient.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogClient.java @@ -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), 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 b0a80f3f6..97eba370f 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration.java @@ -25,51 +25,66 @@ of this software and associated documentation files (the "Software"), to deal package org.datadog.jenkins.plugins.datadog; -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 static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor.getDefaultAgentHost; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor.getDefaultAgentLogCollectionPort; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor.getDefaultAgentPort; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor.getDefaultAgentTraceCollectionPort; +import static org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogTextApiKey.DatadogTextApiKeyDescriptor.getDefaultKey; + +import com.thoughtworks.xstream.annotations.XStreamConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; +import hudson.XmlFile; import hudson.init.InitMilestone; import hudson.init.Initializer; import hudson.model.AbstractProject; -import hudson.model.Item; -import hudson.security.ACL; import hudson.util.FormValidation; import hudson.util.FormValidation.Kind; -import hudson.util.ListBoxModel; import hudson.util.Secret; +import hudson.util.XStream2; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; import jenkins.model.GlobalConfiguration; import jenkins.model.Jenkins; 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.ClientHolder; -import org.datadog.jenkins.plugins.datadog.clients.DatadogAgentClient; -import org.datadog.jenkins.plugins.datadog.clients.DatadogApiClient; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogApiConfiguration; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogClientConfiguration; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntake; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls; +import org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogApiKey; +import org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogCredentialsApiKey; +import org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogTextApiKey; 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.datadog.jenkins.plugins.datadog.util.conversion.PolymorphicReflectionConverter; import org.kohsuke.stapler.*; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.interceptor.RequirePOST; -import javax.annotation.Nullable; -import javax.management.InvalidAttributeValueException; -import javax.servlet.ServletException; -import java.io.IOException; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -import static hudson.Util.fixEmptyAndTrim; - @Extension public class DatadogGlobalConfiguration extends GlobalConfiguration { private static final Logger logger = Logger.getLogger(DatadogGlobalConfiguration.class.getName()); private static final String DISPLAY_NAME = "Datadog Plugin"; + public static final XStream2 XSTREAM; + + static { + XSTREAM = new XStream2(XStream2.getDefaultDriver()); + XSTREAM.autodetectAnnotations(true); + } + // Event String constants public static final String SYSTEM_EVENTS = "ItemLocationChanged," + "ComputerOnline,ComputerOffline,ComputerTemporarilyOnline,ComputerTemporarilyOffline," @@ -77,26 +92,10 @@ public class DatadogGlobalConfiguration extends GlobalConfiguration { public static final String SECURITY_EVENTS = "UserAuthenticated,UserFailedToAuthenticate,UserLoggedOut"; public static final String DEFAULT_EVENTS = "BuildStarted,BuildAborted,BuildCompleted,SCMCheckout"; - // Standard Agent EnvVars - public static final String DD_AGENT_HOST = "DD_AGENT_HOST"; - public static final String DD_AGENT_PORT = "DD_AGENT_PORT"; - public static final String DD_TRACE_AGENT_PORT = "DD_TRACE_AGENT_PORT"; - public static final String DD_TRACE_AGENT_URL = "DD_TRACE_AGENT_URL"; - // Env Var key to get the hostname from the Jenkins workers. public static final String DD_CI_HOSTNAME = "DD_CI_HOSTNAME"; - // Jenkins Agent EnvVars - public static final String TARGET_HOST_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_HOST"; - public static final String TARGET_PORT_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_PORT"; - public static final String TARGET_TRACE_COLLECTION_PORT_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_TRACE_COLLECTION_PORT"; - - private static final String REPORT_WITH_PROPERTY = "DATADOG_JENKINS_PLUGIN_REPORT_WITH"; - private static final String TARGET_API_URL_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_API_URL"; - private static final String TARGET_LOG_INTAKE_URL_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_LOG_INTAKE_URL"; - private static final String TARGET_WEBHOOK_INTAKE_URL_PROPERTY = "DATADOG_JENKINS_TARGET_WEBHOOK_INTAKE_URL"; - private static final String TARGET_API_KEY_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_API_KEY"; - private static final String TARGET_LOG_COLLECTION_PORT_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_LOG_COLLECTION_PORT"; + static final String REPORT_WITH_PROPERTY = "DATADOG_JENKINS_PLUGIN_REPORT_WITH"; private static final String TARGET_TRACE_SERVICE_NAME_PROPERTY = "DATADOG_JENKINS_PLUGIN_TRACE_SERVICE_NAME"; private static final String HOSTNAME_PROPERTY = "DATADOG_JENKINS_PLUGIN_HOSTNAME"; private static final String EXCLUDED_PROPERTY = "DATADOG_JENKINS_PLUGIN_EXCLUDED"; @@ -110,7 +109,6 @@ public class DatadogGlobalConfiguration extends GlobalConfiguration { private static final String GLOBAL_JOB_TAGS_PROPERTY = "DATADOG_JENKINS_PLUGIN_GLOBAL_JOB_TAGS"; private static final String EMIT_SECURITY_EVENTS_PROPERTY = "DATADOG_JENKINS_PLUGIN_EMIT_SECURITY_EVENTS"; private static final String EMIT_SYSTEM_EVENTS_PROPERTY = "DATADOG_JENKINS_PLUGIN_EMIT_SYSTEM_EVENTS"; - private static final String EMIT_CONFIG_CHANGE_EVENTS_PROPERTY = "DATADOG_JENKINS_PLUGIN_EMIT_CONFIG_CHANGE_EVENTS"; private static final String INCLUDE_EVENTS_PROPERTY = "DATADOG_JENKINS_PLUGIN_INCLUDE_EVENTS"; private static final String EXCLUDE_EVENTS_PROPERTY = "DATADOG_JENKINS_PLUGIN_EXCLUDE_EVENTS"; private static final String COLLECT_BUILD_LOGS_PROPERTY = "DATADOG_JENKINS_PLUGIN_COLLECT_BUILD_LOGS"; @@ -121,19 +119,9 @@ public class DatadogGlobalConfiguration extends GlobalConfiguration { private static final String ENABLE_CI_VISIBILITY_PROPERTY = "DATADOG_JENKINS_PLUGIN_ENABLE_CI_VISIBILITY"; private static final String CI_VISIBILITY_CI_INSTANCE_NAME_PROPERTY = "DATADOG_JENKINS_PLUGIN_CI_VISIBILITY_CI_INSTANCE_NAME"; - private static final String DEFAULT_REPORT_WITH_VALUE = DatadogClient.ClientType.HTTP.name(); - private static final String DEFAULT_TARGET_API_URL_VALUE = "https://api.datadoghq.com/api/"; - private static final String DEFAULT_TARGET_LOG_INTAKE_URL_VALUE = "https://http-intake.logs.datadoghq.com/v1/input/"; - private static final String DEFAULT_TARGET_WEBHOOK_INTAKE_URL_VALUE = "https://webhook-intake.datadoghq.com/api/v2/webhook/"; - private static final String DEFAULT_TARGET_HOST_VALUE = "localhost"; - private static final Integer DEFAULT_TARGET_PORT_VALUE = 8125; - private static final Integer DEFAULT_TRACE_COLLECTION_PORT_VALUE = 8126; private static final String DEFAULT_CI_INSTANCE_NAME = "jenkins"; - - private static final Integer DEFAULT_TARGET_LOG_COLLECTION_PORT_VALUE = null; private static final boolean DEFAULT_EMIT_SECURITY_EVENTS_VALUE = true; private static final boolean DEFAULT_EMIT_SYSTEM_EVENTS_VALUE = true; - private static final boolean DEFAULT_EMIT_CONFIG_CHANGE_EVENTS_VALUE = false; private static final boolean DEFAULT_COLLECT_BUILD_LOGS_VALUE = false; private static final boolean DEFAULT_COLLECT_BUILD_TRACES_VALUE = false; private static final boolean DEFAULT_RETRY_LOGS_VALUE = true; @@ -141,21 +129,15 @@ public class DatadogGlobalConfiguration extends GlobalConfiguration { private static final boolean DEFAULT_CACHE_BUILD_RUNS_VALUE = true; private static final boolean DEFAULT_USE_AWS_INSTANCE_HOSTNAME_VALUE = false; - private String reportWith = DEFAULT_REPORT_WITH_VALUE; - private String targetApiURL = DEFAULT_TARGET_API_URL_VALUE; - private String targetLogIntakeURL = DEFAULT_TARGET_LOG_INTAKE_URL_VALUE; - private String targetWebhookIntakeURL = DEFAULT_TARGET_WEBHOOK_INTAKE_URL_VALUE; - private Secret targetApiKey = null; - private String targetCredentialsApiKey = null; - private Secret usedApiKey = null; - private String targetHost = DEFAULT_TARGET_HOST_VALUE; - private Integer targetPort = DEFAULT_TARGET_PORT_VALUE; - private Integer targetLogCollectionPort = DEFAULT_TARGET_LOG_COLLECTION_PORT_VALUE; - private Integer targetTraceCollectionPort = DEFAULT_TRACE_COLLECTION_PORT_VALUE; - private String traceServiceName = DEFAULT_CI_INSTANCE_NAME; + public static final String DATADOG_AGENT_CLIENT_TYPE = "DSD"; + public static final String DATADOG_API_CLIENT_TYPE = "HTTP"; + + @XStreamConverter(PolymorphicReflectionConverter.class) + private DatadogClientConfiguration datadogClientConfiguration; + private String ciInstanceName = DEFAULT_CI_INSTANCE_NAME; private String hostname = null; - private String blacklist = null; - private String whitelist = null; + private String excluded = null; + private String included = null; private String globalTagFile = null; private String globalTags = null; private String globalJobTags = null; @@ -163,9 +145,8 @@ public class DatadogGlobalConfiguration extends GlobalConfiguration { private String excludeEvents = null; private boolean emitSecurityEvents = DEFAULT_EMIT_SECURITY_EVENTS_VALUE; private boolean emitSystemEvents = DEFAULT_EMIT_SYSTEM_EVENTS_VALUE; - private boolean emitConfigChangeEvents = DEFAULT_EMIT_CONFIG_CHANGE_EVENTS_VALUE; private boolean collectBuildLogs = DEFAULT_COLLECT_BUILD_LOGS_VALUE; - private boolean collectBuildTraces = DEFAULT_COLLECT_BUILD_TRACES_VALUE; + private boolean enableCiVisibility = DEFAULT_COLLECT_BUILD_TRACES_VALUE; private transient boolean retryLogs = DEFAULT_RETRY_LOGS_VALUE; // TODO to be removed private boolean refreshDogstatsdClient = DEFAULT_REFRESH_DOGSTATSD_CLIENT_VALUE; private boolean cacheBuildRuns = DEFAULT_CACHE_BUILD_RUNS_VALUE; @@ -174,67 +155,41 @@ public class DatadogGlobalConfiguration extends GlobalConfiguration { @DataBoundConstructor public DatadogGlobalConfiguration() { load(); // Load the persisted global configuration - loadEnvVariables(); // Load environment variables after as they should take precedence. + loadEnvVariables(); // Load environment variables + } + + @Override + protected XmlFile getConfigFile() { + return new XmlFile(XSTREAM, new File(Jenkins.get().getRootDir(), getId() + ".xml")); } @Initializer(after = InitMilestone.SYSTEM_CONFIG_LOADED) public void onStartup() { try { - ClientHolder.setClient(createClient()); + DatadogClient client = this.datadogClientConfiguration.createClient(); + ClientHolder.setClient(client); } catch (Exception e) { DatadogUtilities.logException(logger, Level.INFO, "Could not init Datadog client", e); } } public void loadEnvVariables() { - String reportWithEnvVar = System.getenv(REPORT_WITH_PROPERTY); - if(StringUtils.isNotBlank(reportWithEnvVar) && - (reportWithEnvVar.equals(DatadogClient.ClientType.HTTP.name()) || - reportWithEnvVar.equals(DatadogClient.ClientType.DSD.name()))){ - this.reportWith = reportWithEnvVar; - } - - String targetApiURLEnvVar = System.getenv(TARGET_API_URL_PROPERTY); - if(StringUtils.isNotBlank(targetApiURLEnvVar)){ - this.targetApiURL = targetApiURLEnvVar; - } - - String targetLogIntakeURLEnvVar = System.getenv(TARGET_LOG_INTAKE_URL_PROPERTY); - if(StringUtils.isNotBlank(targetLogIntakeURLEnvVar)){ - this.targetLogIntakeURL = targetLogIntakeURLEnvVar; - } - - String targetWebhookIntakeURLEnvVar = System.getenv(TARGET_WEBHOOK_INTAKE_URL_PROPERTY); - if(StringUtils.isNotBlank(targetWebhookIntakeURLEnvVar)){ - this.targetWebhookIntakeURL = targetWebhookIntakeURLEnvVar; - } - - String targetApiKeyEnvVar = System.getenv(TARGET_API_KEY_PROPERTY); - if(StringUtils.isNotBlank(targetApiKeyEnvVar)){ - this.targetApiKey = Secret.fromString(targetApiKeyEnvVar); - } - - final DatadogAgentConfiguration agentConfig = DatadogAgentConfiguration.resolve(System.getenv()); - if(StringUtils.isNotBlank(agentConfig.getHost())){ - this.targetHost = agentConfig.getHost(); - } - - if(agentConfig.getPort() != null){ - this.targetPort = agentConfig.getPort(); - } - - if(agentConfig.getTracesPort() != null) { - this.targetTraceCollectionPort = agentConfig.getTracesPort(); - } - - String targetLogCollectionPortEnvVar = System.getenv(TARGET_LOG_COLLECTION_PORT_PROPERTY); - if(StringUtils.isNotBlank(targetLogCollectionPortEnvVar) && StringUtils.isNumeric(targetLogCollectionPortEnvVar)){ - this.targetLogCollectionPort = Integer.valueOf(targetLogCollectionPortEnvVar); + // config values set manually in the UI take precedence over the ones provided via environment variables + if (this.datadogClientConfiguration == null) { + String clientType = System.getenv(REPORT_WITH_PROPERTY); + if (DATADOG_AGENT_CLIENT_TYPE.equals(clientType)) { + this.datadogClientConfiguration = new DatadogAgentConfiguration( + getDefaultAgentHost(), getDefaultAgentPort(), getDefaultAgentLogCollectionPort(), getDefaultAgentTraceCollectionPort()); + } else { + DatadogIntake intake = DatadogIntake.getDefaultIntake(); + DatadogTextApiKey apiKey = new DatadogTextApiKey(getDefaultKey()); + this.datadogClientConfiguration = new DatadogApiConfiguration(intake, apiKey); + } } String traceServiceNameVar = System.getenv(TARGET_TRACE_SERVICE_NAME_PROPERTY); if(StringUtils.isNotBlank(traceServiceNameVar)) { - this.traceServiceName = traceServiceNameVar; + this.ciInstanceName = traceServiceNameVar; } String hostnameEnvVar = System.getenv(HOSTNAME_PROPERTY); @@ -247,10 +202,10 @@ public void loadEnvVariables() { // backwards compatibility excludedEnvVar = System.getenv(BLACKLIST_PROPERTY); if(StringUtils.isNotBlank(excludedEnvVar)){ - this.blacklist = excludedEnvVar; + this.excluded = excludedEnvVar; } } else { - this.blacklist = excludedEnvVar; + this.excluded = excludedEnvVar; } String includedEnvVar = System.getenv(INCLUDED_PROPERTY); @@ -258,10 +213,10 @@ public void loadEnvVariables() { // backwards compatibility includedEnvVar = System.getenv(WHITELIST_PROPERTY); if(StringUtils.isNotBlank(includedEnvVar)){ - this.whitelist = includedEnvVar; + this.included = includedEnvVar; } } else { - this.whitelist = includedEnvVar; + this.included = includedEnvVar; } String globalTagFileEnvVar = System.getenv(GLOBAL_TAG_FILE_PROPERTY); @@ -269,11 +224,6 @@ public void loadEnvVariables() { this.globalTagFile = globalTagFileEnvVar; } - String emitConfigChangeEventsEnvVar = System.getenv(EMIT_CONFIG_CHANGE_EVENTS_PROPERTY); - if(StringUtils.isNotBlank(emitConfigChangeEventsEnvVar)){ - this.emitConfigChangeEvents = Boolean.valueOf(emitConfigChangeEventsEnvVar); - } - String globalTagsEnvVar = System.getenv(GLOBAL_TAGS_PROPERTY); if(StringUtils.isNotBlank(globalTagsEnvVar)){ this.globalTags = globalTagsEnvVar; @@ -286,12 +236,12 @@ public void loadEnvVariables() { String emitSecurityEventsEnvVar = System.getenv(EMIT_SECURITY_EVENTS_PROPERTY); if(StringUtils.isNotBlank(emitSecurityEventsEnvVar)){ - this.emitSecurityEvents = Boolean.valueOf(emitSecurityEventsEnvVar); + this.emitSecurityEvents = Boolean.parseBoolean(emitSecurityEventsEnvVar); } String emitSystemEventsEnvVar = System.getenv(EMIT_SYSTEM_EVENTS_PROPERTY); if(StringUtils.isNotBlank(emitSystemEventsEnvVar)){ - this.emitSystemEvents = Boolean.valueOf(emitSystemEventsEnvVar); + this.emitSystemEvents = Boolean.parseBoolean(emitSystemEventsEnvVar); } String includeEventsEnvVar = System.getenv(INCLUDE_EVENTS_PROPERTY); @@ -306,177 +256,33 @@ public void loadEnvVariables() { String collectBuildLogsEnvVar = System.getenv(COLLECT_BUILD_LOGS_PROPERTY); if(StringUtils.isNotBlank(collectBuildLogsEnvVar)){ - this.collectBuildLogs = Boolean.valueOf(collectBuildLogsEnvVar); + this.collectBuildLogs = Boolean.parseBoolean(collectBuildLogsEnvVar); } String refreshDogstatsdClientEnvVar = System.getenv(REFRESH_DOGSTATSD_CLIENT_PROPERTY); if(StringUtils.isNotBlank(refreshDogstatsdClientEnvVar)){ - this.refreshDogstatsdClient = Boolean.valueOf(refreshDogstatsdClientEnvVar); + this.refreshDogstatsdClient = Boolean.parseBoolean(refreshDogstatsdClientEnvVar); } String cacheBuildRunsEnvVar = System.getenv(CACHE_BUILD_RUNS_PROPERTY); if(StringUtils.isNotBlank(cacheBuildRunsEnvVar)){ - this.cacheBuildRuns = Boolean.valueOf(cacheBuildRunsEnvVar); + this.cacheBuildRuns = Boolean.parseBoolean(cacheBuildRunsEnvVar); } String useAwsInstanceHostnameEnvVar = System.getenv(USE_AWS_INSTANCE_HOSTNAME_PROPERTY); if(StringUtils.isNotBlank(useAwsInstanceHostnameEnvVar)){ - this.useAwsInstanceHostname = Boolean.valueOf(useAwsInstanceHostnameEnvVar); + this.useAwsInstanceHostname = Boolean.parseBoolean(useAwsInstanceHostnameEnvVar); } String enableCiVisibilityVar = System.getenv(ENABLE_CI_VISIBILITY_PROPERTY); if(StringUtils.isNotBlank(enableCiVisibilityVar)) { - this.collectBuildTraces = Boolean.valueOf(enableCiVisibilityVar); + this.enableCiVisibility = Boolean.parseBoolean(enableCiVisibilityVar); } String ciVisibilityCiInstanceNameVar = System.getenv(CI_VISIBILITY_CI_INSTANCE_NAME_PROPERTY); if(StringUtils.isNotBlank(ciVisibilityCiInstanceNameVar)) { - this.traceServiceName = ciVisibilityCiInstanceNameVar; - } - } - - /** - * Test the connection to the Logs Collection port in the Datadog Agent. - * - * @param targetHost - The Datadog Agent host - * @param targetLogCollectionPort - The Logs Collection port used to report logs in the Datadog Agent - * @return a FormValidation object used to display a message to the user on the configuration - * screen. - */ - public FormValidation doCheckAgentConnectivityLogs(@QueryParameter("targetHost") String targetHost, @QueryParameter("targetLogCollectionPort") String targetLogCollectionPort) { - return checkAgentConnectivity(targetHost, targetLogCollectionPort); - } - - /** - * Test the connection to the Traces Collection port in the Datadog Agent. - * - * @param targetHost - The Datadog Agent host - * @param targetTraceCollectionPort - The Traces Collection port used to report logs in the Datadog Agent - * @return a FormValidation object used to display a message to the user on the configuration - * screen. - */ - - public FormValidation doCheckAgentConnectivityTraces(@QueryParameter("targetHost") String targetHost, @QueryParameter("targetTraceCollectionPort") String targetTraceCollectionPort) { - return checkAgentConnectivity(targetHost, targetTraceCollectionPort); - } - - private FormValidation checkAgentConnectivity(final String host, final String port) { - if(host == null || host.isEmpty()) { - return FormValidation.error("The Agent host cannot be empty."); - } - - if(port != null && !port.isEmpty()) { - if(!validatePort(port)) { - return FormValidation.error("The port is not valid"); - } - - final DatadogAgentClient.ConnectivityResult connectivity = DatadogAgentClient.checkConnectivity(host, Integer.parseInt(port)); - if(connectivity.isError()) { - return FormValidation.error("Connection to " + host + ":" + port + " FAILED: " + connectivity.getErrorMessage()); - } - } else { - return FormValidation.error("The port cannot be empty."); - } - - return FormValidation.ok("Success!"); - } - - /** - * Gets the StringCredentials object for the given credential ID - * - * @param credentialId - The Id of the credential to get - * @return a StringCredentials object - */ - public StringCredentials getCredentialFromId(String credentialId) { - return CredentialsMatchers.firstOrNull( - CredentialsProvider.lookupCredentials( - StringCredentials.class, - Jenkins.get(), - ACL.SYSTEM, - URIRequirementBuilder.fromUri(null).build()), - CredentialsMatchers.allOf(CredentialsMatchers.withId(credentialId)) - ); - } - - /** - * Gets the correct Secret object representing the API key used for authentication to Datadog - * If a Credential is provided, then use the credential, if not, default to the text submission - * - * @param apiKey - The text API key the user submitted - * @param credentialsApiKey - The Id of the credential the user submitted - * @return a Secret object representing the API key used for authentication to Datadog - */ - public Secret findSecret(String apiKey, String credentialsApiKey) { - Secret secret = Secret.fromString(apiKey); - if (credentialsApiKey != null && !StringUtils.isBlank(credentialsApiKey)) { - StringCredentials credential = this.getCredentialFromId(credentialsApiKey); - if (credential != null && !credential.getSecret().getPlainText().isEmpty()){ - secret = credential.getSecret(); - } - } - return secret; - } - - /** - * Tests the apiKey field from the configuration screen, to check its' validity. - * It is used in the config.jelly resource file. See method="testConnection" - * - * @param targetApiURL - The API Url to validate the apikey. - * @param targetApiKey - A String containing the apiKey submitted from the form on the - * configuration screen, which will be used to authenticate a request to the - * Datadog API. - * @param targetCredentialsApiKey - A String containing the API key as a credential, if it is not specified, - try the connection with the targetApiKey - * @return a FormValidation object used to display a message to the user on the configuration - * screen. - * @throws IOException if there is an input/output exception. - * @throws ServletException if there is a servlet exception. - */ - @RequirePOST - public FormValidation doTestConnection( - @QueryParameter("targetApiKey") final String targetApiKey, - @QueryParameter("targetCredentialsApiKey") final String targetCredentialsApiKey, - @QueryParameter("targetApiURL") final String targetApiURL) - throws IOException, ServletException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); - final Secret secret = findSecret(targetApiKey, targetCredentialsApiKey); - if (DatadogApiClient.validateDefaultIntakeConnection(targetApiURL, secret)) { - return FormValidation.ok("Great! Your API key is valid."); - } else { - return FormValidation.error("Hmmm, your API key seems to be invalid."); - } - } - - /** - * Populates the targetCredentialsApiKey field from the configuration screen with all of the valid credentials - * - * @param item - The context within which to list available credentials - * @param targetCredentialsApiKey - A String containing the API key as a credential - * @return a ListBoxModel object used to display all of the available credentials. - */ - public ListBoxModel doFillTargetCredentialsApiKeyItems( - @AncestorInPath Item item, - @QueryParameter("targetCredentialsApiKey") String targetCredentialsApiKey - ) { - StandardListBoxModel result = new StandardListBoxModel(); - // If the user does not have permissions to list credentials, only list the current value - if (item == null) { - if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { - return result.includeCurrentValue(targetCredentialsApiKey); - } - } else { - if (!item.hasPermission(Item.EXTENDED_READ) - && !item.hasPermission(CredentialsProvider.USE_ITEM)) { - return result.includeCurrentValue(targetCredentialsApiKey); - } + this.ciInstanceName = ciVisibilityCiInstanceNameVar; } - return result.includeEmptyValue() - .includeMatchingAs(ACL.SYSTEM, - Jenkins.get(), - StringCredentials.class, - Collections.emptyList(), - CredentialsMatchers.instanceOf(StringCredentials.class)) - .includeCurrentValue(targetCredentialsApiKey); } /** @@ -491,56 +297,15 @@ public ListBoxModel doFillTargetCredentialsApiKeyItems( * FormValidation.warning() for redundant config, and FormValidation.ok() for all else */ public FormValidation doTestFilteringConfig( - @QueryParameter("emitSecurityEvents") boolean emitSecurityEvents, - @QueryParameter("emitSystemEvents") boolean emitSystemEvents, - @QueryParameter("includeEvents") String includeEvents, - @QueryParameter("excludeEvents") String excludeEvents + @QueryParameter("emitSecurityEvents") boolean emitSecurityEvents, + @QueryParameter("emitSystemEvents") boolean emitSystemEvents, + @QueryParameter("includeEvents") String includeEvents, + @QueryParameter("excludeEvents") String excludeEvents ) { return validateEventFilteringConfig(emitSecurityEvents, emitSystemEvents, includeEvents, excludeEvents); } - - /** - * Tests the targetCredentialsApiKey field from the configuration screen, to check its validity. - * - * @param item - The context within which to list available credentials. - * @param targetCredentialsApiKey - A String containing the API key as a credential - * @return a FormValidation object used to display a message to the user on the configuration - * screen. - */ - @RequirePOST - public FormValidation doCheckTargetCredentialsApiKey( - @AncestorInPath Item item, - @QueryParameter("targetCredentialsApiKey") String targetCredentialsApiKey - ) { - // Don't validate for users that do not have permission to list credentials - if (item == null) { - if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { - return FormValidation.ok(); - } - } else { - if (!item.hasPermission(Item.EXTENDED_READ) - && !item.hasPermission(CredentialsProvider.USE_ITEM)) { - return FormValidation.ok(); - } - } - if (StringUtils.isBlank(targetCredentialsApiKey)) { - return FormValidation.ok(); - } - if (targetCredentialsApiKey.startsWith("${") && targetCredentialsApiKey.endsWith("}")) { - return FormValidation.warning("Cannot validate expression based credentials"); - } - if (CredentialsProvider.listCredentials(StringCredentials.class, - item, - ACL.SYSTEM, - Collections.emptyList(), - CredentialsMatchers.withId(targetCredentialsApiKey)).isEmpty()) { - return FormValidation.error("Cannot find currently selected credentials"); - } - return FormValidation.ok(); - } - /** * Tests the hostname field from the configuration screen, to determine if * the hostname is of a valid format, according to the RFC 1123. @@ -561,124 +326,10 @@ public FormValidation doTestHostname(@QueryParameter("hostname") final String ho } } - /** - * @param targetApiURL - The API URL which the plugin will report to. - * @return a FormValidation object used to display a message to the user on the configuration - * screen. - */ - @RequirePOST - public FormValidation doCheckTargetApiURL(@QueryParameter("targetApiURL") final String targetApiURL) { - if(!validateURL(targetApiURL)) { - return FormValidation.error("The field must be configured in the form :///"); - } - - return FormValidation.ok(); - } - - /** - * @param targetLogIntakeURL - The Log Intake URL which the plugin will report to. - * @return a FormValidation object used to display a message to the user on the configuration - * screen. - */ - @RequirePOST - public FormValidation doCheckTargetLogIntakeURL(@QueryParameter("targetLogIntakeURL") final String targetLogIntakeURL) { - if (!validateURL(targetLogIntakeURL) && collectBuildLogs) { - return FormValidation.error("The field must be configured in the form :///"); - } - - return FormValidation.ok(); - } - - /** - * @param targetWebhookIntakeURL - The Webhook Intake URL which the plugin will report to. - * @return a FormValidation object used to display a message to the user on the configuration - * screen. - */ - @RequirePOST - public FormValidation doCheckTargetWebhookIntakeURL(@QueryParameter("targetWebhookIntakeURL") final String targetWebhookIntakeURL) { - if (!validateURL(targetWebhookIntakeURL) && collectBuildTraces) { - return FormValidation.error("The field must be configured in the form :///"); - } - - return FormValidation.ok(); - } - - private boolean validateTargetHost(String targetHost) { - if(!DatadogClient.ClientType.DSD.name().equals(reportWith)) { - return true; - } - - return StringUtils.isNotBlank(targetHost); - } - - public static boolean validateURL(String targetURL) { - return StringUtils.isNotBlank(targetURL) && targetURL.contains("http"); - } - - /** - * @param targetHost - The dogStatsD Host which the plugin will report to. - * @return a FormValidation object used to display a message to the user on the configuration - * screen. - */ - @RequirePOST - public FormValidation doCheckTargetHost(@QueryParameter("targetHost") final String targetHost) { - if (!validateTargetHost(targetHost)) { - return FormValidation.error("Invalid Host"); - } - - return FormValidation.ok(); - } - - public static boolean validatePort(String targetPort) { - return StringUtils.isNotBlank(targetPort) && StringUtils.isNumeric(targetPort) && NumberUtils.createInteger(targetPort) >= 0; - } - - /** - * @param targetPort - The dogStatsD Port which the plugin will report to. - * @return a FormValidation object used to display a message to the user on the configuration - * screen. - */ - @RequirePOST - public FormValidation doCheckTargetPort(@QueryParameter("targetPort") final String targetPort) { - if (!validatePort(targetPort)) { - return FormValidation.error("Invalid Port"); - } - - return FormValidation.ok(); - } - - /** - * @param targetLogCollectionPort - The Log Collection Port which the plugin will report to. - * @return a FormValidation object used to display a message to the user on the configuration - * screen. - */ - @RequirePOST - public FormValidation doCheckTargetLogCollectionPort(@QueryParameter("targetLogCollectionPort") final String targetLogCollectionPort) { - if (!validatePort(targetLogCollectionPort) && collectBuildLogs) { - return FormValidation.error("Invalid Log Collection Port"); - } - - return FormValidation.ok(); - } - - /** - * @param targetTraceCollectionPort - The Trace Collection Port which the plugin will report to. - * @return a FormValidation object used to display a message to the user on the configuration - * screen. - */ - @RequirePOST - public FormValidation doCheckTargetTraceCollectionPort(@QueryParameter("targetTraceCollectionPort") final String targetTraceCollectionPort) { - if (!validatePort(targetTraceCollectionPort) && collectBuildTraces) { - return FormValidation.error("Invalid Trace Collection Port"); - } - - return FormValidation.ok(); - } - @RequirePOST - public FormValidation doCheckTraceServiceName(@QueryParameter("traceServiceName") final String traceServiceName) { - if(StringUtils.isBlank(traceServiceName) && collectBuildTraces){ - return FormValidation.error("Invalid CI Instance Name"); + public FormValidation doCheckCiInstanceName(@QueryParameter("ciInstanceName") final String ciInstanceName) { + if (StringUtils.isBlank(ciInstanceName) && enableCiVisibility) { + return FormValidation.error("CI Instance Name cannot be blank"); } return FormValidation.ok(); } @@ -696,10 +347,11 @@ public boolean isApplicable(final Class aClass) { } /** - * Getter function for a human readable plugin name, used in the configuration screen. + * Getter function for a human-readable plugin name, used in the configuration screen. * - * @return a String containing the human readable display name for this plugin. + * @return a String containing the human-readable display name for this plugin. */ + @NonNull @Override public String getDisplayName() { return DISPLAY_NAME; @@ -722,55 +374,18 @@ public boolean configure(final StaplerRequest req, final JSONObject formData) th return false; } - final String reportWith = formData.getString("reportWith"); - this.setReportWith(reportWith); - this.setTargetApiURL(formData.getString("targetApiURL")); - this.setTargetLogIntakeURL(formData.getString("targetLogIntakeURL")); - this.setTargetWebhookIntakeURL(formData.getString("targetWebhookIntakeURL")); - this.setTargetApiKey(formData.getString("targetApiKey")); - this.setTargetCredentialsApiKey(formData.getString("targetCredentialsApiKey")); - this.setTargetHost(formData.getString("targetHost")); - String portStr = formData.getString("targetPort"); - if (validatePort(portStr)) { - this.setTargetPort(formData.getInt("targetPort")); - } else { - this.setTargetPort(null); - } - String logCollectionPortStr = formData.getString("targetLogCollectionPort"); - if(validatePort(logCollectionPortStr)){ - this.setTargetLogCollectionPort(formData.getInt("targetLogCollectionPort")); - }else{ - this.setTargetLogCollectionPort(null); - } - - final String traceCollectionPortStr = formData.getString("targetTraceCollectionPort"); - if(validatePort(traceCollectionPortStr)){ - this.setTargetTraceCollectionPort(formData.getInt("targetTraceCollectionPort")); - }else{ - this.setTargetTraceCollectionPort(null); - } + this.datadogClientConfiguration = req.bindJSON(DatadogClientConfiguration.class, formData.getJSONObject("datadogClientConfiguration")); try { final JSONObject ciVisibilityData = formData.getJSONObject("ciVisibilityData"); if (ciVisibilityData != null && !ciVisibilityData.isNullObject()) { - if (!"DSD".equalsIgnoreCase(reportWith)) { - if (!validateURL(formData.getString("targetWebhookIntakeURL"))) { - throw new FormException("CI Visibility requires a Webhook Intake URL", "targetWebhookIntakeURL"); - } - } else { - if(!validatePort(traceCollectionPortStr)) { - throw new FormException("CI Visibility requires a valid Trace Collection port", "collectBuildTraces"); - } - } - - final String ciInstanceName = ciVisibilityData.getString("traceServiceName"); - if (StringUtils.isNotBlank(ciInstanceName)) { - this.setCiInstanceName(ciInstanceName); - } else { - this.setCiInstanceName(DEFAULT_CI_INSTANCE_NAME); - } + this.datadogClientConfiguration.validateTracesConnection(); + + setEnableCiVisibility(true); + + String ciInstanceName = ciVisibilityData.getString("ciInstanceName"); + setCiInstanceName(StringUtils.isNotBlank(ciInstanceName) ? ciInstanceName : DEFAULT_CI_INSTANCE_NAME); } - this.setEnableCiVisibility(ciVisibilityData != null && !ciVisibilityData.isNullObject()); } catch (FormException ex) { //If it is the validation exception, we throw it to the next level. @@ -778,8 +393,8 @@ public boolean configure(final StaplerRequest req, final JSONObject formData) th } catch (Exception ex) { // We disable CI Visibility if there is an error parsing the CI Visibility configuration // because we don't want to prevent the user process the rest of the configuration. - this.setEnableCiVisibility(false); - this.setCiInstanceName(DEFAULT_CI_INSTANCE_NAME); + setEnableCiVisibility(false); + setCiInstanceName(DEFAULT_CI_INSTANCE_NAME); DatadogUtilities.severe(logger, ex, "Failed to configure CI Visibility: " + ex.getMessage()); } @@ -787,22 +402,21 @@ public boolean configure(final StaplerRequest req, final JSONObject formData) th throw new FormException("Your hostname is invalid, likely because it violates the format set in RFC 1123", "hostname"); } - this.setHostname(formData.getString("hostname")); - // These config names have to be kept for backwards compatibility reasons - this.setExcluded(formData.getString("blacklist")); - this.setIncluded(formData.getString("whitelist")); - this.setGlobalTagFile(formData.getString("globalTagFile")); - this.setGlobalTags(formData.getString("globalTags")); - this.setGlobalJobTags(formData.getString("globalJobTags")); - this.setRefreshDogstatsdClient(formData.getBoolean("refreshDogstatsdClient")); - this.setCacheBuildRuns(formData.getBoolean("cacheBuildRuns")); - this.setUseAwsInstanceHostname(formData.getBoolean("useAwsInstanceHostname")); + setHostname(formData.getString("hostname")); + setExcluded(formData.getString("excluded")); + setIncluded(formData.getString("included")); + setGlobalTagFile(formData.getString("globalTagFile")); + setGlobalTags(formData.getString("globalTags")); + setGlobalJobTags(formData.getString("globalJobTags")); + setRefreshDogstatsdClient(formData.getBoolean("refreshDogstatsdClient")); + setCacheBuildRuns(formData.getBoolean("cacheBuildRuns")); + setUseAwsInstanceHostname(formData.getBoolean("useAwsInstanceHostname")); boolean emitSecurityEvents = formData.getBoolean("emitSecurityEvents"); boolean emitSystemEvents = formData.getBoolean("emitSystemEvents"); String includeEvents = formData.getString("includeEvents"); String excludeEvents = formData.getString("excludeEvents"); - FormValidation configStatus = this.validateEventFilteringConfig(emitSecurityEvents, emitSystemEvents, includeEvents, excludeEvents); + FormValidation configStatus = validateEventFilteringConfig(emitSecurityEvents, emitSystemEvents, includeEvents, excludeEvents); if (configStatus.kind == Kind.ERROR) { String message = configStatus.getMessage(); @@ -810,446 +424,129 @@ public boolean configure(final StaplerRequest req, final JSONObject formData) th throw new FormException(message, formField); } - this.setEmitSecurityEvents(emitSecurityEvents); - this.setEmitSystemEvents(emitSystemEvents); - this.setIncludeEvents(includeEvents); - this.setExcludeEvents(excludeEvents); + setEmitSecurityEvents(emitSecurityEvents); + setEmitSystemEvents(emitSystemEvents); + setIncludeEvents(includeEvents); + setExcludeEvents(excludeEvents); - boolean collectBuildLogs = formData.getBoolean("collectBuildLogs"); - if ("DSD".equalsIgnoreCase(reportWith) && collectBuildLogs && !validatePort(logCollectionPortStr)) { - throw new FormException("Logs Collection requires a valid Log Collection port", "collectBuildLogs"); + setCollectBuildLogs(formData.getBoolean("collectBuildLogs")); + if (this.collectBuildLogs) { + this.datadogClientConfiguration.validateLogsConnection(); } - this.setCollectBuildLogs(formData.getBoolean("collectBuildLogs")); - final Secret apiKeySecret = findSecret(formData.getString("targetApiKey"), formData.getString("targetCredentialsApiKey")); - this.setUsedApiKey(apiKeySecret); - - DatadogClient client = createClient(); + DatadogClient client = this.datadogClientConfiguration.createClient(); ClientHolder.setClient(client); // Persist global configuration information save(); return true; - }catch(Exception e){ - // Intercept all FormException instances. - if(e instanceof FormException){ - throw (FormException)e; - } - + } catch(FormException e) { + throw e; + } catch(Exception e) { DatadogUtilities.severe(logger, e, "Failed to save configuration"); return false; } } - @Nullable - public DatadogClient createClient() { - if (StringUtils.isBlank(reportWith)) { - return null; - } - - DatadogClient.ClientType type = DatadogClient.ClientType.valueOf(reportWith); - switch(type){ - case HTTP: - return new DatadogApiClient(targetApiURL, targetLogIntakeURL, targetWebhookIntakeURL, usedApiKey); - case DSD: - return new DatadogAgentClient(targetHost, targetPort, targetLogCollectionPort, targetTraceCollectionPort); - default: - throw new IllegalArgumentException("Unsupported client type: " + type); - } + public DatadogClientConfiguration getDatadogClientConfiguration() { + return datadogClientConfiguration; } + public void setDatadogClientConfiguration(DatadogClientConfiguration datadogClientConfiguration) { + this.datadogClientConfiguration = datadogClientConfiguration; + } /** - * Getter function for the reportWith global configuration. + * Getter function for the ciInstanceName global configuration. * - * @return a String containing the reportWith global configuration. + * @return a String containing the ciInstanceName global configuration. */ - public String getReportWith() { - return reportWith; + public String getCiInstanceName() { + return this.ciInstanceName; } /** - * Setter function for the reportWith global configuration. + * Setter function for the ciInstanceName global configuration. * - * @param reportWith = A string containing the reportWith global configuration. + * @param ciInstanceName = A string containing the CI Instance Name */ - @DataBoundSetter - public void setReportWith(String reportWith) { - this.reportWith = reportWith; + public void setCiInstanceName(String ciInstanceName) { + this.ciInstanceName = ciInstanceName; } /** - * Getter function for the targetApiURL global configuration. + * Getter function for the hostname global configuration. * - * @return a String containing the targetApiURL global configuration. + * @return a String containing the hostname global configuration. */ - public String getTargetApiURL() { - return targetApiURL; + public String getHostname() { + return hostname; } /** - * Setter function for the targetApiURL global configuration. + * Setter function for the hostname global configuration. * - * @param targetApiURL = A string containing the DataDog API URL + * @param hostname - A String containing the hostname of the Jenkins host. */ - @DataBoundSetter - public void setTargetApiURL(String targetApiURL) { - this.targetApiURL = targetApiURL; + public void setHostname(final String hostname) { + this.hostname = hostname; } /** - * Setter function for the targetLogIntakeURL global configuration. + * Getter function for the excluded global configuration, containing + * a comma-separated list of jobs to exclude from monitoring. * - * @param targetLogIntakeURL = A string containing the DataDog Log Intake URL + * @return a String array containing the excluded global configuration. */ - @DataBoundSetter - public void setTargetLogIntakeURL(String targetLogIntakeURL) { - this.targetLogIntakeURL = targetLogIntakeURL; + public String getExcluded() { + return excluded; } /** - * Getter function for the targetLogIntakeURL global configuration. + * Setter function for the excluded jobs global configuration, + * accepting a comma-separated string of jobs. * - * @return a String containing the targetLogIntakeURL global configuration. + * @param jobs - a comma-separated list of jobs to exclude from monitoring. */ - public String getTargetLogIntakeURL() { - return targetLogIntakeURL; + public void setExcluded(final String jobs) { + this.excluded = jobs; } /** - * Setter function for the targetWebhookIntakeURL global configuration. + * Getter function for the included global configuration, containing + * a comma-separated list of jobs to include for monitoring. * - * @param targetWebhookIntakeURL = A string containing the DataDog Webhook Intake URL + * @return a String array containing the included global configuration. */ - @DataBoundSetter - public void setTargetWebhookIntakeURL(String targetWebhookIntakeURL) { - this.targetWebhookIntakeURL = targetWebhookIntakeURL; + public String getIncluded() { + return included; } /** - * Getter function for the targetWebhookIntakeURL global configuration. + * Setter function for the includedd global configuration, + * accepting a comma-separated string of jobs. * - * @return a String containing the targetWebhookIntakeURL global configuration. + * @param jobs - a comma-separated list of jobs to include for monitoring. */ - public String getTargetWebhookIntakeURL() { - return targetWebhookIntakeURL; + public void setIncluded(final String jobs) { + this.included = jobs; } /** - * Getter function for the targetApiKey global configuration. + * Gets the globalTagFile set in the job configuration. * - * @return a Secret containing the targetApiKey global configuration. + * @return a String representing the relative path to a globalTagFile */ - public Secret getTargetApiKey() { - return targetApiKey; + public String getGlobalTagFile() { + return globalTagFile; } /** - * Setter function for the apiKey global configuration. + * Setter function for the globalFile global configuration, + * accepting a comma-separated string of tags. * - * @param targetApiKey = A string containing the plaintext representation of a - * DataDog API Key + * @param globalTagFile - a comma-separated list of tags. */ - @DataBoundSetter - public void setTargetApiKey(final String targetApiKey) { - this.targetApiKey = Secret.fromString(fixEmptyAndTrim(targetApiKey)); - } - - /** - * Getter function for the API key global configuration. - * - * @return a Secret containing the usedApiKey global configuration. - */ - public Secret getUsedApiKey() { - // FIXME this is a temporary workaround for supporting Configuration as Code - if (usedApiKey == null) { - Secret secret = findSecret(targetApiKey != null ? targetApiKey.getEncryptedValue() : null, targetCredentialsApiKey); - if (!Secret.toString(secret).isEmpty()) { - this.usedApiKey = secret; - } - } - return usedApiKey; - } - - /** - * Setter function for the API key global configuration.. - * - * @param usedApiKey = A Secret containing the DataDog API Key - */ - @DataBoundSetter - public void setUsedApiKey(final Secret usedApiKey) { - this.usedApiKey = usedApiKey; - } - - /** - * Getter function for the targetCredentialsApiKey global configuration. - * - * @return a String containing the ID of the targetCredentialsApiKey global configuration. - */ - public String getTargetCredentialsApiKey() { - return targetCredentialsApiKey; - } - - /** - * Setter function for the credentials apiKey global configuration. - * - * @param targetCredentialsApiKey = A string containing the plaintext representation of a - * DataDog API Key - */ - @DataBoundSetter - public void setTargetCredentialsApiKey(final String targetCredentialsApiKey) { - this.targetCredentialsApiKey = targetCredentialsApiKey; - } - - /** - * Getter function for the targetHost global configuration. - * - * @return a String containing the targetHost global configuration. - */ - public String getTargetHost() { - return targetHost; - } - - /** - * Setter function for the targetHost global configuration. - * - * @param targetHost = A string containing the DogStatsD Host - */ - @DataBoundSetter - public void setTargetHost(String targetHost) { - this.targetHost = targetHost; - } - - /** - * Getter function for the targetPort global configuration. - * - * @return a Integer containing the targetPort global configuration. - */ - public Integer getTargetPort() { - return targetPort; - } - - /** - * Setter function for the targetPort global configuration. - * - * @param targetPort = A string containing the DogStatsD Port - */ - @DataBoundSetter - public void setTargetPort(Integer targetPort) { - this.targetPort = targetPort; - } - - /** - * Getter function for the targetLogCollectionPort global configuration. - * - * @return a Integer containing the targetLogCollectionPort global configuration. - */ - public Integer getTargetLogCollectionPort() { - return targetLogCollectionPort; - } - - /** - * Setter function for the targetLogCollectionPort global configuration. - * - * @param targetLogCollectionPort = A string containing the Log Collection Port - */ - @DataBoundSetter - public void setTargetLogCollectionPort(Integer targetLogCollectionPort) { - this.targetLogCollectionPort = targetLogCollectionPort; - } - - /** - * Getter function for the targetTraceCollectionPort global configuration. - * - * @return a Integer containing the targetTraceCollectionPort global configuration. - */ - public Integer getTargetTraceCollectionPort() { - return targetTraceCollectionPort; - } - - /** - * Setter function for the targetLogCollectionPort global configuration. - * - * @param targetTraceCollectionPort = A string containing the Trace Collection Port - */ - @DataBoundSetter - public void setTargetTraceCollectionPort(Integer targetTraceCollectionPort) { - this.targetTraceCollectionPort = targetTraceCollectionPort; - } - - /** - * Getter function for the traceServiceName global configuration. - * - * @return a String containing the traceServiceName global configuration. - * @deprecated use getCiInstanceName. - */ - @Deprecated - public String getTraceServiceName() { - return traceServiceName; - } - - /** - * Setter function for the traceServiceName global configuration. - * - * @param traceServiceName = A string containing the Trace Service Name - * @deprecated Use setCiInstanceName. - */ - @Deprecated - @DataBoundSetter - public void setTraceServiceName(String traceServiceName) { - this.traceServiceName = traceServiceName; - } - - /** - * Getter function for the traceServiceName global configuration. - * - * @return a String containing the traceServiceName global configuration. - */ - public String getCiInstanceName() { - return this.traceServiceName; - } - - /** - * Setter function for the traceServiceName global configuration. - * - * @param ciInstanceName = A string containing the CI Instance Name - */ - public void setCiInstanceName(String ciInstanceName) { - this.traceServiceName = ciInstanceName; - } - - /** - * Getter function for the hostname global configuration. - * - * @return a String containing the hostname global configuration. - */ - public String getHostname() { - return hostname; - } - - /** - * Setter function for the hostname global configuration. - * - * @param hostname - A String containing the hostname of the Jenkins host. - */ - @DataBoundSetter - public void setHostname(final String hostname) { - this.hostname = hostname; - } - - /** - * @deprecated replaced by {@link #getExcluded()} - * @return a String array containing the excluded global configuration. - **/ - @Deprecated - public String getBlacklist() { - return blacklist; - } - - /** - * Getter function for the excluded global configuration, containing - * a comma-separated list of jobs to exclude from monitoring. - * - * @return a String array containing the excluded global configuration. - */ - public String getExcluded() { - return blacklist; - } - - /** - * @deprecated replaced by {@link #setExcluded(String)} - * @param jobs - a comma-separated list of jobs to exclude from monitoring. - **/ - @Deprecated - @DataBoundSetter - public void setBlacklist(final String jobs) { - this.blacklist = jobs; - } - - /** - * Setter function for the excluded jobs global configuration, - * accepting a comma-separated string of jobs. - * - * @param jobs - a comma-separated list of jobs to exclude from monitoring. - */ - @DataBoundSetter - public void setExcluded(final String jobs) { - this.blacklist = jobs; - } - - /** - * @deprecated replaced by {@link #getIncluded()} - * @return a String array containing the included global configuration. - **/ - @Deprecated - public String getWhitelist() { - return whitelist; - } - - /** - * Getter function for the included global configuration, containing - * a comma-separated list of jobs to include for monitoring. - * - * @return a String array containing the included global configuration. - */ - public String getIncluded() { - return whitelist; - } - - /** - * @deprecated replaced by {@link #setIncluded(String)} - * @param jobs - a comma-separated list of jobs to include for monitoring. - **/ - @Deprecated - @DataBoundSetter - public void setWhitelist(final String jobs) { - this.whitelist = jobs; - } - - /** - * Setter function for the includedd global configuration, - * accepting a comma-separated string of jobs. - * - * @param jobs - a comma-separated list of jobs to include for monitoring. - */ - @DataBoundSetter - public void setIncluded(final String jobs) { - this.whitelist = jobs; - } - - /** - * @return - A {@link Boolean} indicating if the user has configured Datadog to emit Config Change events. - */ - public boolean isEmitConfigChangeEvents() { - return emitConfigChangeEvents; - } - - /** - * Used for CasC - * accepting a comma-separated string of events. - * - * @param emitConfigChangeEvents - The checkbox status (checked/unchecked) - */ - @DataBoundSetter - public void setEmitConfigChangeEvents(boolean emitConfigChangeEvents) { - this.emitConfigChangeEvents = emitConfigChangeEvents; - } - /** - * Gets the globalTagFile set in the job configuration. - * - * @return a String representing the relative path to a globalTagFile - */ - public String getGlobalTagFile() { - return globalTagFile; - } - - /** - * Setter function for the globalFile global configuration, - * accepting a comma-separated string of tags. - * - * @param globalTagFile - a comma-separated list of tags. - */ - @DataBoundSetter public void setGlobalTagFile(String globalTagFile) { this.globalTagFile = globalTagFile; } @@ -1270,7 +567,6 @@ public String getGlobalTags() { * * @param globalTags - a comma-separated list of tags. */ - @DataBoundSetter public void setGlobalTags(String globalTags) { this.globalTags = globalTags; } @@ -1291,7 +587,6 @@ public String getGlobalJobTags() { * * @param globalJobTags - a comma-separated list of jobs to include from monitoring. */ - @DataBoundSetter public void setGlobalJobTags(String globalJobTags) { this.globalJobTags = globalJobTags; } @@ -1325,7 +620,6 @@ public boolean isRefreshDogstatsdClient() { * * @param refreshDogstatsdClient - The checkbox status (checked/unchecked) */ - @DataBoundSetter public void setRefreshDogstatsdClient(boolean refreshDogstatsdClient) { this.refreshDogstatsdClient = refreshDogstatsdClient; } @@ -1342,7 +636,6 @@ public boolean isCacheBuildRuns() { * * @param cacheBuildRuns - The checkbox status (checked/unchecked) */ - @DataBoundSetter public void setCacheBuildRuns(boolean cacheBuildRuns) { this.cacheBuildRuns = cacheBuildRuns; } @@ -1360,7 +653,6 @@ public boolean isUseAwsInstanceHostname() { * * @param useAwsInstanceHostname - The checkbox status (checked/unchecked) */ - @DataBoundSetter public void setUseAwsInstanceHostname(boolean useAwsInstanceHostname) { this.useAwsInstanceHostname = useAwsInstanceHostname; } @@ -1377,7 +669,6 @@ public boolean isEmitSecurityEvents() { * * @param emitSecurityEvents - The checkbox status (checked/unchecked) */ - @DataBoundSetter public void setEmitSecurityEvents(boolean emitSecurityEvents) { this.emitSecurityEvents = emitSecurityEvents; } @@ -1394,7 +685,6 @@ public boolean isEmitSystemEvents() { * * @param emitSystemEvents - The checkbox status (checked/unchecked) */ - @DataBoundSetter public void setEmitSystemEvents(boolean emitSystemEvents) { this.emitSystemEvents = emitSystemEvents; } @@ -1405,8 +695,7 @@ public void setEmitSystemEvents(boolean emitSystemEvents) { * * @param events - a comma-separated list of events to include for sending to agent. */ - @DataBoundSetter - public void setIncludeEvents(String events) throws InvalidAttributeValueException { + public void setIncludeEvents(String events) { this.includeEvents = events; } @@ -1426,8 +715,7 @@ public String getIncludeEvents() { * * @param events - a comma-separated list of events to exclude for sending to agent. */ - @DataBoundSetter - public void setExcludeEvents(String events) throws InvalidAttributeValueException{ + public void setExcludeEvents(String events) { this.excludeEvents = events; } @@ -1453,37 +741,15 @@ public boolean isCollectBuildLogs() { * * @param collectBuildLogs - The checkbox status (checked/unchecked) */ - @DataBoundSetter public void setCollectBuildLogs(boolean collectBuildLogs) { this.collectBuildLogs = collectBuildLogs; } - /** - * @return - A {@link Boolean} indicating if the user has configured Datadog to collect traces. - * @deprecated Use isEnabledCiVisibility - */ - @Deprecated - public boolean isCollectBuildTraces() { - return collectBuildTraces; - } - - /** - * Set the checkbox in the UI, used for Jenkins data binding - * - * @param collectBuildTraces - The checkbox status (checked/unchecked) - * @deprecated Use setEnableCiVisibility - */ - @DataBoundSetter - @Deprecated - public void setCollectBuildTraces(boolean collectBuildTraces) { - this.collectBuildTraces = collectBuildTraces; - } - /** * @return - A {@link Boolean} indicating if the user has configured Datadog to enable CI Visibility. */ public boolean getEnableCiVisibility() { - return this.collectBuildTraces; + return this.enableCiVisibility; } /** @@ -1491,29 +757,15 @@ public boolean getEnableCiVisibility() { * * @param enableCiVisibility - The checkbox status (checked/unchecked) */ - @DataBoundSetter public void setEnableCiVisibility(boolean enableCiVisibility) { - this.collectBuildTraces = enableCiVisibility; - } - - /** - * Helper function to determine if strings are overlapping in any way. - * - * @param firstString first string - * @param stringToCompare second string - * @return true if strings are overlapping (shared event) - */ - private boolean isOverlappingStrings(String firstString, String stringToCompare) { - if (stringToCompare == null || stringToCompare.isEmpty()) return false; - - return Arrays.asList(stringToCompare.split(",")).stream().anyMatch(firstString::contains); + this.enableCiVisibility = enableCiVisibility; } /** * @see #doTestFilteringConfig */ private FormValidation validateEventFilteringConfig(boolean emitSecurityEvents, boolean emitSystemEvents, - String includeEvents, String excludeEvents) { + String includeEvents, String excludeEvents) { String commaSeparatedRegex = "((\\w+,)*\\w+)?"; if (!includeEvents.matches(commaSeparatedRegex)) { return FormValidation.error("The included events list is not correctly written in a comma-separated list."); @@ -1522,11 +774,11 @@ private FormValidation validateEventFilteringConfig(boolean emitSecurityEvents, return FormValidation.error("The excluded events list is not correctly written in a comma-separated list."); } - List includedEventsList = (includeEvents.isEmpty()) ? new ArrayList() : Arrays.asList(includeEvents.split(",")); - List excludedEventsList = (excludeEvents.isEmpty()) ? new ArrayList() : Arrays.asList(excludeEvents.split(",")); + List includedEventsList = (includeEvents.isEmpty()) ? new ArrayList<>() : Arrays.asList(includeEvents.split(",")); + List excludedEventsList = (excludeEvents.isEmpty()) ? new ArrayList<>() : Arrays.asList(excludeEvents.split(",")); List allEvents = Arrays.asList( - String.format("%s,%s,%s", SYSTEM_EVENTS, SECURITY_EVENTS, DEFAULT_EVENTS).split(",")); + String.format("%s,%s,%s", SYSTEM_EVENTS, SECURITY_EVENTS, DEFAULT_EVENTS).split(",")); if (!includedEventsList.stream().allMatch(allEvents::contains)) { return FormValidation.error("The included events list contains one or more unrecognized events."); } @@ -1535,11 +787,11 @@ private FormValidation validateEventFilteringConfig(boolean emitSecurityEvents, } Set intersection = includedEventsList.stream() - .distinct() - .filter(excludedEventsList::contains) - .collect(Collectors.toSet()); + .distinct() + .filter(excludedEventsList::contains) + .collect(Collectors.toSet()); - if (intersection.size() > 0) { + if (!intersection.isEmpty()) { return FormValidation.error("The following events are in both the include and exclude lists: " + String.join(",", intersection)); } @@ -1548,14 +800,364 @@ private FormValidation validateEventFilteringConfig(boolean emitSecurityEvents, if (systemListToCheck.stream().anyMatch(SYSTEM_EVENTS::contains)) { return FormValidation.warning("Redundant filtering: One or more system events have been toggled " + - ((emitSystemEvents) ? "on" : "off") + " as well as written in the " + ((emitSystemEvents) ? "include" : "exclude") + " list manually"); + ((emitSystemEvents) ? "on" : "off") + " as well as written in the " + ((emitSystemEvents) ? "include" : "exclude") + " list manually"); } if (securityListToCheck.stream().anyMatch(SECURITY_EVENTS::contains)) { return FormValidation.warning("Redundant filtering: One or more security events have been toggled " + - ((emitSecurityEvents) ? "on" : "off") + " as well as written in the " + ((emitSecurityEvents) ? "include" : "exclude") + " list manually"); + ((emitSecurityEvents) ? "on" : "off") + " as well as written in the " + ((emitSecurityEvents) ? "include" : "exclude") + " list manually"); } return FormValidation.ok("Your filtering configuration looks good!"); } -} + + public Collection getDatadogClientConfigOptions() { + return DatadogClientConfiguration.DatadogClientConfigurationDescriptor.all(); + } + + /* **************************************************************************************************************** + * The fields/methods below are deprecated. + * They are here only to ensure backward compatibility. + * Do not use them. + * ***************************************************************************************************************/ + + /** @deprecated use {@link #ciInstanceName} */ + @Deprecated + private String traceServiceName; + /** @deprecated use {@link #excluded} */ + @Deprecated + private String blacklist; + /** @deprecated use {@link #included} */ + @Deprecated + private String whitelist; + /** @deprecated use {@link #enableCiVisibility} */ + @Deprecated + boolean collectBuildTraces; + /** @deprecated use {@link #datadogClientConfiguration} */ + @Deprecated + private String reportWith; + /** @deprecated use {@link #datadogClientConfiguration} */ + @Deprecated + private String targetApiURL; + /** @deprecated use {@link #datadogClientConfiguration} */ + @Deprecated + private String targetLogIntakeURL; + /** @deprecated use {@link #datadogClientConfiguration} */ + @Deprecated + private String targetWebhookIntakeURL; + /** @deprecated use {@link #datadogClientConfiguration} */ + @Deprecated + private Secret targetApiKey; + /** @deprecated use {@link #datadogClientConfiguration} */ + @Deprecated + @SuppressWarnings("lgtm[jenkins/plaintext-storage]") // not the actual key, but the ID of Jenkins credentials + private String targetCredentialsApiKey; + /** @deprecated use {@link #datadogClientConfiguration} */ + @Deprecated + private String targetHost; + /** @deprecated use {@link #datadogClientConfiguration} */ + @Deprecated + private Integer targetPort; + /** @deprecated use {@link #datadogClientConfiguration} */ + @Deprecated + private Integer targetLogCollectionPort; + /** @deprecated use {@link #datadogClientConfiguration} */ + @Deprecated + private Integer targetTraceCollectionPort; + + /** @deprecated use {@link #setCiInstanceName(String)} */ + public void setTraceServiceName(String traceServiceName) { + this.traceServiceName = traceServiceName; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setExcluded(String)} */ + public void setBlacklist(String blacklist) { + this.blacklist = blacklist; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setIncluded(String)} */ + public void setWhitelist(String whitelist) { + this.whitelist = whitelist; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setEnableCiVisibility(boolean)} */ + public void setCollectBuildTraces(boolean collectBuildTraces) { + this.collectBuildTraces = collectBuildTraces; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setDatadogClientConfiguration(DatadogClientConfiguration)} */ + public void setReportWith(String reportWith) { + this.reportWith = reportWith; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setDatadogClientConfiguration(DatadogClientConfiguration)} */ + public void setTargetApiURL(String targetApiURL) { + this.targetApiURL = targetApiURL; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setDatadogClientConfiguration(DatadogClientConfiguration)} */ + public void setTargetLogIntakeURL(String targetLogIntakeURL) { + this.targetLogIntakeURL = targetLogIntakeURL; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setDatadogClientConfiguration(DatadogClientConfiguration)} */ + public void setTargetWebhookIntakeURL(String targetWebhookIntakeURL) { + this.targetWebhookIntakeURL = targetWebhookIntakeURL; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setDatadogClientConfiguration(DatadogClientConfiguration)} */ + public void setTargetApiKey(Secret targetApiKey) { + this.targetApiKey = targetApiKey; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setDatadogClientConfiguration(DatadogClientConfiguration)} */ + public void setTargetCredentialsApiKey(String targetCredentialsApiKey) { + this.targetCredentialsApiKey = targetCredentialsApiKey; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setDatadogClientConfiguration(DatadogClientConfiguration)} */ + public void setTargetHost(String targetHost) { + this.targetHost = targetHost; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setDatadogClientConfiguration(DatadogClientConfiguration)} */ + public void setTargetPort(Integer targetPort) { + this.targetPort = targetPort; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setDatadogClientConfiguration(DatadogClientConfiguration)} */ + public void setTargetLogCollectionPort(Integer targetLogCollectionPort) { + this.targetLogCollectionPort = targetLogCollectionPort; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated use {@link #setDatadogClientConfiguration(DatadogClientConfiguration)} */ + public void setTargetTraceCollectionPort(Integer targetTraceCollectionPort) { + this.targetTraceCollectionPort = targetTraceCollectionPort; + // Configuration-as-Code plugin does not call readResolve() so it has to be done manually + // This also ensures backward compatibility for programmatic configuration with Groovy + readResolve(); + } + + /** @deprecated this configuration property has been removed */ + public void setEmitConfigChangeEvents(boolean ignored) { + // this method is here only to avoid errors if someone tries to call it from Groovy configuration scripts + } + + /** @deprecated use {@link #getCiInstanceName()} */ + @Deprecated + public String getTraceServiceName() { + return ciInstanceName; + } + + /** @deprecated use {@link #getExcluded()} */ + @Deprecated + public String getBlacklist() { + return excluded; + } + + /** @deprecated use {@link #getIncluded()} */ + @Deprecated + public String getWhitelist() { + return included; + } + + /** @deprecated use {@link #getEnableCiVisibility()} */ + @Deprecated + public boolean isCollectBuildTraces() { + return enableCiVisibility; + } + + /** @deprecated use {@link #getDatadogClientConfiguration()} */ + @Deprecated + public String getReportWith() { + if (datadogClientConfiguration instanceof DatadogAgentConfiguration) { + return DATADOG_AGENT_CLIENT_TYPE; + } + if (datadogClientConfiguration instanceof DatadogApiConfiguration) { + return DATADOG_API_CLIENT_TYPE; + } + return null; + } + + /** @deprecated use {@link #getDatadogClientConfiguration()} */ + @Deprecated + public String getTargetApiURL() { + if (datadogClientConfiguration instanceof DatadogApiConfiguration) { + DatadogApiConfiguration apiConfiguration = (DatadogApiConfiguration) datadogClientConfiguration; + DatadogIntake intake = apiConfiguration.getIntake(); + return intake.getApiUrl(); + } + return null; + } + + /** @deprecated use {@link #getDatadogClientConfiguration()} */ + @Deprecated + public String getTargetLogIntakeURL() { + if (datadogClientConfiguration instanceof DatadogApiConfiguration) { + DatadogApiConfiguration apiConfiguration = (DatadogApiConfiguration) datadogClientConfiguration; + DatadogIntake intake = apiConfiguration.getIntake(); + return intake.getLogsUrl(); + } + return null; + } + + /** @deprecated use {@link #getDatadogClientConfiguration()} */ + @Deprecated + public String getTargetWebhookIntakeURL() { + if (datadogClientConfiguration instanceof DatadogApiConfiguration) { + DatadogApiConfiguration apiConfiguration = (DatadogApiConfiguration) datadogClientConfiguration; + DatadogIntake intake = apiConfiguration.getIntake(); + return intake.getWebhooksUrl(); + } + return null; + } + + /** @deprecated use {@link #getDatadogClientConfiguration()} */ + @Deprecated + public Secret getTargetApiKey() { + if (datadogClientConfiguration instanceof DatadogApiConfiguration) { + DatadogApiConfiguration apiConfiguration = (DatadogApiConfiguration) datadogClientConfiguration; + DatadogApiKey apiKey = apiConfiguration.getApiKey(); + if (apiKey instanceof DatadogTextApiKey) { + DatadogTextApiKey textApiKey = (DatadogTextApiKey) apiKey; + return textApiKey.getKey(); + } + } + return null; + } + + /** @deprecated use {@link #getDatadogClientConfiguration()} */ + @Deprecated + public String getTargetCredentialsApiKey() { + if (datadogClientConfiguration instanceof DatadogApiConfiguration) { + DatadogApiConfiguration apiConfiguration = (DatadogApiConfiguration) datadogClientConfiguration; + DatadogApiKey apiKey = apiConfiguration.getApiKey(); + if (apiKey instanceof DatadogCredentialsApiKey) { + DatadogCredentialsApiKey credentialsApiKey = (DatadogCredentialsApiKey) apiKey; + return credentialsApiKey.getCredentialsId(); + } + } + return null; + } + + /** @deprecated use {@link #getDatadogClientConfiguration()} */ + @Deprecated + public String getTargetHost() { + if (datadogClientConfiguration instanceof DatadogAgentConfiguration) { + DatadogAgentConfiguration agentConfiguration = (DatadogAgentConfiguration) datadogClientConfiguration; + return agentConfiguration.getAgentHost(); + } + return null; + } + + /** @deprecated use {@link #getDatadogClientConfiguration()} */ + @Deprecated + public Integer getTargetPort() { + if (datadogClientConfiguration instanceof DatadogAgentConfiguration) { + DatadogAgentConfiguration agentConfiguration = (DatadogAgentConfiguration) datadogClientConfiguration; + return agentConfiguration.getAgentPort(); + } + return null; + } + + /** @deprecated use {@link #getDatadogClientConfiguration()} */ + @Deprecated + public Integer getTargetLogCollectionPort() { + if (datadogClientConfiguration instanceof DatadogAgentConfiguration) { + DatadogAgentConfiguration agentConfiguration = (DatadogAgentConfiguration) datadogClientConfiguration; + return agentConfiguration.getAgentLogCollectionPort(); + } + return null; + } + + /** @deprecated use {@link #getDatadogClientConfiguration()} */ + @Deprecated + public Integer getTargetTraceCollectionPort() { + if (datadogClientConfiguration instanceof DatadogAgentConfiguration) { + DatadogAgentConfiguration agentConfiguration = (DatadogAgentConfiguration) datadogClientConfiguration; + return agentConfiguration.getAgentTraceCollectionPort(); + } + return null; + } + + /** @deprecated this configuration property has been removed */ + @Deprecated + public boolean getEmitConfigChangeEvents() { + // this method is here only to avoid errors if someone tries to call it from Groovy configuration scripts + return false; + } + + /** + * Maintains backwards compatibility. Invoked by XStream when this object is deserialized. + */ + @SuppressWarnings("UnusedReturnValue") + protected Object readResolve() { + if (this.collectBuildTraces) { + this.enableCiVisibility = true; + } + if (StringUtils.isNotBlank(this.traceServiceName)) { + this.ciInstanceName = this.traceServiceName; + } + if (StringUtils.isNotBlank(this.blacklist)) { + this.excluded = this.blacklist; + } + if (StringUtils.isNotBlank(this.whitelist)) { + this.included = this.whitelist; + } + if (DATADOG_AGENT_CLIENT_TYPE.equals(reportWith)) { + this.datadogClientConfiguration = new DatadogAgentConfiguration(this.targetHost, this.targetPort, this.targetLogCollectionPort, this.targetTraceCollectionPort); + } + if (DATADOG_API_CLIENT_TYPE.equals(reportWith)) { + DatadogIntakeUrls intake = new DatadogIntakeUrls(this.targetApiURL, this.targetLogIntakeURL, this.targetWebhookIntakeURL); + DatadogApiKey apiKey; + if (StringUtils.isNotBlank(this.targetCredentialsApiKey)) { + apiKey = new DatadogCredentialsApiKey(this.targetCredentialsApiKey); + } else if (this.targetApiKey != null) { + apiKey = new DatadogTextApiKey(this.targetApiKey); + } else { + apiKey = null; + } + this.datadogClientConfiguration = new DatadogApiConfiguration(intake, apiKey); + } + return this; + } +} \ No newline at end of file diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/apm/DatadogTracerConfigurator.java b/src/main/java/org/datadog/jenkins/plugins/datadog/apm/DatadogTracerConfigurator.java index 446df3ce1..4b7545f5f 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/apm/DatadogTracerConfigurator.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/apm/DatadogTracerConfigurator.java @@ -10,11 +10,6 @@ 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; @@ -22,10 +17,10 @@ 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 { @@ -125,20 +120,9 @@ private static Map 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 clientEnvironmentVariables = clientConfiguration.toEnvironmentVariables(); + variables.putAll(clientEnvironmentVariables); Map additionalVariables = tracerConfig.getAdditionalVariables(); if (additionalVariables != null) { @@ -148,31 +132,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"); - } else { - return traceCollectionPort.toString(); - } - } private static final class ConfigureTracerAction extends DatadogPluginAction { private final String nodeHostname; diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogAgentClient.java b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogAgentClient.java index 6d3e582be..f113f7aed 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogAgentClient.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogAgentClient.java @@ -30,6 +30,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; @@ -44,18 +62,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. @@ -91,8 +97,6 @@ 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; @@ -100,31 +104,6 @@ public DatadogAgentClient(String hostname, Integer port, Integer logCollectionPo 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"); - } - - if (DatadogUtilities.getDatadogGlobalDescriptor().getEnableCiVisibility() && traceCollectionPort == null) { - logger.warning("Datadog Trace Collection Port is not set properly"); - } - } - - 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()); - } - } - public static class ConnectivityResult { private final boolean error; private final String errorMessage; diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogApiClient.java b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogApiClient.java index 82ca50bb1..c435614d0 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogApiClient.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/clients/DatadogApiClient.java @@ -30,13 +30,16 @@ of this software and associated documentation files (the "Software"), to deal import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.logging.Logger; import net.sf.json.JSON; import net.sf.json.JSONArray; import net.sf.json.JSONObject; -import net.sf.json.JSONSerializer; import org.datadog.jenkins.plugins.datadog.DatadogClient; import org.datadog.jenkins.plugins.datadog.DatadogEvent; import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration; @@ -80,7 +83,6 @@ public class DatadogApiClient implements DatadogClient { private final HttpClient httpClient; public DatadogApiClient(String url, String logIntakeUrl, String webhookIntakeUrl, Secret apiKey) { - validate(url, logIntakeUrl, webhookIntakeUrl, apiKey); this.url = url; this.apiKey = apiKey; this.logIntakeUrl = logIntakeUrl; @@ -88,92 +90,6 @@ public DatadogApiClient(String url, String logIntakeUrl, String webhookIntakeUrl this.httpClient = new HttpClient(HTTP_TIMEOUT_MS); } - private static void validate(String url, String logIntakeUrl, String webhookIntakeUrl, Secret apiKey) throws IllegalArgumentException { - if (url == null || url.isEmpty()) { - throw new IllegalArgumentException("Datadog Target URL is not set properly"); - } - if (apiKey == null || Secret.toString(apiKey).isEmpty()){ - throw new IllegalArgumentException("Datadog API Key is not set properly"); - } - - if (!validateDefaultIntakeConnection(url, apiKey)) { - throw new IllegalArgumentException("Connection broken, please double check both your API URL and Key"); - } - - if (DatadogUtilities.getDatadogGlobalDescriptor().isCollectBuildLogs() ) { - if (logIntakeUrl == null || logIntakeUrl.isEmpty()) { - throw new IllegalArgumentException("Datadog Log Intake URL is not set properly"); - } - if (!validateLogIntakeConnection(logIntakeUrl, apiKey)) { - throw new IllegalArgumentException("Connection broken, please double check both your Log Intake URL and Key"); - } - } - - if (DatadogUtilities.getDatadogGlobalDescriptor().getEnableCiVisibility() ) { - if (webhookIntakeUrl == null || webhookIntakeUrl.isEmpty()) { - throw new IllegalArgumentException("Datadog Webhook Intake URL is not set properly"); - } - if (!validateWebhookIntakeConnection(webhookIntakeUrl, apiKey)) { - throw new IllegalArgumentException("Connection broken, please double check both your Webhook Intake URL and Key"); - } - } - } - - public static boolean validateDefaultIntakeConnection(String validatedUrl, Secret apiKey) { - String urlParameters = "?api_key=" + Secret.toString(apiKey); - String url = validatedUrl + VALIDATE + urlParameters; - try { - JSONObject json = (JSONObject) new HttpClient(HTTP_TIMEOUT_MS).get(url, Collections.emptyMap(), JSONSerializer::toJSON); - return json.getBoolean("valid"); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - DatadogUtilities.severe(logger, e, "Failed to validate webhook connection"); - return false; - } catch (Exception e) { - DatadogUtilities.severe(logger, e, "Failed to validate webhook connection"); - return false; - } - } - - @SuppressFBWarnings("DLS_DEAD_LOCAL_STORE") - public static boolean validateWebhookIntakeConnection(String webhookIntakeUrl, Secret apiKey) { - Map headers = new HashMap<>(); - headers.put("DD-API-KEY", Secret.toString(apiKey)); - - byte[] body = "{}".getBytes(StandardCharsets.UTF_8); - try { - JSON jsonResponse = new HttpClient(HTTP_TIMEOUT_MS).post(webhookIntakeUrl, headers, "application/json", body, JSONSerializer::toJSON); - // consider test successful if JSON was parsed without errors - return true; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - DatadogUtilities.severe(logger, e, "Failed to validate webhook connection"); - return false; - } catch (Exception e) { - DatadogUtilities.severe(logger, e, "Failed to validate webhook connection"); - return false; - } - } - - public static boolean validateLogIntakeConnection(String logsIntakeUrl, Secret apiKey) { - HttpClient httpClient = new HttpClient(HTTP_TIMEOUT_MS); - - Map headers = new HashMap<>(); - headers.put("DD-API-KEY", Secret.toString(apiKey)); - - String payload = "{\"message\":\"[datadog-plugin] Check connection\", " + - "\"ddsource\":\"Jenkins\", \"service\":\"Jenkins\", " + - "\"hostname\":\"" + DatadogUtilities.getHostname(null) + "\"}"; - byte[] body = payload.getBytes(StandardCharsets.UTF_8); - try { - httpClient.post(logsIntakeUrl, headers, "application/json", body, Function.identity()); - return true; - } catch (Exception e) { - DatadogUtilities.severe(logger, e, "Failed to post logs"); - return false; - } - } - public boolean event(DatadogEvent event) { logger.fine("Sending event"); try { diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration.java b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration.java new file mode 100644 index 000000000..273ffcbd6 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration.java @@ -0,0 +1,341 @@ +package org.datadog.jenkins.plugins.datadog.configuration; + +import hudson.Extension; +import hudson.RelativePath; +import hudson.model.Descriptor; +import hudson.util.FormValidation; +import java.net.MalformedURLException; +import java.net.Socket; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Logger; +import javax.annotation.Nonnull; +import jenkins.model.Jenkins; +import org.apache.commons.lang.StringUtils; +import org.datadog.jenkins.plugins.datadog.DatadogClient; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.clients.DatadogAgentClient; +import org.jenkinsci.Symbol; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; + +@Symbol("datadogAgentConfiguration") +public class DatadogAgentConfiguration extends DatadogClientConfiguration { + + private static final Logger logger = Logger.getLogger(DatadogAgentConfiguration.class.getName()); + + public static final String TARGET_HOST_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_HOST"; + public static final String TARGET_PORT_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_PORT"; + public static final String TARGET_TRACE_COLLECTION_PORT_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_TRACE_COLLECTION_PORT"; + public static final String TARGET_LOG_COLLECTION_PORT_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_LOG_COLLECTION_PORT"; + public static final String DD_AGENT_HOST = "DD_AGENT_HOST"; + public static final String DD_AGENT_PORT = "DD_AGENT_PORT"; + public static final String DD_TRACE_AGENT_PORT = "DD_TRACE_AGENT_PORT"; + public static final String DD_TRACE_AGENT_URL = "DD_TRACE_AGENT_URL"; + + static final String DEFAULT_AGENT_HOST_VALUE = "localhost"; + static final Integer DEFAULT_AGENT_PORT_VALUE = 8125; + static final Integer DEFAULT_TRACE_COLLECTION_PORT_VALUE = 8126; + static final Integer DEFAULT_LOG_COLLECTION_PORT_VALUE = null; + + private final String agentHost; + private final Integer agentPort; + private final Integer agentLogCollectionPort; + private final Integer agentTraceCollectionPort; + + @DataBoundConstructor + public DatadogAgentConfiguration(String agentHost, Integer agentPort, Integer agentLogCollectionPort, Integer agentTraceCollectionPort) { + this.agentHost = agentHost; + this.agentPort = agentPort; + this.agentLogCollectionPort = agentLogCollectionPort; + this.agentTraceCollectionPort = agentTraceCollectionPort; + } + + public String getAgentHost() { + return agentHost; + } + + public Integer getAgentPort() { + return agentPort; + } + + public Integer getAgentLogCollectionPort() { + return agentLogCollectionPort; + } + + public Integer getAgentTraceCollectionPort() { + return agentTraceCollectionPort; + } + + @Override + public DatadogClient createClient() { + return new DatadogAgentClient(agentHost, agentPort, agentLogCollectionPort, agentTraceCollectionPort); + } + + @Override + public void validateTracesConnection() throws Descriptor.FormException { + if (StringUtils.isBlank(agentHost)) { + throw new Descriptor.FormException("CI Visibility requires agent host to be set", "agentHost"); + } + if (agentTraceCollectionPort == null) { + throw new Descriptor.FormException("CI Visibility requires agent trace collection port to be set", "agentTraceCollectionPort"); + } + String errorMessage = checkConnectivity(agentHost, agentTraceCollectionPort); + if (errorMessage != null) { + throw new Descriptor.FormException("CI Visibility connectivity check failed: " + errorMessage, "ciVisibilityData"); + } + } + + private static String checkConnectivity(final String host, final int port) { + try (Socket ignored = new Socket(host, port)) { + return null; + } catch (Exception ex) { + DatadogUtilities.severe(logger, ex, "Failed to create socket to host: " + host + ", port: " + port); + return ex.getMessage(); + } + } + + @Override + public void validateLogsConnection() throws Descriptor.FormException { + if (StringUtils.isBlank(agentHost)) { + throw new Descriptor.FormException("Logs collection requires agent host to be set", "agentHost"); + } + if (agentLogCollectionPort == null) { + throw new Descriptor.FormException("Logs collection requires agent log collection port to be set", "agentLogCollectionPort"); + } + String errorMessage = checkConnectivity(agentHost, agentLogCollectionPort); + if (errorMessage != null) { + throw new Descriptor.FormException("Logs collection connectivity check failed: " + errorMessage, "collectBuildLogs"); + } + } + + @Override + public Map toEnvironmentVariables() { + if (StringUtils.isBlank(agentHost)) { + throw new IllegalArgumentException("Agent host is not set"); + } + if (agentTraceCollectionPort == null) { + throw new IllegalArgumentException("Traces collection port is not set"); + } + + Map variables = new HashMap<>(); + variables.put("DD_AGENT_HOST", agentHost); + variables.put("DD_TRACE_AGENT_PORT", agentTraceCollectionPort.toString()); + return variables; + } + + @Override + public Descriptor getDescriptor() { + Jenkins jenkins = Jenkins.getInstanceOrNull(); + if (jenkins == null) { + throw new RuntimeException("Jenkins instance is null"); + } + return jenkins.getDescriptorOrDie(DatadogAgentConfiguration.class); + } + + @Extension + public static final class DatadogAgentConfigurationDescriptor extends DatadogClientConfiguration.DatadogClientConfigurationDescriptor { + public DatadogAgentConfigurationDescriptor() { + load(); + } + + @Override + @Nonnull + public String getDisplayName() { + return "Use the Datadog Agent to report to Datadog (recommended)"; + } + + @RequirePOST + public FormValidation doCheckAgentHost(@QueryParameter("agentHost") final String agentHost) { + if (StringUtils.isBlank(agentHost)) { + return FormValidation.error("Please enter host value"); + } + return FormValidation.ok(); + } + + @RequirePOST + public FormValidation doCheckAgentPort(@QueryParameter("agentPort") Integer agentPort) { + if (agentPort == null) { + return FormValidation.error("Please enter port value"); + } + return FormValidation.ok(); + } + + @RequirePOST + public FormValidation doCheckAgentLogCollectionPort(@QueryParameter("agentLogCollectionPort") Integer agentLogCollectionPort, + @RelativePath("..") + @QueryParameter("collectBuildLogs") final boolean collectBuildLogs) { + if (collectBuildLogs && agentLogCollectionPort == null) { + return FormValidation.error("Log collection is enabled, please enter log collection port value"); + } + return FormValidation.ok(); + } + + @RequirePOST + public FormValidation doCheckAgentTraceCollectionPort(@QueryParameter("agentTraceCollectionPort") Integer agentTraceCollectionPort, + @RelativePath("..") + @QueryParameter("ciVisibilityData") final boolean enableCiVisibility) { + if (enableCiVisibility && agentTraceCollectionPort == null) { + return FormValidation.error("CI Visibility is enabled, please enter traces collection port value"); + } + return FormValidation.ok(); + } + + @RequirePOST + @SuppressWarnings("lgtm[jenkins/no-permission-check]") // no side effects, no private information returned + public FormValidation doCheckLogConnectivity(@QueryParameter("agentHost") final String agentHost, + @QueryParameter("agentLogCollectionPort") Integer agentLogCollectionPort) { + if (StringUtils.isBlank(agentHost)) { + return FormValidation.error("Please enter host value"); + } + if (agentLogCollectionPort == null) { + return FormValidation.error("Please enter log collection port value"); + } + String errorMessage = checkConnectivity(agentHost, agentLogCollectionPort); + if (errorMessage != null) { + return FormValidation.error("Connectivity check failed: " + errorMessage); + } + return FormValidation.ok("Success!"); + } + + @RequirePOST + @SuppressWarnings("lgtm[jenkins/no-permission-check]") // no side effects, no private information returned + public FormValidation doCheckTraceConnectivity(@QueryParameter("agentHost") final String agentHost, + @QueryParameter("agentTraceCollectionPort") Integer agentTraceCollectionPort) { + if (StringUtils.isBlank(agentHost)) { + return FormValidation.error("Please enter host value"); + } + if (agentTraceCollectionPort == null) { + return FormValidation.error("Please enter trace collection port value"); + } + String errorMessage = checkConnectivity(agentHost, agentTraceCollectionPort); + if (errorMessage != null) { + return FormValidation.error("Connectivity check failed: " + errorMessage); + } + return FormValidation.ok("Success!"); + } + + public static String getDefaultAgentHost() { + Map envVars = System.getenv(); + + String host = envVars.get(TARGET_HOST_PROPERTY); + if (StringUtils.isNotBlank(host)) { + return host; + } + + final URL ddTraceAgentUrl = buildURL(envVars.get(DD_TRACE_AGENT_URL)); + if (ddTraceAgentUrl != null) { + String traceAgentUrlHost = ddTraceAgentUrl.getHost(); + if (StringUtils.isNotBlank(traceAgentUrlHost)) { + return traceAgentUrlHost; + } + } + + String agentHost = envVars.get(DD_AGENT_HOST); + if (StringUtils.isNotBlank(agentHost)) { + return agentHost; + } + + return DEFAULT_AGENT_HOST_VALUE; + } + + public static Integer getDefaultAgentPort() { + Map envVars = System.getenv(); + Integer port = getPort(envVars, TARGET_PORT_PROPERTY); + if (port != null) { + return port; + } + Integer agentPort = getPort(envVars, DD_AGENT_PORT); + if (agentPort != null) { + return agentPort; + } + return DEFAULT_AGENT_PORT_VALUE; + } + + public static Integer getDefaultAgentLogCollectionPort() { + Map envVars = System.getenv(); + Integer logsPort = getPort(envVars, TARGET_LOG_COLLECTION_PORT_PROPERTY); + if (logsPort != null) { + return logsPort; + } + return DEFAULT_LOG_COLLECTION_PORT_VALUE; + } + + public static Integer getDefaultAgentTraceCollectionPort() { + Map envVars = System.getenv(); + Integer traceCollectionPort = getPort(envVars, TARGET_TRACE_COLLECTION_PORT_PROPERTY); + if (traceCollectionPort != null) { + return traceCollectionPort; + } + + final URL ddTraceAgentUrl = buildURL(envVars.get(DD_TRACE_AGENT_URL)); + if (ddTraceAgentUrl != null) { + return ddTraceAgentUrl.getPort(); + } + + Integer traceAgentPort = getPort(envVars, DD_TRACE_AGENT_PORT); + if (traceAgentPort != null) { + return traceAgentPort; + } + return DEFAULT_TRACE_COLLECTION_PORT_VALUE; + } + + private static URL buildURL(String urlStr) { + try { + if (StringUtils.isNotBlank(urlStr)) { + return new URL(urlStr); + } + } catch (MalformedURLException ex) { + // ignore + } + return null; + } + + private static Integer getPort(Map envVars, String propertyName) { + String property = envVars.get(propertyName); + if (StringUtils.isBlank(property)) { + return null; + } + try { + int port = Integer.parseInt(property); + if (port >= 0 && port <= 65535) { + return port; + } else { + DatadogUtilities.severe(logger, null, "Invalid port value provided in " + propertyName + ": " + port); + return null; + } + } catch (NumberFormatException e) { + DatadogUtilities.severe(logger, e, "Failed to parse numeric property " + propertyName + " with value " + property); + return null; + } + } + + @Override + public int getOrder() { + return 0; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DatadogAgentConfiguration that = (DatadogAgentConfiguration) o; + return Objects.equals(agentHost, that.agentHost) + && Objects.equals(agentPort, that.agentPort) + && Objects.equals(agentLogCollectionPort, that.agentLogCollectionPort) + && Objects.equals(agentTraceCollectionPort, that.agentTraceCollectionPort); + } + + @Override + public int hashCode() { + return Objects.hash(agentHost, agentPort, agentLogCollectionPort, agentTraceCollectionPort); + } +} \ No newline at end of file diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/DatadogApiConfiguration.java b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/DatadogApiConfiguration.java new file mode 100644 index 000000000..f205b97d9 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/DatadogApiConfiguration.java @@ -0,0 +1,194 @@ +package org.datadog.jenkins.plugins.datadog.configuration; + +import static org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogTextApiKey.DatadogTextApiKeyDescriptor.getDefaultKey; + +import com.thoughtworks.xstream.annotations.XStreamConverter; +import hudson.Extension; +import hudson.model.Descriptor; +import hudson.util.FormValidation; +import hudson.util.Secret; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import javax.annotation.Nonnull; +import jenkins.model.Jenkins; +import net.sf.json.JSONSerializer; +import org.apache.commons.lang.StringUtils; +import org.datadog.jenkins.plugins.datadog.DatadogClient; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.clients.DatadogApiClient; +import org.datadog.jenkins.plugins.datadog.clients.HttpClient; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntake; +import org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogApiKey; +import org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogTextApiKey; +import org.datadog.jenkins.plugins.datadog.util.conversion.PolymorphicReflectionConverter; +import org.jenkinsci.Symbol; +import org.kohsuke.stapler.DataBoundConstructor; + +@Symbol("datadogApiConfiguration") +public class DatadogApiConfiguration extends DatadogClientConfiguration { + + @XStreamConverter(PolymorphicReflectionConverter.class) + private final DatadogIntake intake; + + @XStreamConverter(PolymorphicReflectionConverter.class) + private final DatadogApiKey apiKey; + + @DataBoundConstructor + public DatadogApiConfiguration(DatadogIntake intake, DatadogApiKey apiKey) { + this.intake = intake; + this.apiKey = apiKey; + } + + public DatadogIntake getIntake() { + return intake; + } + + public DatadogApiKey getApiKey() { + return apiKey; + } + + private Secret getApiKeyValue() { + return apiKey != null ? apiKey.getKey() : null; + } + + @Override + public DatadogClient createClient() { + return new DatadogApiClient(intake.getApiUrl(), intake.getLogsUrl(), intake.getWebhooksUrl(), getApiKeyValue()); + } + + public void validateApiConnection() throws IllegalArgumentException { + if (intake == null) { + throw new IllegalArgumentException("CI Visibility requires Datadog intake to be configured"); + } + String apiUrl = intake.getApiUrl(); + if (StringUtils.isBlank(apiUrl)) { + throw new IllegalArgumentException("Datadog API URL is not configured"); + } + Secret apiKeyValue = getApiKeyValue(); + if (apiKeyValue == null){ + throw new IllegalArgumentException("Datadog API key is not configured"); + } + FormValidation validationResult = DatadogApiKey.validateApiConnection(apiUrl, apiKeyValue); + if (validationResult.kind != FormValidation.Kind.OK) { + throw new IllegalArgumentException(validationResult.getMessage()); + } + } + + @Override + public void validateTracesConnection() throws Descriptor.FormException { + if (intake == null) { + throw new Descriptor.FormException("CI Visibility requires Datadog intake to be configured", "intake"); + } + String webhooksUrl = intake.getWebhooksUrl(); + if (StringUtils.isBlank(webhooksUrl)) { + throw new Descriptor.FormException("CI Visibility requires Datadog webhook intake to be configured", "intake"); + } + checkConnectivity("{}", webhooksUrl, JSONSerializer::toJSON, "webhookIntakeUrl"); + } + + @Override + public void validateLogsConnection() throws Descriptor.FormException { + if (intake == null) { + throw new Descriptor.FormException("Logs collection requires Datadog intake to be configured", "intake"); + } + String logsUrl = intake.getLogsUrl(); + if (StringUtils.isBlank(logsUrl)) { + throw new Descriptor.FormException("Logs collection requires Datadog logs intake to be configured", "intake"); + } + String payload = "{\"message\":\"[datadog-plugin] Check connection\", " + + "\"ddsource\":\"Jenkins\", \"service\":\"Jenkins\", " + + "\"hostname\":\"" + DatadogUtilities.getHostname(null) + "\"}"; + checkConnectivity(payload, logsUrl, Function.identity(), "logIntakeUrl"); + } + + private void checkConnectivity(String payload, String url, Function responseParser, String field) throws Descriptor.FormException { + Map headers = new HashMap<>(); + headers.put("DD-API-KEY", Secret.toString(getApiKeyValue())); + + byte[] body = payload.getBytes(StandardCharsets.UTF_8); + try { + new HttpClient(30_000).post(url, headers, "application/json", body, responseParser); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new Descriptor.FormException("Interrupted while trying to check logs intake connectivity", field); + + } catch (Exception e) { + throw new Descriptor.FormException("Datadog connectivity check failed: " + e.getMessage(), field); + } + } + + @Override + public Map toEnvironmentVariables() { + Map variables = new HashMap<>(); + variables.put("DD_CIVISIBILITY_AGENTLESS_ENABLED", "true"); + variables.put("DD_SITE", intake.getSiteName()); + variables.put("DD_API_KEY", Secret.toString(getApiKeyValue())); + return variables; + } + + @Override + public Descriptor getDescriptor() { + Jenkins jenkins = Jenkins.getInstanceOrNull(); + if (jenkins == null) { + throw new RuntimeException("Jenkins instance is null"); + } + return jenkins.getDescriptorOrDie(DatadogApiConfiguration.class); + } + + @Extension + public static final class DatadogApiConfigurationDescriptor extends DatadogClientConfigurationDescriptor { + public DatadogApiConfigurationDescriptor() { + load(); + } + + @Override + @Nonnull + public String getDisplayName() { + return "Use Datadog site and API key to report to Datadog"; + } + + public static DatadogIntake getDefaultIntake() { + return DatadogIntake.getDefaultIntake(); + } + + public static DatadogApiKey getDefaultApiKey() { + return new DatadogTextApiKey(getDefaultKey()); + } + + public List getIntakeOptions() { + return DatadogIntake.DatadogIntakeDescriptor.all(); + } + + public List getApiKeyOptions() { + return DatadogApiKey.DatadogApiKeyDescriptor.all(); + } + + @Override + public int getOrder() { + return 1; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DatadogApiConfiguration that = (DatadogApiConfiguration) o; + return Objects.equals(intake, that.intake) + && Objects.equals(apiKey, that.apiKey); + } + + @Override + public int hashCode() { + return Objects.hash(intake, apiKey); + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/DatadogClientConfiguration.java b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/DatadogClientConfiguration.java new file mode 100644 index 000000000..4db0b3b86 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/DatadogClientConfiguration.java @@ -0,0 +1,37 @@ +package org.datadog.jenkins.plugins.datadog.configuration; + +import hudson.model.Describable; +import hudson.model.Descriptor; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import jenkins.model.Jenkins; +import org.datadog.jenkins.plugins.datadog.DatadogClient; + +public abstract class DatadogClientConfiguration implements Describable, Serializable { + + public abstract DatadogClient createClient(); + + public abstract void validateTracesConnection() throws Descriptor.FormException; + + public abstract void validateLogsConnection() throws Descriptor.FormException; + + public abstract Map toEnvironmentVariables(); + + public static abstract class DatadogClientConfigurationDescriptor extends Descriptor { + public static List all() { + Jenkins jenkins = Jenkins.getInstanceOrNull(); + if (jenkins == null) { + throw new RuntimeException("Jenkins instance is null"); + } + List descriptors = jenkins.getDescriptorList(DatadogClientConfiguration.class); + List sortedDescriptors = new ArrayList<>(descriptors); + sortedDescriptors.sort(Comparator.comparingInt(DatadogClientConfigurationDescriptor::getOrder)); + return sortedDescriptors; + } + + public abstract int getOrder(); + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntake.java b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntake.java new file mode 100644 index 000000000..d037ba4a2 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntake.java @@ -0,0 +1,53 @@ +package org.datadog.jenkins.plugins.datadog.configuration.api.intake; + +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeSite.DatadogIntakeSiteDescriptor.getDefaultSite; +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls.DatadogIntakeUrlsDescriptor.getDefaultApiUrl; +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls.DatadogIntakeUrlsDescriptor.getDefaultLogsUrl; +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls.DatadogIntakeUrlsDescriptor.getDefaultWebhooksUrl; +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls.TARGET_API_URL_PROPERTY; +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls.TARGET_LOG_INTAKE_URL_PROPERTY; +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls.TARGET_WEBHOOK_INTAKE_URL_PROPERTY; + +import hudson.model.Describable; +import hudson.model.Descriptor; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import jenkins.model.Jenkins; + +public abstract class DatadogIntake implements Describable, Serializable { + + public abstract String getApiUrl(); + public abstract String getLogsUrl(); + public abstract String getWebhooksUrl(); + public abstract String getSiteName(); + + public static abstract class DatadogIntakeDescriptor extends Descriptor { + public static List all() { + Jenkins jenkins = Jenkins.getInstanceOrNull(); + if (jenkins == null) { + throw new RuntimeException("Jenkins instance is null"); + } + List descriptors = jenkins.getDescriptorList(DatadogIntake.class); + List sortedDescriptors = new ArrayList<>(descriptors); + sortedDescriptors.sort(Comparator.comparingInt(DatadogIntakeDescriptor::getOrder)); + return sortedDescriptors; + } + + public abstract int getOrder(); + } + + public static DatadogIntake getDefaultIntake() { + Map env = System.getenv(); + if (env.containsKey(TARGET_API_URL_PROPERTY) + || env.containsKey(TARGET_LOG_INTAKE_URL_PROPERTY) + || env.containsKey(TARGET_WEBHOOK_INTAKE_URL_PROPERTY)) { + // default to URLs intake if any of the URLs are set with environment variables + return new DatadogIntakeUrls(getDefaultApiUrl(), getDefaultLogsUrl(), getDefaultWebhooksUrl()); + } else { + return new DatadogIntakeSite(getDefaultSite()); + } + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeSite.java b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeSite.java new file mode 100644 index 000000000..137074538 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeSite.java @@ -0,0 +1,114 @@ +package org.datadog.jenkins.plugins.datadog.configuration.api.intake; + +import hudson.Extension; +import hudson.model.Descriptor; +import jenkins.model.Jenkins; +import org.jenkinsci.Symbol; +import org.kohsuke.stapler.DataBoundConstructor; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.Objects; + +@Symbol("datadogIntakeSite") +public class DatadogIntakeSite extends DatadogIntake { + + public static final String DATADOG_SITE_PROPERTY = "DATADOG_JENKINS_PLUGIN_DATADOG_SITE"; + + static final DatadogSite DEFAULT_DATADOG_SITE_VALUE = DatadogSite.US1; + + private final DatadogSite site; + + @DataBoundConstructor + public DatadogIntakeSite(DatadogSite site) { + this.site = site; + } + + public DatadogSite getSite() { + return site; + } + + @Override + public String getApiUrl() { + return site.getApiUrl(); + } + + @Override + public String getLogsUrl() { + return site.getLogsUrl(); + } + + @Override + public String getWebhooksUrl() { + return site.getWebhooksUrl(); + } + + @Override + public String getSiteName() { + return site.getSiteName(); + } + + @Override + public Descriptor getDescriptor() { + Jenkins jenkins = Jenkins.getInstanceOrNull(); + if (jenkins == null) { + throw new RuntimeException("Jenkins instance is null"); + } + return jenkins.getDescriptorOrDie(DatadogIntakeSite.class); + } + + @Extension + public static final class DatadogIntakeSiteDescriptor extends DatadogIntakeDescriptor { + public DatadogIntakeSiteDescriptor() { + load(); + } + + @Override + @Nonnull + public String getDisplayName() { + return "Pick a site"; + } + + @Override + public String getHelpFile() { + return getHelpFile("siteBlock"); + } + + public static DatadogSite getDefaultSite() { + String site = System.getenv().get(DATADOG_SITE_PROPERTY); + if (site != null) { + try { + return DatadogSite.valueOf(site); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException( + "Illegal " + DATADOG_SITE_PROPERTY + " environment property value set: " + site + + ". Allowed values are " + Arrays.toString(DatadogSite.values()), e); + } + } else { + return DEFAULT_DATADOG_SITE_VALUE; + } + } + + @Override + public int getOrder() { + return 0; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DatadogIntakeSite that = (DatadogIntakeSite) o; + return site == that.site; + } + + @Override + public int hashCode() { + return Objects.hashCode(site); + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeUrls.java b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeUrls.java new file mode 100644 index 000000000..fe82eafb0 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeUrls.java @@ -0,0 +1,186 @@ +package org.datadog.jenkins.plugins.datadog.configuration.api.intake; + +import hudson.Extension; +import hudson.RelativePath; +import hudson.model.Descriptor; +import hudson.util.FormValidation; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Objects; +import javax.annotation.Nonnull; +import jenkins.model.Jenkins; +import org.apache.commons.lang.StringUtils; +import org.jenkinsci.Symbol; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; + +@Symbol("datadogIntakeUrls") +public class DatadogIntakeUrls extends DatadogIntake { + + public static final String TARGET_API_URL_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_API_URL"; + public static final String TARGET_LOG_INTAKE_URL_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_LOG_INTAKE_URL"; + public static final String TARGET_WEBHOOK_INTAKE_URL_PROPERTY = "DATADOG_JENKINS_TARGET_WEBHOOK_INTAKE_URL"; + + static final String DEFAULT_API_URL_VALUE = "https://api.datadoghq.com/api/"; + static final String DEFAULT_LOG_INTAKE_URL_VALUE = "https://http-intake.logs.datadoghq.com/v1/input/"; + static final String DEFAULT_WEBHOOK_INTAKE_URL_VALUE = "https://webhook-intake.datadoghq.com/api/v2/webhook/"; + + private final String apiUrl; + private final String logsUrl; + private final String webhooksUrl; + + @DataBoundConstructor + public DatadogIntakeUrls(String apiUrl, String logsUrl, String webhooksUrl) { + this.apiUrl = apiUrl; + this.logsUrl = logsUrl; + this.webhooksUrl = webhooksUrl; + } + + @Override + public String getApiUrl() { + return apiUrl; + } + + @Override + public String getLogsUrl() { + return logsUrl; + } + + @Override + public String getWebhooksUrl() { + return webhooksUrl; + } + + @Override + public String getSiteName() { + // 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("\\."); + StringBuilder siteName = new StringBuilder(); + for (int i = 1; i < parts.length; i++) { + siteName.append(parts[i]); + boolean isLastPart = i + 1 == parts.length; + if (!isLastPart) { + siteName.append('.'); + } + } + return siteName.toString(); + + } catch (MalformedURLException | URISyntaxException e) { + throw new IllegalArgumentException("Cannot parse Datadog API URL", e); + } + } + + @Override + public Descriptor getDescriptor() { + Jenkins jenkins = Jenkins.getInstanceOrNull(); + if (jenkins == null) { + throw new RuntimeException("Jenkins instance is null"); + } + return jenkins.getDescriptorOrDie(DatadogIntakeUrls.class); + } + + @Extension + public static final class DatadogIntakeUrlsDescriptor extends DatadogIntake.DatadogIntakeDescriptor { + public DatadogIntakeUrlsDescriptor() { + load(); + } + + @Override + @Nonnull + public String getDisplayName() { + return "Enter URLs manually"; + } + + @RequirePOST + @SuppressWarnings("lgtm[jenkins/no-permission-check]") // no side effects, no private information returned + public FormValidation doCheckApiUrl(@QueryParameter("apiUrl") final String apiUrl) { + if (StringUtils.isBlank(apiUrl)) { + return FormValidation.error("Please enter the API URL"); + } + return validateUrl(apiUrl); + } + + @RequirePOST + @SuppressWarnings("lgtm[jenkins/no-permission-check]") // no side effects, no private information returned + public FormValidation doCheckLogsUrl(@QueryParameter("logsUrl") final String logsUrl, + @RelativePath("../..") // parent's parent (this -> API client configuration -> global configuration + @QueryParameter("collectBuildLogs") final boolean collectBuildLogs) { + if (collectBuildLogs && StringUtils.isBlank(logsUrl)) { + return FormValidation.error("Log collection is enabled, please enter log intake URL"); + } + return validateUrl(logsUrl); + } + + @RequirePOST + @SuppressWarnings("lgtm[jenkins/no-permission-check]") // no side effects, no private information returned + public FormValidation doCheckWebhooksUrl(@QueryParameter("webhooksUrl") final String webhooksUrl, + @RelativePath("../..") // parent's parent (this -> API client configuration -> global configuration + @QueryParameter("ciVisibilityData") final boolean enableCiVisibility) { + if (enableCiVisibility && StringUtils.isBlank(webhooksUrl)) { + return FormValidation.error("CI Visibility is enabled, please enter webhook intake URL"); + } + return validateUrl(webhooksUrl); + } + + private static FormValidation validateUrl(String urlString) { + if (!StringUtils.isBlank(urlString)) { + try { + URL url = new URL(urlString); + if (!url.getProtocol().contains("http")) { + return FormValidation.error("The URL has to use either HTTP or HTTPS protocol"); + } + } catch (MalformedURLException e) { + return FormValidation.error("Please enter a valid URL: " + e.getMessage()); + } + } + return FormValidation.ok(); + } + + public static String getDefaultApiUrl() { + return System.getenv().getOrDefault(TARGET_API_URL_PROPERTY, DEFAULT_API_URL_VALUE); + } + + public static String getDefaultLogsUrl() { + return System.getenv().getOrDefault(TARGET_LOG_INTAKE_URL_PROPERTY, DEFAULT_LOG_INTAKE_URL_VALUE); + } + + public static String getDefaultWebhooksUrl() { + return System.getenv().getOrDefault(TARGET_WEBHOOK_INTAKE_URL_PROPERTY, DEFAULT_WEBHOOK_INTAKE_URL_VALUE); + } + + @Override + public int getOrder() { + return 1; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DatadogIntakeUrls that = (DatadogIntakeUrls) o; + return Objects.equals(apiUrl, that.apiUrl) + && Objects.equals(logsUrl, that.logsUrl) + && Objects.equals(webhooksUrl, that.webhooksUrl); + } + + @Override + public int hashCode() { + return Objects.hash(apiUrl, logsUrl, webhooksUrl); + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogSite.java b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogSite.java new file mode 100644 index 000000000..e99cb4caa --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogSite.java @@ -0,0 +1,62 @@ +package org.datadog.jenkins.plugins.datadog.configuration.api.intake; + +public enum DatadogSite { + US1( + "datadoghq.com", + "https://api.datadoghq.com/api/", + "https://http-intake.logs.datadoghq.com/v1/input/", + "https://webhook-intake.datadoghq.com/api/v2/webhook/"), + US3( + "us3.datadoghq.com", + "https://api.us3.datadoghq.com/api/", + "https://http-intake.logs.us3.datadoghq.com/v1/input/", + "https://webhook-intake.us3.datadoghq.com/api/v2/webhook/"), + US5( + "us5.datadoghq.com", + "https://api.us5.datadoghq.com/api/", + "https://http-intake.logs.us5.datadoghq.com/v1/input/", + "https://webhook-intake.us5.datadoghq.com/api/v2/webhook/"), + US1_FED( + "ddog-gov.com", + "https://api.ddog-gov.com/api/", + "https://http-intake.logs.ddog-gov.com/v1/input/", + "https://webhook-intake.ddog-gov.com/api/v2/webhook/"), + EU1( + "datadoghq.eu", + "https://api.datadoghq.eu/api/", + "https://http-intake.logs.datadoghq.eu/v1/input/", + "https://webhook-intake.datadoghq.eu/api/v2/webhook/"), + AP1( + "ap1.datadoghq.com", + "https://api.ap1.datadoghq.com/api/", + "https://http-intake.logs.ap1.datadoghq.com/v1/input/", + "https://webhook-intake.ap1.datadoghq.com/api/v2/webhook/"); + + private final String siteName; + private final String apiUrl; + private final String logsUrl; + private final String webhooksUrl; + + DatadogSite(String siteName, String apiUrl, String logsUrl, String webhooksUrl) { + this.siteName = siteName; + this.apiUrl = apiUrl; + this.logsUrl = logsUrl; + this.webhooksUrl = webhooksUrl; + } + + public String getSiteName() { + return siteName; + } + + public String getApiUrl() { + return apiUrl; + } + + public String getLogsUrl() { + return logsUrl; + } + + public String getWebhooksUrl() { + return webhooksUrl; + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogApiKey.java b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogApiKey.java new file mode 100644 index 000000000..0effa7e84 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogApiKey.java @@ -0,0 +1,83 @@ +package org.datadog.jenkins.plugins.datadog.configuration.api.key; + +import hudson.model.Describable; +import hudson.model.Descriptor; +import hudson.util.FormValidation; +import hudson.util.Secret; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.logging.Logger; +import jenkins.model.Jenkins; +import net.sf.json.JSONObject; +import net.sf.json.JSONSerializer; +import org.apache.commons.lang.StringUtils; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.clients.HttpClient; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntake; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeSite; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogSite; + +public abstract class DatadogApiKey implements Describable, Serializable { + + private static final Logger logger = Logger.getLogger(DatadogApiKey.class.getName()); + + private static final String VALIDATE_ENDPOINT = "v1/validate"; + + public abstract Secret getKey(); + + public static abstract class DatadogApiKeyDescriptor extends Descriptor { + public static List all() { + Jenkins jenkins = Jenkins.getInstanceOrNull(); + if (jenkins == null) { + throw new RuntimeException("Jenkins instance is null"); + } + List descriptors = jenkins.getDescriptorList(DatadogApiKey.class); + List sortedDescriptors = new ArrayList<>(descriptors); + sortedDescriptors.sort(Comparator.comparingInt(DatadogApiKeyDescriptor::getOrder)); + return sortedDescriptors; + } + + public abstract int getOrder(); + } + + static FormValidation checkConnectivity(Secret apiKeyValue, int intakeIdx, String site, String apiUrl) { + String validationUrl; + List intakes = DatadogIntake.DatadogIntakeDescriptor.all(); + if (intakes.get(intakeIdx) instanceof DatadogIntakeSite.DatadogIntakeSiteDescriptor) { + if (StringUtils.isBlank(site)) { + return FormValidation.error("Please select a site"); + } + DatadogSite datadogSite = DatadogSite.valueOf(site); + validationUrl = datadogSite.getApiUrl(); + } else { + if (StringUtils.isBlank(apiUrl)) { + return FormValidation.error("Please fill in the API url"); + } + validationUrl = apiUrl; + } + return validateApiConnection(validationUrl, apiKeyValue); + } + + public static FormValidation validateApiConnection(String apiUrl, Secret apiKeyValue) { + String urlParameters = "?api_key=" + Secret.toString(apiKeyValue); + String url = apiUrl + VALIDATE_ENDPOINT + urlParameters; + try { + JSONObject json = (JSONObject) new HttpClient(30_000).get(url, Collections.emptyMap(), JSONSerializer::toJSON); + if (json.getBoolean("valid")) { + return FormValidation.ok("Great! Your API key is valid."); + } else { + return FormValidation.error("Validation endpoint returned 'false'"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + DatadogUtilities.severe(logger, e, "Failed to validate webhook connection"); + return FormValidation.error("Interrupted while validating API key"); + } catch (Exception e) { + DatadogUtilities.severe(logger, e, "Failed to validate webhook connection"); + return FormValidation.error("Error while validating API key: " + e.getMessage()); + } + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogCredentialsApiKey.java b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogCredentialsApiKey.java new file mode 100644 index 000000000..81668eb68 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogCredentialsApiKey.java @@ -0,0 +1,193 @@ +package org.datadog.jenkins.plugins.datadog.configuration.api.key; + +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.Descriptor; +import hudson.model.Item; +import hudson.security.ACL; +import hudson.util.FormValidation; +import hudson.util.ListBoxModel; +import hudson.util.Secret; +import java.util.Collections; +import java.util.Objects; +import javax.annotation.Nonnull; +import jenkins.model.Jenkins; +import org.apache.commons.lang.StringUtils; +import org.jenkinsci.Symbol; +import org.jenkinsci.plugins.plaincredentials.StringCredentials; +import org.kohsuke.stapler.AncestorInPath; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; + +@Symbol("datadogCredentialsApiKey") +public class DatadogCredentialsApiKey extends DatadogApiKey { + + private final String credentialsId; + + @DataBoundConstructor + public DatadogCredentialsApiKey(String credentialsId) { + this.credentialsId = credentialsId; + } + + public String getCredentialsId() { + return credentialsId; + } + + @Override + public Secret getKey() { + StringCredentials credential = getCredentialFromId(credentialsId); + return credential != null ? credential.getSecret() : null; + } + + /** + * Gets the StringCredentials object for the given credential ID + * + * @param credentialId - The ID of the credential to get + * @return a StringCredentials object + */ + static StringCredentials getCredentialFromId(String credentialId) { + if (StringUtils.isBlank(credentialId)) { + return null; + } + return CredentialsMatchers.firstOrNull( + CredentialsProvider.lookupCredentials( + StringCredentials.class, + Jenkins.get(), + ACL.SYSTEM, + URIRequirementBuilder.fromUri(null).build()), + CredentialsMatchers.allOf(CredentialsMatchers.withId(credentialId)) + ); + } + + @Override + public Descriptor getDescriptor() { + Jenkins jenkins = Jenkins.getInstanceOrNull(); + if (jenkins == null) { + throw new RuntimeException("Jenkins instance is null"); + } + return jenkins.getDescriptorOrDie(DatadogCredentialsApiKey.class); + } + + @Extension + public static final class DatadogCredentialsApiKeyDescriptor extends DatadogApiKeyDescriptor { + public DatadogCredentialsApiKeyDescriptor() { + load(); + } + + @Override + @Nonnull + public String getDisplayName() { + return "Select from credentials"; + } + + @Override + public String getHelpFile() { + return getHelpFile("credentialsKeyBlock"); + } + + /** + * Populates the API key credentials ID dropdown in the configuration screen with all the valid credentials + * + * @param item - The context within which to list available credentials + * @param credentialsId - ID of the credential containing the API key + * @return a ListBoxModel object used to display all the available credentials. + */ + @RequirePOST + public ListBoxModel doFillCredentialsIdItems( + @AncestorInPath Item item, + @QueryParameter("credentialsId") String credentialsId + ) { + StandardListBoxModel result = new StandardListBoxModel(); + // If the user does not have permissions to list credentials, only list the current value + if (item == null) { + if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { + return result.includeCurrentValue(credentialsId); + } + } else { + if (!item.hasPermission(Item.EXTENDED_READ) + && !item.hasPermission(CredentialsProvider.USE_ITEM)) { + return result.includeCurrentValue(credentialsId); + } + } + return result.includeEmptyValue() + .includeMatchingAs(ACL.SYSTEM, + Jenkins.get(), + StringCredentials.class, + Collections.emptyList(), + CredentialsMatchers.instanceOf(StringCredentials.class)) + .includeCurrentValue(credentialsId); + } + + @RequirePOST + public FormValidation doCheckCredentialsId( + @AncestorInPath Item item, + @QueryParameter("credentialsId") String credentialsId + ) { + // Don't validate for users that do not have permission to list credentials + if (item == null) { + if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { + return FormValidation.ok(); + } + } else { + if (!item.hasPermission(Item.EXTENDED_READ) + && !item.hasPermission(CredentialsProvider.USE_ITEM)) { + return FormValidation.ok(); + } + } + if (StringUtils.isBlank(credentialsId)) { + return FormValidation.ok(); + } + if (credentialsId.startsWith("${") && credentialsId.endsWith("}")) { + return FormValidation.warning("Cannot validate expression based credentials"); + } + if (CredentialsProvider.listCredentials(StringCredentials.class, + item, + ACL.SYSTEM, + Collections.emptyList(), + CredentialsMatchers.withId(credentialsId)).isEmpty()) { + return FormValidation.error("Cannot find currently selected credentials"); + } + return FormValidation.ok(); + } + + @RequirePOST + public FormValidation doCheckConnectivity(@QueryParameter("credentialsId") final String credentialsId, + @QueryParameter("intake") final int intakeIdx, + @QueryParameter("site") final String site, + @QueryParameter("apiUrl") final String apiUrl) { + Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + StringCredentials credentialFromId = getCredentialFromId(credentialsId); + if (credentialFromId == null) { + return FormValidation.error("Could not find credentials with the given ID"); + } + Secret apiKeyValue = credentialFromId.getSecret(); + return checkConnectivity(apiKeyValue, intakeIdx, site, apiUrl); + } + + @Override + public int getOrder() { + return 1; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DatadogCredentialsApiKey that = (DatadogCredentialsApiKey) o; + return Objects.equals(credentialsId, that.credentialsId); + } + + @Override + public int hashCode() { + return Objects.hashCode(credentialsId); + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogTextApiKey.java b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogTextApiKey.java new file mode 100644 index 000000000..75867f21e --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogTextApiKey.java @@ -0,0 +1,109 @@ +package org.datadog.jenkins.plugins.datadog.configuration.api.key; + +import hudson.DescriptorExtensionList; +import hudson.Extension; +import hudson.model.Descriptor; +import hudson.util.FormValidation; +import hudson.util.Secret; +import javax.annotation.Nonnull; +import jenkins.model.Jenkins; +import org.apache.commons.lang.StringUtils; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntake; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeSite; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogSite; +import org.jenkinsci.Symbol; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; + +import java.util.Objects; + +@Symbol("datadogTextApiKey") +public class DatadogTextApiKey extends DatadogApiKey { + + public static final String TARGET_API_KEY_PROPERTY = "DATADOG_JENKINS_PLUGIN_TARGET_API_KEY"; + + private final Secret key; + + @DataBoundConstructor + public DatadogTextApiKey(Secret key) { + this.key = key; + } + + @Override + public Secret getKey() { + return key; + } + + @Override + public Descriptor getDescriptor() { + Jenkins jenkins = Jenkins.getInstanceOrNull(); + if (jenkins == null) { + throw new RuntimeException("Jenkins instance is null"); + } + return jenkins.getDescriptorOrDie(DatadogTextApiKey.class); + } + + @Extension + public static final class DatadogTextApiKeyDescriptor extends DatadogApiKeyDescriptor { + public DatadogTextApiKeyDescriptor() { + load(); + } + + @Override + @Nonnull + public String getDisplayName() { + return "Enter manually"; + } + + @Override + public String getHelpFile() { + return getHelpFile("textKeyBlock"); + } + + @RequirePOST + public FormValidation doCheckKey(@QueryParameter("key") final String key) { + if (StringUtils.isBlank(key)) { + return FormValidation.error("Please enter API key"); + } + return FormValidation.ok(); + } + + @RequirePOST + public FormValidation doCheckConnectivity(@QueryParameter("key") final String key, + @QueryParameter("intake") final int intakeIdx, + @QueryParameter("site") final String site, + @QueryParameter("apiUrl") final String apiUrl) { + Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Secret apiKeyValue = Secret.fromString(key); + return checkConnectivity(apiKeyValue, intakeIdx, site, apiUrl); + } + + public static Secret getDefaultKey() { + String apiKeyPropertyValue = System.getenv().get(TARGET_API_KEY_PROPERTY); + return StringUtils.isNotBlank(apiKeyPropertyValue) ? Secret.fromString(apiKeyPropertyValue) : null; + } + + @Override + public int getOrder() { + return 0; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DatadogTextApiKey that = (DatadogTextApiKey) o; + return Objects.equals(key, that.key); + } + + @Override + public int hashCode() { + return Objects.hashCode(key); + } +} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/flare/ConnectivityChecksFlare.java b/src/main/java/org/datadog/jenkins/plugins/datadog/flare/ConnectivityChecksFlare.java index f607c6dc1..e51e119b6 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/flare/ConnectivityChecksFlare.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/flare/ConnectivityChecksFlare.java @@ -1,16 +1,15 @@ package org.datadog.jenkins.plugins.datadog.flare; import hudson.Extension; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import net.sf.json.JSONObject; import org.apache.commons.io.IOUtils; -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.clients.DatadogApiClient; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogApiConfiguration; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogClientConfiguration; @Extension public class ConnectivityChecksFlare implements FlareContributor { @@ -34,26 +33,33 @@ public String getFilename() { public void writeFileContents(OutputStream out) throws IOException { JSONObject payload = new JSONObject(); - // TODO rework the checks below following configuration refactoring DatadogGlobalConfiguration globalConfiguration = DatadogUtilities.getDatadogGlobalDescriptor(); - DatadogClient.ClientType clientType = DatadogClient.ClientType.valueOf(globalConfiguration.getReportWith()); + DatadogClientConfiguration clientConfiguration = globalConfiguration.getDatadogClientConfiguration(); - if (clientType == DatadogClient.ClientType.DSD) { - payload.put("client-type", DatadogClient.ClientType.DSD); - payload.put("logs-connectivity", globalConfiguration.doCheckAgentConnectivityLogs(globalConfiguration.getTargetHost(), String.valueOf(globalConfiguration.getTargetLogCollectionPort())).toString()); - payload.put("traces-connectivity", globalConfiguration.doCheckAgentConnectivityTraces(globalConfiguration.getTargetHost(), String.valueOf(globalConfiguration.getTargetTraceCollectionPort())).toString()); + payload.put("client-type", clientConfiguration.getClass().getSimpleName()); + payload.put("traces-connectivity", validateConnectivity(clientConfiguration::validateTracesConnection)); + payload.put("logs-connectivity", validateConnectivity(clientConfiguration::validateLogsConnection)); - } else if (clientType == DatadogClient.ClientType.HTTP) { - payload.put("client-type", DatadogClient.ClientType.HTTP); - payload.put("api-connectivity", DatadogApiClient.validateDefaultIntakeConnection(globalConfiguration.getTargetApiURL(), globalConfiguration.getUsedApiKey())); - payload.put("logs-connectivity", DatadogApiClient.validateLogIntakeConnection(globalConfiguration.getTargetLogIntakeURL(), globalConfiguration.getUsedApiKey())); - payload.put("traces-connectivity", DatadogApiClient.validateWebhookIntakeConnection(globalConfiguration.getTargetWebhookIntakeURL(), globalConfiguration.getUsedApiKey())); - - } else { - throw new IllegalArgumentException("Unsupported client type: " + clientType); + if (clientConfiguration instanceof DatadogApiConfiguration){ + DatadogApiConfiguration apiConfiguration = (DatadogApiConfiguration) clientConfiguration; + payload.put("api-connectivity", validateConnectivity(apiConfiguration::validateApiConnection)); } String payloadString = payload.toString(2); IOUtils.write(payloadString, out, StandardCharsets.UTF_8); } + + private String validateConnectivity(ConnectivityValidator validator) { + try { + validator.validate(); + return "OK"; + } catch (Exception e) { + return e.getMessage(); + } + } + + @FunctionalInterface + private interface ConnectivityValidator { + void validate() throws Exception; + } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/flare/DatadogConfigFlare.java b/src/main/java/org/datadog/jenkins/plugins/datadog/flare/DatadogConfigFlare.java index e941ba251..6e1715613 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/flare/DatadogConfigFlare.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/flare/DatadogConfigFlare.java @@ -1,24 +1,14 @@ package org.datadog.jenkins.plugins.datadog.flare; import hudson.Extension; -import hudson.util.XStream2; -import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration; -import org.datadog.jenkins.plugins.datadog.DatadogUtilities; - import java.io.IOException; import java.io.OutputStream; +import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration; +import org.datadog.jenkins.plugins.datadog.DatadogUtilities; @Extension public class DatadogConfigFlare implements FlareContributor { - // TODO use XSTREAM from DatadogGlobalConfiguration following configuration refactor - private static final XStream2 XSTREAM; - - static { - XSTREAM = new XStream2(); - XSTREAM.autodetectAnnotations(true); - } - @Override public int order() { return ORDER.CONFIG; @@ -37,6 +27,6 @@ public String getFilename() { @Override public void writeFileContents(OutputStream out) throws IOException { DatadogGlobalConfiguration globalConfiguration = DatadogUtilities.getDatadogGlobalDescriptor(); - XSTREAM.toXMLUTF8(globalConfiguration, out); + DatadogGlobalConfiguration.XSTREAM.toXMLUTF8(globalConfiguration, out); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/model/GitCommitAction.java b/src/main/java/org/datadog/jenkins/plugins/datadog/model/GitCommitAction.java index 143a6d048..ba32116b1 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/model/GitCommitAction.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/model/GitCommitAction.java @@ -1,12 +1,14 @@ package org.datadog.jenkins.plugins.datadog.model; +import static org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter.ignoreOldData; + import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import java.util.Objects; -import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogActionConverter; +import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogConverter; import org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter; /** @@ -141,9 +143,9 @@ public String toString() { '}'; } - public static final class ConverterImpl extends DatadogActionConverter { + public static final class ConverterImpl extends DatadogConverter { public ConverterImpl(XStream xs) { - super(new ConverterV1()); + super(ignoreOldData(), new ConverterV1()); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/model/GitRepositoryAction.java b/src/main/java/org/datadog/jenkins/plugins/datadog/model/GitRepositoryAction.java index 048217a53..91fb86560 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/model/GitRepositoryAction.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/model/GitRepositoryAction.java @@ -1,5 +1,7 @@ package org.datadog.jenkins.plugins.datadog.model; +import static org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter.ignoreOldData; + import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; @@ -7,7 +9,7 @@ import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import java.util.Objects; import javax.annotation.Nullable; -import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogActionConverter; +import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogConverter; import org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter; /** @@ -79,9 +81,9 @@ public String toString() { '}'; } - public static final class ConverterImpl extends DatadogActionConverter { + public static final class ConverterImpl extends DatadogConverter { public ConverterImpl(XStream xs) { - super(new ConverterV1()); + super(ignoreOldData(), new ConverterV1()); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/model/PipelineNodeInfoAction.java b/src/main/java/org/datadog/jenkins/plugins/datadog/model/PipelineNodeInfoAction.java index 3e0b8762c..67c30ffd9 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/model/PipelineNodeInfoAction.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/model/PipelineNodeInfoAction.java @@ -1,5 +1,7 @@ package org.datadog.jenkins.plugins.datadog.model; +import static org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter.ignoreOldData; + import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; @@ -8,7 +10,7 @@ import java.util.Collections; import java.util.Objects; import java.util.Set; -import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogActionConverter; +import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogConverter; import org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter; public class PipelineNodeInfoAction extends DatadogPluginAction { @@ -75,9 +77,9 @@ public String toString() { '}'; } - public static final class ConverterImpl extends DatadogActionConverter { + public static final class ConverterImpl extends DatadogConverter { public ConverterImpl(XStream xs) { - super(new ConverterV1(), new ConverterV2()); + super(ignoreOldData(), new ConverterV1(), new ConverterV2()); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/model/PipelineQueueInfoAction.java b/src/main/java/org/datadog/jenkins/plugins/datadog/model/PipelineQueueInfoAction.java index 8bf5c74cd..ff2e5b509 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/model/PipelineQueueInfoAction.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/model/PipelineQueueInfoAction.java @@ -1,12 +1,14 @@ package org.datadog.jenkins.plugins.datadog.model; +import static org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter.ignoreOldData; + import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import java.util.Objects; -import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogActionConverter; +import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogConverter; import org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter; public class PipelineQueueInfoAction extends DatadogPluginAction { @@ -60,9 +62,9 @@ public String toString() { '}'; } - public static final class ConverterImpl extends DatadogActionConverter { + public static final class ConverterImpl extends DatadogConverter { public ConverterImpl(XStream xs) { - super(new ConverterV1()); + super(ignoreOldData(), new ConverterV1()); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/model/TraceInfoAction.java b/src/main/java/org/datadog/jenkins/plugins/datadog/model/TraceInfoAction.java index 141e29d18..10d7ba51b 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/model/TraceInfoAction.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/model/TraceInfoAction.java @@ -1,5 +1,7 @@ package org.datadog.jenkins.plugins.datadog.model; +import static org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter.*; + import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; @@ -11,7 +13,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.datadog.jenkins.plugins.datadog.traces.IdGenerator; -import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogActionConverter; +import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogConverter; import org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter; /** @@ -79,9 +81,9 @@ public String toString() { '}'; } - public static final class ConverterImpl extends DatadogActionConverter { + public static final class ConverterImpl extends DatadogConverter { public ConverterImpl(XStream xs) { - super(new ConverterV1()); + super(ignoreOldData(), new ConverterV1()); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/DequeueAction.java b/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/DequeueAction.java index 0291e3fc4..013158752 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/DequeueAction.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/DequeueAction.java @@ -1,5 +1,7 @@ package org.datadog.jenkins.plugins.datadog.model.node; +import static org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter.ignoreOldData; + import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; @@ -7,7 +9,7 @@ import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import java.util.Objects; import java.util.concurrent.TimeUnit; -import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogActionConverter; +import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogConverter; import org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter; public class DequeueAction extends QueueInfoAction { @@ -42,9 +44,9 @@ public String toString() { return "DequeueAction{queueTimeMillis=" + queueTimeMillis + '}'; } - public static final class ConverterImpl extends DatadogActionConverter { + public static final class ConverterImpl extends DatadogConverter { public ConverterImpl(XStream xs) { - super(new ConverterV1(), new ConverterV2()); + super(ignoreOldData(), new ConverterV1(), new ConverterV2()); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/EnqueueAction.java b/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/EnqueueAction.java index 1d0b25a2b..5d9066430 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/EnqueueAction.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/EnqueueAction.java @@ -1,5 +1,7 @@ package org.datadog.jenkins.plugins.datadog.model.node; +import static org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter.ignoreOldData; + import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; @@ -7,7 +9,7 @@ import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import java.util.Objects; import java.util.concurrent.TimeUnit; -import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogActionConverter; +import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogConverter; import org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter; public class EnqueueAction extends QueueInfoAction { @@ -42,9 +44,9 @@ public String toString() { return "EnqueueAction{timestampMillis=" + timestampMillis + '}'; } - public static final class ConverterImpl extends DatadogActionConverter { + public static final class ConverterImpl extends DatadogConverter { public ConverterImpl(XStream xs) { - super(new ConverterV1(), new ConverterV2()); + super(ignoreOldData(), new ConverterV1(), new ConverterV2()); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/NodeInfoAction.java b/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/NodeInfoAction.java index c0a221bc8..5b49bfc5d 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/NodeInfoAction.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/NodeInfoAction.java @@ -1,5 +1,7 @@ package org.datadog.jenkins.plugins.datadog.model.node; +import static org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter.ignoreOldData; + import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; @@ -9,7 +11,7 @@ import java.util.Objects; import java.util.Set; import org.datadog.jenkins.plugins.datadog.model.DatadogPluginAction; -import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogActionConverter; +import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogConverter; import org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter; public class NodeInfoAction extends DatadogPluginAction { @@ -78,9 +80,9 @@ public String toString() { '}'; } - public static final class ConverterImpl extends DatadogActionConverter { + public static final class ConverterImpl extends DatadogConverter { public ConverterImpl(XStream xs) { - super(new ConverterV1(), new ConverterV2()); + super(ignoreOldData(), new ConverterV1(), new ConverterV2()); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/StatusAction.java b/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/StatusAction.java index 0a254463e..950677e18 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/StatusAction.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/model/node/StatusAction.java @@ -1,5 +1,7 @@ package org.datadog.jenkins.plugins.datadog.model.node; +import static org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter.ignoreOldData; + import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; @@ -8,7 +10,7 @@ import java.util.Objects; import org.datadog.jenkins.plugins.datadog.model.DatadogPluginAction; import org.datadog.jenkins.plugins.datadog.model.Status; -import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogActionConverter; +import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogConverter; import org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter; public class StatusAction extends DatadogPluginAction { @@ -52,9 +54,9 @@ public int hashCode() { return Objects.hash(status, propagate); } - public static final class ConverterImpl extends DatadogActionConverter { + public static final class ConverterImpl extends DatadogConverter { public ConverterImpl(XStream xs) { - super(new ConverterV1()); + super(ignoreOldData(), new ConverterV1()); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/traces/BuildSpanAction.java b/src/main/java/org/datadog/jenkins/plugins/datadog/traces/BuildSpanAction.java index b03cce946..aaf66cf7d 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/traces/BuildSpanAction.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/traces/BuildSpanAction.java @@ -1,5 +1,7 @@ package org.datadog.jenkins.plugins.datadog.traces; +import static org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter.ignoreOldData; + import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; @@ -10,7 +12,7 @@ import javax.annotation.Nullable; import org.datadog.jenkins.plugins.datadog.model.DatadogPluginAction; import org.datadog.jenkins.plugins.datadog.traces.message.TraceSpan; -import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogActionConverter; +import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogConverter; import org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter; /** @@ -80,9 +82,9 @@ public String toString() { '}'; } - public static final class ConverterImpl extends DatadogActionConverter { + public static final class ConverterImpl extends DatadogConverter { public ConverterImpl(XStream xs) { - super(new ConverterV1(), new ConverterV2()); + super(ignoreOldData(), new ConverterV1(), new ConverterV2()); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/traces/message/TraceSpan.java b/src/main/java/org/datadog/jenkins/plugins/datadog/traces/message/TraceSpan.java index 47fd5b401..23cc4d24c 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/traces/message/TraceSpan.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/traces/message/TraceSpan.java @@ -1,5 +1,7 @@ package org.datadog.jenkins.plugins.datadog.traces.message; +import static org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter.ignoreOldData; + import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; @@ -10,7 +12,7 @@ import java.util.Map; import java.util.Objects; import org.datadog.jenkins.plugins.datadog.traces.IdGenerator; -import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogActionConverter; +import org.datadog.jenkins.plugins.datadog.util.conversion.DatadogConverter; import org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter; public class TraceSpan { @@ -182,9 +184,9 @@ public String toString() { '}'; } - public static final class ConverterImpl extends DatadogActionConverter { + public static final class ConverterImpl extends DatadogConverter { public ConverterImpl(XStream xs) { - super(new ConverterV1()); + super(ignoreOldData(), new ConverterV1()); } } diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/util/config/DatadogAgentConfiguration.java b/src/main/java/org/datadog/jenkins/plugins/datadog/util/config/DatadogAgentConfiguration.java deleted file mode 100644 index 4a10ab971..000000000 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/util/config/DatadogAgentConfiguration.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.datadog.jenkins.plugins.datadog.util.config; - -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.DD_AGENT_HOST; -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.DD_AGENT_PORT; -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.DD_TRACE_AGENT_PORT; -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.DD_TRACE_AGENT_URL; -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.TARGET_HOST_PROPERTY; -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.TARGET_PORT_PROPERTY; -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.TARGET_TRACE_COLLECTION_PORT_PROPERTY; - -import org.apache.commons.lang.StringUtils; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Map; - -public class DatadogAgentConfiguration { - - private final String host; - private final Integer port; - private final Integer tracesPort; - - public DatadogAgentConfiguration(final String host, final Integer port, final Integer tracesPort) { - this.host = host; - this.port = port; - this.tracesPort = tracesPort; - } - - public static DatadogAgentConfiguration resolve(final Map envVars) { - final URL ddTraceAgentUrl = buildURL(envVars.get(DD_TRACE_AGENT_URL)); - - // Host resolution - String host = null; - final String targetHostEnvVar = envVars.get(TARGET_HOST_PROPERTY); - final String ddAgentHostEnvVar = envVars.get(DD_AGENT_HOST); - if(StringUtils.isNotBlank(targetHostEnvVar)) { - host = targetHostEnvVar; - } else if(ddTraceAgentUrl != null) { - host = ddTraceAgentUrl.getHost(); - } else if (StringUtils.isNotBlank(ddAgentHostEnvVar)) { - host = ddAgentHostEnvVar; - } - - - // Port resolution - Integer port = null; - final String targetPortEnvVar = envVars.get(TARGET_PORT_PROPERTY); - final String ddAgentPortEnvVar = envVars.get(DD_AGENT_PORT); - if (StringUtils.isNotBlank(targetPortEnvVar) && StringUtils.isNumeric(targetPortEnvVar)) { - port = Integer.parseInt(targetPortEnvVar); - } else if(StringUtils.isNotBlank(ddAgentPortEnvVar) && StringUtils.isNumeric(ddAgentPortEnvVar)) { - port = Integer.parseInt(ddAgentPortEnvVar); - } - - // Traces port resolution - Integer tracesPort = null; - final String targetTraceCollectionPortEnvVar = envVars.get(TARGET_TRACE_COLLECTION_PORT_PROPERTY); - final String ddTraceAgentPortEnvVar = envVars.get(DD_TRACE_AGENT_PORT); - if(StringUtils.isNotBlank(targetTraceCollectionPortEnvVar) && StringUtils.isNumeric(targetTraceCollectionPortEnvVar)) { - tracesPort = Integer.parseInt(targetTraceCollectionPortEnvVar); - } else if(ddTraceAgentUrl != null) { - tracesPort = ddTraceAgentUrl.getPort(); - } else if(StringUtils.isNotBlank(ddTraceAgentPortEnvVar) && StringUtils.isNumeric(ddTraceAgentPortEnvVar)) { - tracesPort = Integer.parseInt(ddTraceAgentPortEnvVar); - } - - return new DatadogAgentConfiguration(host, port, tracesPort); - } - - private static URL buildURL(final String urlStr) { - try { - URL url = null; - if(StringUtils.isNotBlank(urlStr)) { - url = new URL(urlStr); - } - return url; - } catch (MalformedURLException ex) { - return null; - } - } - - public String getHost() { - return this.host; - } - - public Integer getPort() { - return this.port; - } - - public Integer getTracesPort() { - return this.tracesPort; - } -} diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/DatadogActionConverter.java b/src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/DatadogConverter.java similarity index 89% rename from src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/DatadogActionConverter.java rename to src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/DatadogConverter.java index 0e45a34ca..e04b39942 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/DatadogActionConverter.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/DatadogConverter.java @@ -27,15 +27,17 @@ * * @param The type of converted objects (should be as narrow as possible, so that different converters do not conflict) */ -public abstract class DatadogActionConverter implements Converter { +public abstract class DatadogConverter implements Converter { private static final String VERSION_ATTRIBUTE = "v"; + private final Class convertedType; private final VersionedConverter writeConverter; private final Map> readConverters; - private final Class convertedType; + private final VersionedConverter legacyConverter; /** + * @param legacyConverter Converter that is used to unmarshall legacy data written by older plugin versions * @param converters The list of versioned converters. * Writing is always done with the most up-to-date converter (the one with the maximum version). * Reading is done with the appropriate converter (the one that has version that matches the data version). @@ -43,7 +45,9 @@ public abstract class DatadogActionConverter implements Converter { * no deserialization is performed. */ @SafeVarargs - protected DatadogActionConverter(@Nonnull VersionedConverter... converters) { + protected DatadogConverter(VersionedConverter legacyConverter, @Nonnull VersionedConverter... converters) { + this.legacyConverter = legacyConverter; + if (converters.length == 0) { throw new IllegalArgumentException("At least one converter is needed"); } @@ -64,8 +68,8 @@ protected DatadogActionConverter(@Nonnull VersionedConverter... converters) { .orElseThrow(() -> new IllegalArgumentException("Cannot find converter with max version")); // only direct children are supported, to keep logic simple - if (!getClass().getSuperclass().equals(DatadogActionConverter.class)) { - throw new IllegalArgumentException(getClass().getName() + " is not a direct descendant of " + DatadogActionConverter.class.getName()); + if (!getClass().getSuperclass().equals(DatadogConverter.class)) { + throw new IllegalArgumentException(getClass().getName() + " is not a direct descendant of " + DatadogConverter.class.getName()); } ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass(); @@ -102,7 +106,7 @@ private VersionedConverter getReadConverter(HierarchicalStreamReader reader) String versionString = reader.getAttribute(VERSION_ATTRIBUTE); if (versionString == null) { // no attribute, data was written by an old version of the plugin - return null; + return legacyConverter; } int version = Integer.parseInt(versionString); return readConverters.get(version); diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/PolymorphicReflectionConverter.java b/src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/PolymorphicReflectionConverter.java new file mode 100644 index 000000000..98c02c023 --- /dev/null +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/PolymorphicReflectionConverter.java @@ -0,0 +1,31 @@ +package org.datadog.jenkins.plugins.datadog.util.conversion; + +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter; +import com.thoughtworks.xstream.converters.reflection.ReflectionConverter; +import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import com.thoughtworks.xstream.mapper.Mapper; + +/** + * A subtype of reflection converter that is capable of handling polymorphic fields + * (when the declared field type is an interface or a parent class and its value is an instance of an implementation or a child class). + *

+ * When marshalling, the converter writes additional {@code resolves-to} attribute that contains the name of the serialized class. + * When unmarshalling, the {@link AbstractReflectionConverter#instantiateNewInstance(HierarchicalStreamReader, UnmarshallingContext)} method + * reads the attribute and creates an instance of the correct class. + */ +public class PolymorphicReflectionConverter extends ReflectionConverter { + + public PolymorphicReflectionConverter(Mapper mapper, ReflectionProvider reflectionProvider) { + super(mapper, reflectionProvider); + } + + @Override + public void marshal(Object original, final HierarchicalStreamWriter writer, final MarshallingContext context) { + writer.addAttribute("resolves-to", mapper.serializedClass(original.getClass())); + super.marshal(original, writer, context); + } +} \ No newline at end of file diff --git a/src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/VersionedConverter.java b/src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/VersionedConverter.java index f38c0065f..749092286 100644 --- a/src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/VersionedConverter.java +++ b/src/main/java/org/datadog/jenkins/plugins/datadog/util/conversion/VersionedConverter.java @@ -32,4 +32,9 @@ protected F readField(HierarchicalStreamReader reader, UnmarshallingContext reader.moveUp(); return value; } + + public static VersionedConverter ignoreOldData() { + // syntax sugar to indicate that old data will be discarded + return null; + } } diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/config.jelly b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/config.jelly index fa805dac1..8e71b1872 100644 --- a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/config.jelly +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/config.jelly @@ -14,54 +14,7 @@ --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -70,9 +23,9 @@ - - - + + +
@@ -91,12 +44,12 @@
- - + + - - + + @@ -136,12 +89,10 @@ - - diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-traceServiceNameEntry.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-ciInstanceName.html similarity index 100% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-traceServiceNameEntry.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-ciInstanceName.html diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-blacklistEntry.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-excludedEntry.html similarity index 100% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-blacklistEntry.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-excludedEntry.html diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-whitelistEntry.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-includedEntry.html similarity index 100% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-whitelistEntry.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-includedEntry.html diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetApiKeyEntry.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetApiKeyEntry.html deleted file mode 100644 index 85559287d..000000000 --- a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetApiKeyEntry.html +++ /dev/null @@ -1,4 +0,0 @@ -

- Enter your Datadog API key, which can be found - here. -
diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetCredentialsApiKeyEntry.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetCredentialsApiKeyEntry.html deleted file mode 100644 index 417a9dec3..000000000 --- a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetCredentialsApiKeyEntry.html +++ /dev/null @@ -1,6 +0,0 @@ -
- Enter your Datadog API key, which can be found - here. - This field is optional, use it only if you would like to store your API key with - Jenkins Credentials. -
diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetMetricURLEntry.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetMetricURLEntry.html deleted file mode 100644 index 5c85be45b..000000000 --- a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetMetricURLEntry.html +++ /dev/null @@ -1,4 +0,0 @@ -
- API URL which the plugin reports to. Defaults to https://api.datadoghq.com/api/
- To submit metrics to a Datadog EU site organization, use: https://api.datadoghq.eu/api -
diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetSite.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetSite.html deleted file mode 100644 index 1e2a8e725..000000000 --- a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetSite.html +++ /dev/null @@ -1,6 +0,0 @@ -
- Datadog offers different sites throughout the world. - Each site is completely independent, and you cannot share data across sites. - Each site gives you benefits (for example, government security regulations) or allows you to store your data in specific locations around the world. - See Datadog documentation for more information about sites. -
diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration/config.jelly b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration/config.jelly new file mode 100644 index 000000000..39b64252d --- /dev/null +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration/config.jelly @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetHostEntry.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration/help-agentHost.html similarity index 100% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetHostEntry.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration/help-agentHost.html diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetLogCollectionPortEntry.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration/help-agentLogCollectionPort.html similarity index 100% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetLogCollectionPortEntry.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration/help-agentLogCollectionPort.html diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetPortEntry.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration/help-agentPort.html similarity index 100% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetPortEntry.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration/help-agentPort.html diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetTraceCollectionPortEntry.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration/help-agentTraceCollectionPort.html similarity index 100% rename from src/main/resources/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfiguration/help-targetTraceCollectionPortEntry.html rename to src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfiguration/help-agentTraceCollectionPort.html diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogApiConfiguration/config.jelly b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogApiConfiguration/config.jelly new file mode 100644 index 000000000..959c25a5d --- /dev/null +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/DatadogApiConfiguration/config.jelly @@ -0,0 +1,30 @@ + + + + + + + + + + + + +
Datadog Site
+ +
+ + + + + + + + + + +
Datadog API key
+ +
+ +
diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeSite/config.jelly b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeSite/config.jelly new file mode 100644 index 000000000..b131989ab --- /dev/null +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeSite/config.jelly @@ -0,0 +1,8 @@ + + + + + ${it} + + + diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeSite/help-siteBlock.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeSite/help-siteBlock.html new file mode 100644 index 000000000..dba2ad97c --- /dev/null +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeSite/help-siteBlock.html @@ -0,0 +1,4 @@ +
+ Select your Datadog site. + See Datadog documentation for more information about sites. +
diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeUrls/config.jelly b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeUrls/config.jelly new file mode 100644 index 000000000..5a46fe7d5 --- /dev/null +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeUrls/config.jelly @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogCredentialsApiKey/config.jelly b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogCredentialsApiKey/config.jelly new file mode 100644 index 000000000..bcaa2e0bf --- /dev/null +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogCredentialsApiKey/config.jelly @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogCredentialsApiKey/help-credentialsKeyBlock.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogCredentialsApiKey/help-credentialsKeyBlock.html new file mode 100644 index 000000000..454350eb3 --- /dev/null +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogCredentialsApiKey/help-credentialsKeyBlock.html @@ -0,0 +1,4 @@ +
+ Select a Datadog API key (can be found here) + from your Jenkins Credentials. +
diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogTextApiKey/config.jelly b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogTextApiKey/config.jelly new file mode 100644 index 000000000..cbda599e0 --- /dev/null +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogTextApiKey/config.jelly @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogTextApiKey/help-textKeyBlock.html b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogTextApiKey/help-textKeyBlock.html new file mode 100644 index 000000000..6d17e472c --- /dev/null +++ b/src/main/resources/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogTextApiKey/help-textKeyBlock.html @@ -0,0 +1,3 @@ +
+ Enter your Datadog API key (can be found here). +
diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationAsCodeTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationAsCodeTest.java new file mode 100644 index 000000000..dce1d13dc --- /dev/null +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationAsCodeTest.java @@ -0,0 +1,193 @@ +package org.datadog.jenkins.plugins.datadog; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import io.jenkins.plugins.casc.misc.ConfiguredWithCode; +import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogApiConfiguration; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogClientConfiguration; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntake; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogSite; +import org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogCredentialsApiKey; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; + +public class DatadogGlobalConfigurationAsCodeTest { + + @Rule + public JenkinsConfiguredWithCodeRule r = new JenkinsConfiguredWithCodeRule(); + + @Test + @ConfiguredWithCode("test-config.yml") + public void testConfigurationAsCodeCompatibility() { + DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor(); + + DatadogClientConfiguration datadogClientConfiguration = cfg.getDatadogClientConfiguration(); + Assert.assertTrue(datadogClientConfiguration instanceof DatadogApiConfiguration); + DatadogApiConfiguration apiConfiguration = (DatadogApiConfiguration) datadogClientConfiguration; + + DatadogIntake intake = apiConfiguration.getIntake(); + Assert.assertEquals("my-api-url", intake.getApiUrl()); + Assert.assertEquals("my-log-intake-url", intake.getLogsUrl()); + Assert.assertEquals("my-webhook-intake-url", intake.getWebhooksUrl()); + + assertTrue(apiConfiguration.getApiKey() instanceof DatadogCredentialsApiKey); + DatadogCredentialsApiKey credentialsApiKey = (DatadogCredentialsApiKey) apiConfiguration.getApiKey(); + assertEquals("my-api-key-credentials-id", credentialsApiKey.getCredentialsId()); + + Assert.assertTrue(cfg.getEnableCiVisibility()); + Assert.assertEquals("my-ci-instance-name", cfg.getCiInstanceName()); + Assert.assertEquals("my-excluded", cfg.getExcluded()); + Assert.assertEquals("my-included", cfg.getIncluded()); + Assert.assertEquals("my-hostname", cfg.getHostname()); + Assert.assertEquals("my-global-tag-file", cfg.getGlobalTagFile()); + Assert.assertEquals("my-global-tags", cfg.getGlobalTags()); + Assert.assertEquals("my-global-job-tags", cfg.getGlobalJobTags()); + Assert.assertEquals("my-include-events", cfg.getIncludeEvents()); + Assert.assertEquals("my-exclude-events", cfg.getExcludeEvents()); + Assert.assertTrue(cfg.isEmitSecurityEvents()); + Assert.assertTrue(cfg.isEmitSystemEvents()); + Assert.assertTrue(cfg.isCollectBuildLogs()); + Assert.assertTrue(cfg.isRefreshDogstatsdClient()); + Assert.assertTrue(cfg.isCacheBuildRuns()); + Assert.assertTrue(cfg.isUseAwsInstanceHostname()); + } + + @Test + @ConfiguredWithCode("test-config-site.yml") + public void testSiteConfigurationAsCodeCompatibility() { + DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor(); + + DatadogClientConfiguration datadogClientConfiguration = cfg.getDatadogClientConfiguration(); + Assert.assertTrue(datadogClientConfiguration instanceof DatadogApiConfiguration); + DatadogApiConfiguration apiConfiguration = (DatadogApiConfiguration) datadogClientConfiguration; + + DatadogIntake intake = apiConfiguration.getIntake(); + assertEquals(DatadogSite.US3.getApiUrl(), intake.getApiUrl()); + assertEquals(DatadogSite.US3.getLogsUrl(), intake.getLogsUrl()); + assertEquals(DatadogSite.US3.getWebhooksUrl(), intake.getWebhooksUrl()); + + assertTrue(apiConfiguration.getApiKey() instanceof DatadogCredentialsApiKey); + DatadogCredentialsApiKey credentialsApiKey = (DatadogCredentialsApiKey) apiConfiguration.getApiKey(); + assertEquals("my-api-key-credentials-id", credentialsApiKey.getCredentialsId()); + + Assert.assertTrue(cfg.getEnableCiVisibility()); + Assert.assertEquals("my-ci-instance-name", cfg.getCiInstanceName()); + Assert.assertEquals("my-excluded", cfg.getExcluded()); + Assert.assertEquals("my-included", cfg.getIncluded()); + Assert.assertEquals("my-hostname", cfg.getHostname()); + Assert.assertEquals("my-global-tag-file", cfg.getGlobalTagFile()); + Assert.assertEquals("my-global-tags", cfg.getGlobalTags()); + Assert.assertEquals("my-global-job-tags", cfg.getGlobalJobTags()); + Assert.assertEquals("my-include-events", cfg.getIncludeEvents()); + Assert.assertEquals("my-exclude-events", cfg.getExcludeEvents()); + Assert.assertTrue(cfg.isEmitSecurityEvents()); + Assert.assertTrue(cfg.isEmitSystemEvents()); + Assert.assertTrue(cfg.isCollectBuildLogs()); + Assert.assertTrue(cfg.isRefreshDogstatsdClient()); + Assert.assertTrue(cfg.isCacheBuildRuns()); + Assert.assertTrue(cfg.isUseAwsInstanceHostname()); + } + + @Test + @ConfiguredWithCode("test-config-agent.yml") + public void testAgentConfigurationAsCodeCompatibility() { + DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor(); + DatadogClientConfiguration datadogClientConfiguration = cfg.getDatadogClientConfiguration(); + Assert.assertTrue(datadogClientConfiguration instanceof DatadogAgentConfiguration); + + DatadogAgentConfiguration agentConfiguration = (DatadogAgentConfiguration) datadogClientConfiguration; + Assert.assertEquals("my-agent-host", agentConfiguration.getAgentHost()); + Assert.assertEquals((Integer) 1357, agentConfiguration.getAgentPort()); + Assert.assertEquals((Integer) 2468, agentConfiguration.getAgentLogCollectionPort()); + Assert.assertEquals((Integer) 3579, agentConfiguration.getAgentTraceCollectionPort()); + + Assert.assertTrue(cfg.getEnableCiVisibility()); + Assert.assertEquals("my-ci-instance-name", cfg.getCiInstanceName()); + Assert.assertEquals("my-excluded", cfg.getExcluded()); + Assert.assertEquals("my-included", cfg.getIncluded()); + Assert.assertEquals("my-hostname", cfg.getHostname()); + Assert.assertEquals("my-global-tag-file", cfg.getGlobalTagFile()); + Assert.assertEquals("my-global-tags", cfg.getGlobalTags()); + Assert.assertEquals("my-global-job-tags", cfg.getGlobalJobTags()); + Assert.assertEquals("my-include-events", cfg.getIncludeEvents()); + Assert.assertEquals("my-exclude-events", cfg.getExcludeEvents()); + Assert.assertFalse(cfg.isEmitSecurityEvents()); + Assert.assertFalse(cfg.isEmitSystemEvents()); + Assert.assertFalse(cfg.isCollectBuildLogs()); + Assert.assertFalse(cfg.isRefreshDogstatsdClient()); + Assert.assertFalse(cfg.isCacheBuildRuns()); + Assert.assertFalse(cfg.isUseAwsInstanceHostname()); + } + + @Test + @ConfiguredWithCode("test-config-legacy.yml") + public void testLegacyConfigurationAsCodeCompatibility() { + DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor(); + + DatadogClientConfiguration datadogClientConfiguration = cfg.getDatadogClientConfiguration(); + Assert.assertTrue(datadogClientConfiguration instanceof DatadogApiConfiguration); + DatadogApiConfiguration apiConfiguration = (DatadogApiConfiguration) datadogClientConfiguration; + + DatadogIntake intake = apiConfiguration.getIntake(); + Assert.assertEquals("my-target-api-url", intake.getApiUrl()); + Assert.assertEquals("my-target-log-intake-url", intake.getLogsUrl()); + Assert.assertEquals("my-target-webhook-intake-url", intake.getWebhooksUrl()); + + assertTrue(apiConfiguration.getApiKey() instanceof DatadogCredentialsApiKey); + DatadogCredentialsApiKey credentialsApiKey = (DatadogCredentialsApiKey) apiConfiguration.getApiKey(); + assertEquals("my-target-credentials-api-key", credentialsApiKey.getCredentialsId()); + + Assert.assertTrue(cfg.getEnableCiVisibility()); + Assert.assertEquals("my-trace-service-name", cfg.getCiInstanceName()); + Assert.assertEquals("my-blacklist", cfg.getExcluded()); + Assert.assertEquals("my-whitelist", cfg.getIncluded()); + Assert.assertEquals("my-hostname", cfg.getHostname()); + Assert.assertEquals("my-global-tag-file", cfg.getGlobalTagFile()); + Assert.assertEquals("my-global-tags", cfg.getGlobalTags()); + Assert.assertEquals("my-global-job-tags", cfg.getGlobalJobTags()); + Assert.assertEquals("my-include-events", cfg.getIncludeEvents()); + Assert.assertEquals("my-exclude-events", cfg.getExcludeEvents()); + Assert.assertFalse(cfg.isEmitSecurityEvents()); + Assert.assertTrue(cfg.isEmitSystemEvents()); + Assert.assertFalse(cfg.isCollectBuildLogs()); + Assert.assertFalse(cfg.isRefreshDogstatsdClient()); + Assert.assertTrue(cfg.isCacheBuildRuns()); + Assert.assertFalse(cfg.isUseAwsInstanceHostname()); + } + + @Test + @ConfiguredWithCode("test-config-legacy-agent.yml") + public void testLegacyAgentConfigurationAsCodeCompatibility() { + DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor(); + DatadogClientConfiguration datadogClientConfiguration = cfg.getDatadogClientConfiguration(); + Assert.assertTrue(datadogClientConfiguration instanceof DatadogAgentConfiguration); + + DatadogAgentConfiguration agentConfiguration = (DatadogAgentConfiguration) datadogClientConfiguration; + Assert.assertEquals("my-target-host", agentConfiguration.getAgentHost()); + Assert.assertEquals((Integer) 1357, agentConfiguration.getAgentPort()); + Assert.assertEquals((Integer) 2468, agentConfiguration.getAgentLogCollectionPort()); + Assert.assertEquals((Integer) 3579, agentConfiguration.getAgentTraceCollectionPort()); + + Assert.assertFalse(cfg.getEnableCiVisibility()); + Assert.assertEquals("my-trace-service-name", cfg.getCiInstanceName()); + Assert.assertEquals("my-blacklist", cfg.getExcluded()); + Assert.assertEquals("my-whitelist", cfg.getIncluded()); + Assert.assertEquals("my-hostname", cfg.getHostname()); + Assert.assertEquals("my-global-tag-file", cfg.getGlobalTagFile()); + Assert.assertEquals("my-global-tags", cfg.getGlobalTags()); + Assert.assertEquals("my-global-job-tags", cfg.getGlobalJobTags()); + Assert.assertEquals("my-include-events", cfg.getIncludeEvents()); + Assert.assertEquals("my-exclude-events", cfg.getExcludeEvents()); + Assert.assertTrue(cfg.isEmitSecurityEvents()); + Assert.assertFalse(cfg.isEmitSystemEvents()); + Assert.assertTrue(cfg.isCollectBuildLogs()); + Assert.assertTrue(cfg.isRefreshDogstatsdClient()); + Assert.assertFalse(cfg.isCacheBuildRuns()); + Assert.assertTrue(cfg.isUseAwsInstanceHostname()); + } +} + diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationEnvVariablesTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationEnvVariablesTest.java new file mode 100644 index 000000000..f904c5a78 --- /dev/null +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationEnvVariablesTest.java @@ -0,0 +1,207 @@ +package org.datadog.jenkins.plugins.datadog; + +import com.github.stefanbirkner.systemlambda.SystemLambda; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogApiConfiguration; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntake; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeSite; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogSite; +import org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogApiKey; +import org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogTextApiKey; +import org.junit.ClassRule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import static org.junit.Assert.*; + +public class DatadogGlobalConfigurationEnvVariablesTest { + + @ClassRule + public static final JenkinsRule jenkinsRule = new JenkinsRule(); + + @Test + public void testDefaultValues() throws Exception { + DatadogGlobalConfiguration configuration = SystemLambda + .withEnvironmentVariable(DatadogGlobalConfiguration.REPORT_WITH_PROPERTY, null) + .and(DatadogIntakeSite.DATADOG_SITE_PROPERTY, null) + .and(DatadogIntakeUrls.TARGET_API_URL_PROPERTY, null) + .and(DatadogIntakeUrls.TARGET_WEBHOOK_INTAKE_URL_PROPERTY, null) + .and(DatadogIntakeUrls.TARGET_LOG_INTAKE_URL_PROPERTY, null) + .and(DatadogTextApiKey.TARGET_API_KEY_PROPERTY, null) + .execute(DatadogGlobalConfiguration::new); + + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogApiConfiguration); + DatadogApiConfiguration datadogClientConfiguration = (DatadogApiConfiguration) configuration.getDatadogClientConfiguration(); + + DatadogIntake intake = datadogClientConfiguration.getIntake(); + assertEquals("https://api.datadoghq.com/api/", intake.getApiUrl()); + assertEquals("https://http-intake.logs.datadoghq.com/v1/input/", intake.getLogsUrl()); + assertEquals("https://webhook-intake.datadoghq.com/api/v2/webhook/", intake.getWebhooksUrl()); + + DatadogApiKey apiKey = datadogClientConfiguration.getApiKey(); + assertNull(apiKey.getKey()); + } + + @Test + public void testAgentDefaultValues() throws Exception { + DatadogGlobalConfiguration configuration = SystemLambda + .withEnvironmentVariable(DatadogGlobalConfiguration.REPORT_WITH_PROPERTY, "DSD") + .and(DatadogAgentConfiguration.TARGET_HOST_PROPERTY, null) + .and(DatadogAgentConfiguration.TARGET_PORT_PROPERTY, null) + .and(DatadogAgentConfiguration.TARGET_TRACE_COLLECTION_PORT_PROPERTY, null) + .and(DatadogAgentConfiguration.TARGET_LOG_COLLECTION_PORT_PROPERTY, null) + .and(DatadogAgentConfiguration.DD_AGENT_HOST, null) + .and(DatadogAgentConfiguration.DD_AGENT_PORT, null) + .and(DatadogAgentConfiguration.DD_TRACE_AGENT_PORT, null) + .and(DatadogAgentConfiguration.DD_TRACE_AGENT_URL, null) + .execute(DatadogGlobalConfiguration::new); + + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogAgentConfiguration); + DatadogAgentConfiguration datadogClientConfiguration = (DatadogAgentConfiguration) configuration.getDatadogClientConfiguration(); + + assertEquals("localhost", datadogClientConfiguration.getAgentHost()); + assertEquals((Integer) 8125, datadogClientConfiguration.getAgentPort()); + assertEquals((Integer) 8126, datadogClientConfiguration.getAgentTraceCollectionPort()); + assertEquals((Integer) null, datadogClientConfiguration.getAgentLogCollectionPort()); + } + + @Test + public void testIntakeConfiguration() throws Exception { + DatadogGlobalConfiguration configuration = SystemLambda + .withEnvironmentVariable(DatadogGlobalConfiguration.REPORT_WITH_PROPERTY, "HTTP") + .and(DatadogIntakeSite.DATADOG_SITE_PROPERTY, null) + .and(DatadogIntakeUrls.TARGET_API_URL_PROPERTY, "my-target-api-url") + .and(DatadogIntakeUrls.TARGET_WEBHOOK_INTAKE_URL_PROPERTY, "my-webhook-intake-url") + .and(DatadogIntakeUrls.TARGET_LOG_INTAKE_URL_PROPERTY, "my-log-intake-url") + .and(DatadogTextApiKey.TARGET_API_KEY_PROPERTY, "my-api-key") + .execute(DatadogGlobalConfiguration::new); + + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogApiConfiguration); + DatadogApiConfiguration datadogClientConfiguration = (DatadogApiConfiguration) configuration.getDatadogClientConfiguration(); + + DatadogIntake intake = datadogClientConfiguration.getIntake(); + assertEquals("my-target-api-url", intake.getApiUrl()); + assertEquals("my-log-intake-url", intake.getLogsUrl()); + assertEquals("my-webhook-intake-url", intake.getWebhooksUrl()); + + DatadogApiKey apiKey = datadogClientConfiguration.getApiKey(); + assertEquals("my-api-key", apiKey.getKey().getPlainText()); + } + + @Test + public void testSiteConfiguration() throws Exception { + DatadogGlobalConfiguration configuration = SystemLambda + .withEnvironmentVariable(DatadogGlobalConfiguration.REPORT_WITH_PROPERTY, "HTTP") + .and(DatadogIntakeSite.DATADOG_SITE_PROPERTY, "US1") + .and(DatadogIntakeUrls.TARGET_API_URL_PROPERTY, null) + .and(DatadogIntakeUrls.TARGET_WEBHOOK_INTAKE_URL_PROPERTY, null) + .and(DatadogIntakeUrls.TARGET_LOG_INTAKE_URL_PROPERTY, null) + .and(DatadogTextApiKey.TARGET_API_KEY_PROPERTY, "my-api-key") + .execute(DatadogGlobalConfiguration::new); + + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogApiConfiguration); + DatadogApiConfiguration datadogClientConfiguration = (DatadogApiConfiguration) configuration.getDatadogClientConfiguration(); + + DatadogIntake intake = datadogClientConfiguration.getIntake(); + assertEquals(DatadogSite.US1.getApiUrl(), intake.getApiUrl()); + assertEquals(DatadogSite.US1.getLogsUrl(), intake.getLogsUrl()); + assertEquals(DatadogSite.US1.getWebhooksUrl(), intake.getWebhooksUrl()); + + DatadogApiKey apiKey = datadogClientConfiguration.getApiKey(); + assertEquals("my-api-key", apiKey.getKey().getPlainText()); + } + + @Test + public void testAgentConfiguration() throws Exception { + DatadogGlobalConfiguration configuration = SystemLambda + .withEnvironmentVariable(DatadogGlobalConfiguration.REPORT_WITH_PROPERTY, "DSD") + .and(DatadogAgentConfiguration.TARGET_HOST_PROPERTY, "my-target-host") + .and(DatadogAgentConfiguration.TARGET_PORT_PROPERTY, "123") + .and(DatadogAgentConfiguration.TARGET_TRACE_COLLECTION_PORT_PROPERTY, "456") + .and(DatadogAgentConfiguration.TARGET_LOG_COLLECTION_PORT_PROPERTY, "789") + .and(DatadogAgentConfiguration.DD_AGENT_HOST, null) + .and(DatadogAgentConfiguration.DD_AGENT_PORT, null) + .and(DatadogAgentConfiguration.DD_TRACE_AGENT_PORT, null) + .and(DatadogAgentConfiguration.DD_TRACE_AGENT_URL, null) + .execute(DatadogGlobalConfiguration::new); + + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogAgentConfiguration); + DatadogAgentConfiguration datadogClientConfiguration = (DatadogAgentConfiguration) configuration.getDatadogClientConfiguration(); + + assertEquals("my-target-host", datadogClientConfiguration.getAgentHost()); + assertEquals((Integer) 123, datadogClientConfiguration.getAgentPort()); + assertEquals((Integer) 456, datadogClientConfiguration.getAgentTraceCollectionPort()); + assertEquals((Integer) 789, datadogClientConfiguration.getAgentLogCollectionPort()); + } + + @Test + public void testLegacyAgentConfiguration() throws Exception { + DatadogGlobalConfiguration configuration = SystemLambda + .withEnvironmentVariable(DatadogGlobalConfiguration.REPORT_WITH_PROPERTY, "DSD") + .and(DatadogAgentConfiguration.TARGET_HOST_PROPERTY, null) + .and(DatadogAgentConfiguration.TARGET_PORT_PROPERTY, null) + .and(DatadogAgentConfiguration.TARGET_TRACE_COLLECTION_PORT_PROPERTY, "456") + .and(DatadogAgentConfiguration.TARGET_LOG_COLLECTION_PORT_PROPERTY, "789") + .and(DatadogAgentConfiguration.DD_AGENT_HOST, "my-target-host") + .and(DatadogAgentConfiguration.DD_AGENT_PORT, "123") + .and(DatadogAgentConfiguration.DD_TRACE_AGENT_PORT, null) + .and(DatadogAgentConfiguration.DD_TRACE_AGENT_URL, null) + .execute(DatadogGlobalConfiguration::new); + + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogAgentConfiguration); + DatadogAgentConfiguration datadogClientConfiguration = (DatadogAgentConfiguration) configuration.getDatadogClientConfiguration(); + + assertEquals("my-target-host", datadogClientConfiguration.getAgentHost()); + assertEquals((Integer) 123, datadogClientConfiguration.getAgentPort()); + assertEquals((Integer) 456, datadogClientConfiguration.getAgentTraceCollectionPort()); + assertEquals((Integer) 789, datadogClientConfiguration.getAgentLogCollectionPort()); + } + + @Test + public void testLegacyAgentPortConfiguration() throws Exception { + DatadogGlobalConfiguration configuration = SystemLambda + .withEnvironmentVariable(DatadogGlobalConfiguration.REPORT_WITH_PROPERTY, "DSD") + .and(DatadogAgentConfiguration.TARGET_HOST_PROPERTY, null) + .and(DatadogAgentConfiguration.TARGET_PORT_PROPERTY, "123") + .and(DatadogAgentConfiguration.TARGET_TRACE_COLLECTION_PORT_PROPERTY, null) + .and(DatadogAgentConfiguration.TARGET_LOG_COLLECTION_PORT_PROPERTY, "789") + .and(DatadogAgentConfiguration.DD_AGENT_HOST, "my-target-host") + .and(DatadogAgentConfiguration.DD_AGENT_PORT, null) + .and(DatadogAgentConfiguration.DD_TRACE_AGENT_PORT, "456") + .and(DatadogAgentConfiguration.DD_TRACE_AGENT_URL, null) + .execute(DatadogGlobalConfiguration::new); + + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogAgentConfiguration); + DatadogAgentConfiguration datadogClientConfiguration = (DatadogAgentConfiguration) configuration.getDatadogClientConfiguration(); + + assertEquals("my-target-host", datadogClientConfiguration.getAgentHost()); + assertEquals((Integer) 123, datadogClientConfiguration.getAgentPort()); + assertEquals((Integer) 456, datadogClientConfiguration.getAgentTraceCollectionPort()); + assertEquals((Integer) 789, datadogClientConfiguration.getAgentLogCollectionPort()); + } + + @Test + public void testLegacyAgentUrlConfiguration() throws Exception { + DatadogGlobalConfiguration configuration = SystemLambda + .withEnvironmentVariable(DatadogGlobalConfiguration.REPORT_WITH_PROPERTY, "DSD") + .and(DatadogAgentConfiguration.TARGET_HOST_PROPERTY, null) + .and(DatadogAgentConfiguration.TARGET_PORT_PROPERTY, "123") + .and(DatadogAgentConfiguration.TARGET_TRACE_COLLECTION_PORT_PROPERTY, null) + .and(DatadogAgentConfiguration.TARGET_LOG_COLLECTION_PORT_PROPERTY, "789") + .and(DatadogAgentConfiguration.DD_AGENT_HOST, null) + .and(DatadogAgentConfiguration.DD_AGENT_PORT, null) + .and(DatadogAgentConfiguration.DD_TRACE_AGENT_PORT, null) + .and(DatadogAgentConfiguration.DD_TRACE_AGENT_URL, "http://my-target-host:456") + .execute(DatadogGlobalConfiguration::new); + + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogAgentConfiguration); + DatadogAgentConfiguration datadogClientConfiguration = (DatadogAgentConfiguration) configuration.getDatadogClientConfiguration(); + + assertEquals("my-target-host", datadogClientConfiguration.getAgentHost()); + assertEquals((Integer) 123, datadogClientConfiguration.getAgentPort()); + assertEquals((Integer) 456, datadogClientConfiguration.getAgentTraceCollectionPort()); + assertEquals((Integer) 789, datadogClientConfiguration.getAgentLogCollectionPort()); + } + +} diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationSaveTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationSaveTest.java new file mode 100644 index 000000000..46f435180 --- /dev/null +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationSaveTest.java @@ -0,0 +1,97 @@ +package org.datadog.jenkins.plugins.datadog; + +import com.thoughtworks.xstream.XStream; +import hudson.util.XStream2; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogApiConfiguration; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.net.URL; +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; + +/** + * Verifies that configuration stays the same after being saved and then loaded + */ +@RunWith(Parameterized.class) +public class DatadogGlobalConfigurationSaveTest { + + @Parameterized.Parameters(name = "{0}") + public static Collection parameters() { + return Arrays.asList(new Object[][] { + { "globalConfiguration.xml" }, + { "globalConfigurationCredentialsKey.xml" }, + { "globalConfigurationSite.xml" }, + { "globalConfigurationAgent.xml" }, + { "globalConfigurationLegacyFormat.xml" }, + { "globalConfigurationLegacyFormatCredentialsKey.xml" }, + { "globalConfigurationLegacyFormatAgent.xml" }, + }); + } + + private final String configurationResource; + + public DatadogGlobalConfigurationSaveTest(String configurationResource) { + this.configurationResource = configurationResource; + } + + @Test + public void canSaveAndLoadGlobalConfiguration() { + DatadogGlobalConfiguration configuration = parseConfigurationFromResource(configurationResource); + DatadogGlobalConfiguration loadedConfiguration = parseConfigurationFromString(serializeConfiguration(configuration)); + + assertEquals(configuration.getDatadogClientConfiguration(), loadedConfiguration.getDatadogClientConfiguration()); + assertEquals(configuration.getExcluded(), loadedConfiguration.getExcluded()); + assertEquals(configuration.getIncluded(), loadedConfiguration.getIncluded()); + assertEquals(configuration.getCiInstanceName(), loadedConfiguration.getCiInstanceName()); + assertEquals(configuration.getHostname(), loadedConfiguration.getHostname()); + assertEquals(configuration.getGlobalTagFile(), loadedConfiguration.getGlobalTagFile()); + assertEquals(configuration.getGlobalTags(), loadedConfiguration.getGlobalTags()); + assertEquals(configuration.getGlobalJobTags(), loadedConfiguration.getGlobalJobTags()); + assertEquals(configuration.getIncludeEvents(), loadedConfiguration.getIncludeEvents()); + assertEquals(configuration.getExcludeEvents(), loadedConfiguration.getExcludeEvents()); + assertEquals(configuration.isEmitSecurityEvents(), loadedConfiguration.isEmitSecurityEvents()); + assertEquals(configuration.isEmitSystemEvents(), loadedConfiguration.isEmitSystemEvents()); + assertEquals(configuration.isCollectBuildLogs(), loadedConfiguration.isCollectBuildLogs()); + assertEquals(configuration.getEnableCiVisibility(), loadedConfiguration.getEnableCiVisibility()); + assertEquals(configuration.isRefreshDogstatsdClient(), loadedConfiguration.isRefreshDogstatsdClient()); + assertEquals(configuration.isCacheBuildRuns(), loadedConfiguration.isCacheBuildRuns()); + assertEquals(configuration.isUseAwsInstanceHostname(), loadedConfiguration.isUseAwsInstanceHostname()); + assertEquals(configuration.getTraceServiceName(), loadedConfiguration.getTraceServiceName()); + assertEquals(configuration.getBlacklist(), loadedConfiguration.getBlacklist()); + assertEquals(configuration.getWhitelist(), loadedConfiguration.getWhitelist()); + assertEquals(configuration.isCollectBuildTraces(), loadedConfiguration.isCollectBuildTraces()); + assertEquals(configuration.getReportWith(), loadedConfiguration.getReportWith()); + assertEquals(configuration.getTargetApiURL(), loadedConfiguration.getTargetApiURL()); + assertEquals(configuration.getTargetLogIntakeURL(), loadedConfiguration.getTargetLogIntakeURL()); + assertEquals(configuration.getTargetWebhookIntakeURL(), loadedConfiguration.getTargetWebhookIntakeURL()); + assertEquals(configuration.getTargetApiKey(), loadedConfiguration.getTargetApiKey()); + assertEquals(configuration.getTargetCredentialsApiKey(), loadedConfiguration.getTargetCredentialsApiKey()); + assertEquals(configuration.getTargetHost(), loadedConfiguration.getTargetHost()); + assertEquals(configuration.getTargetPort(), loadedConfiguration.getTargetPort()); + assertEquals(configuration.getTargetLogCollectionPort(), loadedConfiguration.getTargetLogCollectionPort()); + assertEquals(configuration.getTargetTraceCollectionPort(), loadedConfiguration.getTargetTraceCollectionPort()); + } + + private static final XStream XSTREAM = new XStream2(XStream2.getDefaultDriver()); + + static { + XSTREAM.processAnnotations(new Class[] { DatadogGlobalConfiguration.class, DatadogApiConfiguration.class }); + } + + private static DatadogGlobalConfiguration parseConfigurationFromResource(String resourceName) { + URL resource = DatadogGlobalConfigurationSaveTest.class.getResource(resourceName); + return (DatadogGlobalConfiguration) XSTREAM.fromXML(resource); + } + + private static DatadogGlobalConfiguration parseConfigurationFromString(String serializedConfiguration) { + return (DatadogGlobalConfiguration) XSTREAM.fromXML(serializedConfiguration); + } + + private static String serializeConfiguration(DatadogGlobalConfiguration configuration) { + return XSTREAM.toXML(configuration); + } +} diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationTest.java index b3ef6783c..dcf3901e4 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationTest.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/DatadogGlobalConfigurationTest.java @@ -1,83 +1,287 @@ package org.datadog.jenkins.plugins.datadog; -import org.junit.Assert; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Rule; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.thoughtworks.xstream.XStream; +import hudson.util.Secret; +import hudson.util.XStream2; +import java.net.URL; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogApiConfiguration; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntake; +import org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogSite; +import org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogCredentialsApiKey; +import org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogTextApiKey; import org.junit.Test; -import org.jvnet.hudson.test.JenkinsRule; +public class DatadogGlobalConfigurationTest { -import hudson.util.Secret; -import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl; -import org.jenkinsci.plugins.plaincredentials.StringCredentials; + @Test + public void canLoadGlobalConfiguration() { + DatadogGlobalConfiguration configuration = parseConfigurationFromResource("globalConfiguration.xml"); + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogApiConfiguration); + DatadogApiConfiguration datadogClientConfiguration = (DatadogApiConfiguration) configuration.getDatadogClientConfiguration(); -import com.cloudbees.plugins.credentials.CredentialsProvider; -import com.cloudbees.plugins.credentials.CredentialsScope; -import com.cloudbees.plugins.credentials.CredentialsStore; -import com.cloudbees.plugins.credentials.domains.Domain; + DatadogIntake intake = datadogClientConfiguration.getIntake(); + assertEquals("https://my-api-url.com/api/", intake.getApiUrl()); + assertEquals("https://my-log-intake-url.com/v1/input/", intake.getLogsUrl()); + assertEquals("https://my-webhook-intake-url.com/api/v2/webhook/", intake.getWebhooksUrl()); -import io.jenkins.plugins.casc.misc.ConfiguredWithCode; -import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule; + assertTrue(datadogClientConfiguration.getApiKey() instanceof DatadogTextApiKey); + DatadogTextApiKey textApiKey = (DatadogTextApiKey) datadogClientConfiguration.getApiKey(); + assertEquals("{AQAAABAAAAAwB78atDHwzelG9W0Fw5FRNq0ZUaLh1BwpQPKhvs2u84lxkRPDqeUNoVZel+MKwfyTOjuetnituHYGMdvE9bc3kg==}", Secret.toString(textApiKey.getKey())); -import java.io.IOException; + assertEquals("my-jenkins-service-name", configuration.getCiInstanceName()); + assertEquals("my-hostname", configuration.getHostname()); + assertEquals("my-blacklist", configuration.getExcluded()); + assertEquals("my-whitelist", configuration.getIncluded()); + assertEquals("my-global-tag-file", configuration.getGlobalTagFile()); + assertEquals("my-global-tags", configuration.getGlobalTags()); + assertEquals("my-global-job-tags", configuration.getGlobalJobTags()); + assertEquals("my-include-events", configuration.getIncludeEvents()); + assertEquals("my-exclude-evens", configuration.getExcludeEvents()); + assertFalse(configuration.isEmitSecurityEvents()); + assertFalse(configuration.isEmitSystemEvents()); + assertTrue(configuration.isCollectBuildLogs()); + assertTrue(configuration.getEnableCiVisibility()); + assertTrue(configuration.isRefreshDogstatsdClient()); + assertFalse(configuration.isCacheBuildRuns()); + assertTrue(configuration.isUseAwsInstanceHostname()); + } -public class DatadogGlobalConfigurationTest { + @Test + public void canLoadGlobalConfigurationWithCredentialsApiKey() { + DatadogGlobalConfiguration configuration = parseConfigurationFromResource("globalConfigurationCredentialsKey.xml"); + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogApiConfiguration); + DatadogApiConfiguration datadogClientConfiguration = (DatadogApiConfiguration) configuration.getDatadogClientConfiguration(); - @ClassRule - public static JenkinsRule jenkinsRule; + DatadogIntake intake = datadogClientConfiguration.getIntake(); + assertEquals("https://my-api-url.com/api/", intake.getApiUrl()); + assertEquals("https://my-log-intake-url.com/v1/input/", intake.getLogsUrl()); + assertEquals("https://my-webhook-intake-url.com/api/v2/webhook/", intake.getWebhooksUrl()); - static { - jenkinsRule = new JenkinsRule(); - jenkinsRule.timeout = 600; // default value of 180 is too small for all the test cases in this class + assertTrue(datadogClientConfiguration.getApiKey() instanceof DatadogCredentialsApiKey); + DatadogCredentialsApiKey credentialsApiKey = (DatadogCredentialsApiKey) datadogClientConfiguration.getApiKey(); + assertEquals("my-api-key-credentials-id", credentialsApiKey.getCredentialsId()); + + assertEquals("my-jenkins-service-name", configuration.getCiInstanceName()); + assertEquals("my-hostname", configuration.getHostname()); + assertEquals("my-blacklist", configuration.getExcluded()); + assertEquals("my-whitelist", configuration.getIncluded()); + assertEquals("my-global-tag-file", configuration.getGlobalTagFile()); + assertEquals("my-global-tags", configuration.getGlobalTags()); + assertEquals("my-global-job-tags", configuration.getGlobalJobTags()); + assertEquals("my-include-events", configuration.getIncludeEvents()); + assertEquals("my-exclude-evens", configuration.getExcludeEvents()); + assertFalse(configuration.isEmitSecurityEvents()); + assertFalse(configuration.isEmitSystemEvents()); + assertTrue(configuration.isCollectBuildLogs()); + assertTrue(configuration.getEnableCiVisibility()); + assertTrue(configuration.isRefreshDogstatsdClient()); + assertFalse(configuration.isCacheBuildRuns()); + assertTrue(configuration.isUseAwsInstanceHostname()); + } + + @Test + public void canLoadGlobalConfigurationWithSite() { + DatadogGlobalConfiguration configuration = parseConfigurationFromResource("globalConfigurationSite.xml"); + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogApiConfiguration); + DatadogApiConfiguration datadogClientConfiguration = (DatadogApiConfiguration) configuration.getDatadogClientConfiguration(); + + DatadogIntake intake = datadogClientConfiguration.getIntake(); + assertEquals(DatadogSite.US3.getApiUrl(), intake.getApiUrl()); + assertEquals(DatadogSite.US3.getLogsUrl(), intake.getLogsUrl()); + assertEquals(DatadogSite.US3.getWebhooksUrl(), intake.getWebhooksUrl()); + + assertTrue(datadogClientConfiguration.getApiKey() instanceof DatadogCredentialsApiKey); + DatadogCredentialsApiKey credentialsApiKey = (DatadogCredentialsApiKey) datadogClientConfiguration.getApiKey(); + assertEquals("my-api-key-credentials-id", credentialsApiKey.getCredentialsId()); + + assertEquals("my-jenkins-service-name", configuration.getCiInstanceName()); + assertEquals("my-hostname", configuration.getHostname()); + assertEquals("my-blacklist", configuration.getExcluded()); + assertEquals("my-whitelist", configuration.getIncluded()); + assertEquals("my-global-tag-file", configuration.getGlobalTagFile()); + assertEquals("my-global-tags", configuration.getGlobalTags()); + assertEquals("my-global-job-tags", configuration.getGlobalJobTags()); + assertEquals("my-include-events", configuration.getIncludeEvents()); + assertEquals("my-exclude-evens", configuration.getExcludeEvents()); + assertFalse(configuration.isEmitSecurityEvents()); + assertFalse(configuration.isEmitSystemEvents()); + assertTrue(configuration.isCollectBuildLogs()); + assertTrue(configuration.getEnableCiVisibility()); + assertTrue(configuration.isRefreshDogstatsdClient()); + assertFalse(configuration.isCacheBuildRuns()); + assertTrue(configuration.isUseAwsInstanceHostname()); } - @Rule public JenkinsConfiguredWithCodeRule r = new JenkinsConfiguredWithCodeRule(); + @Test + public void canLoadGlobalConfigurationReportingToAgent() { + DatadogGlobalConfiguration configuration = parseConfigurationFromResource("globalConfigurationAgent.xml"); + + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogAgentConfiguration); + DatadogAgentConfiguration datadogAgentConfiguration = (DatadogAgentConfiguration) configuration.getDatadogClientConfiguration(); + assertEquals("my-agent-host", datadogAgentConfiguration.getAgentHost()); + assertEquals((Integer) 9876, datadogAgentConfiguration.getAgentPort()); + assertEquals((Integer) 8765, datadogAgentConfiguration.getAgentLogCollectionPort()); + assertEquals((Integer) 7654, datadogAgentConfiguration.getAgentTraceCollectionPort()); + + assertEquals("my-jenkins-service-name", configuration.getCiInstanceName()); + assertEquals("my-hostname", configuration.getHostname()); + assertEquals("my-blacklist", configuration.getExcluded()); + assertEquals("my-whitelist", configuration.getIncluded()); + assertEquals("my-global-tag-file", configuration.getGlobalTagFile()); + assertEquals("my-global-tags", configuration.getGlobalTags()); + assertEquals("my-global-job-tags", configuration.getGlobalJobTags()); + assertEquals("my-include-events", configuration.getIncludeEvents()); + assertEquals("my-exclude-evens", configuration.getExcludeEvents()); + assertTrue(configuration.isEmitSecurityEvents()); + assertTrue(configuration.isEmitSystemEvents()); + assertTrue(configuration.isCollectBuildLogs()); + assertTrue(configuration.getEnableCiVisibility()); + assertTrue(configuration.isRefreshDogstatsdClient()); + assertTrue(configuration.isCacheBuildRuns()); + assertTrue(configuration.isUseAwsInstanceHostname()); + } @Test - @ConfiguredWithCode("test-config.yml") - public void TestConfigurationAsCodeCompatibility() throws Exception { - DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor(); - Assert.assertTrue(cfg.isEmitConfigChangeEvents()); + public void canLoadGlobalConfigurationFromLegacyFormat() { + DatadogGlobalConfiguration configuration = parseConfigurationFromResource("globalConfigurationLegacyFormat.xml"); + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogApiConfiguration); + DatadogApiConfiguration datadogClientConfiguration = (DatadogApiConfiguration) configuration.getDatadogClientConfiguration(); + + DatadogIntake intake = datadogClientConfiguration.getIntake(); + assertEquals("my-target-api-url", intake.getApiUrl()); + assertEquals("my-target-log-intake-url", intake.getLogsUrl()); + assertEquals("my-target-webhook-intake-url", intake.getWebhooksUrl()); + + assertTrue(datadogClientConfiguration.getApiKey() instanceof DatadogTextApiKey); + DatadogTextApiKey textApiKey = (DatadogTextApiKey) datadogClientConfiguration.getApiKey(); + assertEquals("{AQAAABAAAAAwB78atDHwzelG9W0Fw5FRNq0ZUaLh1BwpQPKhvs2u84lxkRPDqeUNoVZel+MKwfyTOjuetnituHYGMdvE9bc3kg==}", Secret.toString(textApiKey.getKey())); + + assertEquals("my-jenkins-service-name", configuration.getCiInstanceName()); + assertEquals("my-hostname", configuration.getHostname()); + assertEquals("my-blacklist", configuration.getExcluded()); + assertEquals("my-whitelist", configuration.getIncluded()); + assertEquals("my-global-tag-file", configuration.getGlobalTagFile()); + assertEquals("my-global-tags", configuration.getGlobalTags()); + assertEquals("my-global-job-tags", configuration.getGlobalJobTags()); + assertEquals("my-include-events", configuration.getIncludeEvents()); + assertEquals("my-exclude-evens", configuration.getExcludeEvents()); + assertFalse(configuration.isEmitSecurityEvents()); + assertFalse(configuration.isEmitSystemEvents()); + assertTrue(configuration.isCollectBuildLogs()); + assertTrue(configuration.getEnableCiVisibility()); + assertTrue(configuration.isRefreshDogstatsdClient()); + assertFalse(configuration.isCacheBuildRuns()); + assertTrue(configuration.isUseAwsInstanceHostname()); } @Test - public void testCanGetCredentialFromId() throws IOException { - CredentialsStore credentialsStore = CredentialsProvider.lookupStores(jenkinsRule).iterator().next(); + public void canLoadGlobalConfigurationFromLegacyFormatWithCredentialsApiKey() { + DatadogGlobalConfiguration configuration = parseConfigurationFromResource("globalConfigurationLegacyFormatCredentialsKey.xml"); + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogApiConfiguration); + DatadogApiConfiguration datadogClientConfiguration = (DatadogApiConfiguration) configuration.getDatadogClientConfiguration(); + + DatadogIntake intake = datadogClientConfiguration.getIntake(); + assertEquals("my-target-api-url", intake.getApiUrl()); + assertEquals("my-target-log-intake-url", intake.getLogsUrl()); + assertEquals("my-target-webhook-intake-url", intake.getWebhooksUrl()); - StringCredentials credential1 = new StringCredentialsImpl(CredentialsScope.SYSTEM, "string-cred-id", "description", Secret.fromString("api-key")); - credentialsStore.addCredentials(Domain.global(), credential1); - DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor(); - Assert.assertTrue(cfg.getCredentialFromId("string-cred-id").equals(credential1)); + assertTrue(datadogClientConfiguration.getApiKey() instanceof DatadogCredentialsApiKey); + DatadogCredentialsApiKey credentialsApiKey = (DatadogCredentialsApiKey) datadogClientConfiguration.getApiKey(); + assertEquals("target-credentials-api-key", credentialsApiKey.getCredentialsId()); - StringCredentials credential2 = new StringCredentialsImpl(CredentialsScope.SYSTEM, "string-cred-id2", "description", Secret.fromString("api-key")); - credentialsStore.addCredentials(Domain.global(), credential2); - Assert.assertTrue(cfg.getCredentialFromId("string-cred-id").equals(credential1)); - Assert.assertTrue(cfg.getCredentialFromId("string-cred-id2").equals(credential2)); + assertEquals("my-jenkins-service-name", configuration.getCiInstanceName()); + assertEquals("my-hostname", configuration.getHostname()); + assertEquals("my-blacklist", configuration.getExcluded()); + assertEquals("my-whitelist", configuration.getIncluded()); + assertEquals("my-global-tag-file", configuration.getGlobalTagFile()); + assertEquals("my-global-tags", configuration.getGlobalTags()); + assertEquals("my-global-job-tags", configuration.getGlobalJobTags()); + assertEquals("my-include-events", configuration.getIncludeEvents()); + assertEquals("my-exclude-evens", configuration.getExcludeEvents()); + assertFalse(configuration.isEmitSecurityEvents()); + assertFalse(configuration.isEmitSystemEvents()); + assertTrue(configuration.isCollectBuildLogs()); + assertTrue(configuration.getEnableCiVisibility()); + assertTrue(configuration.isRefreshDogstatsdClient()); + assertFalse(configuration.isCacheBuildRuns()); + assertTrue(configuration.isUseAwsInstanceHostname()); + } + + @Test + public void canLoadGlobalConfigurationFromLegacyFormatReportingToAgent() { + DatadogGlobalConfiguration configuration = parseConfigurationFromResource("globalConfigurationLegacyFormatAgent.xml"); - Assert.assertNull(cfg.getCredentialFromId("string-cred-id-fake")); + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogAgentConfiguration); + DatadogAgentConfiguration datadogAgentConfiguration = (DatadogAgentConfiguration) configuration.getDatadogClientConfiguration(); + assertEquals("datadog", datadogAgentConfiguration.getAgentHost()); + assertEquals((Integer) 1357, datadogAgentConfiguration.getAgentPort()); + assertEquals((Integer) 2468, datadogAgentConfiguration.getAgentLogCollectionPort()); + assertEquals((Integer) 3579, datadogAgentConfiguration.getAgentTraceCollectionPort()); + assertEquals("my-jenkins-service-name", configuration.getCiInstanceName()); + assertEquals("my-hostname", configuration.getHostname()); + assertEquals("my-blacklist", configuration.getExcluded()); + assertEquals("my-whitelist", configuration.getIncluded()); + assertEquals("my-global-tag-file", configuration.getGlobalTagFile()); + assertEquals("my-global-tags", configuration.getGlobalTags()); + assertEquals("my-global-job-tags", configuration.getGlobalJobTags()); + assertEquals("my-include-events", configuration.getIncludeEvents()); + assertEquals("my-exclude-evens", configuration.getExcludeEvents()); + assertTrue(configuration.isEmitSecurityEvents()); + assertTrue(configuration.isEmitSystemEvents()); + assertFalse(configuration.isCollectBuildLogs()); + assertFalse(configuration.getEnableCiVisibility()); + assertFalse(configuration.isRefreshDogstatsdClient()); + assertTrue(configuration.isCacheBuildRuns()); + assertFalse(configuration.isUseAwsInstanceHostname()); } @Test - public void testFindSecret() throws IOException { - CredentialsStore credentialsStore = CredentialsProvider.lookupStores(jenkinsRule).iterator().next(); + public void canLoadGlobalConfigurationFromLegacyFormatWithEmptyCredentialsKey() { + DatadogGlobalConfiguration configuration = parseConfigurationFromResource("globalConfigurationLegacyFormatCredentialsKeyEmpty.xml"); + assertTrue(configuration.getDatadogClientConfiguration() instanceof DatadogApiConfiguration); + DatadogApiConfiguration datadogClientConfiguration = (DatadogApiConfiguration) configuration.getDatadogClientConfiguration(); + + DatadogIntake intake = datadogClientConfiguration.getIntake(); + assertEquals("my-target-api-url", intake.getApiUrl()); + assertEquals("my-target-log-intake-url", intake.getLogsUrl()); + assertEquals("my-target-webhook-intake-url", intake.getWebhooksUrl()); - StringCredentials credential1 = new StringCredentialsImpl(CredentialsScope.SYSTEM, "string-cred-id", "description", Secret.fromString("api-key")); - credentialsStore.addCredentials(Domain.global(), credential1); + assertTrue(datadogClientConfiguration.getApiKey() instanceof DatadogTextApiKey); + DatadogTextApiKey textApiKey = (DatadogTextApiKey) datadogClientConfiguration.getApiKey(); + assertEquals("{AQAAABAAAAAwB78atDHwzelG9W0Fw5FRNq0ZUaLh1BwpQPKhvs2u84lxkRPDqeUNoVZel+MKwfyTOjuetnituHYGMdvE9bc3kg==}", Secret.toString(textApiKey.getKey())); - StringCredentials credential2 = new StringCredentialsImpl(CredentialsScope.SYSTEM, "string-cred-id2", "description", Secret.fromString("api-key-2")); - credentialsStore.addCredentials(Domain.global(), credential2); + assertEquals("my-jenkins-service-name", configuration.getCiInstanceName()); + assertEquals("my-hostname", configuration.getHostname()); + assertEquals("my-blacklist", configuration.getExcluded()); + assertEquals("my-whitelist", configuration.getIncluded()); + assertEquals("my-global-tag-file", configuration.getGlobalTagFile()); + assertEquals("my-global-tags", configuration.getGlobalTags()); + assertEquals("my-global-job-tags", configuration.getGlobalJobTags()); + assertEquals("my-include-events", configuration.getIncludeEvents()); + assertEquals("my-exclude-evens", configuration.getExcludeEvents()); + assertFalse(configuration.isEmitSecurityEvents()); + assertFalse(configuration.isEmitSystemEvents()); + assertTrue(configuration.isCollectBuildLogs()); + assertTrue(configuration.getEnableCiVisibility()); + assertTrue(configuration.isRefreshDogstatsdClient()); + assertFalse(configuration.isCacheBuildRuns()); + assertTrue(configuration.isUseAwsInstanceHostname()); + } - DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor(); - Assert.assertTrue(cfg.findSecret("api-test", "string-cred-id").getPlainText().equals("api-key")); - Assert.assertTrue(cfg.findSecret("api-test", "string-cred-id2").getPlainText().equals("api-key-2")); + private static final XStream XSTREAM = new XStream2(XStream2.getDefaultDriver()); - Assert.assertTrue(cfg.findSecret("api-test", "").getPlainText().equals("api-test")); - Assert.assertTrue(cfg.findSecret("api-test", null).getPlainText().equals("api-test")); - Assert.assertTrue(cfg.findSecret("", null).getPlainText().equals("")); + static { + XSTREAM.processAnnotations(new Class[] { DatadogGlobalConfiguration.class, DatadogApiConfiguration.class }); + } - Assert.assertTrue(cfg.findSecret(null, "").getPlainText().equals("")); + private static DatadogGlobalConfiguration parseConfigurationFromResource(String resourceName) { + URL resource = DatadogGlobalConfigurationTest.class.getResource(resourceName); + return (DatadogGlobalConfiguration) XSTREAM.fromXML(resource); } } - diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/apm/TracerInjectionIT.java b/src/test/java/org/datadog/jenkins/plugins/datadog/apm/TracerInjectionIT.java index 4f1ecf4b2..174a7f74d 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/apm/TracerInjectionIT.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/apm/TracerInjectionIT.java @@ -1,5 +1,10 @@ package org.datadog.jenkins.plugins.datadog.apm; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor.getDefaultAgentHost; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor.getDefaultAgentLogCollectionPort; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor.getDefaultAgentPort; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor.getDefaultAgentTraceCollectionPort; + import hudson.FilePath; import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; @@ -20,6 +25,7 @@ import org.apache.commons.io.IOUtils; import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration; import org.datadog.jenkins.plugins.datadog.DatadogUtilities; +import org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.job.WorkflowRun; @@ -45,7 +51,10 @@ public class TracerInjectionIT { @BeforeClass public static void suiteSetUp() throws Exception { DatadogGlobalConfiguration datadogConfig = DatadogUtilities.getDatadogGlobalDescriptor(); - datadogConfig.setReportWith("DSD"); + // There is no agent and the injected tracers will fail to actually send anything, + // but for the purposes of this test this is enough, since it is only asserted that the tracer initialisation was reported in the logs + datadogConfig.setDatadogClientConfiguration(new DatadogAgentConfiguration( + getDefaultAgentHost(), getDefaultAgentPort(), getDefaultAgentLogCollectionPort(), getDefaultAgentTraceCollectionPort())); agentNode = jenkinsRule.createOnlineSlave(); } diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientTest.java deleted file mode 100644 index 2bd1c1865..000000000 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/DatadogClientTest.java +++ /dev/null @@ -1,167 +0,0 @@ -/* -The MIT License - -Copyright (c) 2015-Present Datadog, Inc -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - */ - -package org.datadog.jenkins.plugins.datadog.clients; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration; -import org.datadog.jenkins.plugins.datadog.DatadogUtilities; -import org.datadog.jenkins.plugins.datadog.metrics.MetricKey; -import org.datadog.jenkins.plugins.datadog.metrics.Metrics; -import org.junit.Assert; -import org.junit.ClassRule; -import org.junit.Test; -import org.jvnet.hudson.test.JenkinsRule; -import org.mockito.Mockito; - -public class DatadogClientTest { - - @ClassRule - public static JenkinsRule j = new JenkinsRule(); - - @Test - public void testHttpClientGetInstanceApiKey() { - //validateConfiguration throws an error when given an invalid API key when the urls are valid - Exception exception = Assert.assertThrows(IllegalArgumentException.class, () -> new DatadogApiClient("http", "test", "test", null)); - - String expectedMessage = "Datadog API Key is not set properly"; - String actualMessage = exception.getMessage(); - Assert.assertEquals(actualMessage, expectedMessage); - } - - @Test - public void testHttpClientGetInstanceApiUrl() { - // validateConfiguration throws an error when given an invalid url - Exception exception = Assert.assertThrows(IllegalArgumentException.class, () -> new DatadogApiClient("", null, null, null)); - String expectedMessage = "Datadog Target URL is not set properly"; - String actualMessage = exception.getMessage(); - Assert.assertEquals(actualMessage, expectedMessage); - } - - - @Test - public void testHttpClientGetInstanceEnableValidations() { - Assert.assertThrows(IllegalArgumentException.class, () -> new DatadogApiClient("https", null, null, null)); - } - - @Test - public void testDogstatsDClientGetInstanceTargetPort() { - // validateConfiguration throws an error when given an invalid port - Exception exception = Assert.assertThrows(IllegalArgumentException.class, () -> new DatadogAgentClient("test", null, null, null)); - - String expectedMessage = "Datadog Target Port is not set properly"; - String actualMessage = exception.getMessage(); - Assert.assertEquals(actualMessage, expectedMessage); - } - - @Test - public void testDogstatsDClientGetInstanceEnableValidations() { - // calling getInstance with invalid data returns null - Assert.assertThrows(IllegalArgumentException.class, () -> new DatadogAgentClient("https", null, null, null)); - } - - @Test - public void testEvpProxyEnabled() { - DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor(); - cfg.setEnableCiVisibility(true); - DatadogAgentClient client = Mockito.spy(new DatadogAgentClient("test", 1234, 1235, 1236, 1_000)); - Mockito.doReturn(new HashSet<>(Collections.singletonList("/evp_proxy/v3/"))).when(client).fetchAgentSupportedEndpoints(); - Assert.assertTrue(client.isEvpProxySupported()); - } - - @Test - public void testEvpProxyDisabled() { - DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor(); - cfg.setEnableCiVisibility(true); - DatadogAgentClient client = Mockito.spy(new DatadogAgentClient("test", 1234, 1235, 1236, 1_000)); - Mockito.doReturn(new HashSet()).when(client).fetchAgentSupportedEndpoints(); - Assert.assertFalse(client.isEvpProxySupported()); - } - - @Test - public void testEmptyAgentSupportedEndpointsWithNoAgent() { - DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor(); - cfg.setEnableCiVisibility(true); - DatadogAgentClient client = new DatadogAgentClient("test", 1234, 1235, 1236, 1_000); - Assert.assertTrue(client.fetchAgentSupportedEndpoints().isEmpty()); - } - - @Test - public void testIncrementCountAndFlush() { - Map> tags1 = new HashMap<>(); - DatadogClientStub.addTagToMap(tags1, "tag1", "value"); - DatadogClientStub.addTagToMap(tags1, "tag2", "value"); - Metrics.getInstance().incrementCounter("metric1", "host1", tags1); - Metrics.getInstance().incrementCounter("metric1", "host1", tags1); - - Map> tags2 = new HashMap<>(); - DatadogClientStub.addTagToMap(tags2, "tag1", "value"); - DatadogClientStub.addTagToMap(tags2, "tag2", "value"); - DatadogClientStub.addTagToMap(tags2, "tag3", "value"); - Metrics.getInstance().incrementCounter("metric1", "host1", tags2); - - Metrics.getInstance().incrementCounter("metric1", "host2", tags2); - Metrics.getInstance().incrementCounter("metric1", "host2", tags2); - - Metrics.getInstance().incrementCounter("metric2", "host2", tags2); - - // The following code should be the same as in the flushCounters method - Map counters = Metrics.getInstance().getAndResetCounters(); - - // Check counter is reset as expected - Map countersEmpty = Metrics.getInstance().getAndResetCounters(); - Assert.assertEquals("size = " + countersEmpty.size(), 0, countersEmpty.size()); - - // Check that metrics to submit are correct - boolean check1 = false, check2 = false, check3 = false, check4 = false; - Assert.assertEquals("counters = " + counters.size(), 4, counters.size()); - for (MetricKey counterMetric: counters.keySet()) { - int count = counters.get(counterMetric); - if (counterMetric.getMetricName().equals("metric1") && counterMetric.getHostname().equals("host1") - && counterMetric.getTags().size() == 2) { - Assert.assertEquals("count = " + count, 2, count); - check1 = true; - } else if (counterMetric.getMetricName().equals("metric1") && counterMetric.getHostname().equals("host1") - && counterMetric.getTags().size() == 3) { - Assert.assertEquals("count = " + count, 1, count); - check2 = true; - } else if (counterMetric.getMetricName().equals("metric1") && counterMetric.getHostname().equals("host2") - && counterMetric.getTags().size() == 3) { - Assert.assertEquals("count = " + count, 2, count); - check3 = true; - } else if (counterMetric.getMetricName().equals("metric2") && counterMetric.getHostname().equals("host2") - && counterMetric.getTags().size() == 3) { - Assert.assertEquals("count = " + count, 1, count); - check4 = true; - } - } - Assert.assertTrue(check1 + " " + check2 + " " + check3 + " " + check4, - check1 && check2 && check3 && check4); - } -} diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/MetricsTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/MetricsTest.java index f85eb80a8..fcd379bc7 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/clients/MetricsTest.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/clients/MetricsTest.java @@ -16,6 +16,59 @@ public class MetricsTest { + @Test + public void testIncrementCountAndFlush() { + Map> tags1 = new HashMap<>(); + DatadogClientStub.addTagToMap(tags1, "tag1", "value"); + DatadogClientStub.addTagToMap(tags1, "tag2", "value"); + Metrics.getInstance().incrementCounter("metric1", "host1", tags1); + Metrics.getInstance().incrementCounter("metric1", "host1", tags1); + + Map> tags2 = new HashMap<>(); + DatadogClientStub.addTagToMap(tags2, "tag1", "value"); + DatadogClientStub.addTagToMap(tags2, "tag2", "value"); + DatadogClientStub.addTagToMap(tags2, "tag3", "value"); + Metrics.getInstance().incrementCounter("metric1", "host1", tags2); + + Metrics.getInstance().incrementCounter("metric1", "host2", tags2); + Metrics.getInstance().incrementCounter("metric1", "host2", tags2); + + Metrics.getInstance().incrementCounter("metric2", "host2", tags2); + + // The following code should be the same as in the flushCounters method + Map counters = Metrics.getInstance().getAndResetCounters(); + + // Check counter is reset as expected + Map countersEmpty = Metrics.getInstance().getAndResetCounters(); + Assert.assertEquals("size = " + countersEmpty.size(), 0, countersEmpty.size()); + + // Check that metrics to submit are correct + boolean check1 = false, check2 = false, check3 = false, check4 = false; + Assert.assertEquals("counters = " + counters.size(), 4, counters.size()); + for (MetricKey counterMetric: counters.keySet()) { + int count = counters.get(counterMetric); + if (counterMetric.getMetricName().equals("metric1") && counterMetric.getHostname().equals("host1") + && counterMetric.getTags().size() == 2) { + Assert.assertEquals("count = " + count, 2, count); + check1 = true; + } else if (counterMetric.getMetricName().equals("metric1") && counterMetric.getHostname().equals("host1") + && counterMetric.getTags().size() == 3) { + Assert.assertEquals("count = " + count, 1, count); + check2 = true; + } else if (counterMetric.getMetricName().equals("metric1") && counterMetric.getHostname().equals("host2") + && counterMetric.getTags().size() == 3) { + Assert.assertEquals("count = " + count, 2, count); + check3 = true; + } else if (counterMetric.getMetricName().equals("metric2") && counterMetric.getHostname().equals("host2") + && counterMetric.getTags().size() == 3) { + Assert.assertEquals("count = " + count, 1, count); + check4 = true; + } + } + Assert.assertTrue(check1 + " " + check2 + " " + check3 + " " + check4, + check1 && check2 && check3 && check4); + } + @Test public void testIncrementCountAndFlushThreadedEnv() { ExecutorService executor = Executors.newFixedThreadPool(2); diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfigurationTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfigurationTest.java new file mode 100644 index 000000000..6da02c0cf --- /dev/null +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/configuration/DatadogAgentConfigurationTest.java @@ -0,0 +1,101 @@ +package org.datadog.jenkins.plugins.datadog.configuration; + +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DD_AGENT_HOST; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DD_AGENT_PORT; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DD_TRACE_AGENT_PORT; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DD_TRACE_AGENT_URL; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DEFAULT_AGENT_HOST_VALUE; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DEFAULT_AGENT_PORT_VALUE; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DEFAULT_LOG_COLLECTION_PORT_VALUE; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.DEFAULT_TRACE_COLLECTION_PORT_VALUE; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.TARGET_HOST_PROPERTY; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.TARGET_LOG_COLLECTION_PORT_PROPERTY; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.TARGET_PORT_PROPERTY; +import static org.datadog.jenkins.plugins.datadog.configuration.DatadogAgentConfiguration.TARGET_TRACE_COLLECTION_PORT_PROPERTY; +import static org.junit.Assert.assertEquals; + +import com.github.stefanbirkner.systemlambda.SystemLambda; +import org.junit.Test; + +public class DatadogAgentConfigurationTest { + + @Test + public void testGetDefaultAgentHost() throws Exception { + // check default value + assertEquals(DEFAULT_AGENT_HOST_VALUE, DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor.getDefaultAgentHost()); + // check environment property overrides + assertEquals("target-host", SystemLambda + .withEnvironmentVariable(TARGET_HOST_PROPERTY, "target-host") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentHost)); + assertEquals("agent-host", SystemLambda + .withEnvironmentVariable(DD_TRACE_AGENT_URL, "http://agent-host:1234") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentHost)); + assertEquals("dd-agent-host", SystemLambda + .withEnvironmentVariable(DD_AGENT_HOST, "dd-agent-host") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentHost)); + // check environment property priorities + assertEquals("target-host", SystemLambda + .withEnvironmentVariable(TARGET_HOST_PROPERTY, "target-host") + .and(DD_TRACE_AGENT_URL, "http://agent-host:1234") + .and(DD_AGENT_HOST, "dd-agent-host") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentHost)); + assertEquals("agent-host", SystemLambda + .withEnvironmentVariable(DD_TRACE_AGENT_URL, "http://agent-host:1234") + .and(DD_AGENT_HOST, "dd-agent-host") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentHost)); + } + + @Test + public void testGetDefaultAgentPort() throws Exception { + // check default value + assertEquals(DEFAULT_AGENT_PORT_VALUE, DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor.getDefaultAgentPort()); + // check environment property overrides + assertEquals((Integer) 1234, SystemLambda + .withEnvironmentVariable(TARGET_PORT_PROPERTY, "1234") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentPort)); + assertEquals((Integer) 5678, SystemLambda + .withEnvironmentVariable(DD_AGENT_PORT, "5678") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentPort)); + // check environment property priorities + assertEquals((Integer) 1234, SystemLambda + .withEnvironmentVariable(TARGET_PORT_PROPERTY, "1234") + .and(DD_AGENT_PORT, "5678") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentPort)); + } + + @Test + public void testGetDefaultAgentLogCollectionPort() throws Exception { + // check default value + assertEquals(DEFAULT_LOG_COLLECTION_PORT_VALUE, DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor.getDefaultAgentLogCollectionPort()); + // check environment property overrides + assertEquals((Integer) 1234, SystemLambda + .withEnvironmentVariable(TARGET_LOG_COLLECTION_PORT_PROPERTY, "1234") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentLogCollectionPort)); + } + + @Test + public void testGetDefaultAgentTraceCollectionPort() throws Exception { + // check default value + assertEquals(DEFAULT_TRACE_COLLECTION_PORT_VALUE, DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor.getDefaultAgentTraceCollectionPort()); + // check environment property overrides + assertEquals((Integer) 1234, SystemLambda + .withEnvironmentVariable(TARGET_TRACE_COLLECTION_PORT_PROPERTY, "1234") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentTraceCollectionPort)); + assertEquals((Integer) 2468, SystemLambda + .withEnvironmentVariable(DD_TRACE_AGENT_URL, "http://agent-host:2468") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentTraceCollectionPort)); + assertEquals((Integer) 5678, SystemLambda + .withEnvironmentVariable(DD_TRACE_AGENT_PORT, "5678") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentTraceCollectionPort)); + // check environment property priorities + assertEquals((Integer) 1234, SystemLambda + .withEnvironmentVariable(TARGET_TRACE_COLLECTION_PORT_PROPERTY, "1234") + .and(DD_TRACE_AGENT_URL, "http://agent-host:2468") + .and(DD_TRACE_AGENT_PORT, "5678") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentTraceCollectionPort)); + assertEquals((Integer) 2468, SystemLambda + .withEnvironmentVariable(DD_TRACE_AGENT_URL, "http://agent-host:2468") + .and(DD_TRACE_AGENT_PORT, "5678") + .execute(DatadogAgentConfiguration.DatadogAgentConfigurationDescriptor::getDefaultAgentTraceCollectionPort)); + } +} \ No newline at end of file diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeUrlsTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeUrlsTest.java new file mode 100644 index 000000000..ede3d8fbb --- /dev/null +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/configuration/api/intake/DatadogIntakeUrlsTest.java @@ -0,0 +1,53 @@ +package org.datadog.jenkins.plugins.datadog.configuration.api.intake; + +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls.DEFAULT_API_URL_VALUE; +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls.DEFAULT_LOG_INTAKE_URL_VALUE; +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls.DEFAULT_WEBHOOK_INTAKE_URL_VALUE; +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls.TARGET_API_URL_PROPERTY; +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls.TARGET_LOG_INTAKE_URL_PROPERTY; +import static org.datadog.jenkins.plugins.datadog.configuration.api.intake.DatadogIntakeUrls.TARGET_WEBHOOK_INTAKE_URL_PROPERTY; +import static org.junit.Assert.assertEquals; + +import com.github.stefanbirkner.systemlambda.SystemLambda; +import org.junit.Test; + +public class DatadogIntakeUrlsTest { + + @Test + public void testGetSiteName() { + for (DatadogSite site : DatadogSite.values()) { + DatadogIntakeUrls intakeUrls = new DatadogIntakeUrls(site.getApiUrl(), site.getLogsUrl(), site.getWebhooksUrl()); + assertEquals(site.getSiteName(), intakeUrls.getSiteName()); + } + } + + @Test + public void testGetDefaultApiUrl() throws Exception { + // check default value + assertEquals(DEFAULT_API_URL_VALUE, DatadogIntakeUrls.DatadogIntakeUrlsDescriptor.getDefaultApiUrl()); + // check environment property overrides + assertEquals("overridden-url", SystemLambda + .withEnvironmentVariable(TARGET_API_URL_PROPERTY, "overridden-url") + .execute(DatadogIntakeUrls.DatadogIntakeUrlsDescriptor::getDefaultApiUrl)); + } + + @Test + public void testGetDefaultLogIntakeUrl() throws Exception { + // check default value + assertEquals(DEFAULT_LOG_INTAKE_URL_VALUE, DatadogIntakeUrls.DatadogIntakeUrlsDescriptor.getDefaultLogsUrl()); + // check environment property overrides + assertEquals("overridden-url", SystemLambda + .withEnvironmentVariable(TARGET_LOG_INTAKE_URL_PROPERTY, "overridden-url") + .execute(DatadogIntakeUrls.DatadogIntakeUrlsDescriptor::getDefaultLogsUrl)); + } + + @Test + public void testGetDefaultWebhookIntakeUrl() throws Exception { + // check default value + assertEquals(DEFAULT_WEBHOOK_INTAKE_URL_VALUE, DatadogIntakeUrls.DatadogIntakeUrlsDescriptor.getDefaultWebhooksUrl()); + // check environment property overrides + assertEquals("overridden-url", SystemLambda + .withEnvironmentVariable(TARGET_WEBHOOK_INTAKE_URL_PROPERTY, "overridden-url") + .execute(DatadogIntakeUrls.DatadogIntakeUrlsDescriptor::getDefaultWebhooksUrl)); + } +} \ No newline at end of file diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogCredentialsApiKeyTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogCredentialsApiKeyTest.java new file mode 100644 index 000000000..a7026e02e --- /dev/null +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogCredentialsApiKeyTest.java @@ -0,0 +1,43 @@ +package org.datadog.jenkins.plugins.datadog.configuration.api.key; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsScope; +import com.cloudbees.plugins.credentials.CredentialsStore; +import com.cloudbees.plugins.credentials.domains.Domain; +import hudson.util.Secret; +import org.jenkinsci.plugins.plaincredentials.StringCredentials; +import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl; +import org.junit.ClassRule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +public class DatadogCredentialsApiKeyTest { + + @ClassRule + public static JenkinsRule jenkinsRule; + + static { + jenkinsRule = new JenkinsRule(); + jenkinsRule.timeout = 300; // default value of 180 is too small for all the test cases in this class + } + + @Test + public void testCanGetCredentialFromId() throws Exception { + CredentialsStore credentialsStore = CredentialsProvider.lookupStores(jenkinsRule).iterator().next(); + + StringCredentials credential1 = new StringCredentialsImpl(CredentialsScope.SYSTEM, "string-cred-id", "description", Secret.fromString("api-key")); + credentialsStore.addCredentials(Domain.global(), credential1); + assertEquals(credential1, DatadogCredentialsApiKey.getCredentialFromId("string-cred-id")); + + StringCredentials credential2 = new StringCredentialsImpl(CredentialsScope.SYSTEM, "string-cred-id2", "description", Secret.fromString("api-key")); + credentialsStore.addCredentials(Domain.global(), credential2); + assertEquals(credential1, DatadogCredentialsApiKey.getCredentialFromId("string-cred-id")); + assertEquals(credential2, DatadogCredentialsApiKey.getCredentialFromId("string-cred-id2")); + + assertNull(DatadogCredentialsApiKey.getCredentialFromId("string-cred-id-fake")); + } + +} diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogTextApiKeyTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogTextApiKeyTest.java new file mode 100644 index 000000000..74c2ccdc9 --- /dev/null +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/configuration/api/key/DatadogTextApiKeyTest.java @@ -0,0 +1,22 @@ +package org.datadog.jenkins.plugins.datadog.configuration.api.key; + +import static org.datadog.jenkins.plugins.datadog.configuration.api.key.DatadogTextApiKey.TARGET_API_KEY_PROPERTY; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.github.stefanbirkner.systemlambda.SystemLambda; +import hudson.util.Secret; +import org.junit.Test; + +public class DatadogTextApiKeyTest { + + @Test + public void testGetDefaultApiKey() throws Exception { + // check default value + assertNull(DatadogTextApiKey.DatadogTextApiKeyDescriptor.getDefaultKey()); + // check environment property overrides + assertEquals(Secret.fromString("an-api-key"), SystemLambda + .withEnvironmentVariable(TARGET_API_KEY_PROPERTY, "an-api-key") + .execute(DatadogTextApiKey.DatadogTextApiKeyDescriptor::getDefaultKey)); + } +} diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/util/config/DatadogAgentConfigurationTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/util/config/DatadogAgentConfigurationTest.java deleted file mode 100644 index 6569e7fa3..000000000 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/util/config/DatadogAgentConfigurationTest.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.datadog.jenkins.plugins.datadog.util.config; - -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.DD_AGENT_HOST; -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.DD_AGENT_PORT; -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.DD_TRACE_AGENT_PORT; -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.DD_TRACE_AGENT_URL; -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.TARGET_HOST_PROPERTY; -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.TARGET_PORT_PROPERTY; -import static org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration.TARGET_TRACE_COLLECTION_PORT_PROPERTY; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import org.junit.Test; - -import java.util.HashMap; -import java.util.Map; - -public class DatadogAgentConfigurationTest { - - @Test - public void testResolveEmptyConfig() { - final DatadogAgentConfiguration config = DatadogAgentConfiguration.resolve(new HashMap<>()); - assertNull(config.getHost()); - assertNull(config.getPort()); - assertNull(config.getTracesPort()); - } - - @Test - public void testResolveGenericEnvVars() { - final Map envVars = new HashMap<>(); - envVars.put(DD_AGENT_HOST, "localhost"); - envVars.put(DD_AGENT_PORT, "8125"); - envVars.put(DD_TRACE_AGENT_PORT, "8126"); - final DatadogAgentConfiguration config = DatadogAgentConfiguration.resolve(envVars); - assertEquals("localhost", config.getHost()); - assertEquals(Integer.valueOf(8125), config.getPort()); - assertEquals(Integer.valueOf(8126), config.getTracesPort()); - } - - @Test - public void testResolveUsingTraceUrl() { - final Map usingTraceUrl = new HashMap<>(); - usingTraceUrl.put(DD_TRACE_AGENT_URL, "http://localhost:8126"); - usingTraceUrl.put(DD_AGENT_PORT, "8125"); - - final DatadogAgentConfiguration config = DatadogAgentConfiguration.resolve(usingTraceUrl); - assertEquals("localhost", config.getHost()); - assertEquals(Integer.valueOf(8125), config.getPort()); - assertEquals(Integer.valueOf(8126), config.getTracesPort()); - } - - @Test - public void testResolveSpecificEnvVars() { - final Map envVars = new HashMap<>(); - envVars.put(TARGET_HOST_PROPERTY, "localhost"); - envVars.put(TARGET_PORT_PROPERTY, "8125"); - envVars.put(TARGET_TRACE_COLLECTION_PORT_PROPERTY, "8126"); - - final DatadogAgentConfiguration config = DatadogAgentConfiguration.resolve(envVars); - assertEquals("localhost", config.getHost()); - assertEquals(Integer.valueOf(8125), config.getPort()); - assertEquals(Integer.valueOf(8126), config.getTracesPort()); - } - - @Test - public void testPrevalenceSpecificEnvVars() { - final Map envVars = new HashMap<>(); - envVars.put(DD_AGENT_HOST, "some-host"); - envVars.put(DD_AGENT_PORT, "1111"); - envVars.put(DD_TRACE_AGENT_PORT, "1112"); - envVars.put(TARGET_HOST_PROPERTY, "localhost"); - envVars.put(TARGET_PORT_PROPERTY, "8125"); - envVars.put(TARGET_TRACE_COLLECTION_PORT_PROPERTY, "8126"); - - final DatadogAgentConfiguration config = DatadogAgentConfiguration.resolve(envVars); - assertEquals("localhost", config.getHost()); - assertEquals(Integer.valueOf(8125), config.getPort()); - assertEquals(Integer.valueOf(8126), config.getTracesPort()); - } - - @Test - public void testPrevalenceTraceUrlEnvVars() { - final Map envVars = new HashMap<>(); - envVars.put(DD_TRACE_AGENT_URL, "http://localhost:8126"); - envVars.put(DD_AGENT_PORT, "8125"); - envVars.put(DD_TRACE_AGENT_PORT, "1112"); - - final DatadogAgentConfiguration config = DatadogAgentConfiguration.resolve(envVars); - assertEquals("localhost", config.getHost()); - assertEquals(Integer.valueOf(8125), config.getPort()); - assertEquals(Integer.valueOf(8126), config.getTracesPort()); - } - -} \ No newline at end of file diff --git a/src/test/java/org/datadog/jenkins/plugins/datadog/util/conversion/DatadogActionConverterTest.java b/src/test/java/org/datadog/jenkins/plugins/datadog/util/conversion/DatadogActionConverterTest.java index 9ae1ad01c..48e0ddfc0 100644 --- a/src/test/java/org/datadog/jenkins/plugins/datadog/util/conversion/DatadogActionConverterTest.java +++ b/src/test/java/org/datadog/jenkins/plugins/datadog/util/conversion/DatadogActionConverterTest.java @@ -1,5 +1,6 @@ package org.datadog.jenkins.plugins.datadog.util.conversion; +import static org.datadog.jenkins.plugins.datadog.util.conversion.VersionedConverter.ignoreOldData; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -73,9 +74,9 @@ private static final class MyClass { private int deserializedWithVersion; } - private static final class MyConverter extends DatadogActionConverter { + private static final class MyConverter extends DatadogConverter { public MyConverter() { - super(new MyVersionedConverter(1), new MyVersionedConverter(2), new MyVersionedConverter(3)); + super(ignoreOldData(), new MyVersionedConverter(1), new MyVersionedConverter(2), new MyVersionedConverter(3)); } } diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfiguration.xml b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfiguration.xml new file mode 100644 index 000000000..04d843885 --- /dev/null +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfiguration.xml @@ -0,0 +1,30 @@ + + + + + https://my-api-url.com/api/ + https://my-log-intake-url.com/v1/input/ + https://my-webhook-intake-url.com/api/v2/webhook/ + + + {AQAAABAAAAAwB78atDHwzelG9W0Fw5FRNq0ZUaLh1BwpQPKhvs2u84lxkRPDqeUNoVZel+MKwfyTOjuetnituHYGMdvE9bc3kg==} + + + my-jenkins-service-name + my-hostname + my-blacklist + my-whitelist + my-global-tag-file + my-global-tags + my-global-job-tags + my-include-events + my-exclude-evens + false + false + true + true + false + true + false + true + \ No newline at end of file diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationAgent.xml b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationAgent.xml new file mode 100644 index 000000000..d75bfd322 --- /dev/null +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationAgent.xml @@ -0,0 +1,26 @@ + + + + my-agent-host + 9876 + 8765 + 7654 + + my-jenkins-service-name + my-hostname + my-blacklist + my-whitelist + my-global-tag-file + my-global-tags + my-global-job-tags + my-include-events + my-exclude-evens + true + true + true + true + true + true + true + true + \ No newline at end of file diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationCredentialsKey.xml b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationCredentialsKey.xml new file mode 100644 index 000000000..8b2b22f6e --- /dev/null +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationCredentialsKey.xml @@ -0,0 +1,30 @@ + + + + + https://my-api-url.com/api/ + https://my-log-intake-url.com/v1/input/ + https://my-webhook-intake-url.com/api/v2/webhook/ + + + my-api-key-credentials-id + + + my-jenkins-service-name + my-hostname + my-blacklist + my-whitelist + my-global-tag-file + my-global-tags + my-global-job-tags + my-include-events + my-exclude-evens + false + false + true + true + false + true + false + true + \ No newline at end of file diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationLegacyFormat.xml b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationLegacyFormat.xml new file mode 100644 index 000000000..6fd220dbd --- /dev/null +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationLegacyFormat.xml @@ -0,0 +1,31 @@ + + + HTTP + my-target-api-url + my-target-log-intake-url + my-target-webhook-intake-url + {AQAAABAAAAAwB78atDHwzelG9W0Fw5FRNq0ZUaLh1BwpQPKhvs2u84lxkRPDqeUNoVZel+MKwfyTOjuetnituHYGMdvE9bc3kg==} + {AQAAABAAAAAwB78atDHwzelG9W0Fw5FRNq0ZUaLh1BwpQPKhvs2u84lxkRPDqeUNoVZel+MKwfyTOjuetnituHYGMdvE9bc3kg==} + datadog + 1357 + 2468 + 3579 + my-jenkins-service-name + my-hostname + my-blacklist + my-whitelist + my-global-tag-file + my-global-tags + my-global-job-tags + my-include-events + my-exclude-evens + false + false + true + true + true + false + true + false + true + \ No newline at end of file diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationLegacyFormatAgent.xml b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationLegacyFormatAgent.xml new file mode 100644 index 000000000..ec7e20d3a --- /dev/null +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationLegacyFormatAgent.xml @@ -0,0 +1,32 @@ + + + DSD + my-target-api-url + my-target-log-intake-url + my-target-webhook-intake-url + {AQAAABAAAAAwB78atDHwzelG9W0Fw5FRNq0ZUaLh1BwpQPKhvs2u84lxkRPDqeUNoVZel+MKwfyTOjuetnituHYGMdvE9bc3kg==} + target-credentials-api-key + {AQAAABAAAAAwB78atDHwzelG9W0Fw5FRNq0ZUaLh1BwpQPKhvs2u84lxkRPDqeUNoVZel+MKwfyTOjuetnituHYGMdvE9bc3kg==} + datadog + 1357 + 2468 + 3579 + my-jenkins-service-name + my-hostname + my-blacklist + my-whitelist + my-global-tag-file + my-global-tags + my-global-job-tags + my-include-events + my-exclude-evens + true + true + false + false + false + true + false + true + false + \ No newline at end of file diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationLegacyFormatCredentialsKey.xml b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationLegacyFormatCredentialsKey.xml new file mode 100644 index 000000000..785c89c84 --- /dev/null +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationLegacyFormatCredentialsKey.xml @@ -0,0 +1,32 @@ + + + HTTP + my-target-api-url + my-target-log-intake-url + my-target-webhook-intake-url + {AQAAABAAAAAwB78atDHwzelG9W0Fw5FRNq0ZUaLh1BwpQPKhvs2u84lxkRPDqeUNoVZel+MKwfyTOjuetnituHYGMdvE9bc3kg==} + target-credentials-api-key + {AQAAABAAAAAwB78atDHwzelG9W0Fw5FRNq0ZUaLh1BwpQPKhvs2u84lxkRPDqeUNoVZel+MKwfyTOjuetnituHYGMdvE9bc3kg==} + datadog + 1357 + 2468 + 3579 + my-jenkins-service-name + my-hostname + my-blacklist + my-whitelist + my-global-tag-file + my-global-tags + my-global-job-tags + my-include-events + my-exclude-evens + false + false + true + true + true + false + true + false + true + \ No newline at end of file diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationLegacyFormatCredentialsKeyEmpty.xml b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationLegacyFormatCredentialsKeyEmpty.xml new file mode 100644 index 000000000..d5b3437fc --- /dev/null +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationLegacyFormatCredentialsKeyEmpty.xml @@ -0,0 +1,32 @@ + + + HTTP + my-target-api-url + my-target-log-intake-url + my-target-webhook-intake-url + {AQAAABAAAAAwB78atDHwzelG9W0Fw5FRNq0ZUaLh1BwpQPKhvs2u84lxkRPDqeUNoVZel+MKwfyTOjuetnituHYGMdvE9bc3kg==} + + {AQAAABAAAAAwB78atDHwzelG9W0Fw5FRNq0ZUaLh1BwpQPKhvs2u84lxkRPDqeUNoVZel+MKwfyTOjuetnituHYGMdvE9bc3kg==} + datadog + 1357 + 2468 + 3579 + my-jenkins-service-name + my-hostname + my-blacklist + my-whitelist + my-global-tag-file + my-global-tags + my-global-job-tags + my-include-events + my-exclude-evens + false + false + true + true + true + false + true + false + true + \ No newline at end of file diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationSite.xml b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationSite.xml new file mode 100644 index 000000000..d16f6a64f --- /dev/null +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/globalConfigurationSite.xml @@ -0,0 +1,28 @@ + + + + + US3 + + + my-api-key-credentials-id + + + my-jenkins-service-name + my-hostname + my-blacklist + my-whitelist + my-global-tag-file + my-global-tags + my-global-job-tags + my-include-events + my-exclude-evens + false + false + true + true + false + true + false + true + \ No newline at end of file diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config-agent.yml b/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config-agent.yml new file mode 100644 index 000000000..9262bf2e8 --- /dev/null +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config-agent.yml @@ -0,0 +1,27 @@ +jenkins: + systemMessage: "Example of configuring datadog in Jenkins" + +unclassified: + datadogGlobalConfiguration: + datadogClientConfiguration: + datadogAgentConfiguration: + agentHost: 'my-agent-host' + agentPort: 1357 + agentLogCollectionPort: 2468 + agentTraceCollectionPort: 3579 + enableCiVisibility: true + ciInstanceName: 'my-ci-instance-name' + excluded: 'my-excluded' + included: 'my-included' + hostname: 'my-hostname' + globalTagFile: 'my-global-tag-file' + globalTags: 'my-global-tags' + globalJobTags: 'my-global-job-tags' + includeEvents: 'my-include-events' + excludeEvents: 'my-exclude-events' + emitSecurityEvents: false + emitSystemEvents: false + collectBuildLogs: false + refreshDogstatsdClient: false + cacheBuildRuns: false + useAwsInstanceHostname: false diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config-legacy-agent.yml b/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config-legacy-agent.yml new file mode 100644 index 000000000..b40a88907 --- /dev/null +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config-legacy-agent.yml @@ -0,0 +1,27 @@ +jenkins: + systemMessage: "Example of configuring datadog in Jenkins" + +unclassified: + datadogGlobalConfiguration: + reportWith: 'DSD' + targetHost: 'my-target-host' + targetPort: 1357 + targetLogCollectionPort: 2468 + targetTraceCollectionPort: 3579 + collectBuildTraces: false + traceServiceName: 'my-trace-service-name' + blacklist: 'my-blacklist' + whitelist: 'my-whitelist' + hostname: 'my-hostname' + globalTagFile: 'my-global-tag-file' + globalTags: 'my-global-tags' + globalJobTags: 'my-global-job-tags' + includeEvents: 'my-include-events' + excludeEvents: 'my-exclude-events' + emitConfigChangeEvents: false + emitSecurityEvents: true + emitSystemEvents: false + collectBuildLogs: true + refreshDogstatsdClient: true + cacheBuildRuns: false + useAwsInstanceHostname: true diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config-legacy.yml b/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config-legacy.yml new file mode 100644 index 000000000..4d54cb26d --- /dev/null +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config-legacy.yml @@ -0,0 +1,30 @@ +jenkins: + systemMessage: "Example of configuring datadog in Jenkins" + +unclassified: + datadogGlobalConfiguration: + reportWith: 'HTTP' + targetCredentialsApiKey: 'my-target-credentials-api-key' + targetApiURL: 'my-target-api-url' + targetLogIntakeURL: 'my-target-log-intake-url' + targetWebhookIntakeURL: 'my-target-webhook-intake-url' + collectBuildTraces: true + traceServiceName: 'my-trace-service-name' + blacklist: 'my-blacklist' + whitelist: 'my-whitelist' + hostname: 'my-hostname' + globalTagFile: 'my-global-tag-file' + globalTags: 'my-global-tags' + globalJobTags: 'my-global-job-tags' + includeEvents: 'my-include-events' + excludeEvents: 'my-exclude-events' + emitConfigChangeEvents: true + emitSecurityEvents: false + emitSystemEvents: true + collectBuildLogs: false + refreshDogstatsdClient: false + cacheBuildRuns: true + useAwsInstanceHostname: false + + + diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config-site.yml b/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config-site.yml new file mode 100644 index 000000000..067fbbe2d --- /dev/null +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config-site.yml @@ -0,0 +1,29 @@ +jenkins: + systemMessage: "Example of configuring datadog in Jenkins" + +unclassified: + datadogGlobalConfiguration: + datadogClientConfiguration: + datadogApiConfiguration: + intake: + datadogIntakeSite: + site: 'US3' + apiKey: + datadogCredentialsApiKey: + credentialsId: 'my-api-key-credentials-id' + enableCiVisibility: true + ciInstanceName: 'my-ci-instance-name' + excluded: 'my-excluded' + included: 'my-included' + hostname: 'my-hostname' + globalTagFile: 'my-global-tag-file' + globalTags: 'my-global-tags' + globalJobTags: 'my-global-job-tags' + includeEvents: 'my-include-events' + excludeEvents: 'my-exclude-events' + emitSecurityEvents: true + emitSystemEvents: true + collectBuildLogs: true + refreshDogstatsdClient: true + cacheBuildRuns: true + useAwsInstanceHostname: true diff --git a/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config.yml b/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config.yml index c27db3948..f399472c6 100644 --- a/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config.yml +++ b/src/test/resources/org/datadog/jenkins/plugins/datadog/test-config.yml @@ -2,7 +2,30 @@ jenkins: systemMessage: "Example of configuring datadog in Jenkins" unclassified: - datadogGlobalConfiguration: - reportWith: 'HTTP' - targetApiKey: 'test' - emitConfigChangeEvents: true + datadogGlobalConfiguration: + datadogClientConfiguration: + datadogApiConfiguration: + intake: + datadogIntakeUrls: + apiUrl: 'my-api-url' + logsUrl: 'my-log-intake-url' + webhooksUrl: 'my-webhook-intake-url' + apiKey: + datadogCredentialsApiKey: + credentialsId: 'my-api-key-credentials-id' + enableCiVisibility: true + ciInstanceName: 'my-ci-instance-name' + excluded: 'my-excluded' + included: 'my-included' + hostname: 'my-hostname' + globalTagFile: 'my-global-tag-file' + globalTags: 'my-global-tags' + globalJobTags: 'my-global-job-tags' + includeEvents: 'my-include-events' + excludeEvents: 'my-exclude-events' + emitSecurityEvents: true + emitSystemEvents: true + collectBuildLogs: true + refreshDogstatsdClient: true + cacheBuildRuns: true + useAwsInstanceHostname: true