Skip to content

Commit f190d98

Browse files
committed
Add component JSON schema generation to mdatagen
Signed-off-by: Pavol Loffay <[email protected]>
1 parent 250a1ca commit f190d98

File tree

21 files changed

+1871
-1
lines changed

21 files changed

+1871
-1
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. receiver/otlp)
7+
component: cmd/mdatagen
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Add config JSON schema generation
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [9769]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
The component config JSON schema can be optionally enabled by:
20+
```
21+
schema:
22+
enabled: true
23+
```
24+
25+
# Optional: The change log or logs in which this entry should be included.
26+
# e.g. '[user]' or '[user, api]'
27+
# Include 'user' if the change is relevant to end users.
28+
# Include 'api' if there is a change to a library API.
29+
# Default: '[user]'
30+
change_logs: [api]

cmd/mdatagen/go.mod

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ go 1.24.0
44

55
require (
66
github.com/google/go-cmp v0.7.0
7+
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
78
github.com/spf13/cobra v1.10.2
89
github.com/stretchr/testify v1.11.1
910
go.opentelemetry.io/collector/component v1.49.0
1011
go.opentelemetry.io/collector/component/componenttest v0.143.0
12+
go.opentelemetry.io/collector/config/configopaque v1.49.0
13+
go.opentelemetry.io/collector/config/configoptional v1.49.0
1114
go.opentelemetry.io/collector/confmap v1.49.0
1215
go.opentelemetry.io/collector/confmap/provider/fileprovider v1.49.0
1316
go.opentelemetry.io/collector/connector v0.143.0
@@ -32,6 +35,7 @@ require (
3235
go.uber.org/goleak v1.3.0
3336
go.uber.org/zap v1.27.1
3437
golang.org/x/text v0.32.0
38+
golang.org/x/tools v0.40.0
3539
gopkg.in/yaml.v3 v3.0.1
3640
)
3741

@@ -57,6 +61,7 @@ require (
5761
github.com/spf13/pflag v1.0.10 // indirect
5862
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
5963
go.opentelemetry.io/collector/component/componentstatus v0.143.0 // indirect
64+
go.opentelemetry.io/collector/confmap/xconfmap v0.143.0 // indirect
6065
go.opentelemetry.io/collector/connector/xconnector v0.143.0 // indirect
6166
go.opentelemetry.io/collector/consumer/consumererror v0.143.0 // indirect
6267
go.opentelemetry.io/collector/consumer/xconsumer v0.143.0 // indirect
@@ -72,6 +77,8 @@ require (
7277
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
7378
go.uber.org/multierr v1.11.0 // indirect
7479
go.yaml.in/yaml/v3 v3.0.4 // indirect
80+
golang.org/x/mod v0.31.0 // indirect
81+
golang.org/x/sync v0.19.0 // indirect
7582
golang.org/x/sys v0.39.0 // indirect
7683
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
7784
google.golang.org/grpc v1.78.0 // indirect

cmd/mdatagen/go.sum

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/mdatagen/internal/command.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"golang.org/x/text/cases"
2323
"golang.org/x/text/language"
2424
"gopkg.in/yaml.v3"
25+
26+
"go.opentelemetry.io/collector/cmd/mdatagen/internal/schemagen"
2527
)
2628

2729
const (
@@ -216,6 +218,11 @@ func run(ymlPath string) error {
216218
}
217219
}
218220

221+
// Generate JSON schema if enabled
222+
if err := generateSchema(md, ymlDir); err != nil {
223+
return fmt.Errorf("failed to generate schema: %w", err)
224+
}
225+
219226
return nil
220227
}
221228

@@ -474,3 +481,29 @@ func validateYAMLKeyOrder(raw []byte) error {
474481
}
475482
return nil
476483
}
484+
485+
// generateSchema generates a JSON schema for the component's config if enabled.
486+
func generateSchema(md Metadata, ymlDir string) error {
487+
// Skip if schema generation is not enabled
488+
if md.Schema == nil || !md.Schema.Enabled {
489+
return nil
490+
}
491+
492+
// Skip non-component types
493+
if md.Status == nil || slices.Contains(nonComponents, md.Status.Class) {
494+
return nil
495+
}
496+
497+
// Create schemas directory
498+
outputDir := filepath.Join(ymlDir, "internal", md.GeneratedPackageName, "schemas")
499+
if err := os.MkdirAll(outputDir, 0o700); err != nil {
500+
return fmt.Errorf("failed to create schemas directory: %w", err)
501+
}
502+
503+
// Create analyzer and generator
504+
analyzer := schemagen.NewPackageAnalyzer(ymlDir)
505+
generator := schemagen.NewSchemaGenerator(outputDir, analyzer)
506+
507+
// Generate schema (config type is auto-detected)
508+
return generator.GenerateSchema(md.Status.Class, md.Type, "")
509+
}

cmd/mdatagen/internal/command_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,3 +820,40 @@ func Tracer(settings component.TelemetrySettings) trace.Tracer {
820820
})
821821
}
822822
}
823+
824+
func TestGenerateSchema_Skipped(t *testing.T) {
825+
tests := []struct {
826+
name string
827+
md Metadata
828+
}{
829+
{
830+
name: "schema is nil",
831+
md: Metadata{
832+
Type: "test",
833+
Status: &Status{Class: "exporter"},
834+
},
835+
},
836+
{
837+
name: "explicitly disabled",
838+
md: Metadata{
839+
Type: "test",
840+
Status: &Status{Class: "exporter"},
841+
Schema: &SchemaConfig{Enabled: false},
842+
},
843+
},
844+
{
845+
name: "non-component type",
846+
md: Metadata{
847+
Type: "test",
848+
Status: &Status{Class: "cmd"},
849+
Schema: &SchemaConfig{Enabled: true},
850+
},
851+
},
852+
}
853+
for _, tt := range tests {
854+
t.Run(tt.name, func(t *testing.T) {
855+
err := generateSchema(tt.md, t.TempDir())
856+
require.NoError(t, err)
857+
})
858+
}
859+
}

cmd/mdatagen/internal/loader_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ func TestLoadMetadata(t *testing.T) {
5151
Description: "This receiver is used for testing purposes to check the output of mdatagen.",
5252
SemConvVersion: "1.38.0",
5353
PackageName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplereceiver",
54+
Schema: &SchemaConfig{
55+
Enabled: true,
56+
},
5457
Status: &Status{
5558
DisableCodeCov: true,
5659
Class: "receiver",

cmd/mdatagen/internal/metadata.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ type Metadata struct {
5252
Tests Tests `mapstructure:"tests"`
5353
// PackageName is the name of the package where the component is defined.
5454
PackageName string `mapstructure:"package_name"`
55+
// Schema holds configuration for JSON schema generation.
56+
Schema *SchemaConfig `mapstructure:"schema"`
5557
}
5658

5759
func (md Metadata) GetCodeCovComponentID() string {
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package samplereceiver // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplereceiver"
5+
6+
import (
7+
"time"
8+
9+
"go.opentelemetry.io/collector/component"
10+
"go.opentelemetry.io/collector/config/configopaque"
11+
"go.opentelemetry.io/collector/config/configoptional"
12+
)
13+
14+
var _ component.Factory = (*AnotherStruct)(nil)
15+
16+
type AnotherStruct struct{}
17+
18+
func (a AnotherStruct) Type() component.Type {
19+
// TODO implement me
20+
panic("implement me")
21+
}
22+
23+
func (a AnotherStruct) CreateDefaultConfig() component.Config {
24+
// TODO implement me
25+
panic("implement me")
26+
}
27+
28+
var _ component.Config = (*MyConfig)(nil)
29+
30+
type CustomString string
31+
32+
// NetworkConfig holds network configuration that should be squashed into parent.
33+
type NetworkConfig struct {
34+
// Host is the network host.
35+
Host string `mapstructure:"host"`
36+
37+
// Port is the network port.
38+
Port int `mapstructure:"port"`
39+
}
40+
41+
// MyConfig defines configuration for the sample exporter used to test schema generation.
42+
type MyConfig struct {
43+
// Network is squashed into the parent config.
44+
Network NetworkConfig `mapstructure:",squash"`
45+
46+
// ID is the component identifier.
47+
ID component.ID `mapstructure:"id"`
48+
49+
// Endpoint is the target URL to send data to.
50+
Endpoint string `mapstructure:"endpoint"`
51+
52+
// CustomString is a custom string.
53+
CustomString CustomString `mapstructure:"custom_string"`
54+
55+
// Timeout is the maximum time to wait for a response.
56+
Timeout time.Duration `mapstructure:"timeout"`
57+
58+
// StartTime is the time when the receiver should start collecting data.
59+
StartTime time.Time `mapstructure:"start_time"`
60+
61+
// Enabled controls whether the exporter is active.
62+
Enabled bool `mapstructure:"enabled"`
63+
64+
// BatchSize is the number of items to send in each batch.
65+
BatchSize int `mapstructure:"batch_size"`
66+
67+
// Headers are additional headers to include in requests.
68+
Headers map[string]string `mapstructure:"headers"`
69+
70+
// Retry contains retry configuration.
71+
Retry RetryConfig `mapstructure:"retry"`
72+
73+
// Tags are optional tags to attach.
74+
Tags []string `mapstructure:"tags"`
75+
76+
// APIKey is a secret API key (opaque string).
77+
APIKey configopaque.String `mapstructure:"api_key"`
78+
79+
// OptionalRetry is an optional retry configuration.
80+
OptionalRetry configoptional.Optional[RetryConfig] `mapstructure:"optional_retry"`
81+
82+
// Secrets is a list of secret key-value pairs.
83+
Secrets configopaque.MapList `mapstructure:"secrets"`
84+
85+
// Endpoints is a list of endpoint configurations.
86+
Endpoints []EndpointConfig `mapstructure:"endpoints"`
87+
88+
// InternalState is an internal field that should be excluded from the schema.
89+
InternalState string `mapstructure:"-"`
90+
}
91+
92+
// EndpointConfig holds configuration for a single endpoint.
93+
type EndpointConfig struct {
94+
// URL is the endpoint URL.
95+
URL string `mapstructure:"url"`
96+
97+
// Priority is the endpoint priority.
98+
Priority int `mapstructure:"priority"`
99+
}
100+
101+
// RetryConfig holds retry settings.
102+
type RetryConfig struct {
103+
// MaxRetries is the maximum number of retries.
104+
MaxRetries int `mapstructure:"max_retries"`
105+
106+
// InitialInterval is the initial retry interval.
107+
InitialInterval time.Duration `mapstructure:"initial_interval"`
108+
}

cmd/mdatagen/internal/samplereceiver/factory.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
func NewFactory() receiver.Factory {
1919
return receiver.NewFactory(
2020
metadata.Type,
21-
func() component.Config { return &struct{}{} },
21+
func() component.Config { return &MyConfig{} },
2222
receiver.WithTraces(createTraces, metadata.TracesStability),
2323
receiver.WithMetrics(createMetrics, metadata.MetricsStability),
2424
receiver.WithLogs(createLogs, metadata.LogsStability))

0 commit comments

Comments
 (0)