Skip to content

Commit

Permalink
mdatagen: support custom package name (#11232)
Browse files Browse the repository at this point in the history
#### Description
This PR adds support for a new ~~`--package_name` flag~~
`generated_package_name` config field to allow mdatagen to generate
packages with names other than `metadata`.

#### Link to tracking issue
Fixes #11231 

#### Testing
* Unit tests
* `go install`ing this branch and using it in a test scenario with two
yaml files and generating two packages.

#### Documentation
  • Loading branch information
braydonk authored Oct 10, 2024
1 parent 68f0264 commit 8211777
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 31 deletions.
25 changes: 25 additions & 0 deletions .chloggen/mdatagen_package_gen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
component: mdatagen

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Added generated_package_name config field to support custom generated package name.

# One or more tracking issues or pull requests related to the change
issues: [11231]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user]
27 changes: 26 additions & 1 deletion cmd/mdatagen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ An example of how this generated documentation looks can be found in [documentat
## Using the Metadata Generator

In order for a component to benefit from the metadata generator (`mdatagen`) these requirements need to be met:
1. A `metadata.yaml` file containing the metadata needs to be included in the component
1. A yaml file containing the metadata that needs to be included in the component
2. The component should declare a `go:generate mdatagen` directive which tells `mdatagen` what to generate

As an example, here is a minimal `metadata.yaml` for the [OTLP receiver](https://github.com/open-telemetry/opentelemetry-collector/tree/main/receiver/otlpreceiver):
Expand Down Expand Up @@ -55,6 +55,31 @@ Below are some more examples that can be used for reference:

You can run `cd cmd/mdatagen && $(GOCMD) install .` to install the `mdatagen` tool in `GOBIN` and then run `mdatagen metadata.yaml` to generate documentation for a specific component or you can run `make generate` to generate documentation for all components.

### Generate multiple metadata packages

By default, `mdatagen` will generate a package called `metadata` in the `internal` directory. If you want to generate a package with a different name, you can use the `generated_package_name` configuration field to provide an alternate name.

```yaml
type: otlp
generated_package_name: customname
status:
class: receiver
stability:
beta: [logs]
stable: [metrics, traces]
```

The most common scenario for this would be making major changes to a receiver's metadata without breaking what exists. In this scenario, `mdatagen` could produce separate packages for different metadata specs in the same receiver:

```go
//go:generate mdatagen metadata.yaml
//go:generate mdatagen custom.yaml
package main
```

With two different packages generated, the behaviour for which metadata is used can be easily controlled via featuregate or a similar mechanism.

## Contributing to the Metadata Generator

The code for generating the documentation can be found in [loader.go](./internal/loader.go) and the templates for rendering the documentation can be found in [templates](./internal/templates).
Expand Down
10 changes: 5 additions & 5 deletions cmd/mdatagen/internal/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func run(ymlPath string) error {

tmplDir := "templates"

codeDir := filepath.Join(ymlDir, "internal", "metadata")
codeDir := filepath.Join(ymlDir, "internal", md.GeneratedPackageName)
if err = os.MkdirAll(codeDir, 0700); err != nil {
return fmt.Errorf("unable to create output directory %q: %w", codeDir, err)
}
Expand All @@ -99,7 +99,7 @@ func run(ymlPath string) error {
if err = inlineReplace(
filepath.Join(tmplDir, "readme.md.tmpl"),
filepath.Join(ymlDir, "README.md"),
md, statusStart, statusEnd); err != nil {
md, statusStart, statusEnd, md.GeneratedPackageName); err != nil {
return err
}
}
Expand Down Expand Up @@ -151,7 +151,7 @@ func run(ymlPath string) error {
}

for tmpl, dst := range toGenerate {
if err = generateFile(tmpl, dst, md, "metadata"); err != nil {
if err = generateFile(tmpl, dst, md, md.GeneratedPackageName); err != nil {
return err
}
}
Expand Down Expand Up @@ -371,7 +371,7 @@ func executeTemplate(tmplFile string, md Metadata, goPackage string) ([]byte, er
return buf.Bytes(), nil
}

func inlineReplace(tmplFile string, outputFile string, md Metadata, start string, end string) error {
func inlineReplace(tmplFile string, outputFile string, md Metadata, start string, end string, goPackage string) error {
var readmeContents []byte
var err error
if readmeContents, err = os.ReadFile(outputFile); err != nil { // nolint: gosec
Expand All @@ -387,7 +387,7 @@ func inlineReplace(tmplFile string, outputFile string, md Metadata, start string
md.GithubProject = "open-telemetry/opentelemetry-collector-contrib"
}

buf, err := executeTemplate(tmplFile, md, "metadata")
buf, err := executeTemplate(tmplFile, md, goPackage)
if err != nil {
return err
}
Expand Down
42 changes: 25 additions & 17 deletions cmd/mdatagen/internal/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ func TestRunContents(t *testing.T) {
wantConfigGenerated: true,
wantStatusGenerated: true,
},
{
yml: "custom_generated_package_name.yaml",
wantStatusGenerated: true,
},
}
for _, tt := range tests {
t.Run(tt.yml, func(t *testing.T) {
Expand All @@ -151,58 +155,62 @@ foo
}
require.NoError(t, err)

md, err := LoadMetadata(metadataFile)
require.NoError(t, err)
generatedPackageDir := filepath.Join("internal", md.GeneratedPackageName)

var contents []byte
if tt.wantMetricsGenerated {
require.FileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_metrics.go"))
require.FileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_metrics_test.go"))
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_metrics.go"))
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_metrics_test.go"))
require.FileExists(t, filepath.Join(tmpdir, "documentation.md"))
contents, err = os.ReadFile(filepath.Join(tmpdir, "internal/metadata/generated_metrics.go")) // nolint: gosec
contents, err = os.ReadFile(filepath.Join(tmpdir, generatedPackageDir, "generated_metrics.go")) // nolint: gosec
require.NoError(t, err)
if tt.wantMetricsContext {
require.Contains(t, string(contents), "\"context\"")
} else {
require.NotContains(t, string(contents), "\"context\"")
}
} else {
require.NoFileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_metrics.go"))
require.NoFileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_metrics_test.go"))
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_metrics.go"))
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_metrics_test.go"))
}

if tt.wantConfigGenerated {
require.FileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_config.go"))
require.FileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_config_test.go"))
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_config.go"))
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_config_test.go"))
} else {
require.NoFileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_config.go"))
require.NoFileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_config_test.go"))
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_config.go"))
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_config_test.go"))
}

if tt.wantTelemetryGenerated {
require.FileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_telemetry.go"))
require.FileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_telemetry_test.go"))
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_telemetry.go"))
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_telemetry_test.go"))
require.FileExists(t, filepath.Join(tmpdir, "documentation.md"))
contents, err = os.ReadFile(filepath.Join(tmpdir, "internal/metadata/generated_telemetry.go")) // nolint: gosec
contents, err = os.ReadFile(filepath.Join(tmpdir, generatedPackageDir, "generated_telemetry.go")) // nolint: gosec
require.NoError(t, err)
if tt.wantMetricsContext {
require.Contains(t, string(contents), "\"context\"")
} else {
require.NotContains(t, string(contents), "\"context\"")
}
} else {
require.NoFileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_telemetry.go"))
require.NoFileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_telemetry_test.go"))
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_telemetry.go"))
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_telemetry_test.go"))
}

if !tt.wantMetricsGenerated && !tt.wantTelemetryGenerated && !tt.wantResourceAttributesGenerated {
require.NoFileExists(t, filepath.Join(tmpdir, "documentation.md"))
}

if tt.wantStatusGenerated {
require.FileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_status.go"))
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_status.go"))
contents, err = os.ReadFile(filepath.Join(tmpdir, "README.md")) // nolint: gosec
require.NoError(t, err)
require.NotContains(t, string(contents), "foo")
} else {
require.NoFileExists(t, filepath.Join(tmpdir, "internal/metadata/generated_status.go"))
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_status.go"))
contents, err = os.ReadFile(filepath.Join(tmpdir, "README.md")) // nolint: gosec
require.NoError(t, err)
require.Contains(t, string(contents), "foo")
Expand Down Expand Up @@ -466,7 +474,7 @@ Some info about a component
readmeFile := filepath.Join(tmpdir, "README.md")
require.NoError(t, os.WriteFile(readmeFile, []byte(tt.markdown), 0600))

err := inlineReplace("templates/readme.md.tmpl", readmeFile, md, statusStart, statusEnd)
err := inlineReplace("templates/readme.md.tmpl", readmeFile, md, statusStart, statusEnd, "metadata")
require.NoError(t, err)

require.FileExists(t, filepath.Join(tmpdir, "README.md"))
Expand Down
5 changes: 5 additions & 0 deletions cmd/mdatagen/internal/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,8 @@ type Metadata struct {
Parent string `mapstructure:"parent"`
// Status information for the component.
Status *Status `mapstructure:"status"`
// The name of the package that will be generated.
GeneratedPackageName string `mapstructure:"generated_package_name"`
// Telemetry information for the component.
Telemetry telemetry `mapstructure:"telemetry"`
// SemConvVersion is a version number of OpenTelemetry semantic conventions applied to the scraped metrics.
Expand Down Expand Up @@ -330,6 +332,9 @@ func LoadMetadata(filePath string) (Metadata, error) {
return md, err
}
}
if md.GeneratedPackageName == "" {
md.GeneratedPackageName = "metadata"
}

if err = md.Validate(); err != nil {
return md, err
Expand Down
36 changes: 28 additions & 8 deletions cmd/mdatagen/internal/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ func TestLoadMetadata(t *testing.T) {
{
name: "samplereceiver/metadata.yaml",
want: Metadata{
GithubProject: "open-telemetry/opentelemetry-collector",
Type: "sample",
SemConvVersion: "1.9.0",
GithubProject: "open-telemetry/opentelemetry-collector",
GeneratedPackageName: "metadata",
Type: "sample",
SemConvVersion: "1.9.0",
Status: &Status{
Class: "receiver",
Stability: map[component.StabilityLevel][]string{
Expand Down Expand Up @@ -297,11 +298,30 @@ func TestLoadMetadata(t *testing.T) {
{
name: "testdata/parent.yaml",
want: Metadata{
Type: "subcomponent",
Parent: "parentComponent",
ScopeName: "go.opentelemetry.io/collector/cmd/mdatagen/internal",
ShortFolderName: "testdata",
Tests: tests{Host: "componenttest.NewNopHost()"},
Type: "subcomponent",
Parent: "parentComponent",
GeneratedPackageName: "metadata",
ScopeName: "go.opentelemetry.io/collector/cmd/mdatagen/internal",
ShortFolderName: "testdata",
Tests: tests{Host: "componenttest.NewNopHost()"},
},
},
{
name: "testdata/generated_package_name.yaml",
want: Metadata{
Type: "custom",
GeneratedPackageName: "customname",
ScopeName: "go.opentelemetry.io/collector/cmd/mdatagen/internal",
ShortFolderName: "testdata",
Tests: tests{Host: "componenttest.NewNopHost()"},
Status: &Status{
Class: "receiver",
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelDevelopment: {"logs"},
component.StabilityLevelBeta: {"traces"},
component.StabilityLevelStable: {"metrics"},
},
},
},
},
{
Expand Down
17 changes: 17 additions & 0 deletions cmd/mdatagen/internal/testdata/custom_generated_package_name.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
type: metricreceiver

generated_package_name: custom

status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention

tests:
skip_lifecycle: true
skip_shutdown: true
10 changes: 10 additions & 0 deletions cmd/mdatagen/internal/testdata/generated_package_name.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type: custom

generated_package_name: customname

status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
3 changes: 3 additions & 0 deletions cmd/mdatagen/metadata-schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ parent: string
# Optional: Scope name for the telemetry generated by the component. If not set, name of the go package will be used.
scope_name: string

# Optional: The name of the package that mdatagen generates. If not set, the name "metadata" will be used.
generated_package_name: string

# Required for components (Optional for subcomponents): A high-level view of the development status and use of this component
status:
# Required: The class of the component (For example receiver)
Expand Down

0 comments on commit 8211777

Please sign in to comment.