Skip to content

Commit

Permalink
Support declarative Test Visibility configuration in pipeline scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
nikita-tkachenko-datadog committed Jan 4, 2024
1 parent 0327efa commit fa17562
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 19 deletions.
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,47 @@ The plugin can automatically configure Datadog <a target="_blank" href="https://

Before enabling Test Visibility, be sure to properly configure the plugin to submit data to Datadog.

To enable Test Visibility, go to the `Configure` page of the job or pipeline whose tests need to be traced, tick `Enable Datadog Test Visibility` checkbox in the `General` section and save your changes.
There are two options to enable automatic Test Visibility configuration:

1. Using Jenkins UI: go to the `Configure` page of the job or pipeline whose tests need to be traced, tick `Enable Datadog Test Visibility` checkbox in the `General` section and save your changes. This option is unavailable if you are using Multibranch Pipelines, Organization Folders, or other types of pipelines that are configured entirely with `Jenkinsfile`.
2. Using `datadog` pipeline step:

In declarative pipelines, add the step to a top-level `options` block like so:

```groovy
pipeline {
agent any
options {
datadog(testVisibility: [
enabled: true,
serviceName: "my-service", // the name of service or library being tested
languages: ["JAVA"], // languages that should be instrumented (available options are "JAVA", "JAVASCRIPT", "PYTHON")
additionalVariables: ["my-var": "value"] // additional tracer configuration settings (optional)
])
}
stages {
stage('Example') {
steps {
echo "Hello world."
}
}
}
}
```

In scripted pipeline, wrap the relevant section with the datadog step like so:

```groovy
datadog(testVisibility: [ enabled: true, serviceName: "my-service", languages: ["JAVA"], additionalVariables: [:] ]) {
node {
stage('Example') {
echo "Hello world."
}
}
}
```

The other datadog settings, such as `collectLogs` or `tags` can be added alongside the `testVisibility` block.

Please bear in mind that Test Visibility is a separate Datadog product that is billed separately.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.datadog.jenkins.plugins.datadog.steps;

import hudson.Extension;
import hudson.model.Job;
import hudson.model.Run;
import hudson.model.TaskListener;
import java.io.PrintStream;
Expand All @@ -13,6 +14,7 @@
import jenkins.YesNoMaybe;
import org.datadog.jenkins.plugins.datadog.DatadogUtilities;
import org.datadog.jenkins.plugins.datadog.logs.DatadogTaskListenerDecorator;
import org.datadog.jenkins.plugins.datadog.tracer.DatadogTracerJobProperty;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.log.TaskListenerDecorator;
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
Expand All @@ -31,7 +33,8 @@ public class DatadogOptions extends Step implements Serializable {

private static final long serialVersionUID = 1L;
private boolean collectLogs = false;
private List<String> tags = new ArrayList<String>();
private List<String> tags = new ArrayList<>();
private TestVisibility testVisibility;

/** Constructor. */
@DataBoundConstructor
Expand All @@ -47,6 +50,15 @@ public void setCollectLogs(boolean collectLogs) {
this.collectLogs = collectLogs;
}

public TestVisibility getTestVisibility() {
return testVisibility;
}

@DataBoundSetter
public void setTestVisibility(TestVisibility testVisibility) {
this.testVisibility = testVisibility;
}

public List<String> getTags() {
return tags;
}
Expand All @@ -58,7 +70,7 @@ public void setTags(List<String> tags) {

@Override
public StepExecution start(StepContext context) {
DatadogPipelineAction action = new DatadogPipelineAction(this.collectLogs, this.tags);
DatadogPipelineAction action = new DatadogPipelineAction(this.collectLogs, this.tags, this.testVisibility);
return new ExecutionImpl(context, action);
}

Expand Down Expand Up @@ -90,6 +102,13 @@ public boolean start() throws Exception {
} else {
taskLogger.println("You already defined a datadog step");
}

TestVisibility testVisibility = action.getTestVisibility();
if (testVisibility != null) {
Job<?, ?> job = run.getParent();
job.addProperty(new DatadogTracerJobProperty<>(testVisibility.getEnabled(), testVisibility.getServiceName(), testVisibility.getLanguages(), testVisibility.getAdditionalVariables()));
}

BodyInvoker invoker = context.newBodyInvoker().withCallback(BodyExecutionCallback.wrap(context));
if (this.action.isCollectLogs()) {
if (DatadogUtilities.getDatadogGlobalDescriptor().isCollectBuildLogs()) {
Expand All @@ -110,7 +129,6 @@ public void stop(@Nonnull Throwable cause) throws Exception {
StepContext context = getContext();
context.get(TaskListener.class).getLogger().println("Stop DatadogStep");
context.get(TaskListener.class).getLogger().println(cause.getMessage());

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ public class DatadogPipelineAction implements Action, Serializable {

private boolean collectLogs;
private List<String> tags;
private TestVisibility testVisibility;

public DatadogPipelineAction(boolean collectLogs, List<String> tags) {
public DatadogPipelineAction(boolean collectLogs, List<String> tags, TestVisibility testVisibility) {
this.collectLogs = collectLogs;
this.tags = tags;
this.testVisibility = testVisibility;
}

public List<String> getTags() {
Expand All @@ -24,6 +26,10 @@ public boolean isCollectLogs() {
return collectLogs;
}

public TestVisibility getTestVisibility() {
return testVisibility;
}

@CheckForNull
@Override
public String getIconFileName() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.datadog.jenkins.plugins.datadog.steps;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import org.datadog.jenkins.plugins.datadog.tracer.TracerLanguage;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

public class TestVisibility implements Serializable {
private boolean enabled;
private String serviceName;
private Collection<TracerLanguage> languages = Collections.emptyList();
private Map<String, String> additionalVariables = Collections.emptyMap();

@DataBoundConstructor
public TestVisibility() {
}

public boolean getEnabled() {
return enabled;
}

@DataBoundSetter
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public String getServiceName() {
return serviceName;
}

@DataBoundSetter
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}

public Collection<TracerLanguage> getLanguages() {
return languages;
}

@DataBoundSetter
public void setLanguages(Collection<TracerLanguage> languages) {
this.languages = languages;
}

public Map<String, String> getAdditionalVariables() {
return additionalVariables;
}

@DataBoundSetter
public void setAdditionalVariables(Map<String, String> additionalVariables) {
this.additionalVariables = additionalVariables;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@
import hudson.tasks.Shell;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.datadog.jenkins.plugins.datadog.DatadogGlobalConfiguration;
import org.datadog.jenkins.plugins.datadog.DatadogUtilities;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jetbrains.annotations.NotNull;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
Expand All @@ -26,7 +34,7 @@

public class TracerInjectionIT {

// "mvn" script does not quote MAVEN_OPTS properly, this is outside of our control
// "mvn" script does not quote MAVEN_OPTS properly, this is outside our control
@ClassRule
public static TestRule noSpaceInTmpDirs = FlagRule.systemProperty("jenkins.test.noSpaceInTmpDirs", "true");

Expand Down Expand Up @@ -100,6 +108,18 @@ public void testTracerInjectionInPipelineExecutedOnAgentNode() throws Exception
}
}

@Test
public void testTracerInjectionViaPipelineStep() throws Exception {
WorkflowJob pipeline = givenPipelineProjectWithTracerEnabledStep();
try {
givenPipelineIsAMavenBuild(pipeline, false);
WorkflowRun build = whenRunningBuild(pipeline);
thenTracerIsInjected(build);
} finally {
pipeline.delete();
}
}

private FreeStyleProject givenFreestyleProject() throws IOException {
return jenkinsRule.createFreeStyleProject("freestyleProject");
}
Expand All @@ -113,23 +133,45 @@ private WorkflowJob givenPipelineProjectBuiltOnAgentNode() throws Exception {
}

private WorkflowJob givenPipelineProject(boolean builtOnAgentNode) throws Exception {
Map<String, String> replacements = new HashMap<>();
replacements.put("AGENT_LABEL", builtOnAgentNode ? agentNode.getSelfLabel().getName() : "built-in");
replacements.put("PIPELINE_STEPS", getMavenCommand());

String pipelineDefinition = buildPipelineDefinition("test-maven-pipeline.txt", replacements);
WorkflowJob job = jenkinsRule.jenkins.createProject(WorkflowJob.class, "pipelineProject");
String definition = "pipeline {\n" +
" agent {\n" +
" label '" + (builtOnAgentNode ? agentNode.getSelfLabel().getName() : "built-in") + "'\n" +
" }\n" +
" stages {\n" +
" stage('test'){\n" +
" steps {\n" +
" " + getMavenCommand() + "\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
job.setDefinition(new CpsFlowDefinition(definition, true));
job.setDefinition(new CpsFlowDefinition(pipelineDefinition, true));
return job;
}

private WorkflowJob givenPipelineProjectWithTracerEnabledStep() throws Exception {
Map<String, String> replacements = new HashMap<>();
replacements.put("PIPELINE_STEPS", getMavenCommand());

String pipelineDefinition = buildPipelineDefinition("test-maven-pipeline-with-datadog-step.txt", replacements);
WorkflowJob job = jenkinsRule.jenkins.createProject(WorkflowJob.class, "pipelineProject");
job.setDefinition(new CpsFlowDefinition(pipelineDefinition, true));
return job;
}

private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$([A-Z_]+)");

@NotNull
private static String buildPipelineDefinition(String pipelineName, Map<String, String> replacements) throws IOException {
String pipelineDefinition;
try (InputStream is = TracerInjectionIT.class.getResourceAsStream(pipelineName)) {
StringBuffer pipelineBuilder = new StringBuffer();
String pipelineTemplate = IOUtils.toString(is, Charset.defaultCharset());
Matcher m = PLACEHOLDER_PATTERN.matcher(pipelineTemplate);
while (m.find()) {
String placeholder = m.group(1);
m.appendReplacement(pipelineBuilder, replacements.get(placeholder));
}
m.appendTail(pipelineBuilder);
pipelineDefinition = pipelineBuilder.toString();
}
return pipelineDefinition;
}

private String getMavenCommand() {
return isRunningOnWindows()
? "bat \"./mvnw.cmd clean\""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pipeline {
agent {
label 'built-in'
}
options {
datadog(testVisibility: [ enabled: true, serviceName: "my-service", languages: ["JAVA"], additionalVariables: ["my-var": "value"] ])
}
stages {
stage('test') {
steps {
$PIPELINE_STEPS
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pipeline {
agent {
label '$AGENT_LABEL'
}
stages {
stage('test') {
steps {
$PIPELINE_STEPS
}
}
}
}

0 comments on commit fa17562

Please sign in to comment.