Skip to content
Open
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
1 change: 1 addition & 0 deletions cmd/cmdtrace/cmd_span.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func Setup(cmd *cobra.Command, dockerCli command.Cli, args []string) error {
cmdSpan.SetAttributes(
attribute.StringSlice("cli.flags", getFlags(cmd.Flags())),
attribute.Bool("cli.isatty", dockerCli.In().IsTerminal()),
attribute.String("docker.context", dockerCli.CurrentContext()),
)

cmd.SetContext(ctx)
Expand Down
13 changes: 13 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"github.com/docker/cli/cli/command"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/resource"

"github.com/docker/compose/v5/cmd/cmdtrace"
"github.com/docker/compose/v5/cmd/compatibility"
Expand All @@ -35,6 +37,11 @@ import (
"github.com/docker/compose/v5/pkg/compose"
)

var (
serviceNameKey = attribute.Key("service.name")
serviceVersionKey = attribute.Key("service.version")
)

func pluginMain() {
plugin.Run(
func(cli command.Cli) *cobra.Command {
Expand Down Expand Up @@ -79,6 +86,12 @@ func pluginMain() {
Version: internal.Version,
},
command.WithUserAgent("compose/"+internal.Version),
command.WithResourceOptions(
resource.WithAttributes(
serviceNameKey.String("compose"),
serviceVersionKey.String(internal.Version),
),
),
)
}

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,5 @@ exclude (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
)

replace github.com/docker/cli => github.com/jsternberg/cli v25.0.1-0.20261023181239-bcd15ee400ad+incompatible

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[high/security] replace directive pins docker/cli to an unmerged personal fork

The added line substitutes the official github.com/docker/cli module with the PR author's personal fork (github.com/jsternberg/cli) on a pseudo-version commit that is outside Docker's control:

replace github.com/docker/cli => github.com/jsternberg/cli v25.0.1-0.20261023181239-bcd15ee400ad+incompatible

While the full 12-hex commit suffix pins exact bytes (integrity), the fork lives on a personal GitHub account — it can be deleted, the account can be compromised, or the pseudo-version can be yanked — any of which breaks builds or introduces malicious code for every downstream consumer of docker/compose.

The PR description acknowledges this as a temporary workaround pending docker/cli#7433. This replace directive must not be merged into main; it should be removed once the upstream fix is published as a proper release from the official docker/cli repository.

Confidence Score
🟢 strong 100/100

4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/cli v29.9.2+incompatible h1:trup/vUxx5uUo2t+87Cd9AmITR+DvKUe4H2tTtZN8FY=
github.com/docker/cli v29.9.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.11.0 h1:7d8QARFb7QEobizqxmEM7fOteZEHwH/zWgHQtHZEcfE=
github.com/docker/cli-docs-tool v0.11.0/go.mod h1:ma8BKiisUo8D6W05XEYIh3oa1UbgrZhi1nowyKFJa8Q=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
Expand Down Expand Up @@ -135,6 +133,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s=
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4=
github.com/jsternberg/cli v25.0.1-0.20261023181239-bcd15ee400ad+incompatible h1:Ea9EzYKBBWMmDmbqkmuQCXCfqfBTwTHiTAc8sH+oi2Q=
github.com/jsternberg/cli v25.0.1-0.20261023181239-bcd15ee400ad+incompatible/go.mod h1:QSUBBpjaw2KRnlXj0e8Uvl9cGOAZdpBdRMRCse8Xv2U=
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/mattn/go-runewidth v0.0.31 h1:1oCV4ReTlosDBc6rYRw5cL8S/4y8iv5oRl4SJ5IBgwo=
Expand Down
123 changes: 0 additions & 123 deletions internal/tracing/docker_context.go

This file was deleted.

69 changes: 0 additions & 69 deletions internal/tracing/mux.go

This file was deleted.

108 changes: 3 additions & 105 deletions internal/tracing/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,126 +18,24 @@ package tracing

import (
"context"
"errors"
"fmt"
"os"
"strings"

"github.com/docker/cli/cli/command"
_ "github.com/moby/buildkit/util/tracing/childprocess" //nolint:blank-imports // initializes BuildKit tracing child process propagation.
"github.com/moby/buildkit/util/tracing/detect"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"

"github.com/docker/compose/v5/internal"
)

func init() {
detect.ServiceName = "compose"
}

// OTLPConfig contains the necessary values to initialize an OTLP client
// manually.
//
// This supports a minimal set of options based on what is necessary for
// automatic OTEL configuration from Docker context metadata.
type OTLPConfig struct {
Endpoint string
}

// ShutdownFunc flushes and stops an OTEL exporter.
type ShutdownFunc func(ctx context.Context) error

// envMap is a convenience type for OS environment variables.
type envMap map[string]string

func InitTracing(dockerCli command.Cli) (ShutdownFunc, error) {
// set global propagator to tracecontext (the default is no-op).
otel.SetTextMapPropagator(propagation.TraceContext{})
return InitProvider(dockerCli)
}

func InitProvider(dockerCli command.Cli) (ShutdownFunc, error) {
ctx := context.Background()

var errs []error
var exporters []sdktrace.SpanExporter

envClient, otelEnv := traceClientFromEnv()
if envClient != nil {
if envExporter, err := otlptrace.New(ctx, envClient); err != nil {
errs = append(errs, err)
} else if envExporter != nil {
exporters = append(exporters, envExporter)
}
}

if dcClient, err := traceClientFromDockerContext(dockerCli, otelEnv); err != nil {
errs = append(errs, err)
} else if dcClient != nil {
if dcExporter, err := otlptrace.New(ctx, dcClient); err != nil {
errs = append(errs, err)
} else if dcExporter != nil {
exporters = append(exporters, dcExporter)
}
}
if len(errs) != 0 {
return nil, errors.Join(errs...)
}

res, err := resource.New(
ctx,
resource.WithAttributes(
semconv.ServiceName("compose"),
semconv.ServiceVersion(internal.Version),
attribute.String("docker.context", dockerCli.CurrentContext()),
),
)
if err != nil {
return nil, fmt.Errorf("failed to create resource: %w", err)
}

muxExporter := MuxExporter{exporters: exporters}
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithResource(res),
sdktrace.WithBatcher(muxExporter),
)
otel.SetTracerProvider(tracerProvider)

// Shutdown will flush any remaining spans and shut down the exporter.
return tracerProvider.Shutdown, nil
}

// traceClientFromEnv creates a GRPC OTLP client based on OS environment
// variables.
//
// https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/
func traceClientFromEnv() (otlptrace.Client, envMap) {
hasOtelEndpointInEnv := false
otelEnv := make(map[string]string)
for _, kv := range os.Environ() {
k, v, ok := strings.Cut(kv, "=")
if !ok {
continue
}
if strings.HasPrefix(k, "OTEL_") {
otelEnv[k] = v
if strings.HasSuffix(k, "ENDPOINT") {
hasOtelEndpointInEnv = true
}
}
}

if !hasOtelEndpointInEnv {
return nil, nil
}

client := otlptracegrpc.NewClient()
return client, otelEnv
return func(ctx context.Context) error {
return nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] InitProvider returns a no-op shutdown — buffered OTLP spans will be dropped on exit

The old implementation returned tracerProvider.Shutdown, which flushed buffered spans to the OTLP exporter before the process exited. The new implementation returns a no-op:

func InitProvider(dockerCli command.Cli) (ShutdownFunc, error) {
    return func(ctx context.Context) error {
        return nil
    }, nil
}

wrapRunE in cmd/cmdtrace/cmd_span.go still calls tracingShutdown with a 100 ms timeout, but since tracingShutdown is now a no-op that call does nothing. The PR description explicitly states that "the current docker/cli library doesn't correctly shut down the tracer and meter provider yet" (pending docker/cli#7433), so the docker/cli side also does not flush.

The net effect is a regression in observability: any spans buffered in the TracerProvider after a compose command completes will be silently dropped rather than exported to the collector. This was working correctly before this PR.

Consider either: (a) keeping a local tracerProvider.Shutdown call until the upstream fix ships, or (b) documenting this as a known temporary limitation with a // TODO(jsternberg): remove once docker/cli#7433 is merged comment so it is not overlooked.

Confidence Score
🟢 strong 100/100

}, nil
}
Loading
Loading