Skip to content
Draft
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
2 changes: 2 additions & 0 deletions otdfctl/cmd/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ func Execute(opts ...ExecuteOptFunc) {
if c.mountTo != nil {
err := MountRoot(c.mountTo, c.renameCmd)
if err != nil {
finishTrace()
os.Exit(cli.ExitCodeError)
}
} else {
err := RootCmd.Execute()
if err != nil {
finishTrace()
os.Exit(cli.ExitCodeError)
}
}
Expand Down
34 changes: 34 additions & 0 deletions otdfctl/cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package cmd

import (
"context"
"fmt"
"log/slog"
"os"
"sync"

"github.com/opentdf/platform/otdfctl/cmd/auth"
cfg "github.com/opentdf/platform/otdfctl/cmd/config"
Expand All @@ -14,15 +16,22 @@ import (
"github.com/opentdf/platform/otdfctl/pkg/cli"
"github.com/opentdf/platform/otdfctl/pkg/config"
"github.com/opentdf/platform/otdfctl/pkg/man"
"github.com/opentdf/platform/otdfctl/pkg/tracing"
"github.com/opentdf/platform/sdk"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel"
)

var (
clientCredsFile string
clientCredsJSON string

RootCmd = &man.Docs.GetDoc("<root>").Command

// finishTrace ends the root command span and flushes the tracer provider.
// It is invoked from PersistentPostRunE (normal exit) and via cli.OnExit
// (os.Exit paths), guarded so it runs exactly once.
finishTrace = func() {}
)

type version struct {
Expand Down Expand Up @@ -86,6 +95,31 @@ func init() {

slog.SetDefault(logger)
}

// Start OpenTelemetry tracing for this command. This is a no-op unless
// OTEL_EXPORTER_OTLP_ENDPOINT is set, so it adds no cost to normal runs.
shutdown, enabled := tracing.Init(context.Background())
if enabled {
ctx := tracing.ExtractParentFromEnv(context.Background())
ctx, span := otel.Tracer("otdfctl").Start(ctx, "cli."+cmd.Name())
cmd.SetContext(ctx)

var once sync.Once
finishTrace = func() {
once.Do(func() {
span.End()
shutdown()
})
}
// Ensure spans flush even when a command exits via os.Exit.
cli.OnExit = finishTrace
}
return nil
}

// Flush traces on the normal (non-os.Exit) return path.
RootCmd.PersistentPostRunE = func(_ *cobra.Command, _ []string) error {
finishTrace()
return nil
}

Expand Down
1 change: 1 addition & 0 deletions otdfctl/cmd/tdf/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ func encryptRun(cmd *cobra.Command, args []string) {

// Do the encryption
encrypted, err := h.EncryptBytes(
c.Context(),
tdfType,
bytesSlice,
attrValues,
Expand Down
14 changes: 11 additions & 3 deletions otdfctl/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module github.com/opentdf/platform/otdfctl
go 1.25.0

require (
connectrpc.com/connect v1.20.0
connectrpc.com/otelconnect v0.9.0
github.com/adrg/frontmatter v0.2.0
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7
github.com/charmbracelet/bubbletea v1.3.10
Expand All @@ -23,6 +25,9 @@ require (
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
github.com/zitadel/oidc/v3 v3.45.1
go.opentelemetry.io/otel v1.43.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0
go.opentelemetry.io/otel/sdk v1.43.0
golang.org/x/oauth2 v0.36.0
golang.org/x/term v0.43.0
google.golang.org/grpc v1.81.1
Expand All @@ -32,14 +37,14 @@ require (
require (
al.essio.dev/pkg/shellescape v1.5.1 // indirect
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 // indirect
connectrpc.com/connect v1.20.0 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/Masterminds/semver/v3 v3.5.0 // indirect
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
Expand All @@ -63,6 +68,7 @@ require (
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gowebpki/jcs v1.0.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
Expand Down Expand Up @@ -97,15 +103,17 @@ require (
github.com/zitadel/logging v0.6.2 // indirect
github.com/zitadel/schema v1.3.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
golang.org/x/crypto v0.52.0 // indirect
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/net v0.54.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.37.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
20 changes: 18 additions & 2 deletions otdfctl/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ=
connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
connectrpc.com/grpchealth v1.4.0 h1:MJC96JLelARPgZTiRF9KRfY/2N9OcoQvF2EWX07v2IE=
connectrpc.com/grpchealth v1.4.0/go.mod h1:WhW6m1EzTmq3Ky1FE8EfkIpSDc6TfUx2M2KqZO3ts/Q=
connectrpc.com/otelconnect v0.9.0 h1:NggB3pzRC3pukQWaYbRHJulxuXvmCKCKkQ9hbrHAWoA=
connectrpc.com/otelconnect v0.9.0/go.mod h1:AEkVLjCPXra+ObGFCOClcJkNjS7zPaQSqvO0lCyjfZc=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
Expand All @@ -32,6 +34,8 @@ github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
Expand Down Expand Up @@ -125,6 +129,8 @@ github.com/gowebpki/jcs v1.0.1 h1:Qjzg8EOkrOTuWP7DqQ1FbYtcpEbeTzUoTN9bptp8FOU=
github.com/gowebpki/jcs v1.0.1/go.mod h1:CID1cNZ+sHp1CCpAR8mPf6QRtagFBgPJE0FCUQ6+BrI=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand Down Expand Up @@ -244,6 +250,10 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
Expand All @@ -252,6 +262,10 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
Expand Down Expand Up @@ -309,8 +323,10 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
Expand Down
15 changes: 15 additions & 0 deletions otdfctl/pkg/cli/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ const (
ExitCodeError = 1
)

// OnExit, if set, is invoked immediately before the process exits via any of
// the Exit* helpers. It gives long-lived resources (e.g. an OpenTelemetry
// tracer provider) a chance to flush, since os.Exit does not run deferred
// functions. It must be safe to call more than once.
var OnExit func()

func runOnExit() {
if OnExit != nil {
OnExit()
}
}

func ExitWithError(errMsg string, err error) {
// This is temporary until we can refactor the code to use the Cli struct
(&Cli{printer: defaultPrinter()}).ExitWithError(errMsg, err)
Expand Down Expand Up @@ -67,12 +79,14 @@ func (c *Cli) ExitWithMessage(msg string, code int) {
} else {
c.println(w, msg)
}
runOnExit()
os.Exit(code)
}

func (c *Cli) ExitWithJSON(v interface{}, code int) {
if c.printer.json {
c.printJSON(v, os.Stdout)
runOnExit()
os.Exit(code)
}
}
Expand All @@ -85,6 +99,7 @@ func (c *Cli) ExitWith(styledMsg string, jsonMsg interface{}, code int, w io.Wri
} else {
c.println(w, styledMsg)
}
runOnExit()
os.Exit(code)
}

Expand Down
12 changes: 12 additions & 0 deletions otdfctl/pkg/handlers/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"errors"
"log/slog"

"connectrpc.com/connect"
"connectrpc.com/otelconnect"
"github.com/opentdf/platform/otdfctl/pkg/auth"
"github.com/opentdf/platform/otdfctl/pkg/profiles"
"github.com/opentdf/platform/otdfctl/pkg/utils"
Expand Down Expand Up @@ -88,6 +90,16 @@ func New(opts ...handlerOptsFunc) (Handler, error) {
sdk.WithConnectionValidation(),
sdk.WithLogger(slog.Default()),
}

// Propagate the active trace context to platform/KAS so their spans join
// the CLI's trace. When no tracer provider is configured this uses the
// global no-op provider and is effectively inert.
if otelInterceptor, err := otelconnect.NewInterceptor(otelconnect.WithoutTraceEvents()); err != nil {
slog.Warn("failed to create otel connect interceptor; tracing disabled", slog.Any("error", err))
} else {
defaultSDKOpts = append(defaultSDKOpts, sdk.WithExtraClientOptions(connect.WithInterceptors(otelInterceptor)))
}

if o.TLSNoVerify {
defaultSDKOpts = append(defaultSDKOpts, sdk.WithInsecureSkipVerifyConn())
}
Expand Down
10 changes: 9 additions & 1 deletion otdfctl/pkg/handlers/tdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type TDFInspect struct {
}

func (h Handler) EncryptBytes(
ctx context.Context,
tdfType string,
unencrypted []byte,
attrValues []string,
Expand Down Expand Up @@ -94,7 +95,7 @@ func (h Handler) EncryptBytes(
opts = append(opts, sdk.WithTargetMode(targetMode))
}

_, err := h.sdk.CreateTDF(enc, bytes.NewReader(unencrypted), opts...)
_, err := h.sdk.CreateTDFContext(ctx, enc, bytes.NewReader(unencrypted), opts...)
return enc, err
default:
return nil, errors.New("unknown TDF type")
Expand Down Expand Up @@ -149,6 +150,13 @@ func (h Handler) DecryptBytes(
if err != nil {
return nil, err
}
// Perform the key unwrap (KAS Rewrap) up front with the caller's
// context so the request propagates the active trace. Without this the
// unwrap is triggered lazily by io.Copy below using context.Background,
// which would start a new, disconnected trace.
if err = r.Init(ctx); err != nil {
return nil, formatDecryptError(ctx, r.Obligations, err)
}
//nolint:errorlint // callers intended to test error equality directly
if _, err = io.Copy(pt, r); err != nil && err != io.EOF {
return nil, formatDecryptError(ctx, r.Obligations, err)
Expand Down
Loading
Loading