Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
18410b7
fix(otlphttpexporter): unwrap url.Error to show actual modified desti…
EdgeN8v Mar 3, 2026
a283a19
[otelcol] Fix unredacted print-config omitting default values
EdgeN8v Mar 11, 2026
c642ac7
Fix changelog component name
EdgeN8v Mar 11, 2026
18ed377
Fix govet shadow lint error
EdgeN8v Mar 11, 2026
c6eb283
Add unit tests for restoreSecrets slice and nested logic
EdgeN8v Mar 12, 2026
88c822b
Fix code formatting by adding blank line
EdgeN8v Mar 12, 2026
10bb21d
Remove unused errOnlyRedacted field
EdgeN8v Mar 13, 2026
e3e74cf
Fix struct field alignment after removing errOnlyRedacted
EdgeN8v Mar 13, 2026
e84b6e6
refactor: introduce confmap.MarshalOption for unredacted config printing
EdgeN8v Mar 16, 2026
ea53c2f
fix: manually resolve test conflict and fix changelog yaml array format
EdgeN8v Mar 16, 2026
270327f
style: fix formatting, trailing spaces, and missing newlines
EdgeN8v Mar 16, 2026
c4fb3e6
docs: specify issue number in changelog
EdgeN8v Mar 16, 2026
2a0bbb0
refactor: decouple mapstructure unredacted hook and format code
EdgeN8v Mar 16, 2026
8885dbf
style: fix formatting
EdgeN8v Mar 16, 2026
6b249c2
test: add missing unit tests for unredacted unmarshaling to satisfy C…
EdgeN8v Mar 16, 2026
c6b43b1
build: run make gotidy to remove unused assert dependencies
EdgeN8v Mar 16, 2026
37e323e
refactor: use reflection for unredacted print, revert configopaque, a…
EdgeN8v Mar 17, 2026
0d0e628
Merge branch 'main' into fix/14750-unredacted-defaults
EdgeN8v Mar 17, 2026
4e12171
Apply suggestion from @VihasMakwana
dmathieu Mar 19, 2026
a7ba2c4
Merge branch 'main' into fix/14750-unredacted-defaults
EdgeN8v Mar 30, 2026
b724310
Merge branch 'main' into fix/14750-unredacted-defaults
mx-psi Apr 6, 2026
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
7 changes: 7 additions & 0 deletions .chloggen/fix-unredacted-defaults.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
change_type: bug_fix
component: pkg/otelcol
note: Fix missing default values in unredacted print-config command by introducing confmap.WithUnredacted MarshalOption.
subtext: |
Resolves an issue where the unredacted mode output omitted all default-valued options. By introducing a new MarshalOption to disable redaction directly at the confmap encoding level, the unredacted mode now preserves all component defaults natively without requiring post-processing.
issues: [14750]
change_logs: [user]
21 changes: 15 additions & 6 deletions confmap/internal/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,22 @@ import (
// EncoderConfig returns a default encoder.EncoderConfig that includes
// an EncodeHook that handles both TextMarshaler and Marshaler
// interfaces.
func EncoderConfig(rawVal any, _ MarshalOptions) *encoder.EncoderConfig {
func EncoderConfig(rawVal any, opts MarshalOptions) *encoder.EncoderConfig {
hooks := []mapstructure.DecodeHookFunc{
encoder.YamlMarshalerHookFunc(),
}

if opts.OpaqueUnredacted {
hooks = append(hooks, encoder.StringTextUnredactedHookFunc())
}

hooks = append(hooks,
encoder.TextMarshalerHookFunc(),
marshalerHookFunc(rawVal),
)

return &encoder.EncoderConfig{
EncodeHook: mapstructure.ComposeDecodeHookFunc(
encoder.YamlMarshalerHookFunc(),
encoder.TextMarshalerHookFunc(),
marshalerHookFunc(rawVal),
),
EncodeHook: mapstructure.ComposeDecodeHookFunc(hooks...),
}
}

Expand Down
16 changes: 16 additions & 0 deletions confmap/internal/encoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package internal // import "go.opentelemetry.io/collector/confmap/internal"

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestEncoderConfigUnredacted(t *testing.T) {
opts := MarshalOptions{OpaqueUnredacted: true}
cfg := EncoderConfig(nil, opts)
require.NotNil(t, cfg)
}
16 changes: 16 additions & 0 deletions confmap/internal/mapstructure/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,19 @@ func YamlMarshalerHookFunc() mapstructure.DecodeHookFuncValue {
return from.Interface(), nil
}
}

// StringTextUnredactedHookFunc returns a DecodeHookFuncValue that extracts the underlying
// unredacted string from custom string types that implement encoding.TextMarshaler.
func StringTextUnredactedHookFunc() mapstructure.DecodeHookFuncValue {
return func(from, _ reflect.Value) (any, error) {
// If the underlying kind is a string, and it implements TextMarshaler
// (which obfuscated types like configopaque.String usually do).
if from.Kind() == reflect.String {
if _, ok := from.Interface().(encoding.TextMarshaler); ok {
// Extract the raw string via reflection, bypassing any custom methods.
return from.String(), nil
}
Comment on lines +279 to +282

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.

This feels like quite a subtle tight coupling to configopaque.Opaque. Instead, perhaps we should introduce an interface like

type UnredactedStringer interface {
    UnredactedString() string
}

and have configopaque.Opaque implement it.

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.

++

}
return from.Interface(), nil
}
}
23 changes: 23 additions & 0 deletions confmap/internal/mapstructure/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/stretchr/testify/require"
)

type obfuscatedTestString string

type TestComplexStruct struct {
Skipped TestEmptyStruct `mapstructure:",squash"`
Nested TestSimpleStruct `mapstructure:",squash"`
Expand Down Expand Up @@ -392,3 +394,24 @@ func testHookFunc() mapstructure.DecodeHookFuncValue {
return got.skipped, nil
}
}

func (m obfuscatedTestString) MarshalText() ([]byte, error) {
return []byte("[REDACTED]"), nil
}

func TestStringTextUnredactedHookFunc(t *testing.T) {
hook := StringTextUnredactedHookFunc()

val, err := hook(reflect.ValueOf(123), reflect.Value{})
require.NoError(t, err)
require.Equal(t, 123, val)

val, err = hook(reflect.ValueOf("plain"), reflect.Value{})
require.NoError(t, err)
require.Equal(t, "plain", val)

testSecret := obfuscatedTestString("secret")
val, err = hook(reflect.ValueOf(testSecret), reflect.Value{})
require.NoError(t, err)
require.Equal(t, "secret", val)
}
5 changes: 4 additions & 1 deletion confmap/internal/marshaloption.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ type MarshalOption interface {

// MarshalOptions is used by (*Conf).Marshal to toggle unmarshaling settings.
// It is in the `internal` package so experimental options can be added in xconfmap.
type MarshalOptions struct{}
type MarshalOptions struct {
// OpaqueUnredacted specifies whether opaque strings should be marshaled unredacted.
OpaqueUnredacted bool
Comment thread
dmathieu marked this conversation as resolved.
}

type MarshalOptionFunc func(*MarshalOptions)

Expand Down
8 changes: 8 additions & 0 deletions confmap/xconfmap/confmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,11 @@ type ExpandedValue = internal.ExpandedValue
func ToStringMapRaw(conf *confmap.Conf) map[string]any {
return internal.ToStringMapRaw(conf)
}

// WithUnredacted returns a MarshalOption that configures the confmap to
// bypass redaction of opaque string values.
func WithUnredacted() confmap.MarshalOption {
return internal.MarshalOptionFunc(func(opts *internal.MarshalOptions) {
opts.OpaqueUnredacted = true
})
}
21 changes: 21 additions & 0 deletions confmap/xconfmap/confmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package xconfmap

import (
"testing"

"github.com/stretchr/testify/require"

"go.opentelemetry.io/collector/confmap/internal"
)

func TestWithUnredacted(t *testing.T) {
opt := WithUnredacted()
require.NotNil(t, opt)

opts := &internal.MarshalOptions{}
opt.(internal.MarshalOptionFunc)(opts)
require.True(t, opts.OpaqueUnredacted)
}
34 changes: 15 additions & 19 deletions otelcol/command_print.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,36 +140,31 @@ func (pctx *printContext) getPrintableConfig() (any, error) {
return cfg, nil
}

// printUnredactedConfig prints resolved configuration before interpreting
// with the intended types for each component, thus it shows the full
// configuration without considering configuopaque. Use with caution.
// printUnredactedConfig prints the resolved configuration before interpreting
// with the intended types for each component. It uses unredacted mode to
// reveal the full configuration including opaque values. Use with caution.
func (pctx *printContext) printUnredactedConfig() error {
cfg, err := pctx.getPrintableConfig()
if err != nil {
return err
}

if pctx.validate {
// Validation serves prevent revealing invalid data.
cfg, err := pctx.getPrintableConfig()
if err != nil {
return err
}
if err = xconfmap.Validate(cfg); err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}

// Note: we discard the validated configuration.
}
resolver, err := confmap.NewResolver(pctx.set.ConfigProviderSettings.ResolverSettings)
if err != nil {
return fmt.Errorf("failed to create new resolver: %w", err)
}
conf, err := resolver.Resolve(pctx.cmd.Context())
if err != nil {
return fmt.Errorf("error while resolving config: %w", err)

confMap := confmap.New()
if err := confMap.Marshal(cfg, xconfmap.WithUnredacted()); err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
return pctx.printConfigData(conf.ToStringMap())

return pctx.printConfigData(confMap.ToStringMap())
}

// printRedactedConfig prints resolved configuration with its assigned
// types, but without validation. Notably, configopaque strings are printed
// as "[redacted]". This is the default.
func (pctx *printContext) printRedactedConfig() error {
cfg, err := pctx.getPrintableConfig()
if err != nil {
Expand All @@ -186,5 +181,6 @@ func (pctx *printContext) printRedactedConfig() error {
if err := confMap.Marshal(cfg); err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}

Comment thread
dmathieu marked this conversation as resolved.
Outdated
return pctx.printConfigData(confMap.ToStringMap())
}
24 changes: 7 additions & 17 deletions otelcol/command_print_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestPrintCommand(t *testing.T) {
name: "invalid syntax without validate",
path: invalidConfig1,
errString: "'timeout' time: invalid duration",
errOnlyRedacted: true,
Comment thread
EdgeN8v marked this conversation as resolved.
errOnlyRedacted: false,
},
{
name: "validation fail",
Expand All @@ -101,23 +101,17 @@ func TestPrintCommand(t *testing.T) {
name: "default field value",
path: defaultConfig,
outString: map[string]string{
"redacted": `timeout: 1s`,

// Since the structure is empty before
// interpretation, no default is expanded.
"unredacted": `e: null`,
"redacted": `timeout: 1s`,
"unredacted": `timeout: 1s`,
},
},
{
name: "field is set json",
ofmt: "json",
path: validConfig,
outString: map[string]string{
// Note: JSON does not format as a time.Duration
"redacted": `"timeout": 5000000000`,

// Note: the original input is "5s"
"unredacted": `"timeout": "5s"`,
"redacted": `"timeout": 5000000000`,
"unredacted": `"timeout": 5000000000`,
},
},
{
Expand All @@ -132,12 +126,8 @@ func TestPrintCommand(t *testing.T) {
name: "opaque default",
path: defaultConfig,
outString: map[string]string{
"redacted": `opaque: '[REDACTED]'`,

// Note: the default opaque value does not print,
// the other value is set in defaultConfig so that
// the whole component config is not defaulted.
"unredacted": `other: lala`,
"redacted": `opaque: '[REDACTED]'`,
"unredacted": `opaque: "1234"`,
},
},
}
Expand Down
Loading