Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export Otel configuration in shell steps: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS #155

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ The context of the current span is exposed as environment variables to ease inte
* `TRACE_ID`: the trace id of the job / pipeline
* `SPAN_ID`: the id of the pipeline shell step span

When the configuration options "Export OpenTelemetry configuration as environment variables", the following [OpenTelemetry environment variables](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.6.0/specification/protocol/exporter.md) will be exported according to the settings of the plugin:
* `OTEL_EXPORTER_OTLP_ENDPOINT`: Target to which the exporter is going to send spans or metrics.
* `OTEL_EXPORTER_OTLP_INSECURE`: Whether to enable client transport security for the exporter's gRPC connection
* `OTEL_EXPORTER_OTLP_HEADERS`: Key-value pairs to be used as headers associated with gRPC or HTTP requests. Typically used to pass credentials.
* `OTEL_EXPORTER_OTLP_TIMEOUT`: Maximum time the OTLP exporter will wait for each batch export.
* `OTEL_EXPORTER_OTLP_CERTIFICATE`: The trusted certificate to use when verifying a server's TLS credentials.

In addition, if the backends were configured then there will be an environment variable for each of them pointing to the URL with the span/transactions:

* `OTEL_CUSTOM_URL`
Expand Down Expand Up @@ -316,6 +323,7 @@ unclassified:
openTelemetry:
authentication: "noAuthentication"
endpoint: "otel-collector-contrib:4317"
exportOtelConfigurationAsEnvironmentVariables: true
exporterIntervalMillis: 60000
exporterTimeoutMillis: 30000
ignoredSteps: "dir,echo,isUnix,pwd,properties"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -55,6 +56,9 @@
public class JenkinsOpenTelemetryPluginConfiguration extends GlobalConfiguration {
private final static Logger LOGGER = Logger.getLogger(JenkinsOpenTelemetryPluginConfiguration.class.getName());

/**
* OTLP endpoint prefixed by "http://" or "https://"
*/
private String endpoint;

private String trustedCertificatesPem;
Expand All @@ -71,6 +75,8 @@ public class JenkinsOpenTelemetryPluginConfiguration extends GlobalConfiguration

private transient OpenTelemetrySdkProvider openTelemetrySdkProvider;

private boolean exportOtelConfigurationAsEnvironmentVariables;

private transient SpanNamingStrategy spanNamingStrategy;

private transient ConcurrentMap<String, StepPlugin> loadedStepsPlugins = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -209,6 +215,34 @@ public void setIgnoredSteps(String ignoredSteps) {
this.ignoredSteps = ignoredSteps;
}

public boolean isExportOtelConfigurationAsEnvironmentVariables() {
return exportOtelConfigurationAsEnvironmentVariables;
}

@DataBoundSetter
public void setExportOtelConfigurationAsEnvironmentVariables(boolean exportOtelConfigurationAsEnvironmentVariables) {
this.exportOtelConfigurationAsEnvironmentVariables = exportOtelConfigurationAsEnvironmentVariables;
}

@Nonnull
public Map<String, String> getOtelConfigurationAsEnvironmentVariables() {
Map<String, String> environmentVariables = new HashMap<>();
environmentVariables.put("OTEL_EXPORTER_OTLP_ENDPOINT", this.endpoint);
String sanitizeOtlpEndpoint = sanitizeOtlpEndpoint(this.endpoint);
if (sanitizeOtlpEndpoint != null && sanitizeOtlpEndpoint.startsWith("http://")) {
environmentVariables.put("OTEL_EXPORTER_OTLP_INSECURE", Boolean.TRUE.toString());
}
this.authentication.enrichOtelEnvironmentVariables(environmentVariables);
String trustedCertificatesPem = this.getTrustedCertificatesPem();
if (trustedCertificatesPem != null && !trustedCertificatesPem.isEmpty()) {
environmentVariables.put("OTEL_EXPORTER_OTLP_CERTIFICATE", trustedCertificatesPem);
}
if (this.exporterTimeoutMillis != 0) {
environmentVariables.put("OTEL_EXPORTER_OTLP_TIMEOUT", this.exporterTimeoutMillis + "ms");
}
return environmentVariables;
}

/**
* For visualisation in config.jelly
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -70,6 +71,12 @@ public void configure(@Nonnull OtlpGrpcSpanExporterBuilder spanExporterBuilder)
spanExporterBuilder.addHeader("Authorization", "Bearer " + this.getAuthenticationHeaderValue());
}

@Override
public void enrichOtelEnvironmentVariables(Map<String, String> environmentVariables) {
// TODO don't overwrite OTEL_EXPORTER_OTLP_HEADERS if already defined, just append to it
environmentVariables.put("OTEL_EXPORTER_OTLP_HEADERS", "authorization=Bearer " + this.getAuthenticationHeaderValue());
}

public String getTokenId() {
return tokenId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.kohsuke.stapler.DataBoundSetter;

import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -63,6 +64,11 @@ public void configure(OtlpGrpcSpanExporterBuilder spanExporterBuilder) {
spanExporterBuilder.addHeader(this.getHeaderName(), this.getAuthenticationHeaderValue());
}

@Override
public void enrichOtelEnvironmentVariables(Map<String, String> environmentVariables) {
// TODO don't overwrite OTEL_EXPORTER_OTLP_HEADERS if already defined, just append to it
environmentVariables.put("OTEL_EXPORTER_OTLP_HEADERS", this.getHeaderName() + "=" + this.getAuthenticationHeaderValue());
}
public String getHeaderName() {
return headerName;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.kohsuke.stapler.DataBoundConstructor;

import javax.annotation.Nonnull;
import java.util.Map;
import java.util.logging.Logger;

@Extension
Expand All @@ -30,6 +31,10 @@ public void configure(@Nonnull OtlpGrpcMetricExporterBuilder metricExporterBuild
public void configure(@Nonnull OtlpGrpcSpanExporterBuilder spanExporterBuilder) {
}

@Override
public void enrichOtelEnvironmentVariables(Map<String, String> environmentVariables) {
}

@Override
public String toString() {
return "NoAuthentication{}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,28 @@
import jenkins.model.Jenkins;

import javax.annotation.Nonnull;
import java.util.Map;

public abstract class OtlpAuthentication implements Describable<OtlpAuthentication>, ExtensionPoint {
/**
* Configure the given {@code metricExporterBuilder} injecting authentication settings
* @param metricExporterBuilder the builder to configure
*/
public abstract void configure(@Nonnull OtlpGrpcMetricExporterBuilder metricExporterBuilder);

/**
* Configure the given {@code spanExporterBuilder} injecting authentication settings
* @param spanExporterBuilder the builder to configure
*/
public abstract void configure(@Nonnull OtlpGrpcSpanExporterBuilder spanExporterBuilder);

/**
* Enrich the provided environment variables injecting the authentication settings,
* typically appending credentials to the {@code OTEL_EXPORTER_OTLP_HEADERS} variable
* @param environmentVariables the builder to configure
*/
public abstract void enrichOtelEnvironmentVariables(@Nonnull Map<String, String> environmentVariables);

@Override
public Descriptor<OtlpAuthentication> getDescriptor() {
return Jenkins.get().getDescriptorOrDie(getClass());
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,22 @@
public class OtelEnvironmentContributor extends EnvironmentContributor {
private final static Logger LOGGER = Logger.getLogger(OtelEnvironmentContributor.class.getName());

private OtelEnvironmentContributorService otelEnvironmentContributorService;

private OtelTraceService otelTraceService;

@Override
public void buildEnvironmentFor(@Nonnull Run run, @Nonnull EnvVars envs, @Nonnull TaskListener listener) {
EnvironmentContributorUtils.setEnvironmentVariables(run, envs, otelTraceService.getSpan(run, false));
otelEnvironmentContributorService.addEnvironmentVariables(run, envs, otelTraceService.getSpan(run, false));
}

@Inject
public void setOtelTraceService(OtelTraceService otelTraceService) {
this.otelTraceService = otelTraceService;
}

@Inject
public void setEnvironmentContributorService(OtelEnvironmentContributorService otelEnvironmentContributorService) {
this.otelEnvironmentContributorService = otelEnvironmentContributorService;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.jenkins.plugins.opentelemetry.job;

import hudson.EnvVars;
import hudson.Extension;
import hudson.model.Run;
import io.jenkins.plugins.opentelemetry.JenkinsOpenTelemetryPluginConfiguration;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.context.propagation.TextMapSetter;
import org.jenkinsci.plugins.workflow.steps.StepEnvironmentContributor;

import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Inject environment variables in shell steps
*
* @see StepEnvironmentContributor
* @see hudson.model.EnvironmentContributor
*/
@Extension
public class OtelEnvironmentContributorService {

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

private JenkinsOpenTelemetryPluginConfiguration jenkinsOpenTelemetryPluginConfiguration;

public void addEnvironmentVariables(@Nonnull Run run, @Nonnull EnvVars envs, @Nonnull Span span) {
String spanId = span.getSpanContext().getSpanId();
String traceId = span.getSpanContext().getTraceId();
try (Scope ignored = span.makeCurrent()) {
envs.putIfAbsent(JenkinsOtelSemanticAttributes.TRACE_ID, traceId);
envs.put(JenkinsOtelSemanticAttributes.SPAN_ID, spanId);
TextMapSetter<EnvVars> setter = (carrier, key, value) -> carrier.put(key.toUpperCase(), value);
W3CTraceContextPropagator.getInstance().inject(Context.current(), envs, setter);
}

MonitoringAction action = new MonitoringAction(traceId, spanId);
action.onAttached(run);
for (MonitoringAction.ObservabilityBackendLink link : action.getLinks()) {
// Default backend link got an empty environment variable.
if (link.getEnvironmentVariableName() != null) {
envs.put(link.getEnvironmentVariableName(), link.getUrl());
}
}

if (this.jenkinsOpenTelemetryPluginConfiguration.isExportOtelConfigurationAsEnvironmentVariables()) {
Map<String, String> otelConfiguration = jenkinsOpenTelemetryPluginConfiguration.getOtelConfigurationAsEnvironmentVariables();
for (Map.Entry<String, String> otelEnvironmentVariable : otelConfiguration.entrySet()) {
String previousValue = envs.put(otelEnvironmentVariable.getKey(), otelEnvironmentVariable.getValue());
if (previousValue != null) {
LOGGER.log(Level.FINE, "Overwrite environment variable '" + otelEnvironmentVariable.getKey() + "'");
}
}
} else {
// skip
}
}

@Inject
public void setJenkinsOpenTelemetryPluginConfiguration(JenkinsOpenTelemetryPluginConfiguration jenkinsOpenTelemetryPluginConfiguration) {
this.jenkinsOpenTelemetryPluginConfiguration = jenkinsOpenTelemetryPluginConfiguration;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public class OtelStepEnvironmentContributor extends StepEnvironmentContributor {

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

private OtelEnvironmentContributorService otelEnvironmentContributorService;

private OtelTraceService otelTraceService;

@Override
Expand All @@ -42,7 +44,12 @@ public void buildEnvironmentFor(@Nonnull StepContext stepContext, @Nonnull EnvVa
span = otelTraceService.getSpan(run, flowNode);
}

EnvironmentContributorUtils.setEnvironmentVariables(run, envs, span);
otelEnvironmentContributorService.addEnvironmentVariables(run, envs, span);
}

@Inject
public void setEnvironmentContributorService(OtelEnvironmentContributorService otelEnvironmentContributorService) {
this.otelEnvironmentContributorService = otelEnvironmentContributorService;
}

@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
<f:hetero-radio
descriptors="${authenticationDescriptors}" field="authentication"/>
</f:entry>
<f:entry
title="Export OpenTelemetry configuration as environment variables"
field="exportOtelConfigurationAsEnvironmentVariables">
<f:checkbox />
</f:entry>

<f:entry title="Visualisation" description="${instance.observabilityBackends.isEmpty() ? instance.getVisualisationObservabilityBackendsString() : null}">
<j:invokeStatic var="backendDescriptors" className="io.jenkins.plugins.opentelemetry.backend.ObservabilityBackend" method="allDescriptors"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div>
Export the OpenTelemetry configuration as environment variables in shell and bat steps using the
<a href="https://github.com/open-telemetry/opentelemetry-specification/blob/v1.6.0/specification/protocol/exporter.md">
standard OpenTelemetry environment variables</a>:
<code>OTEL_EXPORTER_OTLP_ENDPOINT</code>,
<code>OTEL_EXPORTER_OTLP_INSECURE</code>,
<code>OTEL_EXPORTER_OTLP_HEADERS</code>,
<code>OTEL_EXPORTER_OTLP_TIMEOUT</code>,
<code>OTEL_EXPORTER_OTLP_CERTIFICATE</code>...
Note that OpenTelemetry credentials, if configured, will be exposed as environment variables (likely in <code>OTEL_EXPORTER_OTLP_HEADERS</code>).
</div>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
authentication: "noAuthentication"
endpoint: "http://otel-collector-contrib:4317"
exportOtelConfigurationAsEnvironmentVariables: false
exporterIntervalMillis: 60000
exporterTimeoutMillis: 30000
ignoredSteps: "dir,echo,isUnix,pwd,properties"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
authentication: "noAuthentication"
endpoint: "http://otel-collector-contrib:4317"
exportOtelConfigurationAsEnvironmentVariables: false
exporterIntervalMillis: 60000
exporterTimeoutMillis: 30000
ignoredSteps: "dir,echo,isUnix,pwd,properties"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
unclassified:
openTelemetry:
endpoint: "http://otel-collector-contrib:4317"
exportOtelConfigurationAsEnvironmentVariables: false
observabilityBackends:
- elastic:
name: "My Elastic"
Expand Down