Skip to content

Commit

Permalink
Add tags configured by the user to Jenkins traces (#210)
Browse files Browse the repository at this point in the history
  • Loading branch information
drodriguezhdez committed May 31, 2021
1 parent 8439795 commit d759eda
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,20 @@ public static Map<String, Set<String>> getBuildTags(Run run, EnvVars envVars) {

result = TagsUtil.merge(result, getTagsFromGlobalJobTags(jobName, globalJobTags));

result = TagsUtil.merge(result, getTagsFromPipelineAction(run));

return result;
}

/**
* Pipeline extraTags if any are configured in the Job from DatadogPipelineAction.
*
* @param run - Current build
* @return A {@link HashMap} containing the key,value pairs of tags if any.
*/
public static Map<String, Set<String>> getTagsFromPipelineAction(Run run) {
// pipeline defined tags
final Map<String, Set<String>> result = new HashMap<>();
DatadogPipelineAction action = run.getAction(DatadogPipelineAction.class);
if(action != null) {
List<String> pipelineTags = action.getTags();
Expand All @@ -163,6 +176,7 @@ public static Map<String, Set<String>> getBuildTags(Run run, EnvVars envVars) {
}
}
}

return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,17 @@ public Map<String, Set<String>> getTags() {
return allTags;
}

public Map<String, String> getTagsForTraces() {
Map<String, Set<String>> allTags = new HashMap<>();
try {
allTags = DatadogUtilities.getTagsFromGlobalTags();
} catch(NullPointerException e){
//noop
}
allTags = TagsUtil.merge(allTags, tags);
return TagsUtil.convertTagsToMapSingleValues(allTags);
}

public void setTags(Map<String, Set<String>> tags) {
this.tags = tags;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.datadog.jenkins.plugins.datadog.model;

import hudson.model.InvisibleAction;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

public class CIGlobalTagsAction extends InvisibleAction implements Serializable {

private final Map<String, String> tags;

public CIGlobalTagsAction(final Map<String, String> tags) {
this.tags = tags != null ? tags : new HashMap<>();
}

public Map<String, String> getTags() {
return tags;
}

public void putAll(Map<String, String> tags) {
this.tags.putAll(tags);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
import org.datadog.jenkins.plugins.datadog.DatadogUtilities;
import org.datadog.jenkins.plugins.datadog.model.BuildData;
import org.datadog.jenkins.plugins.datadog.model.BuildPipelineNode;
import org.datadog.jenkins.plugins.datadog.model.CIGlobalTagsAction;
import org.datadog.jenkins.plugins.datadog.model.PipelineNodeInfoAction;
import org.datadog.jenkins.plugins.datadog.model.PipelineQueueInfoAction;
import org.datadog.jenkins.plugins.datadog.model.StageBreakdownAction;
import org.datadog.jenkins.plugins.datadog.model.StageData;
import org.datadog.jenkins.plugins.datadog.steps.DatadogPipelineAction;
import org.datadog.jenkins.plugins.datadog.util.json.JsonUtils;

import java.util.ArrayList;
Expand Down Expand Up @@ -79,6 +81,9 @@ public void startBuildTrace(final BuildData buildData, Run run) {

final PipelineQueueInfoAction pipelineQueueInfoAction = new PipelineQueueInfoAction();
run.addAction(pipelineQueueInfoAction);

final CIGlobalTagsAction ciGlobalTags = new CIGlobalTagsAction(buildData.getTagsForTraces());
run.addAction(ciGlobalTags);
}

public void finishBuildTrace(final BuildData buildData, final Run<?,?> run) {
Expand Down Expand Up @@ -209,6 +214,15 @@ public void finishBuildTrace(final BuildData buildData, final Run<?,?> run) {
buildSpan.setTag(CITags.ERROR, true);
}

// CI Tags propagation
final CIGlobalTagsAction ciGlobalTagsAction = run.getAction(CIGlobalTagsAction.class);
if(ciGlobalTagsAction != null) {
final Map<String, String> tags = ciGlobalTagsAction.getTags();
for(Map.Entry<String, String> tagEntry : tags.entrySet()) {
buildSpan.setTag(tagEntry.getKey(), tagEntry.getValue());
}
}

// If the build is a Jenkins Pipeline, the queue time is included in the root span duration.
// We need to adjust the endTime of the root span subtracting the queue time reported by its child span.
// The propagated queue time is set DatadogTracePipelineLogic#updateBuildData method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import datadog.trace.api.DDTags;
import datadog.trace.api.interceptor.MutableSpan;
import hudson.EnvVars;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import io.opentracing.Span;
Expand All @@ -20,18 +19,18 @@
import org.datadog.jenkins.plugins.datadog.model.BuildData;
import org.datadog.jenkins.plugins.datadog.model.BuildPipeline;
import org.datadog.jenkins.plugins.datadog.model.BuildPipelineNode;
import org.datadog.jenkins.plugins.datadog.model.CIGlobalTagsAction;
import org.datadog.jenkins.plugins.datadog.model.GitCommitAction;
import org.datadog.jenkins.plugins.datadog.model.GitRepositoryAction;
import org.datadog.jenkins.plugins.datadog.model.PipelineNodeInfoAction;
import org.datadog.jenkins.plugins.datadog.model.StageBreakdownAction;
import org.datadog.jenkins.plugins.datadog.model.StageData;
import org.datadog.jenkins.plugins.datadog.util.TagsUtil;
import org.datadog.jenkins.plugins.datadog.util.git.GitUtils;
import org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode;
import org.jenkinsci.plugins.workflow.graph.BlockEndNode;
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
import org.jenkinsci.plugins.workflow.graph.FlowEndNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.graph.FlowStartNode;
import org.jenkinsci.plugins.workflow.graphanalysis.DepthFirstScanner;

import java.io.PrintWriter;
Expand Down Expand Up @@ -77,6 +76,7 @@ public void execute(Run run, FlowNode flowNode) {
final BuildPipelineNode pipelineNode = buildPipelineNode(flowNode);
updateStageBreakdown(run, pipelineNode);
updateBuildData(buildData, run, pipelineNode, flowNode);
updateCIGlobalTags(run);
return;
}

Expand All @@ -97,6 +97,7 @@ public void execute(Run run, FlowNode flowNode) {

final SpanContext spanContext = tracer.extract(Format.Builtin.TEXT_MAP, new BuildTextMapAdapter(buildSpanAction.getBuildSpanPropatation()));
final BuildPipelineNode root = pipeline.buildTree();

try {
sendTrace(tracer, run, buildData, root, spanContext);
} catch (Exception e){
Expand Down Expand Up @@ -364,6 +365,15 @@ private Map<String, Object> buildTraceTags(final Run run, final BuildPipelineNod
tags.put(BuildPipelineNode.NodeType.STAGE.getTagName() + CITags._NAME, current.getStageName());
}

// CI Tags propagation
final CIGlobalTagsAction ciGlobalTagsAction = run.getAction(CIGlobalTagsAction.class);
if(ciGlobalTagsAction != null) {
final Map<String, String> globalTags = ciGlobalTagsAction.getTags();
for(Map.Entry<String, String> globalTagEntry : globalTags.entrySet()) {
tags.put(globalTagEntry.getKey(), globalTagEntry.getValue());
}
}

return tags;
}

Expand Down Expand Up @@ -472,4 +482,14 @@ private void updateStageBreakdown(final Run<?,?> run, BuildPipelineNode pipeline

stageBreakdownAction.put(stageData.getName(), stageData);
}

private void updateCIGlobalTags(Run run) {
final CIGlobalTagsAction ciGlobalTagsAction = run.getAction(CIGlobalTagsAction.class);
if(ciGlobalTagsAction == null) {
return;
}

final Map<String, String> tags = TagsUtil.convertTagsToMapSingleValues(DatadogUtilities.getTagsFromPipelineAction(run));
ciGlobalTagsAction.putAll(tags);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ of this software and associated documentation files (the "Software"), to deal
package org.datadog.jenkins.plugins.datadog.util;

import net.sf.json.JSONArray;
import org.datadog.jenkins.plugins.datadog.model.BuildData;

import java.util.*;
import java.util.logging.Logger;

public class TagsUtil {

private static transient final Logger LOGGER = Logger.getLogger(TagsUtil.class.getName());

public static Map<String, Set<String>> merge(Map<String, Set<String>> dest, Map<String, Set<String>> orig) {
if (dest == null) {
dest = new HashMap<>();
Expand Down Expand Up @@ -99,4 +103,21 @@ public static Map<String,Set<String>> addTagToTags(Map<String, Set<String>> tags
tags.get(name).add(value);
return tags;
}

public static Map<String, String> convertTagsToMapSingleValues(Map<String, Set<String>> tags) {
if(tags == null) {
return Collections.emptyMap();
}

final Map<String, String> result = new HashMap<>();
for (Map.Entry<String, Set<String>> entry : tags.entrySet()) {
if(entry.getValue() != null && entry.getValue().size() == 1) {
result.put(entry.getKey(), entry.getValue().iterator().next());
} else {
LOGGER.warning("Unsupported multi-value tag in this context: Tag '"+ entry.getKey() + "' will be ignored.");
}
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public void beforeEach() throws IOException {
DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor();
cfg.setCollectBuildTraces(true);
cfg.setTraceServiceName(SAMPLE_SERVICE_NAME);
cfg.setGlobalJobTags(null);
cfg.setGlobalTags(null);
EnvVars.masterEnvVars.remove("ENV_VAR");

clientStub = new DatadogClientStub();
ClientFactory.setTestClient(clientStub);
Expand Down Expand Up @@ -174,6 +177,28 @@ public void testTracesDisabled() throws Exception {
assertEquals(0, tracerWriter.size());
}

@Test
public void testCITagsOnTraces() throws Exception {
DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor();
cfg.setGlobalJobTags("(.*?)_job, global_job_tag:$ENV_VAR");
cfg.setGlobalTags("global_tag:$ENV_VAR");
EnvVars.masterEnvVars.put("ENV_VAR", "value");

final FreeStyleProject project = jenkinsRule.createFreeStyleProject("buildIntegrationSuccessTags_job");
project.scheduleBuild2(0).get();

final ListWriter tracerWriter = clientStub.tracerWriter();
tracerWriter.waitForTraces(1);
assertEquals(1, tracerWriter.size());

final List<DDSpan> buildTrace = tracerWriter.get(0);
assertEquals(1, buildTrace.size());

final DDSpan buildSpan = buildTrace.get(0);
assertEquals("value", buildSpan.getTag("global_job_tag"));
assertEquals("value", buildSpan.getTag("global_tag"));
}


private void assertGitVariables(DDSpan span, String defaultBranch) {
assertEquals("Initial commit\n", span.getTag(CITags.GIT_COMMIT_MESSAGE));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public void beforeEach() throws IOException {
DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor();
cfg.setCollectBuildTraces(true);
cfg.setTraceServiceName(SAMPLE_SERVICE_NAME);
cfg.setGlobalJobTags(null);
cfg.setGlobalTags(null);
EnvVars.masterEnvVars.remove("ENV_VAR");

listener = new DatadogGraphListener();
clientStub = new DatadogClientStub();
Expand Down Expand Up @@ -713,6 +716,50 @@ public void testStagesNodeNames_complexPipelineStages01() throws Exception {
assertEquals(worker03.getNodeName(), ciStep.getTag(CITags.NODE_NAME));
}

@Test
public void testGlobalTagsPropagationsTraces() throws Exception {
DatadogGlobalConfiguration cfg = DatadogUtilities.getDatadogGlobalDescriptor();
cfg.setGlobalJobTags("(.*?)_job, global_job_tag:$ENV_VAR");
cfg.setGlobalTags("global_tag:$ENV_VAR");
EnvVars.masterEnvVars.put("ENV_VAR", "value");

jenkinsRule.createOnlineSlave(new LabelAtom("testGlobalTags"));
WorkflowJob job = jenkinsRule.jenkins.createProject(WorkflowJob.class, "pipelineIntegration-GlobalTagsPropagation_job");
String definition = IOUtils.toString(
this.getClass().getResourceAsStream("testPipelineGlobalTags.txt"),
"UTF-8"
);
job.setDefinition(new CpsFlowDefinition(definition, true));
job.scheduleBuild2(0).get();

//Traces
final ListWriter tracerWriter = clientStub.tracerWriter();
tracerWriter.waitForTraces(2);
assertEquals(2, tracerWriter.size());

final List<DDSpan> buildTrace = tracerWriter.get(0);
assertEquals(1, buildTrace.size());
final DDSpan buildSpan = buildTrace.get(0);
assertEquals("value", buildSpan.getTag("global_tag"));
assertEquals("value", buildSpan.getTag("global_job_tag"));
assertEquals("pipeline_tag_v2", buildSpan.getTag("pipeline_tag"));
assertEquals("pipeline_tag", buildSpan.getTag("pipeline_tag_v2"));

final List<DDSpan> pipelineTrace = tracerWriter.get(1);
assertEquals(2, pipelineTrace.size());
final DDSpan stageSpan = pipelineTrace.get(0);
assertEquals("value", stageSpan.getTag("global_tag"));
assertEquals("value", stageSpan.getTag("global_job_tag"));
assertEquals("pipeline_tag_v2", stageSpan.getTag("pipeline_tag"));
assertEquals("pipeline_tag", stageSpan.getTag("pipeline_tag_v2"));

final DDSpan stepSpan = pipelineTrace.get(1);
assertEquals("value", stepSpan.getTag("global_tag"));
assertEquals("value", stepSpan.getTag("global_job_tag"));
assertEquals("pipeline_tag_v2", stepSpan.getTag("pipeline_tag"));
assertEquals("pipeline_tag", stepSpan.getTag("pipeline_tag_v2"));
}

private void assertNodeNameParallelBlock(DDSpan stageSpan, DumbSlave worker01, DumbSlave worker02) {
switch ((String)stageSpan.getResourceName()){
case "Prepare01":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ of this software and associated documentation files (the "Software"), to deal
import org.junit.Assert;
import org.junit.Test;

import javax.xml.crypto.Data;
import java.util.*;

public class TagsUtilTest {
Expand Down Expand Up @@ -101,4 +102,28 @@ public void testMerge(){

}

@Test
public void testTagsToMapSingleValues() {
Map<String, String> emptyTags = TagsUtil.convertTagsToMapSingleValues(null);
Assert.assertTrue(emptyTags.isEmpty());

Map<String, Set<String>> singleValueTags = new HashMap<>();
DatadogClientStub.addTagToMap(singleValueTags, "tagKey1", "tagValue1");
DatadogClientStub.addTagToMap(singleValueTags, "tagKey2", "tagValue2");
Map<String, String> resultSingleValues = TagsUtil.convertTagsToMapSingleValues(singleValueTags);
Assert.assertEquals(2, resultSingleValues.size());
Assert.assertEquals("tagValue1", resultSingleValues.get("tagKey1"));
Assert.assertEquals("tagValue2", resultSingleValues.get("tagKey2"));


Map<String, Set<String>> multipleValueTags = new HashMap<>();
DatadogClientStub.addTagToMap(multipleValueTags, "tagKey1", "tagValue1");
DatadogClientStub.addTagToMap(multipleValueTags, "tagKey2", "tagValue2_1");
DatadogClientStub.addTagToMap(multipleValueTags, "tagKey2", "tagValue2_2");
Map<String, String> resultMultipleValues = TagsUtil.convertTagsToMapSingleValues(multipleValueTags);
Assert.assertEquals(1, resultMultipleValues.size());
Assert.assertEquals("tagValue1", resultMultipleValues.get("tagKey1"));
Assert.assertNull(resultMultipleValues.get("tagKey2"));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
def DD_TEST = "pipeline_tag_v2"
pipeline {
agent {
label "testGlobalTags"
}
options {
datadog(collectLogs: false, tags: ["pipeline_tag:${DD_TEST}", "${DD_TEST}:pipeline_tag"])
}
stages {
stage('Stage'){
steps {
echo 'Done'
}
}
}
}

0 comments on commit d759eda

Please sign in to comment.