Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 7 additions & 0 deletions cmd/mdatagen/internal/cfggen/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,10 @@ func (r *Ref) String() string {
func (r *Ref) CacheKey() string {
return r.String()
}

func LocalizeRef(refPath, importRootPath string) string {
if importRootPath == "" || !strings.HasPrefix(refPath, importRootPath+"/") {
return refPath
}
return strings.TrimPrefix(refPath, importRootPath)
}
28 changes: 28 additions & 0 deletions cmd/mdatagen/internal/cfggen/namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,31 @@ func TestNamespaceOf_NoSlash(t *testing.T) {
result := namespaceOf("noslash")
require.Empty(t, result)
}

func TestLocalizeRef(t *testing.T) {
tests := []struct {
name string
refPath string
importRootPath string
expected string
}{
{
name: "same root collector ref becomes local absolute",
refPath: "go.opentelemetry.io/collector/filter.config",
importRootPath: "go.opentelemetry.io/collector",
expected: "/filter.config",
},
{
name: "different root ref stays external",
refPath: "go.opentelemetry.io/collector/filter.config",
importRootPath: "github.com/open-telemetry/opentelemetry-collector-contrib",
expected: "go.opentelemetry.io/collector/filter.config",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, LocalizeRef(tt.refPath, tt.importRootPath))
})
}
}
22 changes: 12 additions & 10 deletions cmd/mdatagen/internal/cfggen/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,22 @@ const (
)

type Resolver struct {
pkgID string
class string
name string
loader Loader
pkgID string
class string
name string
importRootPath string
loader Loader
}

func NewResolver(pkgID, class, name, dir string) *Resolver {
func NewResolver(pkgID, class, name, dir, importRootPath string) *Resolver {
loader := NewLoader(dir)

return &Resolver{
loader: loader,
pkgID: pkgID,
class: class,
name: name,
loader: loader,
pkgID: pkgID,
class: class,
name: name,
importRootPath: importRootPath,
}
}

Expand Down Expand Up @@ -178,7 +180,7 @@ func (r *Resolver) resolveSchema(root, current, target *ConfigMetadata, origin *
// The origin parameter tracks which namespace the current schema was loaded from,
// enabling local refs in remotely-fetched schemas to be converted to external refs.
func (r *Resolver) resolveRef(root, current *ConfigMetadata, origin *Ref) (*ConfigMetadata, error) {
ref := WithOrigin(current.Ref, origin)
ref := WithOrigin(LocalizeRef(current.Ref, r.importRootPath), origin)

if err := ref.Validate(); err != nil {
return nil, fmt.Errorf("invalid reference format %q: %w", current.Ref, err)
Expand Down
45 changes: 37 additions & 8 deletions cmd/mdatagen/internal/cfggen/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,14 +735,43 @@ func TestResolver_ResolveSchema_ParentRelativeRefWithOrigin(t *testing.T) {
require.Equal(t, "TLS settings", tls.Description)
}

func TestNewResolver(t *testing.T) {
dir := t.TempDir()
r := NewResolver("go.opentelemetry.io/collector/receiver/otlp", "receiver", "otlp", dir)
require.NotNil(t, r)
require.Equal(t, "go.opentelemetry.io/collector/receiver/otlp", r.pkgID)
require.Equal(t, "receiver", r.class)
require.Equal(t, "otlp", r.name)
require.NotNil(t, r.loader)
func TestResolver_ResolveSchema_SameRootExternalRefLocalized(t *testing.T) {
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
"/config/confighttp.client_config": {
Defs: map[string]*ConfigMetadata{
"client_config": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"endpoint": {Type: "string"},
},
},
},
},
},
}

resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/receiver/otlpreceiver",
class: "receiver",
name: "otlp",
importRootPath: "go.opentelemetry.io/collector",
loader: ml,
}

src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"http": {
Ref: "go.opentelemetry.io/collector/config/confighttp.client_config",
},
},
}

result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.Equal(t, "object", result.Properties["http"].Type)
require.Equal(t, "string", result.Properties["http"].Properties["endpoint"].Type)
}

func TestResolver_ResolveSchema_UnknownNamespaceFallback(t *testing.T) {
Expand Down
43 changes: 25 additions & 18 deletions cmd/mdatagen/internal/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ func run(ymlPath string) error {

ymlDir := filepath.Dir(ymlPath)
packageName := filepath.Base(ymlDir)
importRootPath, err := helpers.RootPackage(ymlDir)
if err != nil {
return fmt.Errorf("unable to determine import root path: %w", err)
}

raw, readErr := os.ReadFile(filepath.Clean(ymlPath))
if readErr != nil {
Expand All @@ -104,7 +108,7 @@ func run(ymlPath string) error {
if !slices.Contains(nonComponents, md.Status.Class) {
toGenerate[filepath.Join(tmplDir, "status.go.tmpl")] = filepath.Join(codeDir, "generated_status.go")
err = generateFile(filepath.Join(tmplDir, "component_test.go.tmpl"),
filepath.Join(ymlDir, "generated_component_test.go"), md, packageName)
filepath.Join(ymlDir, "generated_component_test.go"), md, packageName, importRootPath)
if err != nil {
return err
}
Expand All @@ -124,7 +128,7 @@ func run(ymlPath string) error {
}

err = generateFile(filepath.Join(tmplDir, "package_test.go.tmpl"),
filepath.Join(ymlDir, "generated_package_test.go"), md, packageName)
filepath.Join(ymlDir, "generated_package_test.go"), md, packageName, importRootPath)
if err != nil {
return err
}
Expand All @@ -133,7 +137,7 @@ func run(ymlPath string) error {
err = inlineReplace(
filepath.Join(tmplDir, "readme.md.tmpl"),
filepath.Join(ymlDir, "README.md"),
md, statusStart, statusEnd, md.GeneratedPackageName)
md, statusStart, statusEnd, md.GeneratedPackageName, importRootPath)
if err != nil {
return err
}
Expand Down Expand Up @@ -225,19 +229,19 @@ func run(ymlPath string) error {
}

for tmpl, dst := range toGenerate {
if err := generateFile(tmpl, dst, md, md.GeneratedPackageName); err != nil {
if err := generateFile(tmpl, dst, md, md.GeneratedPackageName, importRootPath); err != nil {
return err
}
}

if err := generateConfigFiles(md, ymlDir); err != nil {
if err := generateConfigFiles(md, ymlDir, importRootPath); err != nil {
return fmt.Errorf("failed to generate config files: %w", err)
}

return nil
}

func getTemplateFuncMap(md Metadata) template.FuncMap {
func getTemplateFuncMap(md Metadata, importRootPath string) template.FuncMap {
return template.FuncMap{
"publicVar": func(s string) (string, error) {
return helpers.FormatIdentifier(s, true)
Expand Down Expand Up @@ -344,6 +348,9 @@ func getTemplateFuncMap(md Metadata) template.FuncMap {
"toLowerCamelCase": func(s string) string {
return joinCamelCase(strings.Split(s, "_"), false)
},
"schemaRef": func(ref string) string {
return cfggen.LocalizeRef(ref, importRootPath)
},
"inc": func(i int) int { return i + 1 },
"distroURL": distroURL,
"isExporter": func() bool {
Expand Down Expand Up @@ -406,21 +413,21 @@ func templatize(tmplFile string, funcMap template.FuncMap) *template.Template {
ParseFS(TemplateFS, "templates/helper.tmpl", strings.ReplaceAll(tmplFile, "\\", "/")))
}

func executeTemplate(tmplFile string, md Metadata, goPackage string, fns template.FuncMap) ([]byte, error) {
func executeTemplate(tmplFile string, md Metadata, goPackage, importRootPath string, fns template.FuncMap) ([]byte, error) {
tmpl := templatize(tmplFile, fns)
buf := bytes.Buffer{}

if err := tmpl.Execute(&buf, TemplateContext{Metadata: md, Package: goPackage}); err != nil {
if err := tmpl.Execute(&buf, TemplateContext{Metadata: md, Package: goPackage, ImportRootPath: importRootPath}); err != nil {
return []byte{}, fmt.Errorf("failed executing template: %w", err)
}
return buf.Bytes(), nil
}

func generateFile(tmplFile, outputFile string, md Metadata, goPackage string) error {
return generateFileWithFns(tmplFile, outputFile, md, goPackage, getTemplateFuncMap(md))
func generateFile(tmplFile, outputFile string, md Metadata, goPackage, importRootPath string) error {
return generateFileWithFns(tmplFile, outputFile, md, goPackage, importRootPath, getTemplateFuncMap(md, importRootPath))
}

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

buf, err := executeTemplate(tmplFile, md, goPackage, getTemplateFuncMap(md))
buf, err := executeTemplate(tmplFile, md, goPackage, importRootPath, getTemplateFuncMap(md, importRootPath))
if err != nil {
return err
}
Expand All @@ -449,12 +456,12 @@ func inlineReplace(tmplFile, outputFile string, md Metadata, start, end, goPacka
return nil
}

func generateFileWithFns(tmplFile, outputFile string, md Metadata, goPackage string, fns template.FuncMap) error {
func generateFileWithFns(tmplFile, outputFile string, md Metadata, goPackage, importRootPath string, fns template.FuncMap) error {
if err := os.Remove(outputFile); err != nil && !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("unable to remove generated file %q: %w", outputFile, err)
}

result, err := executeTemplate(tmplFile, md, goPackage, fns)
result, err := executeTemplate(tmplFile, md, goPackage, importRootPath, fns)
if err != nil {
return err
}
Expand Down Expand Up @@ -534,9 +541,9 @@ func validateYAMLKeyOrder(raw []byte) error {
return nil
}

func generateConfigFiles(md Metadata, mdDir string) error {
func generateConfigFiles(md Metadata, mdDir, importRootPath string) error {
if md.Config != nil {
resolver := cfggen.NewResolver(md.PackageName, md.Status.Class, md.Type, mdDir)
resolver := cfggen.NewResolver(md.PackageName, md.Status.Class, md.Type, mdDir, importRootPath)
resolvedSchema, err := resolver.ResolveSchema(md.Config)
if err != nil {
return fmt.Errorf("failed to resolve config schema: %w", err)
Expand Down Expand Up @@ -564,8 +571,8 @@ func generateConfigGoStruct(md Metadata, outputDir string) error {
tmplFile := filepath.Join("templates", "config_from_cfggen.go.tmpl")
dstFile := filepath.Join(outputDir, "generated_config.go")

fns := cfggen.WithCfgFns(getTemplateFuncMap(md), rootPkg, md.PackageName)
return generateFileWithFns(tmplFile, dstFile, md, packageName, fns)
fns := cfggen.WithCfgFns(getTemplateFuncMap(md, rootPkg), rootPkg, md.PackageName)
return generateFileWithFns(tmplFile, dstFile, md, packageName, rootPkg, fns)
}

func joinCamelCase(parts []string, exported bool) string {
Expand Down
44 changes: 38 additions & 6 deletions cmd/mdatagen/internal/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ func TestGenerateConfigFiles(t *testing.T) {

gitInit(t, root)
require.NoError(t, os.WriteFile(filepath.Join(root, "go.mod"), []byte("module testmodule\n"), 0o600))
err := generateConfigFiles(tt.md, tmpdir)
err := generateConfigFiles(tt.md, tmpdir, "testmodule")
if tt.wantErr {
require.Error(t, err)
return
Expand Down Expand Up @@ -564,7 +564,7 @@ func TestGenerateConfigFiles_GoStructError(t *testing.T) {
Status: &Status{Class: "receiver"},
Config: &cfggen.ConfigMetadata{Type: "object"},
}
err := generateConfigFiles(md, t.TempDir())
err := generateConfigFiles(md, t.TempDir(), "testmodule")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to generate config Go struct")
}
Expand All @@ -580,7 +580,7 @@ func TestGenerateConfigFiles_WriteError(t *testing.T) {
Type: "object",
},
}
err := generateConfigFiles(md, "/nonexistent/path/that/does/not/exist")
err := generateConfigFiles(md, "/nonexistent/path/that/does/not/exist", "testmodule")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to write config schema")
}
Expand Down Expand Up @@ -855,7 +855,7 @@ Some info about a component
readmeFile := filepath.Join(tmpdir, "README.md")
require.NoError(t, os.WriteFile(readmeFile, []byte(tt.markdown), 0o600))

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

require.FileExists(t, filepath.Join(tmpdir, "README.md"))
Expand Down Expand Up @@ -977,7 +977,7 @@ const (
t.Run(tt.name, func(t *testing.T) {
tmpdir := t.TempDir()
err := generateFile("templates/status.go.tmpl",
filepath.Join(tmpdir, "generated_status.go"), tt.md, "metadata")
filepath.Join(tmpdir, "generated_status.go"), tt.md, "metadata", "go.opentelemetry.io/collector")
require.NoError(t, err)
actual, err := os.ReadFile(filepath.Clean(filepath.Join(tmpdir, "generated_status.go")))
require.NoError(t, err)
Expand Down Expand Up @@ -1063,7 +1063,7 @@ func Tracer(settings component.TelemetrySettings) trace.Tracer {
t.Run(tt.name, func(t *testing.T) {
tmpdir := t.TempDir()
err := generateFile("templates/telemetry.go.tmpl",
filepath.Join(tmpdir, "generated_telemetry.go"), tt.md, "metadata")
filepath.Join(tmpdir, "generated_telemetry.go"), tt.md, "metadata", "go.opentelemetry.io/collector")
require.NoError(t, err)
actual, err := os.ReadFile(filepath.Clean(filepath.Join(tmpdir, "generated_telemetry.go")))
require.NoError(t, err)
Expand All @@ -1072,6 +1072,38 @@ func Tracer(settings component.TelemetrySettings) trace.Tracer {
}
}

func TestGenerateConfigSchema_LocalizesSameRootRefs(t *testing.T) {
enabled := true
md := Metadata{
Type: "foo",
ResourceAttributes: map[AttributeName]Attribute{
"resource.attr": {
Description: "resource attr",
EnabledPtr: &enabled,
FullName: "resource.attr",
},
},
Events: map[EventName]Event{
"default.event": {
Signal: Signal{
Enabled: true,
Description: "event description",
},
},
},
}

tmpdir := t.TempDir()
outputFile := filepath.Join(tmpdir, "config.schema.yaml")
err := generateFile("templates/config.schema.yaml.tmpl", outputFile, md, "metadata", "go.opentelemetry.io/collector")
require.NoError(t, err)

actual, err := os.ReadFile(filepath.Clean(outputFile))
require.NoError(t, err)
require.Contains(t, string(actual), "$ref: /filter.config")
require.NotContains(t, string(actual), "$ref: go.opentelemetry.io/collector/filter.config")
}

func gitInit(t *testing.T, dir string) {
t.Helper()
cmd := exec.Command("git", "init")
Expand Down
2 changes: 2 additions & 0 deletions cmd/mdatagen/internal/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type TemplateContext struct {
Metadata
// Package name for generated code.
Package string
// ImportRootPath is the repo-local import prefix used to localize same-tree schema references.
ImportRootPath string
}

func LoadMetadata(filePath string) (Metadata, error) {
Expand Down
Loading
Loading