Skip to content

Commit 9732f54

Browse files
authored
feat: Define manifest objects examples in code (#477)
## Motivation We're currently lacking in examples for our `v1alpha` objects. This PR covers that hole by adding utilities for generating these examples and verifying them. ## Release Notes Added extended examples to the `v1alpha` objects with different configuration variants.
1 parent ea8a2e1 commit 9732f54

File tree

159 files changed

+28705
-245
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

159 files changed

+28705
-245
lines changed

Makefile

+7-2
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ check/format:
107107
$(call _print_check_step,Checking if files are formatted)
108108
./scripts/check-formatting.sh
109109

110-
.PHONY: generate generate/code generate/diagrams
110+
.PHONY: generate generate/code generate/examples generate/plantuml
111111
## Auto generate files.
112-
generate: generate/code generate/plantuml
112+
generate: generate/code generate/examples generate/plantuml
113113

114114
## Generate Golang code.
115115
generate/code:
@@ -119,6 +119,11 @@ generate/code:
119119
go generate ./... ./docs/mock_example
120120
${MAKE} format/go
121121

122+
## Generate examples from code.
123+
generate/examples:
124+
echo "Generating examples..."
125+
go run internal/cmd/examplegen/main.go
126+
122127
PLANTUML_JAR_URL := https://sourceforge.net/projects/plantuml/files/plantuml.jar/download
123128
PLANTUML_JAR := $(BIN_DIR)/plantuml.jar
124129
DIAGRAMS_PATH ?= .

cspell.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ ignoreRegExpList:
1717
- /nolint.*/g
1818
- /nosec.*/g
1919
- /(errors|fmt|assert|require)\.[^\(]+/g
20+
- /access(key)?id.*/ig
2021
ignorePaths:
2122
- sdk/test_data
2223
- .golangci.yml
@@ -30,7 +31,13 @@ ignorePaths:
3031
- "**/test_data/**"
3132
- "docs/mock_example/mocks/*"
3233
words:
34+
- 00u2y4e4atkzaYkXP4x8
35+
- CONNECTIONFAILURES
36+
- CONNECTIONSESTABLISHED
37+
- CONNECTIONSUCCESSES
38+
- DzpxcSRh
3339
- MAXRTT
40+
- TIMEMAX
3441
- aggs
3542
- alertmethod
3643
- alertpolicy
@@ -61,6 +68,7 @@ words:
6168
- dynatrace
6269
- endef
6370
- enduml
71+
- examplegen
6472
- fatalf
6573
- gobin
6674
- gofile
@@ -72,6 +80,7 @@ words:
7280
- goreleaser
7381
- gosec
7482
- govulncheck
83+
- group-Q72HorLyjjCc
7584
- groupmy
7685
- hrdu
7786
- httpurls
@@ -103,6 +112,7 @@ words:
103112
- opentsdb
104113
- opsgenie
105114
- pagerduty
115+
- pathutils
106116
- plantuml
107117
- preconfigured
108118
- promql

internal/cmd/docgen/go_doc.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515

1616
"golang.org/x/exp/maps"
1717

18-
"github.com/nobl9/nobl9-go/internal/testutils"
18+
"github.com/nobl9/nobl9-go/internal/pathutils"
1919
)
2020

2121
const moduleRootPath = "github.com/nobl9/nobl9-go"
@@ -32,7 +32,7 @@ func (t goTypeDoc) PkgPath() string {
3232
}
3333

3434
func parseGoDocs() map[string]goTypeDoc {
35-
root := testutils.FindModuleRoot()
35+
root := pathutils.FindModuleRoot()
3636
objectsDirectory := filepath.Join(root, "manifest")
3737
directories, err := listDirectories(objectsDirectory)
3838
if err != nil {

internal/cmd/docgen/object_doc.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"reflect"
88
"strings"
99

10-
"github.com/nobl9/nobl9-go/internal/testutils"
10+
"github.com/nobl9/nobl9-go/internal/pathutils"
1111
"github.com/nobl9/nobl9-go/manifest"
1212
)
1313

@@ -27,7 +27,7 @@ func generateObjectDocs(objectNames []string) []*ObjectDoc {
2727
}
2828
}
2929

30-
rootPath := testutils.FindModuleRoot()
30+
rootPath := pathutils.FindModuleRoot()
3131
// Generate object properties based on reflection.
3232
for _, object := range objects {
3333
mapper := newObjectMapper()

internal/cmd/examplegen/main.go

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"sort"
8+
"strings"
9+
10+
"github.com/goccy/go-yaml"
11+
12+
v1alphaExamples "github.com/nobl9/nobl9-go/internal/manifest/v1alpha/examples"
13+
"github.com/nobl9/nobl9-go/internal/pathutils"
14+
"github.com/nobl9/nobl9-go/manifest"
15+
"github.com/nobl9/nobl9-go/sdk"
16+
)
17+
18+
type examplesGeneratorConfig struct {
19+
Examples []v1alphaExamples.Example
20+
Path string
21+
Comments yaml.CommentMap
22+
}
23+
24+
const manifestPath = "manifest"
25+
26+
func main() {
27+
rootPath := pathutils.FindModuleRoot()
28+
configs := getV1alphaExamplesConfigs()
29+
for _, config := range configs {
30+
examples := make([]any, 0, len(config.Examples))
31+
for _, variant := range config.Examples {
32+
examples = append(examples, variant.GetObject())
33+
}
34+
config.Path = filepath.Join(rootPath, config.Path)
35+
var input any
36+
if len(examples) == 1 {
37+
input = examples[0]
38+
} else {
39+
input = examples
40+
}
41+
if err := writeExamples(input, config.Path, config.Comments); err != nil {
42+
panic(err.Error())
43+
}
44+
}
45+
}
46+
47+
func getV1alphaExamplesConfigs() []examplesGeneratorConfig {
48+
basePath := filepath.Join(manifestPath, "v1alpha")
49+
// Non-standard examples.
50+
configs := []examplesGeneratorConfig{
51+
{
52+
Examples: v1alphaExamples.Labels(),
53+
Path: filepath.Join(basePath, "labels_examples.yaml"),
54+
},
55+
{
56+
Examples: v1alphaExamples.MetadataAnnotations(),
57+
Path: filepath.Join(basePath, "metadata_annotations_examples.yaml"),
58+
},
59+
}
60+
// Standard examples.
61+
allExamples := [][]v1alphaExamples.Example{
62+
v1alphaExamples.Project(),
63+
v1alphaExamples.Service(),
64+
v1alphaExamples.AlertMethod(),
65+
v1alphaExamples.SLO(),
66+
v1alphaExamples.Agent(),
67+
v1alphaExamples.Direct(),
68+
v1alphaExamples.AlertPolicy(),
69+
v1alphaExamples.AlertSilence(),
70+
v1alphaExamples.Annotation(),
71+
v1alphaExamples.BudgetAdjustment(),
72+
v1alphaExamples.DataExport(),
73+
v1alphaExamples.RoleBinding(),
74+
}
75+
for _, examples := range allExamples {
76+
object := examples[0].GetObject().(manifest.Object)
77+
basePath := filepath.Join(
78+
manifestPath,
79+
object.GetVersion().VersionString(),
80+
object.GetKind().ToLower(),
81+
)
82+
grouped := groupBy(examples, func(e v1alphaExamples.Example) string { return e.GetVariant() })
83+
// If we don't have any variants, we can write all examples into examples.yaml file.
84+
if len(grouped) == 1 {
85+
configs = append(configs, examplesGeneratorConfig{
86+
Examples: examples,
87+
Path: filepath.Join(basePath, "examples.yaml"),
88+
})
89+
continue
90+
}
91+
for variant, examples := range grouped {
92+
config := examplesGeneratorConfig{
93+
Examples: examples,
94+
Path: filepath.Join(basePath, "examples", strings.ReplaceAll(strings.ToLower(variant), " ", "-")+".yaml"),
95+
Comments: make(yaml.CommentMap),
96+
}
97+
if len(examples) == 1 {
98+
configs = append(configs, config)
99+
continue
100+
}
101+
if examples[0].GetSubVariant() != "" {
102+
sort.Slice(examples, func(i, j int) bool {
103+
return examples[i].GetSubVariant() < examples[j].GetSubVariant()
104+
})
105+
}
106+
for i, example := range examples {
107+
comments := example.GetYAMLComments()
108+
if len(comments) == 0 {
109+
continue
110+
}
111+
for i := range comments {
112+
comments[i] = " " + comments[i]
113+
}
114+
config.Comments[fmt.Sprintf("$[%d]", i)] = []*yaml.Comment{yaml.HeadComment(comments...)}
115+
}
116+
configs = append(configs, config)
117+
}
118+
}
119+
return configs
120+
}
121+
122+
func writeExamples(v any, path string, comments yaml.CommentMap) error {
123+
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
124+
return err
125+
}
126+
// #nosec G304
127+
file, err := os.Create(path)
128+
if err != nil {
129+
return err
130+
}
131+
defer func() { _ = file.Close() }()
132+
if object, ok := v.(manifest.Object); ok {
133+
return sdk.EncodeObject(object, file, manifest.ObjectFormatYAML)
134+
}
135+
opts := []yaml.EncodeOption{
136+
yaml.Indent(2),
137+
yaml.UseLiteralStyleIfMultiline(true),
138+
}
139+
if len(comments) > 0 {
140+
opts = append(opts, yaml.WithComment(comments))
141+
}
142+
enc := yaml.NewEncoder(file, opts...)
143+
return enc.Encode(v)
144+
}
145+
146+
func groupBy[K comparable, V any](s []V, key func(V) K) map[K][]V {
147+
m := make(map[K][]V)
148+
for _, v := range s {
149+
k := key(v)
150+
m[k] = append(m[k], v)
151+
}
152+
return m
153+
}

internal/manifest/objects_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"github.com/stretchr/testify/assert"
99
"github.com/stretchr/testify/require"
1010

11-
"github.com/nobl9/nobl9-go/internal/testutils"
11+
"github.com/nobl9/nobl9-go/internal/pathutils"
1212
"github.com/nobl9/nobl9-go/manifest"
1313
v1alphaParser "github.com/nobl9/nobl9-go/manifest/v1alpha/parser"
1414
"github.com/nobl9/nobl9-go/sdk"
@@ -20,7 +20,7 @@ func TestMain(m *testing.M) {
2020
}
2121

2222
func TestObjectExamples(t *testing.T) {
23-
moduleRoot := testutils.FindModuleRoot()
23+
moduleRoot := pathutils.FindModuleRoot()
2424
objects, err := sdk.ReadObjects(context.Background(), filepath.Join(moduleRoot, "manifest/**/example*.yaml"))
2525
require.NoError(t, err)
2626
assert.Greater(t, len(objects), 0, "no object examples found")

0 commit comments

Comments
 (0)