Skip to content

Commit 40f7db7

Browse files
authored
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`
1 parent d69b479 commit 40f7db7

File tree

24 files changed

+1182
-354
lines changed

24 files changed

+1182
-354
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# ConfigSchema API
2+
3+
This package contains an API that can be used to introspect the configuration
4+
struct of a collector component. It can be used to generate documentation or
5+
tools to help users configure the collector.
Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
package schemagen
15+
package configschema
1616

1717
import (
1818
"go/ast"
@@ -23,12 +23,12 @@ import (
2323
)
2424

2525
// commentsForStruct returns a map of fieldname -> comment for a struct
26-
func commentsForStruct(v reflect.Value, env env) map[string]string {
26+
func commentsForStruct(v reflect.Value, dr DirResolver) map[string]string {
2727
elem := v
2828
if v.Kind() == reflect.Ptr {
2929
elem = v.Elem()
3030
}
31-
dir := packageDir(elem.Type(), env)
31+
dir := dr.PackageDir(elem.Type())
3232
name := trimPackage(elem)
3333
return commentsForStructName(dir, name)
3434
}
@@ -48,13 +48,20 @@ func commentsForStructName(packageDir, structName string) map[string]string {
4848
comments := map[string]string{}
4949
for _, pkg := range pkgs {
5050
for _, file := range pkg.Files {
51-
if obj, ok := file.Scope.Objects[structName]; ok {
52-
if ts, ok := obj.Decl.(*ast.TypeSpec); ok {
53-
if st, ok := ts.Type.(*ast.StructType); ok {
54-
for _, field := range st.Fields.List {
55-
if field.Doc != nil {
56-
if name := fieldName(field); name != "" {
57-
comments[name] = field.Doc.Text()
51+
for _, decl := range file.Decls {
52+
if gd, ok := decl.(*ast.GenDecl); ok {
53+
for _, spec := range gd.Specs {
54+
if ts, ok := spec.(*ast.TypeSpec); ok {
55+
if ts.Name.Name == structName {
56+
if structComments := gd.Doc.Text(); structComments != "" {
57+
comments["_struct"] = structComments
58+
}
59+
if st, ok := ts.Type.(*ast.StructType); ok {
60+
for _, field := range st.Fields.List {
61+
if name := fieldName(field); name != "" {
62+
comments[name] = field.Doc.Text()
63+
}
64+
}
5865
}
5966
}
6067
}

cmd/schemagen/schemagen/comments_test.go renamed to cmd/configschema/configschema/comments_test.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,18 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
package schemagen
15+
package configschema
1616

1717
import (
1818
"reflect"
1919
"testing"
2020

21-
"github.com/stretchr/testify/require"
21+
"github.com/stretchr/testify/assert"
2222
)
2323

2424
func TestFieldComments(t *testing.T) {
2525
v := reflect.ValueOf(testStruct{})
26-
comments := commentsForStruct(v, testEnv())
27-
require.EqualValues(t, map[string]string{
28-
"Duration": "embedded, package qualified\n",
29-
}, comments)
26+
comments := commentsForStruct(v, testDR())
27+
assert.Equal(t, "embedded, package qualified comment\n", comments["Duration"])
28+
assert.Equal(t, "testStruct comment\n", comments["_struct"])
3029
}

cmd/schemagen/schemagen/libfor_test.go renamed to cmd/configschema/configschema/common_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,21 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
package schemagen
15+
package configschema
1616

1717
import "time"
1818

1919
type testPerson struct {
2020
Name string
2121
}
2222

23+
// testStruct comment
2324
type testStruct struct {
2425
One string `mapstructure:"one"`
2526
Two int `mapstructure:"two"`
2627
Three uint `mapstructure:"three"`
2728
Four bool `mapstructure:"four"`
28-
// embedded, package qualified
29+
// embedded, package qualified comment
2930
time.Duration `mapstructure:"duration"`
3031
Squashed testPerson `mapstructure:",squash"`
3132
PersonPtr *testPerson `mapstructure:"person_ptr"`
@@ -35,9 +36,9 @@ type testStruct struct {
3536
Ignored string `mapstructure:"-"`
3637
}
3738

38-
func testEnv() env {
39-
return env{
40-
srcRoot: "../../..",
41-
moduleName: defaultModule,
39+
func testDR() DirResolver {
40+
return DirResolver{
41+
SrcRoot: "../../..",
42+
ModuleName: DefaultModule,
4243
}
4344
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package configschema
16+
17+
import (
18+
"fmt"
19+
20+
"go.opentelemetry.io/collector/component"
21+
"go.opentelemetry.io/collector/config"
22+
)
23+
24+
const (
25+
receiver = "receiver"
26+
extension = "extension"
27+
processor = "processor"
28+
exporter = "exporter"
29+
)
30+
31+
// CfgInfo contains a component config instance, as well as its group name and
32+
// type.
33+
type CfgInfo struct {
34+
// the name of the component group, e.g. "receiver"
35+
Group string
36+
// the component type, e.g. "otlpreceiver.Config"
37+
Type config.Type
38+
// an instance of the component's configuration struct
39+
CfgInstance interface{}
40+
}
41+
42+
// GetAllCfgInfos accepts a Factories struct, then creates and returns a CfgInfo
43+
// for each of its components.
44+
func GetAllCfgInfos(components component.Factories) []CfgInfo {
45+
var out []CfgInfo
46+
for _, f := range components.Receivers {
47+
out = append(out, CfgInfo{
48+
Type: f.Type(),
49+
Group: receiver,
50+
CfgInstance: f.CreateDefaultConfig(),
51+
})
52+
}
53+
for _, f := range components.Extensions {
54+
out = append(out, CfgInfo{
55+
Type: f.Type(),
56+
Group: extension,
57+
CfgInstance: f.CreateDefaultConfig(),
58+
})
59+
}
60+
for _, f := range components.Processors {
61+
out = append(out, CfgInfo{
62+
Type: f.Type(),
63+
Group: processor,
64+
CfgInstance: f.CreateDefaultConfig(),
65+
})
66+
}
67+
for _, f := range components.Exporters {
68+
out = append(out, CfgInfo{
69+
Type: f.Type(),
70+
Group: exporter,
71+
CfgInstance: f.CreateDefaultConfig(),
72+
})
73+
}
74+
return out
75+
}
76+
77+
// GetCfgInfo accepts a Factories struct, then creates and returns the default
78+
// config for the component specified by the passed-in componentType and
79+
// componentName.
80+
func GetCfgInfo(components component.Factories, componentType, componentName string) (CfgInfo, error) {
81+
t := config.Type(componentName)
82+
switch componentType {
83+
case receiver:
84+
f := components.Receivers[t]
85+
if f == nil {
86+
return CfgInfo{}, fmt.Errorf("unknown %s name %q", componentType, componentName)
87+
}
88+
return CfgInfo{
89+
Type: f.Type(),
90+
Group: componentType,
91+
CfgInstance: f.CreateDefaultConfig(),
92+
}, nil
93+
case processor:
94+
f := components.Processors[t]
95+
if f == nil {
96+
return CfgInfo{}, fmt.Errorf("unknown %s name %q", componentType, componentName)
97+
}
98+
return CfgInfo{
99+
Type: f.Type(),
100+
Group: componentType,
101+
CfgInstance: f.CreateDefaultConfig(),
102+
}, nil
103+
case exporter:
104+
f := components.Exporters[t]
105+
if f == nil {
106+
return CfgInfo{}, fmt.Errorf("unknown %s name %q", componentType, componentName)
107+
}
108+
return CfgInfo{
109+
Type: f.Type(),
110+
Group: componentType,
111+
CfgInstance: f.CreateDefaultConfig(),
112+
}, nil
113+
case extension:
114+
f := components.Extensions[t]
115+
if f == nil {
116+
return CfgInfo{}, fmt.Errorf("unknown %s name %q", componentType, componentName)
117+
}
118+
return CfgInfo{
119+
Type: f.Type(),
120+
Group: componentType,
121+
CfgInstance: f.CreateDefaultConfig(),
122+
}, nil
123+
}
124+
return CfgInfo{}, fmt.Errorf("unknown component type %q", componentType)
125+
}

cmd/schemagen/schemagen/cli_single_test.go renamed to cmd/configschema/configschema/configs_test.go

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,30 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
package schemagen
15+
package configschema
1616

1717
import (
18-
"io/ioutil"
19-
"path"
20-
"path/filepath"
21-
"reflect"
2218
"testing"
2319

2420
"github.com/stretchr/testify/require"
25-
"gopkg.in/yaml.v2"
2621

2722
"go.opentelemetry.io/collector/component"
2823
"go.opentelemetry.io/collector/service/defaultcomponents"
2924
)
3025

26+
func TestGetAllConfigs(t *testing.T) {
27+
cfgs := GetAllCfgInfos(testComponents())
28+
require.NotNil(t, cfgs)
29+
}
30+
3131
func TestCreateReceiverConfig(t *testing.T) {
32-
cfg, err := getConfig(testComponents(), "receiver", "otlp")
32+
cfg, err := GetCfgInfo(testComponents(), "receiver", "otlp")
3333
require.NoError(t, err)
3434
require.NotNil(t, cfg)
3535
}
3636

3737
func TestCreateProcesorConfig(t *testing.T) {
38-
cfg, err := getConfig(testComponents(), "processor", "filter")
38+
cfg, err := GetCfgInfo(testComponents(), "processor", "filter")
3939
require.NoError(t, err)
4040
require.NotNil(t, cfg)
4141
}
@@ -64,29 +64,13 @@ func TestGetConfig(t *testing.T) {
6464
}
6565
for _, test := range tests {
6666
t.Run(test.name, func(t *testing.T) {
67-
cfg, err := getConfig(testComponents(), test.componentType, test.name)
67+
cfg, err := GetCfgInfo(testComponents(), test.componentType, test.name)
6868
require.NoError(t, err)
6969
require.NotNil(t, cfg)
7070
})
7171
}
7272
}
7373

74-
func TestCreateSingleSchemaFile(t *testing.T) {
75-
e := testEnv()
76-
tempDir := t.TempDir()
77-
e.yamlFilename = func(reflect.Type, env) string {
78-
return path.Join(tempDir, schemaFilename)
79-
}
80-
createSingleSchemaFile(testComponents(), "exporter", "otlp", e)
81-
file, err := ioutil.ReadFile(filepath.Clean(path.Join(tempDir, schemaFilename)))
82-
require.NoError(t, err)
83-
fld := field{}
84-
err = yaml.Unmarshal(file, &fld)
85-
require.NoError(t, err)
86-
require.Equal(t, "*otlpexporter.Config", fld.Type)
87-
require.NotNil(t, fld.Fields)
88-
}
89-
9074
func testComponents() component.Factories {
9175
components, err := defaultcomponents.Components()
9276
if err != nil {

0 commit comments

Comments
 (0)