From 40f7db7462b79e0a3014e1989e3e069dc8eb556e Mon Sep 17 00:00:00 2001 From: Pablo Collins Date: Tue, 8 Jun 2021 16:39:03 -0400 Subject: [PATCH] Add support for markdown generation (#3100) This change contains a CLI tool, called 'docsgen', that generates markdown files for collector components. The markdown files present the configuration metadata extracted by the `configschema` API in a human readable form that can be used to manually configure the collector. This change also makes some modifications to the package formerly knows as `schemagen`, renaming it to `configschema` and exporting some things, because it no longer generates a schema yaml file, but rather provides an equivalent API used by docsgen. Also, this PR includes one sample, generated .md document: `receiver/otlpreceiver/config.md` --- cmd/{schemagen => configschema}/Makefile | 0 cmd/configschema/configschema/README.md | 5 + .../configschema}/comments.go | 27 +- .../configschema}/comments_test.go | 11 +- .../configschema/common_test.go} | 13 +- cmd/configschema/configschema/configs.go | 125 +++++++ .../configschema/configs_test.go} | 34 +- .../configschema/fields.go} | 72 ++-- .../configschema/fields_test.go} | 52 +-- cmd/configschema/configschema/resolver.go | 58 +++ cmd/configschema/docsgen/README.md | 44 +++ cmd/configschema/docsgen/docsgen/cli.go | 134 +++++++ cmd/configschema/docsgen/docsgen/cli_test.go | 123 +++++++ cmd/configschema/docsgen/docsgen/render.go | 76 ++++ cmd/configschema/docsgen/docsgen/template.go | 70 ++++ .../docsgen/docsgen/template_test.go} | 38 +- .../docsgen/testdata/otlp-receiver.json | 339 ++++++++++++++++++ .../docsgen/docsgen/testdata/test.tmpl | 1 + .../docsgen}/main.go | 8 +- cmd/schemagen/schemagen/cli.go | 66 ---- cmd/schemagen/schemagen/cli_all.go | 44 --- cmd/schemagen/schemagen/cli_single.go | 64 ---- cmd/schemagen/schemagen/common.go | 41 --- receiver/otlpreceiver/config.md | 91 +++++ 24 files changed, 1182 insertions(+), 354 deletions(-) rename cmd/{schemagen => configschema}/Makefile (100%) create mode 100644 cmd/configschema/configschema/README.md rename cmd/{schemagen/schemagen => configschema/configschema}/comments.go (70%) rename cmd/{schemagen/schemagen => configschema/configschema}/comments_test.go (74%) rename cmd/{schemagen/schemagen/libfor_test.go => configschema/configschema/common_test.go} (86%) create mode 100644 cmd/configschema/configschema/configs.go rename cmd/{schemagen/schemagen/cli_single_test.go => configschema/configschema/configs_test.go} (66%) rename cmd/{schemagen/schemagen/schema.go => configschema/configschema/fields.go} (70%) rename cmd/{schemagen/schemagen/schema_test.go => configschema/configschema/fields_test.go} (73%) create mode 100644 cmd/configschema/configschema/resolver.go create mode 100644 cmd/configschema/docsgen/README.md create mode 100644 cmd/configschema/docsgen/docsgen/cli.go create mode 100644 cmd/configschema/docsgen/docsgen/cli_test.go create mode 100644 cmd/configschema/docsgen/docsgen/render.go create mode 100644 cmd/configschema/docsgen/docsgen/template.go rename cmd/{schemagen/schemagen/cli_all_test.go => configschema/docsgen/docsgen/template_test.go} (52%) create mode 100644 cmd/configschema/docsgen/docsgen/testdata/otlp-receiver.json create mode 100644 cmd/configschema/docsgen/docsgen/testdata/test.tmpl rename cmd/{schemagen => configschema/docsgen}/main.go (75%) delete mode 100644 cmd/schemagen/schemagen/cli.go delete mode 100644 cmd/schemagen/schemagen/cli_all.go delete mode 100644 cmd/schemagen/schemagen/cli_single.go delete mode 100644 cmd/schemagen/schemagen/common.go create mode 100644 receiver/otlpreceiver/config.md diff --git a/cmd/schemagen/Makefile b/cmd/configschema/Makefile similarity index 100% rename from cmd/schemagen/Makefile rename to cmd/configschema/Makefile diff --git a/cmd/configschema/configschema/README.md b/cmd/configschema/configschema/README.md new file mode 100644 index 000000000000..6031b2d628fd --- /dev/null +++ b/cmd/configschema/configschema/README.md @@ -0,0 +1,5 @@ +# ConfigSchema API + +This package contains an API that can be used to introspect the configuration +struct of a collector component. It can be used to generate documentation or +tools to help users configure the collector. diff --git a/cmd/schemagen/schemagen/comments.go b/cmd/configschema/configschema/comments.go similarity index 70% rename from cmd/schemagen/schemagen/comments.go rename to cmd/configschema/configschema/comments.go index dba77c6451d5..b322d75fdf41 100644 --- a/cmd/schemagen/schemagen/comments.go +++ b/cmd/configschema/configschema/comments.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package schemagen +package configschema import ( "go/ast" @@ -23,12 +23,12 @@ import ( ) // commentsForStruct returns a map of fieldname -> comment for a struct -func commentsForStruct(v reflect.Value, env env) map[string]string { +func commentsForStruct(v reflect.Value, dr DirResolver) map[string]string { elem := v if v.Kind() == reflect.Ptr { elem = v.Elem() } - dir := packageDir(elem.Type(), env) + dir := dr.PackageDir(elem.Type()) name := trimPackage(elem) return commentsForStructName(dir, name) } @@ -48,13 +48,20 @@ func commentsForStructName(packageDir, structName string) map[string]string { comments := map[string]string{} for _, pkg := range pkgs { for _, file := range pkg.Files { - if obj, ok := file.Scope.Objects[structName]; ok { - if ts, ok := obj.Decl.(*ast.TypeSpec); ok { - if st, ok := ts.Type.(*ast.StructType); ok { - for _, field := range st.Fields.List { - if field.Doc != nil { - if name := fieldName(field); name != "" { - comments[name] = field.Doc.Text() + for _, decl := range file.Decls { + if gd, ok := decl.(*ast.GenDecl); ok { + for _, spec := range gd.Specs { + if ts, ok := spec.(*ast.TypeSpec); ok { + if ts.Name.Name == structName { + if structComments := gd.Doc.Text(); structComments != "" { + comments["_struct"] = structComments + } + if st, ok := ts.Type.(*ast.StructType); ok { + for _, field := range st.Fields.List { + if name := fieldName(field); name != "" { + comments[name] = field.Doc.Text() + } + } } } } diff --git a/cmd/schemagen/schemagen/comments_test.go b/cmd/configschema/configschema/comments_test.go similarity index 74% rename from cmd/schemagen/schemagen/comments_test.go rename to cmd/configschema/configschema/comments_test.go index deef4842dd01..c18116aea1af 100644 --- a/cmd/schemagen/schemagen/comments_test.go +++ b/cmd/configschema/configschema/comments_test.go @@ -12,19 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -package schemagen +package configschema import ( "reflect" "testing" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" ) func TestFieldComments(t *testing.T) { v := reflect.ValueOf(testStruct{}) - comments := commentsForStruct(v, testEnv()) - require.EqualValues(t, map[string]string{ - "Duration": "embedded, package qualified\n", - }, comments) + comments := commentsForStruct(v, testDR()) + assert.Equal(t, "embedded, package qualified comment\n", comments["Duration"]) + assert.Equal(t, "testStruct comment\n", comments["_struct"]) } diff --git a/cmd/schemagen/schemagen/libfor_test.go b/cmd/configschema/configschema/common_test.go similarity index 86% rename from cmd/schemagen/schemagen/libfor_test.go rename to cmd/configschema/configschema/common_test.go index 8f132af60306..74ec77fc4ce2 100644 --- a/cmd/schemagen/schemagen/libfor_test.go +++ b/cmd/configschema/configschema/common_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package schemagen +package configschema import "time" @@ -20,12 +20,13 @@ type testPerson struct { Name string } +// testStruct comment type testStruct struct { One string `mapstructure:"one"` Two int `mapstructure:"two"` Three uint `mapstructure:"three"` Four bool `mapstructure:"four"` - // embedded, package qualified + // embedded, package qualified comment time.Duration `mapstructure:"duration"` Squashed testPerson `mapstructure:",squash"` PersonPtr *testPerson `mapstructure:"person_ptr"` @@ -35,9 +36,9 @@ type testStruct struct { Ignored string `mapstructure:"-"` } -func testEnv() env { - return env{ - srcRoot: "../../..", - moduleName: defaultModule, +func testDR() DirResolver { + return DirResolver{ + SrcRoot: "../../..", + ModuleName: DefaultModule, } } diff --git a/cmd/configschema/configschema/configs.go b/cmd/configschema/configschema/configs.go new file mode 100644 index 000000000000..b6ccb92e1c69 --- /dev/null +++ b/cmd/configschema/configschema/configs.go @@ -0,0 +1,125 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configschema + +import ( + "fmt" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config" +) + +const ( + receiver = "receiver" + extension = "extension" + processor = "processor" + exporter = "exporter" +) + +// CfgInfo contains a component config instance, as well as its group name and +// type. +type CfgInfo struct { + // the name of the component group, e.g. "receiver" + Group string + // the component type, e.g. "otlpreceiver.Config" + Type config.Type + // an instance of the component's configuration struct + CfgInstance interface{} +} + +// GetAllCfgInfos accepts a Factories struct, then creates and returns a CfgInfo +// for each of its components. +func GetAllCfgInfos(components component.Factories) []CfgInfo { + var out []CfgInfo + for _, f := range components.Receivers { + out = append(out, CfgInfo{ + Type: f.Type(), + Group: receiver, + CfgInstance: f.CreateDefaultConfig(), + }) + } + for _, f := range components.Extensions { + out = append(out, CfgInfo{ + Type: f.Type(), + Group: extension, + CfgInstance: f.CreateDefaultConfig(), + }) + } + for _, f := range components.Processors { + out = append(out, CfgInfo{ + Type: f.Type(), + Group: processor, + CfgInstance: f.CreateDefaultConfig(), + }) + } + for _, f := range components.Exporters { + out = append(out, CfgInfo{ + Type: f.Type(), + Group: exporter, + CfgInstance: f.CreateDefaultConfig(), + }) + } + return out +} + +// GetCfgInfo accepts a Factories struct, then creates and returns the default +// config for the component specified by the passed-in componentType and +// componentName. +func GetCfgInfo(components component.Factories, componentType, componentName string) (CfgInfo, error) { + t := config.Type(componentName) + switch componentType { + case receiver: + f := components.Receivers[t] + if f == nil { + return CfgInfo{}, fmt.Errorf("unknown %s name %q", componentType, componentName) + } + return CfgInfo{ + Type: f.Type(), + Group: componentType, + CfgInstance: f.CreateDefaultConfig(), + }, nil + case processor: + f := components.Processors[t] + if f == nil { + return CfgInfo{}, fmt.Errorf("unknown %s name %q", componentType, componentName) + } + return CfgInfo{ + Type: f.Type(), + Group: componentType, + CfgInstance: f.CreateDefaultConfig(), + }, nil + case exporter: + f := components.Exporters[t] + if f == nil { + return CfgInfo{}, fmt.Errorf("unknown %s name %q", componentType, componentName) + } + return CfgInfo{ + Type: f.Type(), + Group: componentType, + CfgInstance: f.CreateDefaultConfig(), + }, nil + case extension: + f := components.Extensions[t] + if f == nil { + return CfgInfo{}, fmt.Errorf("unknown %s name %q", componentType, componentName) + } + return CfgInfo{ + Type: f.Type(), + Group: componentType, + CfgInstance: f.CreateDefaultConfig(), + }, nil + } + return CfgInfo{}, fmt.Errorf("unknown component type %q", componentType) +} diff --git a/cmd/schemagen/schemagen/cli_single_test.go b/cmd/configschema/configschema/configs_test.go similarity index 66% rename from cmd/schemagen/schemagen/cli_single_test.go rename to cmd/configschema/configschema/configs_test.go index 00bf0fb5c91e..345c510b89a8 100644 --- a/cmd/schemagen/schemagen/cli_single_test.go +++ b/cmd/configschema/configschema/configs_test.go @@ -12,30 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -package schemagen +package configschema import ( - "io/ioutil" - "path" - "path/filepath" - "reflect" "testing" "github.com/stretchr/testify/require" - "gopkg.in/yaml.v2" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/service/defaultcomponents" ) +func TestGetAllConfigs(t *testing.T) { + cfgs := GetAllCfgInfos(testComponents()) + require.NotNil(t, cfgs) +} + func TestCreateReceiverConfig(t *testing.T) { - cfg, err := getConfig(testComponents(), "receiver", "otlp") + cfg, err := GetCfgInfo(testComponents(), "receiver", "otlp") require.NoError(t, err) require.NotNil(t, cfg) } func TestCreateProcesorConfig(t *testing.T) { - cfg, err := getConfig(testComponents(), "processor", "filter") + cfg, err := GetCfgInfo(testComponents(), "processor", "filter") require.NoError(t, err) require.NotNil(t, cfg) } @@ -64,29 +64,13 @@ func TestGetConfig(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - cfg, err := getConfig(testComponents(), test.componentType, test.name) + cfg, err := GetCfgInfo(testComponents(), test.componentType, test.name) require.NoError(t, err) require.NotNil(t, cfg) }) } } -func TestCreateSingleSchemaFile(t *testing.T) { - e := testEnv() - tempDir := t.TempDir() - e.yamlFilename = func(reflect.Type, env) string { - return path.Join(tempDir, schemaFilename) - } - createSingleSchemaFile(testComponents(), "exporter", "otlp", e) - file, err := ioutil.ReadFile(filepath.Clean(path.Join(tempDir, schemaFilename))) - require.NoError(t, err) - fld := field{} - err = yaml.Unmarshal(file, &fld) - require.NoError(t, err) - require.Equal(t, "*otlpexporter.Config", fld.Type) - require.NotNil(t, fld.Fields) -} - func testComponents() component.Factories { components, err := defaultcomponents.Components() if err != nil { diff --git a/cmd/schemagen/schemagen/schema.go b/cmd/configschema/configschema/fields.go similarity index 70% rename from cmd/schemagen/schemagen/schema.go rename to cmd/configschema/configschema/fields.go index 33817e3d29ac..5483cc3e4b77 100644 --- a/cmd/schemagen/schemagen/schema.go +++ b/cmd/configschema/configschema/fields.go @@ -12,55 +12,55 @@ // See the License for the specific language governing permissions and // limitations under the License. -package schemagen +package configschema import ( "fmt" - "io/ioutil" "reflect" "strings" "time" "github.com/fatih/structtag" - "gopkg.in/yaml.v2" ) -type field struct { +// Field holds attributes and subfields of a config struct. +type Field struct { Name string `yaml:",omitempty"` Type string `yaml:",omitempty"` Kind string `yaml:",omitempty"` Default interface{} `yaml:",omitempty"` Doc string `yaml:",omitempty"` - Fields []*field `yaml:",omitempty"` + Fields []*Field `yaml:",omitempty"` } -// createSchemaFile creates a `cfg-schema.yaml` file in the directory of the passed-in -// config instance. The yaml file contains the recursive field names, types, -// comments, and default values for the config struct. -func createSchemaFile(cfg interface{}, env env) { - v := reflect.ValueOf(cfg) - f := topLevelField(v, env) - yamlFilename := env.yamlFilename(v.Type().Elem(), env) - writeMarshaled(f, yamlFilename) -} - -func topLevelField(v reflect.Value, env env) *field { +// ReadFields accepts both a config struct's Value, as well as a DirResolver, +// and returns a Field pointer for the top level struct as well as all of its +// recursive subfields. +func ReadFields(v reflect.Value, dr DirResolver) *Field { cfgType := v.Type() - field := &field{ + field := &Field{ Type: cfgType.String(), } - refl(field, v, env) + refl(field, v, dr) return field } -func refl(f *field, v reflect.Value, env env) { +func refl(f *Field, v reflect.Value, dr DirResolver) { if v.Kind() == reflect.Ptr { - refl(f, v.Elem(), env) + refl(f, v.Elem(), dr) } if v.Kind() != reflect.Struct { return } - comments := commentsForStruct(v, env) + comments := commentsForStruct(v, dr) + + // we also check if f.Doc hasn't already been written, thus preventing a + // squashed type with struct comments from overwriting the containing struct's + // comments + if sc, ok := comments["_struct"]; ok && f.Doc == "" { + f.Doc = sc + } + for i := 0; i < v.NumField(); i++ { structField := v.Type().Field(i) tagName, options, err := mapstructure(structField.Tag) @@ -74,7 +74,6 @@ func refl(f *field, v reflect.Value, env env) { fv := v.Field(i) next := f if !containsSquash(options) { - doc := comments[structField.Name] name := tagName if name == "" { name = strings.ToLower(structField.Name) @@ -84,34 +83,34 @@ func refl(f *field, v reflect.Value, env env) { if typeStr == kindStr { typeStr = "" // omit if redundant } - next = &field{ + next = &Field{ Name: name, Type: typeStr, Kind: kindStr, - Doc: doc, + Doc: comments[structField.Name], } f.Fields = append(f.Fields, next) } - handleKinds(fv, next, env) + handleKind(fv, next, dr) } } -func handleKinds(v reflect.Value, f *field, env env) { +func handleKind(v reflect.Value, f *Field, dr DirResolver) { switch v.Kind() { case reflect.Struct: - refl(f, v, env) + refl(f, v, dr) case reflect.Ptr: if v.IsNil() { - refl(f, reflect.New(v.Type().Elem()), env) + refl(f, reflect.New(v.Type().Elem()), dr) } else { - refl(f, v.Elem(), env) + refl(f, v.Elem(), dr) } case reflect.Slice: e := v.Type().Elem() if e.Kind() == reflect.Struct { - refl(f, reflect.New(e), env) + refl(f, reflect.New(e), dr) } else if e.Kind() == reflect.Ptr { - refl(f, reflect.New(e.Elem()), env) + refl(f, reflect.New(e.Elem()), dr) } case reflect.String: if v.String() != "" { @@ -160,14 +159,3 @@ func containsSquash(options []string) bool { } return false } - -func writeMarshaled(field *field, filename string) { - marshaled, err := yaml.Marshal(field) - if err != nil { - panic(err) - } - err = ioutil.WriteFile(filename, marshaled, 0600) - if err != nil { - panic(err) - } -} diff --git a/cmd/schemagen/schemagen/schema_test.go b/cmd/configschema/configschema/fields_test.go similarity index 73% rename from cmd/schemagen/schemagen/schema_test.go rename to cmd/configschema/configschema/fields_test.go index f426e6740a2a..f0102d25ef7f 100644 --- a/cmd/schemagen/schemagen/schema_test.go +++ b/cmd/configschema/configschema/fields_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package schemagen +package configschema import ( "reflect" @@ -21,7 +21,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestTopLevelFieldWithDefaults(t *testing.T) { +func TestReadFieldsWithDefaults(t *testing.T) { defaults := map[string]interface{}{ "one": "1", "two": int64(2), @@ -46,14 +46,14 @@ func TestTopLevelFieldWithDefaults(t *testing.T) { Name: "bar", }, } - testTopLevelField(t, s, defaults) + testReadFields(t, s, defaults) } -func TestTopLevelFieldWithoutDefaults(t *testing.T) { - testTopLevelField(t, testStruct{}, map[string]interface{}{}) +func TestReadFieldsWithoutDefaults(t *testing.T) { + testReadFields(t, testStruct{}, map[string]interface{}{}) } -func getField(fields []*field, name string) *field { +func getField(fields []*Field, name string) *Field { for _, f := range fields { if f.Name == name { return f @@ -62,88 +62,90 @@ func getField(fields []*field, name string) *field { return nil } -func testTopLevelField(t *testing.T, s testStruct, defaults map[string]interface{}) { - root := topLevelField( +func testReadFields(t *testing.T, s testStruct, defaults map[string]interface{}) { + root := ReadFields( reflect.ValueOf(s), - testEnv(), + testDR(), ) - assert.Equal(t, "schemagen.testStruct", root.Type) + assert.Equal(t, "testStruct comment\n", root.Doc) + + assert.Equal(t, "configschema.testStruct", root.Type) assert.Equal(t, 10, len(root.Fields)) - assert.Equal(t, &field{ + assert.Equal(t, &Field{ Name: "one", Kind: "string", Default: defaults["one"], }, getField(root.Fields, "one")) - assert.Equal(t, &field{ + assert.Equal(t, &Field{ Name: "two", Kind: "int", Default: defaults["two"], }, getField(root.Fields, "two")) - assert.Equal(t, &field{ + assert.Equal(t, &Field{ Name: "three", Kind: "uint", Default: defaults["three"], }, getField(root.Fields, "three")) - assert.Equal(t, &field{ + assert.Equal(t, &Field{ Name: "four", Kind: "bool", Default: defaults["four"], }, getField(root.Fields, "four")) - assert.Equal(t, &field{ + assert.Equal(t, &Field{ Name: "duration", Type: "time.Duration", Kind: "int64", Default: defaults["duration"], - Doc: "embedded, package qualified\n", + Doc: "embedded, package qualified comment\n", }, getField(root.Fields, "duration")) - assert.Equal(t, &field{ + assert.Equal(t, &Field{ Name: "name", Kind: "string", Default: defaults["name"], }, getField(root.Fields, "name")) personPtr := getField(root.Fields, "person_ptr") - assert.Equal(t, "*schemagen.testPerson", personPtr.Type) + assert.Equal(t, "*configschema.testPerson", personPtr.Type) assert.Equal(t, "ptr", personPtr.Kind) assert.Equal(t, 1, len(personPtr.Fields)) - assert.Equal(t, &field{ + assert.Equal(t, &Field{ Name: "name", Kind: "string", Default: defaults["person_ptr"], }, getField(personPtr.Fields, "name")) personStruct := getField(root.Fields, "person_struct") - assert.Equal(t, "schemagen.testPerson", personStruct.Type) + assert.Equal(t, "configschema.testPerson", personStruct.Type) assert.Equal(t, "struct", personStruct.Kind) assert.Equal(t, 1, len(personStruct.Fields)) - assert.Equal(t, &field{ + assert.Equal(t, &Field{ Name: "name", Kind: "string", Default: defaults["person_struct"], }, getField(personStruct.Fields, "name")) persons := getField(root.Fields, "persons") - assert.Equal(t, "[]schemagen.testPerson", persons.Type) + assert.Equal(t, "[]configschema.testPerson", persons.Type) assert.Equal(t, "slice", persons.Kind) assert.Equal(t, 1, len(persons.Fields)) - assert.Equal(t, &field{ + assert.Equal(t, &Field{ Name: "name", Kind: "string", }, getField(persons.Fields, "name")) personPtrs := getField(root.Fields, "person_ptrs") - assert.Equal(t, "[]*schemagen.testPerson", personPtrs.Type) + assert.Equal(t, "[]*configschema.testPerson", personPtrs.Type) assert.Equal(t, "slice", personPtrs.Kind) assert.Equal(t, 1, len(personPtrs.Fields)) - assert.Equal(t, &field{ + assert.Equal(t, &Field{ Name: "name", Kind: "string", }, getField(personPtrs.Fields, "name")) diff --git a/cmd/configschema/configschema/resolver.go b/cmd/configschema/configschema/resolver.go new file mode 100644 index 000000000000..ede06d5ef2ae --- /dev/null +++ b/cmd/configschema/configschema/resolver.go @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package configschema + +import ( + "path" + "reflect" + "strings" +) + +// DefaultSrcRoot is the default root of the collector repo, relative to the +// current working directory. Can be used to create a DirResolver. +const DefaultSrcRoot = "." + +// DefaultModule is the module prefix of otelcol. Can be used to create a +// DirResolver. +const DefaultModule = "go.opentelemetry.io/collector" + +// DirResolver is used to resolve the base directory of a given reflect.Type. +type DirResolver struct { + SrcRoot string + ModuleName string +} + +// NewDefaultDirResolver creates a DirResolver with a default SrcRoot and +// ModuleName, suitable for using this package's API using otelcol with an +// executable running from the otelcol's source root (not tests). +func NewDefaultDirResolver() DirResolver { + return NewDirResolver(DefaultSrcRoot, DefaultModule) +} + +// NewDirResolver creates a DirResolver with a custom SrcRoot and ModuleName. +// Useful for testing and for using this package's API from a repository other +// than otelcol (e.g. contrib). +func NewDirResolver(srcRoot string, moduleName string) DirResolver { + return DirResolver{ + SrcRoot: srcRoot, + ModuleName: moduleName, + } +} + +// PackageDir accepts a Type and returns its package dir. +func (dr DirResolver) PackageDir(t reflect.Type) string { + pkg := strings.TrimPrefix(t.PkgPath(), dr.ModuleName+"/") + return path.Join(dr.SrcRoot, pkg) +} diff --git a/cmd/configschema/docsgen/README.md b/cmd/configschema/docsgen/README.md new file mode 100644 index 000000000000..6a1c1f44f403 --- /dev/null +++ b/cmd/configschema/docsgen/README.md @@ -0,0 +1,44 @@ +# Docsgen CLI Tool + +This package contains a CLI tool that generates markdown files for collector +components. The markdown files present the configuration metadata extracted +by the configschema API in a human readable form that can be used to manually +configure the collector. + +## Usage + +There are two modes of operation, one where markdown files are created for all +components, and another where a markdown file is created for only one, specified +component. + +#### All components + +``` +docsgen all +``` + +Creates config.md files in every directory corresponding to a component +configuration type. + +#### Single component + +``` +docsgen component-type component-name +``` + +Creates a single config.md files in the directory corresponding to the +specified component. + +### Usage Example + +To create a config doc for the otlp receiver, use the command + +``` +docsgen receiver otlp +``` + +This creates a file called `config.md` in `receiver/otlpreceiver`. + +### Output Example + +[OTLP Receiver Config Metadata Doc](../../../receiver/otlpreceiver/config.md) diff --git a/cmd/configschema/docsgen/docsgen/cli.go b/cmd/configschema/docsgen/docsgen/cli.go new file mode 100644 index 000000000000..f1c589658545 --- /dev/null +++ b/cmd/configschema/docsgen/docsgen/cli.go @@ -0,0 +1,134 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package docsgen + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path" + "reflect" + "strings" + "text/template" + + "go.opentelemetry.io/collector/cmd/configschema/configschema" + "go.opentelemetry.io/collector/component" +) + +const mdFileName = "config.md" + +// CLI is the entrypoint for this package's functionality. It handles command- +// line arguments for the docsgen executable and produces config documentation +// for the specified components. +func CLI(factories component.Factories, dr configschema.DirResolver) { + tableTmpl, err := tableTemplate() + if err != nil { + panic(err) + } + + handleCLI(factories, dr, tableTmpl, ioutil.WriteFile, os.Stdout, os.Args...) +} + +func handleCLI( + factories component.Factories, + dr configschema.DirResolver, + tableTmpl *template.Template, + writeFile writeFileFunc, + wr io.Writer, + args ...string, +) { + if !(len(args) == 2 || len(args) == 3) { + printLines(wr, "usage:", "docsgen all", "docsgen component-type component-name") + return + } + + componentType := args[1] + if componentType == "all" { + allComponents(dr, tableTmpl, factories, writeFile) + return + } + + singleComponent(dr, tableTmpl, factories, componentType, args[2], writeFile) +} + +func printLines(wr io.Writer, lines ...string) { + for _, line := range lines { + _, _ = fmt.Fprintln(wr, line) + } +} + +func allComponents( + dr configschema.DirResolver, + tableTmpl *template.Template, + factories component.Factories, + writeFile writeFileFunc, +) { + configs := configschema.GetAllCfgInfos(factories) + for _, cfg := range configs { + writeConfigDoc(tableTmpl, dr, cfg, writeFile) + } +} + +func singleComponent( + dr configschema.DirResolver, + tableTmpl *template.Template, + factories component.Factories, + componentType, componentName string, + writeFile writeFileFunc, +) { + cfg, err := configschema.GetCfgInfo(factories, componentType, componentName) + if err != nil { + panic(err) + } + + writeConfigDoc(tableTmpl, dr, cfg, writeFile) +} + +type writeFileFunc func(filename string, data []byte, perm os.FileMode) error + +func writeConfigDoc( + tableTmpl *template.Template, + dr configschema.DirResolver, + ci configschema.CfgInfo, + writeFile writeFileFunc, +) { + v := reflect.ValueOf(ci.CfgInstance) + f := configschema.ReadFields(v, dr) + + f.Type = stripPrefix(f.Type) + + mdBytes := renderHeader(string(ci.Type), ci.Group, f.Doc) + tableBytes, err := renderTable(tableTmpl, f) + if err != nil { + panic(err) + } + mdBytes = append(mdBytes, tableBytes...) + + if hasTimeDuration(f) { + mdBytes = append(mdBytes, durationBlock...) + } + + dir := dr.PackageDir(v.Type().Elem()) + err = writeFile(path.Join(dir, mdFileName), mdBytes, 0644) + if err != nil { + panic(err) + } +} + +func stripPrefix(name string) string { + idx := strings.Index(name, ".") + return name[idx+1:] +} diff --git a/cmd/configschema/docsgen/docsgen/cli_test.go b/cmd/configschema/docsgen/docsgen/cli_test.go new file mode 100644 index 000000000000..d91b83d32a8e --- /dev/null +++ b/cmd/configschema/docsgen/docsgen/cli_test.go @@ -0,0 +1,123 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package docsgen + +import ( + "os" + "path" + "strings" + "testing" + "text/template" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/cmd/configschema/configschema" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/receiver/otlpreceiver" + "go.opentelemetry.io/collector/service/defaultcomponents" +) + +func TestWriteConfigDoc(t *testing.T) { + cfg := otlpreceiver.NewFactory().CreateDefaultConfig() + root := path.Join("..", "..", "..", "..") + dr := configschema.NewDirResolver(root, configschema.DefaultModule) + outputFilename := "" + tmpl := testTemplate(t) + writeConfigDoc(tmpl, dr, configschema.CfgInfo{ + Type: "otlp", + Group: "receiver", + CfgInstance: cfg, + }, func(dir string, bytes []byte, perm os.FileMode) error { + outputFilename = dir + return nil + }) + expectedPath := path.Join(root, "receiver/otlpreceiver/config.md") + assert.Equal(t, expectedPath, outputFilename) +} + +func testTemplate(t *testing.T) *template.Template { + tmpl, err := template.ParseFiles("testdata/test.tmpl") + require.NoError(t, err) + return tmpl +} + +func TestHandleCLI_NoArgs(t *testing.T) { + wr := &fakeIOWriter{} + handleCLI( + defaultComponents(t), + configschema.NewDefaultDirResolver(), + testTemplate(t), + func(filename string, data []byte, perm os.FileMode) error { return nil }, + wr, + ) + assert.Equal(t, 3, len(wr.lines)) +} + +func TestHandleCLI_Single(t *testing.T) { + args := []string{"", "receiver", "otlp"} + cs := defaultComponents(t) + wr := &fakeFilesystemWriter{} + + testHandleCLI(t, cs, wr, args) + + assert.Equal(t, 1, len(wr.configFiles)) + assert.Equal(t, 1, len(wr.fileContents)) + assert.True(t, strings.Contains(wr.fileContents[0], `"otlp" Receiver Reference`)) +} + +func TestHandleCLI_All(t *testing.T) { + args := []string{"", "all"} + cs := defaultComponents(t) + wr := &fakeFilesystemWriter{} + + testHandleCLI(t, cs, wr, args) + + expected := len(cs.Receivers) + len(cs.Processors) + len(cs.Exporters) + len(cs.Extensions) + assert.Equal(t, expected, len(wr.configFiles)) + assert.Equal(t, expected, len(wr.fileContents)) +} + +func testHandleCLI(t *testing.T, cs component.Factories, wr *fakeFilesystemWriter, args []string) { + stdoutWriter := &fakeIOWriter{} + tmpl := testTemplate(t) + dr := configschema.NewDirResolver(path.Join("..", "..", "..", ".."), configschema.DefaultModule) + handleCLI(cs, dr, tmpl, wr.writeFile, stdoutWriter, args...) +} + +func defaultComponents(t *testing.T) component.Factories { + cmps, err := defaultcomponents.Components() + require.NoError(t, err) + return cmps +} + +type fakeFilesystemWriter struct { + configFiles, fileContents []string +} + +func (wr *fakeFilesystemWriter) writeFile(filename string, data []byte, perm os.FileMode) error { + wr.configFiles = append(wr.configFiles, filename) + wr.fileContents = append(wr.fileContents, string(data)) + return nil +} + +type fakeIOWriter struct { + lines []string +} + +func (wr *fakeIOWriter) Write(p []byte) (n int, err error) { + wr.lines = append(wr.lines, string(p)) + return 0, nil +} diff --git a/cmd/configschema/docsgen/docsgen/render.go b/cmd/configschema/docsgen/docsgen/render.go new file mode 100644 index 000000000000..45e7ade17219 --- /dev/null +++ b/cmd/configschema/docsgen/docsgen/render.go @@ -0,0 +1,76 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package docsgen + +import ( + "bytes" + "fmt" + "strings" + "text/template" + + "go.opentelemetry.io/collector/cmd/configschema/configschema" +) + +func renderHeader(typ, group, doc string) []byte { + return []byte(fmt.Sprintf( + "# %q %s Reference\n\n%s\n\n", + typ, + strings.Title(group), + doc, + )) +} + +func renderTable(tmpl *template.Template, field *configschema.Field) ([]byte, error) { + buf := &bytes.Buffer{} + err := executeTableTemplate(tmpl, field, buf) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func executeTableTemplate(tmpl *template.Template, field *configschema.Field, buf *bytes.Buffer) error { + err := tmpl.Execute(buf, field) + if err != nil { + return err + } + for _, subField := range field.Fields { + if subField.Fields == nil { + continue + } + err = executeTableTemplate(tmpl, subField, buf) + if err != nil { + return err + } + } + return nil +} + +const durationBlock = "### time-Duration \n" + + "An optionally signed sequence of decimal numbers, " + + "each with a unit suffix, such as `300ms`, `-1.5h`, " + + "or `2h45m`. Valid time units are `ns`, `us`, `ms`, `s`, `m`, `h`." + +func hasTimeDuration(f *configschema.Field) bool { + if f.Type == "time.Duration" { + return true + } + for _, sub := range f.Fields { + if hasTimeDuration(sub) { + return true + } + } + return false +} diff --git a/cmd/configschema/docsgen/docsgen/template.go b/cmd/configschema/docsgen/docsgen/template.go new file mode 100644 index 000000000000..10571f1de994 --- /dev/null +++ b/cmd/configschema/docsgen/docsgen/template.go @@ -0,0 +1,70 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package docsgen + +import ( + "strings" + "text/template" +) + +func tableTemplate() (*template.Template, error) { + return template.New("table").Funcs( + template.FuncMap{ + "join": join, + "cleanType": cleanType, + "isCompoundField": isCompoundField, + "isDuration": isDuration, + }, + ).Parse(tableTemplateStr) +} + +func isCompoundField(kind string) bool { + return kind == "struct" || kind == "ptr" +} + +func join(s string) string { + return strings.ReplaceAll(s, "\n", " ") +} + +func cleanType(s string) string { + return strings.ReplaceAll(strings.ReplaceAll(s, "*", ""), ".", "-") +} + +func isDuration(s string) bool { + return s == "time.Duration" +} + +const tableTemplateStr = `### {{ cleanType .Type }} + +| Name | Type | Default | Docs | +| ---- | ---- | ------- | ---- | +{{ range .Fields -}} +| {{ .Name }} | +{{- if .Type -}} + {{- if isCompoundField .Kind -}} + [{{ cleanType .Type }}](#{{ cleanType .Type }}) + {{- else -}} + {{- if isDuration .Type -}} + [{{ cleanType .Type }}](#{{ cleanType .Type }}) + {{- else -}} + {{ .Type }} + {{- end -}} + {{- end -}} +{{- else -}} + {{ .Kind }} +{{- end -}} +| {{ .Default }} | {{ join .Doc }} | +{{ end }} +` diff --git a/cmd/schemagen/schemagen/cli_all_test.go b/cmd/configschema/docsgen/docsgen/template_test.go similarity index 52% rename from cmd/schemagen/schemagen/cli_all_test.go rename to cmd/configschema/docsgen/docsgen/template_test.go index e8a59c6b3faa..652fd03052aa 100644 --- a/cmd/schemagen/schemagen/cli_all_test.go +++ b/cmd/configschema/docsgen/docsgen/template_test.go @@ -12,39 +12,33 @@ // See the License for the specific language governing permissions and // limitations under the License. -package schemagen +package docsgen import ( + "encoding/json" "io/ioutil" "path" - "path/filepath" - "reflect" "testing" "github.com/stretchr/testify/require" - "gopkg.in/yaml.v2" + + "go.opentelemetry.io/collector/cmd/configschema/configschema" ) -func TestGetAllConfigs(t *testing.T) { - cfgs := getAllConfigs(testComponents()) - require.NotNil(t, cfgs) +func TestTableTemplate(t *testing.T) { + field := testDataField(t) + tmpl, err := tableTemplate() + require.NoError(t, err) + bytes, err := renderTable(tmpl, field) + require.NoError(t, err) + require.NotNil(t, bytes) } -func TestCreateAllSchemaFiles(t *testing.T) { - e := testEnv() - tempDir := t.TempDir() - e.yamlFilename = func(t reflect.Type, e env) string { - return path.Join(tempDir, t.String()+".yaml") - } - createAllSchemaFiles(testComponents(), e) - fileInfos, err := ioutil.ReadDir(tempDir) - require.NoError(t, err) - require.NotNil(t, fileInfos) - file, err := ioutil.ReadFile(filepath.Clean(path.Join(tempDir, "otlpexporter.Config.yaml"))) +func testDataField(t *testing.T) *configschema.Field { + jsonBytes, err := ioutil.ReadFile(path.Join("testdata", "otlp-receiver.json")) require.NoError(t, err) - fld := field{} - err = yaml.Unmarshal(file, &fld) + field := configschema.Field{} + err = json.Unmarshal(jsonBytes, &field) require.NoError(t, err) - require.Equal(t, "*otlpexporter.Config", fld.Type) - require.NotNil(t, fld.Fields) + return &field } diff --git a/cmd/configschema/docsgen/docsgen/testdata/otlp-receiver.json b/cmd/configschema/docsgen/docsgen/testdata/otlp-receiver.json new file mode 100644 index 000000000000..f49c095a7e99 --- /dev/null +++ b/cmd/configschema/docsgen/docsgen/testdata/otlp-receiver.json @@ -0,0 +1,339 @@ +{ + "Name": "", + "Type": "*otlpreceiver.Config", + "Kind": "", + "Default": null, + "Doc": "", + "Fields": [ + { + "Name": "protocols", + "Type": "otlpreceiver.Protocols", + "Kind": "struct", + "Default": null, + "Doc": "Protocols is the configuration for the supported protocols, currently gRPC and HTTP (Proto and JSON).\n", + "Fields": [ + { + "Name": "grpc", + "Type": "*configgrpc.GRPCServerSettings", + "Kind": "ptr", + "Default": null, + "Doc": "", + "Fields": [ + { + "Name": "endpoint", + "Type": "", + "Kind": "string", + "Default": "0.0.0.0:4317", + "Doc": "Endpoint configures the address for this network connection.\nFor TCP and UDP networks, the address has the form \"host:port\". The host must be a literal IP address,\nor a host name that can be resolved to IP addresses. The port must be a literal port number or a service name.\nIf the host is a literal IPv6 address it must be enclosed in square brackets, as in \"[2001:db8::1]:80\" or\n\"[fe80::1%zone]:80\". The zone specifies the scope of the literal IPv6 address as defined in RFC 4007.\n", + "Fields": null + }, + { + "Name": "transport", + "Type": "", + "Kind": "string", + "Default": "tcp", + "Doc": "Transport to use. Known protocols are \"tcp\", \"tcp4\" (IPv4-only), \"tcp6\" (IPv6-only), \"udp\", \"udp4\" (IPv4-only),\n\"udp6\" (IPv6-only), \"ip\", \"ip4\" (IPv4-only), \"ip6\" (IPv6-only), \"unix\", \"unixgram\" and \"unixpacket\".\n", + "Fields": null + }, + { + "Name": "tls_settings", + "Type": "*configtls.TLSServerSetting", + "Kind": "ptr", + "Default": null, + "Doc": "Configures the protocol to use TLS.\nThe default value is nil, which will cause the protocol to not use TLS.\n", + "Fields": [ + { + "Name": "ca_file", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "Path to the CA cert. For a client this verifies the server certificate.\nFor a server this verifies client certificates. If empty uses system root CA.\n(optional)\n", + "Fields": null + }, + { + "Name": "cert_file", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "Path to the TLS cert to use for TLS required connections. (optional)\n", + "Fields": null + }, + { + "Name": "key_file", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "Path to the TLS key to use for TLS required connections. (optional)\n", + "Fields": null + }, + { + "Name": "client_ca_file", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "Path to the TLS cert to use by the server to verify a client certificate. (optional)\nThis sets the ClientCAs and ClientAuth to RequireAndVerifyClientCert in the TLSConfig. Please refer to\nhttps://godoc.org/crypto/tls#Config for more information. (optional)\n", + "Fields": null + } + ] + }, + { + "Name": "max_recv_msg_size_mib", + "Type": "", + "Kind": "uint64", + "Default": null, + "Doc": "MaxRecvMsgSizeMiB sets the maximum size (in MiB) of messages accepted by the server.\n", + "Fields": null + }, + { + "Name": "max_concurrent_streams", + "Type": "", + "Kind": "uint32", + "Default": null, + "Doc": "MaxConcurrentStreams sets the limit on the number of concurrent streams to each ServerTransport.\nIt has effect only for streaming RPCs.\n", + "Fields": null + }, + { + "Name": "read_buffer_size", + "Type": "", + "Kind": "int", + "Default": 524288, + "Doc": "ReadBufferSize for gRPC server. See grpc.ReadBufferSize\n(https://godoc.org/google.golang.org/grpc#ReadBufferSize).\n", + "Fields": null + }, + { + "Name": "write_buffer_size", + "Type": "", + "Kind": "int", + "Default": null, + "Doc": "WriteBufferSize for gRPC server. See grpc.WriteBufferSize\n(https://godoc.org/google.golang.org/grpc#WriteBufferSize).\n", + "Fields": null + }, + { + "Name": "keepalive", + "Type": "*configgrpc.KeepaliveServerConfig", + "Kind": "ptr", + "Default": null, + "Doc": "Keepalive anchor for all the settings related to keepalive.\n", + "Fields": [ + { + "Name": "server_parameters", + "Type": "*configgrpc.KeepaliveServerParameters", + "Kind": "ptr", + "Default": null, + "Doc": "", + "Fields": [ + { + "Name": "max_connection_idle", + "Type": "time.Duration", + "Kind": "int64", + "Default": null, + "Doc": "", + "Fields": null + }, + { + "Name": "max_connection_age", + "Type": "time.Duration", + "Kind": "int64", + "Default": null, + "Doc": "", + "Fields": null + }, + { + "Name": "max_connection_age_grace", + "Type": "time.Duration", + "Kind": "int64", + "Default": null, + "Doc": "", + "Fields": null + }, + { + "Name": "time", + "Type": "time.Duration", + "Kind": "int64", + "Default": null, + "Doc": "", + "Fields": null + }, + { + "Name": "timeout", + "Type": "time.Duration", + "Kind": "int64", + "Default": null, + "Doc": "", + "Fields": null + } + ] + }, + { + "Name": "enforcement_policy", + "Type": "*configgrpc.KeepaliveEnforcementPolicy", + "Kind": "ptr", + "Default": null, + "Doc": "", + "Fields": [ + { + "Name": "min_time", + "Type": "time.Duration", + "Kind": "int64", + "Default": null, + "Doc": "", + "Fields": null + }, + { + "Name": "permit_without_stream", + "Type": "", + "Kind": "bool", + "Default": null, + "Doc": "", + "Fields": null + } + ] + } + ] + }, + { + "Name": "auth", + "Type": "*configauth.Authentication", + "Kind": "ptr", + "Default": null, + "Doc": "Auth for this receiver\n", + "Fields": [ + { + "Name": "attribute", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "The attribute (header name) to look for auth data. Optional, default value: \"authentication\".\n", + "Fields": null + }, + { + "Name": "oidc", + "Type": "*configauth.OIDC", + "Kind": "ptr", + "Default": null, + "Doc": "OIDC configures this receiver to use the given OIDC provider as the backend for the authentication mechanism.\nRequired.\n", + "Fields": [ + { + "Name": "issuer_url", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "IssuerURL is the base URL for the OIDC provider.\nRequired.\n", + "Fields": null + }, + { + "Name": "audience", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "Audience of the token, used during the verification.\nFor example: \"https://accounts.google.com\" or \"https://login.salesforce.com\".\nRequired.\n", + "Fields": null + }, + { + "Name": "issuer_ca_path", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "The local path for the issuer CA's TLS server cert.\nOptional.\n", + "Fields": null + }, + { + "Name": "username_claim", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "The claim to use as the username, in case the token's 'sub' isn't the suitable source.\nOptional.\n", + "Fields": null + }, + { + "Name": "groups_claim", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "The claim that holds the subject's group membership information.\nOptional.\n", + "Fields": null + } + ] + } + ] + } + ] + }, + { + "Name": "http", + "Type": "*confighttp.HTTPServerSettings", + "Kind": "ptr", + "Default": null, + "Doc": "", + "Fields": [ + { + "Name": "endpoint", + "Type": "", + "Kind": "string", + "Default": "0.0.0.0:55681", + "Doc": "Endpoint configures the listening address for the server.\n", + "Fields": null + }, + { + "Name": "tls_settings", + "Type": "*configtls.TLSServerSetting", + "Kind": "ptr", + "Default": null, + "Doc": "TLSSetting struct exposes TLS client configuration.\n", + "Fields": [ + { + "Name": "ca_file", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "Path to the CA cert. For a client this verifies the server certificate.\nFor a server this verifies client certificates. If empty uses system root CA.\n(optional)\n", + "Fields": null + }, + { + "Name": "cert_file", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "Path to the TLS cert to use for TLS required connections. (optional)\n", + "Fields": null + }, + { + "Name": "key_file", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "Path to the TLS key to use for TLS required connections. (optional)\n", + "Fields": null + }, + { + "Name": "client_ca_file", + "Type": "", + "Kind": "string", + "Default": null, + "Doc": "Path to the TLS cert to use by the server to verify a client certificate. (optional)\nThis sets the ClientCAs and ClientAuth to RequireAndVerifyClientCert in the TLSConfig. Please refer to\nhttps://godoc.org/crypto/tls#Config for more information. (optional)\n", + "Fields": null + } + ] + }, + { + "Name": "cors_allowed_origins", + "Type": "[]string", + "Kind": "slice", + "Default": null, + "Doc": "CorsOrigins are the allowed CORS origins for HTTP/JSON requests to grpc-gateway adapter\nfor the OTLP receiver. See github.com/rs/cors\nAn empty list means that CORS is not enabled at all. A wildcard (*) can be\nused to match any origin or one or more characters of an origin.\n", + "Fields": null + }, + { + "Name": "cors_allowed_headers", + "Type": "[]string", + "Kind": "slice", + "Default": null, + "Doc": "CorsHeaders are the allowed CORS headers for HTTP/JSON requests to grpc-gateway adapter\nfor the OTLP receiver. See github.com/rs/cors\nCORS needs to be enabled first by providing a non-empty list in CorsOrigins\nA wildcard (*) can be used to match any header.\n", + "Fields": null + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/cmd/configschema/docsgen/docsgen/testdata/test.tmpl b/cmd/configschema/docsgen/docsgen/testdata/test.tmpl new file mode 100644 index 000000000000..a82ac1116832 --- /dev/null +++ b/cmd/configschema/docsgen/docsgen/testdata/test.tmpl @@ -0,0 +1 @@ +{{ .Type }} diff --git a/cmd/schemagen/main.go b/cmd/configschema/docsgen/main.go similarity index 75% rename from cmd/schemagen/main.go rename to cmd/configschema/docsgen/main.go index 082330558786..3547f0a2684d 100644 --- a/cmd/schemagen/main.go +++ b/cmd/configschema/docsgen/main.go @@ -15,14 +15,16 @@ package main import ( - "go.opentelemetry.io/collector/cmd/schemagen/schemagen" + "go.opentelemetry.io/collector/cmd/configschema/configschema" + "go.opentelemetry.io/collector/cmd/configschema/docsgen/docsgen" "go.opentelemetry.io/collector/service/defaultcomponents" ) func main() { - components, err := defaultcomponents.Components() + cmps, err := defaultcomponents.Components() if err != nil { panic(err) } - schemagen.CLI(components) + dr := configschema.NewDefaultDirResolver() + docsgen.CLI(cmps, dr) } diff --git a/cmd/schemagen/schemagen/cli.go b/cmd/schemagen/schemagen/cli.go deleted file mode 100644 index 266f51211789..000000000000 --- a/cmd/schemagen/schemagen/cli.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package schemagen - -import ( - "flag" - "fmt" - - "go.opentelemetry.io/collector/component" -) - -// CLI defines the command-line interface for the schemagen util. -func CLI(c component.Factories) { - prepUsage() - - e, componentType, componentName := parseArgs() - e.yamlFilename = yamlFilename - - switch { - case componentType == "all": - createAllSchemaFiles(c, e) - case componentType != "" && componentName != "": - createSingleSchemaFile( - c, - componentType, - componentName, - e, - ) - default: - flag.Usage() - } -} - -func prepUsage() { - const usage = `cfgschema all -cfgschema - -options -` - flag.Usage = func() { - _, _ = fmt.Fprint(flag.CommandLine.Output(), usage) - flag.PrintDefaults() - } -} - -func parseArgs() (env, string, string) { - e := env{} - flag.StringVar(&e.srcRoot, "s", defaultSrcRoot, "collector source root") - flag.StringVar(&e.moduleName, "m", defaultModule, "module name") - flag.Parse() - componentType := flag.Arg(0) - componentName := flag.Arg(1) - return e, componentType, componentName -} diff --git a/cmd/schemagen/schemagen/cli_all.go b/cmd/schemagen/schemagen/cli_all.go deleted file mode 100644 index 3ecb65d866f9..000000000000 --- a/cmd/schemagen/schemagen/cli_all.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package schemagen - -import ( - "go.opentelemetry.io/collector/component" -) - -// createAllSchemaFiles creates config yaml schema files for all registered components -func createAllSchemaFiles(components component.Factories, env env) { - cfgs := getAllConfigs(components) - for _, cfg := range cfgs { - createSchemaFile(cfg, env) - } -} - -func getAllConfigs(components component.Factories) []interface{} { - var cfgs []interface{} - for _, f := range components.Receivers { - cfgs = append(cfgs, f.CreateDefaultConfig()) - } - for _, f := range components.Extensions { - cfgs = append(cfgs, f.CreateDefaultConfig()) - } - for _, f := range components.Processors { - cfgs = append(cfgs, f.CreateDefaultConfig()) - } - for _, f := range components.Exporters { - cfgs = append(cfgs, f.CreateDefaultConfig()) - } - return cfgs -} diff --git a/cmd/schemagen/schemagen/cli_single.go b/cmd/schemagen/schemagen/cli_single.go deleted file mode 100644 index 521fa6910566..000000000000 --- a/cmd/schemagen/schemagen/cli_single.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package schemagen - -import ( - "fmt" - "os" - - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/config" -) - -// createSingleSchemaFile creates a config schema yaml file for a single component -func createSingleSchemaFile(components component.Factories, componentType, componentName string, env env) { - cfg, err := getConfig(components, componentType, componentName) - if err != nil { - println(err.Error()) - os.Exit(1) - } - createSchemaFile(cfg, env) -} - -func getConfig(components component.Factories, componentType, componentName string) (interface{}, error) { - t := config.Type(componentName) - switch componentType { - case "receiver": - c := components.Receivers[t] - if c == nil { - return nil, fmt.Errorf("unknown receiver name %q", componentName) - } - return c.CreateDefaultConfig(), nil - case "processor": - c := components.Processors[t] - if c == nil { - return nil, fmt.Errorf("unknown processor name %q", componentName) - } - return c.CreateDefaultConfig(), nil - case "exporter": - c := components.Exporters[t] - if c == nil { - return nil, fmt.Errorf("unknown exporter name %q", componentName) - } - return c.CreateDefaultConfig(), nil - case "extension": - c := components.Extensions[t] - if c == nil { - return nil, fmt.Errorf("unknown extension name %q", componentName) - } - return c.CreateDefaultConfig(), nil - } - return nil, fmt.Errorf("unknown component type %q", componentType) -} diff --git a/cmd/schemagen/schemagen/common.go b/cmd/schemagen/schemagen/common.go deleted file mode 100644 index a8aa94c665f9..000000000000 --- a/cmd/schemagen/schemagen/common.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package schemagen - -import ( - "path" - "reflect" - "strings" -) - -const defaultSrcRoot = "." -const defaultModule = "go.opentelemetry.io/collector" - -type env struct { - srcRoot string - moduleName string - yamlFilename func(reflect.Type, env) string -} - -const schemaFilename = "cfg-schema.yaml" - -func yamlFilename(t reflect.Type, env env) string { - return path.Join(packageDir(t, env), schemaFilename) -} - -func packageDir(t reflect.Type, env env) string { - pkg := strings.TrimPrefix(t.PkgPath(), env.moduleName+"/") - return path.Join(env.srcRoot, pkg) -} diff --git a/receiver/otlpreceiver/config.md b/receiver/otlpreceiver/config.md new file mode 100644 index 000000000000..c8d067f10845 --- /dev/null +++ b/receiver/otlpreceiver/config.md @@ -0,0 +1,91 @@ +# "otlp" Receiver Reference + +Config defines configuration for OTLP receiver. + + +### Config + +| Name | Type | Default | Docs | +| ---- | ---- | ------- | ---- | +| protocols |[otlpreceiver-Protocols](#otlpreceiver-Protocols)| | Protocols is the configuration for the supported protocols, currently gRPC and HTTP (Proto and JSON). | + +### otlpreceiver-Protocols + +| Name | Type | Default | Docs | +| ---- | ---- | ------- | ---- | +| grpc |[configgrpc-GRPCServerSettings](#configgrpc-GRPCServerSettings)| | GRPCServerSettings defines common settings for a gRPC server configuration. | +| http |[confighttp-HTTPServerSettings](#confighttp-HTTPServerSettings)| | HTTPServerSettings defines settings for creating an HTTP server. | + +### configgrpc-GRPCServerSettings + +| Name | Type | Default | Docs | +| ---- | ---- | ------- | ---- | +| endpoint |string| 0.0.0.0:4317 | Endpoint configures the address for this network connection. For TCP and UDP networks, the address has the form "host:port". The host must be a literal IP address, or a host name that can be resolved to IP addresses. The port must be a literal port number or a service name. If the host is a literal IPv6 address it must be enclosed in square brackets, as in "[2001:db8::1]:80" or "[fe80::1%zone]:80". The zone specifies the scope of the literal IPv6 address as defined in RFC 4007. | +| transport |string| tcp | Transport to use. Known protocols are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and "unixpacket". | +| tls_settings |[configtls-TLSServerSetting](#configtls-TLSServerSetting)| | Configures the protocol to use TLS. The default value is nil, which will cause the protocol to not use TLS. | +| max_recv_msg_size_mib |uint64| | MaxRecvMsgSizeMiB sets the maximum size (in MiB) of messages accepted by the server. | +| max_concurrent_streams |uint32| | MaxConcurrentStreams sets the limit on the number of concurrent streams to each ServerTransport. It has effect only for streaming RPCs. | +| read_buffer_size |int| 524288 | ReadBufferSize for gRPC server. See grpc.ReadBufferSize (https://godoc.org/google.golang.org/grpc#ReadBufferSize). | +| write_buffer_size |int| | WriteBufferSize for gRPC server. See grpc.WriteBufferSize (https://godoc.org/google.golang.org/grpc#WriteBufferSize). | +| keepalive |[configgrpc-KeepaliveServerConfig](#configgrpc-KeepaliveServerConfig)| | Keepalive anchor for all the settings related to keepalive. | +| auth |[configauth-Authentication](#configauth-Authentication)| | Auth for this receiver | + +### configtls-TLSServerSetting + +| Name | Type | Default | Docs | +| ---- | ---- | ------- | ---- | +| ca_file |string| | Path to the CA cert. For a client this verifies the server certificate. For a server this verifies client certificates. If empty uses system root CA. (optional) | +| cert_file |string| | Path to the TLS cert to use for TLS required connections. (optional) | +| key_file |string| | Path to the TLS key to use for TLS required connections. (optional) | +| client_ca_file |string| | Path to the TLS cert to use by the server to verify a client certificate. (optional) This sets the ClientCAs and ClientAuth to RequireAndVerifyClientCert in the TLSConfig. Please refer to https://godoc.org/crypto/tls#Config for more information. (optional) | + +### configgrpc-KeepaliveServerConfig + +| Name | Type | Default | Docs | +| ---- | ---- | ------- | ---- | +| server_parameters |[configgrpc-KeepaliveServerParameters](#configgrpc-KeepaliveServerParameters)| | KeepaliveServerParameters allow configuration of the keepalive.ServerParameters. The same default values as keepalive.ServerParameters are applicable and get applied by the server. See https://godoc.org/google.golang.org/grpc/keepalive#ServerParameters for details. | +| enforcement_policy |[configgrpc-KeepaliveEnforcementPolicy](#configgrpc-KeepaliveEnforcementPolicy)| | KeepaliveEnforcementPolicy allow configuration of the keepalive.EnforcementPolicy. The same default values as keepalive.EnforcementPolicy are applicable and get applied by the server. See https://godoc.org/google.golang.org/grpc/keepalive#EnforcementPolicy for details. | + +### configgrpc-KeepaliveServerParameters + +| Name | Type | Default | Docs | +| ---- | ---- | ------- | ---- | +| max_connection_idle |[time-Duration](#time-Duration)| | | +| max_connection_age |[time-Duration](#time-Duration)| | | +| max_connection_age_grace |[time-Duration](#time-Duration)| | | +| time |[time-Duration](#time-Duration)| | | +| timeout |[time-Duration](#time-Duration)| | | + +### configgrpc-KeepaliveEnforcementPolicy + +| Name | Type | Default | Docs | +| ---- | ---- | ------- | ---- | +| min_time |[time-Duration](#time-Duration)| | | +| permit_without_stream |bool| | | + +### configauth-Authentication + +| Name | Type | Default | Docs | +| ---- | ---- | ------- | ---- | +| authenticator |string| | AuthenticatorName specifies the name of the extension to use in order to authenticate the incoming data point. | + +### confighttp-HTTPServerSettings + +| Name | Type | Default | Docs | +| ---- | ---- | ------- | ---- | +| endpoint |string| 0.0.0.0:55681 | Endpoint configures the listening address for the server. | +| tls_settings |[configtls-TLSServerSetting](#configtls-TLSServerSetting)| | TLSSetting struct exposes TLS client configuration. | +| cors_allowed_origins |[]string| | CorsOrigins are the allowed CORS origins for HTTP/JSON requests to grpc-gateway adapter for the OTLP receiver. See github.com/rs/cors An empty list means that CORS is not enabled at all. A wildcard (*) can be used to match any origin or one or more characters of an origin. | +| cors_allowed_headers |[]string| | CorsHeaders are the allowed CORS headers for HTTP/JSON requests to grpc-gateway adapter for the OTLP receiver. See github.com/rs/cors CORS needs to be enabled first by providing a non-empty list in CorsOrigins A wildcard (*) can be used to match any header. | + +### configtls-TLSServerSetting + +| Name | Type | Default | Docs | +| ---- | ---- | ------- | ---- | +| ca_file |string| | Path to the CA cert. For a client this verifies the server certificate. For a server this verifies client certificates. If empty uses system root CA. (optional) | +| cert_file |string| | Path to the TLS cert to use for TLS required connections. (optional) | +| key_file |string| | Path to the TLS key to use for TLS required connections. (optional) | +| client_ca_file |string| | Path to the TLS cert to use by the server to verify a client certificate. (optional) This sets the ClientCAs and ClientAuth to RequireAndVerifyClientCert in the TLSConfig. Please refer to https://godoc.org/crypto/tls#Config for more information. (optional) | + +### time-Duration +An optionally signed sequence of decimal numbers, each with a unit suffix, such as `300ms`, `-1.5h`, or `2h45m`. Valid time units are `ns`, `us`, `ms`, `s`, `m`, `h`. \ No newline at end of file