Skip to content

Commit

Permalink
Generate config Go code from schema
Browse files Browse the repository at this point in the history
  • Loading branch information
ptodev committed Aug 13, 2024
1 parent bdcf614 commit 4c62d16
Show file tree
Hide file tree
Showing 39 changed files with 1,351 additions and 944 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ gofmt:
gotidy:
@$(MAKE) for-all-target TARGET="tidy"

# TODO: Make sure the CI fails if the config.go code is out of date.
.PHONY: gogenerate
gogenerate:
cd cmd/mdatagen && $(GOCMD) install .
Expand Down
164 changes: 164 additions & 0 deletions cmd/mdatagen/configgen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/atombender/go-jsonschema/pkg/generator"
"github.com/atombender/go-jsonschema/pkg/schemas"
)

const (
//TODO: Get rid of this?
CONFIG_NAME = "config"
)

// GenerateConfig generates a "config.go", as well as any other Go files which "config.go" depends on.
// The inputs are:
// * "goPkgName" is the Go package at the top of the "config.go" file. For example, "batchprocessor".
// * "dir" is the location where the "config.go" file will be written. For example, "./processor/batchprocessor".
// * "conf" is the schema for "config.go". It is a "map[string]any".
//
// The output is a map, where:
// * The key is the absolute path to the file which must be written.
// * The value is the content of the file.
func GenerateConfig(goPkgName string, dir string, conf any) (map[string]string, error) {
// load config
jsonBytes, err := json.Marshal(conf)
if err != nil {
return nil, fmt.Errorf("failed loading config %w", err)
}
var schema schemas.Schema
if err := json.Unmarshal(jsonBytes, &schema); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
}

// defaultOutputName := "config.go"
defaultOutputDir, err := filepath.Abs(dir)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path for %s: %w", dir, err)
}
defaultOutputNameDest := filepath.Join(defaultOutputDir, "config.go")

// TODO: Make this configurable?
repoRootDir := "../../"

// TODO: Make this configurable. Or find a way to get rid of this mapping?
schemaMappings := []generator.SchemaMapping{
generator.SchemaMapping{
SchemaID: "opentelemetry.io/collector/exporter/exporterhelper/queue_sender",
PackageName: "go.opentelemetry.io/collector/exporter/exporterhelper",
OutputName: "./exporter/exporterhelper/queue_sender.go",
},
generator.SchemaMapping{
SchemaID: "opentelemetry.io/collector/config/configretry/backoff/retry_on_failure",
PackageName: "go.opentelemetry.io/collector/config/configretry",
OutputName: "./config/configretry/backoff.go",
},
generator.SchemaMapping{
SchemaID: "opentelemetry.io/collector/config/configtelemetry/configtelemetry",
PackageName: "go.opentelemetry.io/collector/config/configtelemetry",
OutputName: "./config/configtelemetry/configtelemetry.go",
},
generator.SchemaMapping{
SchemaID: "opentelemetry.io/collector/config/confighttp/confighttp",
PackageName: "go.opentelemetry.io/collector/config/confighttp",
OutputName: "./config/confighttp/confighttp.go",
},
generator.SchemaMapping{
SchemaID: "opentelemetry.io/collector/exporter/exporterhelper/timeout_sender",
PackageName: "go.opentelemetry.io/collector/exporter/exporterhelper",
OutputName: "./exporter/exporterhelper/timeout_sender.go",
},
}
for i := range schemaMappings {
if schemaMappings[i].OutputName != "" {
// The file paths in the schema mappings are relative to the repo root.
// Make the paths absolute.
relFilePath := filepath.Clean(filepath.Join(repoRootDir, schemaMappings[i].OutputName))
absFilePath, err := filepath.Abs(relFilePath)
absFilePath = filepath.Clean(absFilePath)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path for %s: %w", schemaMappings[i].OutputName, err)
}
schemaMappings[i].OutputName = absFilePath
}
}

// init generator
cfg := generator.Config{
Warner: func(message string) {
logf("Warning: %s", message)
},
DefaultPackageName: goPkgName,
DefaultOutputName: defaultOutputNameDest,
StructNameFromTitle: true,
Tags: []string{"mapstructure"},
SchemaMappings: schemaMappings,
YAMLExtensions: []string{".yaml", ".yml"},
// YAMLPackage: "gopkg.in/yaml.v3",
}

generator, err := generator.New(cfg)
if err != nil {
return nil, fmt.Errorf("failed to create generator: %w", err)
}
if err = generator.AddFile(CONFIG_NAME, &schema); err != nil {
return nil, fmt.Errorf("failed to add config: %w", err)
}

// hasUnsupportedValidations := len(generator.NotSupportedValidations) > 0

// tplVars := struct {
// ValidatorFuncName string
// }{
// ValidatorFuncName: "Validate",
// }
// if hasUnsupportedValidations {
// tplVars.ValidatorFuncName = "ValidateHelper"
// }

// tpl := `
// func (cfg *Config){{.ValidatorFuncName}}() error {
// b, err := json.Marshal(cfg)
// if err != nil {
// return err
// }
// var config Config
// if err := json.Unmarshal(b, &config); err != nil {
// return err
// }
// return nil
// }`
// tmpl, err := template.New("validator").Parse(tpl)
// if err != nil {
// return fmt.Errorf("failed to parse template: %w", err)
// }

output := make(map[string]string)
for sourceName, source := range generator.Sources() {
fmt.Printf("Writing to %s\n", sourceName)
// buf := bytes.NewBufferString("")
// if err = tmpl.Execute(buf, tplVars); err != nil {
// return fmt.Errorf("failed to execute template: %w", err)
// }
// only write custom validation if there are no unsupported validations
// source = append(source, []byte(tpl)...)
// source = append(source, buf.Bytes()...)
// output[sourceName] = string(source)
output[sourceName] = string(source)
}
fmt.Println("done")
return output, nil
}

func logf(format string, args ...interface{}) {
fmt.Fprint(os.Stderr, "go-jsonschema: ")
fmt.Fprintf(os.Stderr, format, args...)
fmt.Fprint(os.Stderr, "\n")
}
39 changes: 39 additions & 0 deletions cmd/mdatagen/configgen_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"os"
"path/filepath"
"testing"

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

// TODO: This test doesn't compile due to recent refactoring. Fix it.
func TestXxx(t *testing.T) {
inputDir := `./testdata/config_gen/input_schema`
outputDir := `./testdata/config_gen/expected_golang_output/`

inputFiles, err := os.ReadDir(inputDir)
require.NoError(t, err)

for _, inputFile := range inputFiles {
if inputFile.IsDir() {
continue
}

md, err := loadMetadata(filepath.Join(inputDir, inputFile.Name()))
require.NoError(t, err)

err = GenerateConfig(md.Config, buf)
require.NoError(t, err)

actual := buf.String()

expectedOutputFile := filepath.Join(outputDir, inputFile.Name())
var expectedOutput []byte
expectedOutput, err = os.ReadFile(expectedOutputFile)
require.NoError(t, err)

require.Equal(t, string(expectedOutput), actual, "actual", actual)
}
}
17 changes: 16 additions & 1 deletion cmd/mdatagen/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module go.opentelemetry.io/collector/cmd/mdatagen

go 1.21.0
go 1.22

toolchain go1.22.5

require (
github.com/google/go-cmp v0.6.0
Expand All @@ -23,30 +25,40 @@ require (
golang.org/x/text v0.17.0
)

require golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect

require (
github.com/atombender/go-jsonschema v0.16.0
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
github.com/goccy/go-yaml v1.12.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/knadh/koanf/providers/confmap v0.1.0 // indirect
github.com/knadh/koanf/v2 v2.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/sanity-io/litter v1.5.5 // indirect
go.opentelemetry.io/collector/consumer/consumerprofiles v0.106.1 // indirect
go.opentelemetry.io/collector/featuregate v1.12.0 // indirect
go.opentelemetry.io/collector/internal/globalgates v0.106.1 // indirect
Expand All @@ -55,6 +67,7 @@ require (
go.opentelemetry.io/otel/exporters/prometheus v0.50.0 // indirect
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.22.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
Expand Down Expand Up @@ -100,3 +113,5 @@ replace go.opentelemetry.io/collector/internal/globalgates => ../../internal/glo
replace go.opentelemetry.io/collector/consumer/consumerprofiles => ../../consumer/consumerprofiles

replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest

replace github.com/atombender/go-jsonschema => github.com/ptodev/go-jsonschema v0.0.0-20240813163654-5518ba93ee84
36 changes: 36 additions & 0 deletions cmd/mdatagen/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cmd/mdatagen/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ type metadata struct {
ScopeName string `mapstructure:"scope_name"`
// ShortFolderName is the shortened folder name of the component, removing class if present
ShortFolderName string `mapstructure:"-"`
// Config is the component configuration.
Config any `mapstructure:"config"`

Tests tests `mapstructure:"tests"`
}
Expand Down
Loading

0 comments on commit 4c62d16

Please sign in to comment.