feat: encode comments as part of talosctl generated configs

Comments encoding works, defaults encoding works.
Docgen was revamped: now it generates go files.
While markdown files are all handled by
`pkg/machinery/config/encoder/markdown.go`.

Changed scheme for docs. Now it no longer relies on a single `doc.go` in
the root of a package. Instead it can generate separate `*_doc.go` files
for each file in the package. `docgen` now expects to get 3 params
instead of 2. 3rd parameter is used to define a unique method name for
getting the list of structs in the file.

Backward compatibility is supported if we define package name as the 3rd
parameter.

1st parameter no longer scans whole current directory, instead it points
to the particular file that should be processed by docgen.

`talosctl docs` command now supports two flags: `--config` and `--cli`.
They allow generating only docs for v1alpha1 configs or for talosctl. If
no flags are defined, all docs are generated.

Additionally made field types clickable in the output markdown file.

Signed-off-by: Artem Chernyshev <artem.0xD2@gmail.com>
This commit is contained in:
Artem Chernyshev 2020-10-20 22:05:13 +03:00 committed by talos-bot
parent e7f6344d97
commit d0ed6d7cc6
16 changed files with 3414 additions and 797 deletions

View File

@ -90,6 +90,12 @@ RUN protoc -I/api --go_out=plugins=grpc,paths=source_relative:/api cluster/clust
# Gofumports generated files to adjust import order
RUN gofumports -w -local github.com/talos-systems/talos /api/
# run docgen for machinery config
COPY ./pkg/machinery /pkg/machinery
WORKDIR /pkg/machinery
RUN go generate /pkg/machinery/config/types/v1alpha1/
WORKDIR /
FROM scratch AS generate
COPY --from=generate-build /api/common/common.pb.go /pkg/machinery/api/common/
COPY --from=generate-build /api/health/health.pb.go /pkg/machinery/api/health/
@ -99,6 +105,7 @@ COPY --from=generate-build /api/machine/machine.pb.go /pkg/machinery/api/machine
COPY --from=generate-build /api/time/time.pb.go /pkg/machinery/api/time/
COPY --from=generate-build /api/network/network.pb.go /pkg/machinery/api/network/
COPY --from=generate-build /api/cluster/cluster.pb.go /pkg/machinery/api/cluster/
COPY --from=generate-build /pkg/machinery/config/types/v1alpha1/*_doc.go /pkg/machinery/config/types/v1alpha1/
# The base target provides a container that can be used to build all Talos
# assets.
@ -112,6 +119,7 @@ COPY ./cmd ./cmd
COPY ./pkg ./pkg
COPY ./internal ./internal
COPY --from=generate /pkg/machinery/api ./pkg/machinery/api
COPY --from=generate /pkg/machinery/config ./pkg/machinery/config
RUN go list -mod=readonly all >/dev/null
RUN ! go mod tidy -v 2>&1 | grep .
@ -592,13 +600,12 @@ RUN find . -name '*.md' -not -path '*/node_modules/*' -not -path '*/docs/talosct
# The docs target generates documentation.
FROM base AS docs-build
WORKDIR /src/pkg/machinery/config
RUN go generate ./types/v1alpha1
WORKDIR /src
COPY --from=talosctl-linux /talosctl-linux-amd64 /bin/talosctl
RUN mkdir -p /docs/talosctl \
&& env HOME=/home/user TAG=latest /bin/talosctl docs /docs/talosctl
&& env HOME=/home/user TAG=latest /bin/talosctl docs --config /docs/configuration \
&& env HOME=/home/user TAG=latest /bin/talosctl docs --cli /docs/talosctl
FROM scratch AS docs
COPY --from=docs-build /tmp/v1alpha1.md /docs/website/content/v0.7/en/configuration/v1alpha1.md
COPY --from=docs-build /docs/configuration/* /docs/website/content/v0.7/en/configuration/
COPY --from=docs-build /docs/talosctl/* /docs/talosctl/

View File

@ -130,6 +130,7 @@ generate: ## Generates source code from protobuf definitions.
.PHONY: docs
docs: ## Generates the documentation for machine config, and talosctl.
@rm -rf docs/configuration/*
@rm -rf docs/talosctl/*
@$(MAKE) local-$@ DEST=./ PLATFORM=linux/amd64

View File

@ -10,6 +10,8 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
v1alpha1 "github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
)
func filePrepender(filename string) string {
@ -18,10 +20,15 @@ func filePrepender(filename string) string {
func linkHandler(s string) string { return s }
var (
cliDocs bool
configDocs bool
)
// docsCmd represents the docs command.
var docsCmd = &cobra.Command{
Use: "docs <output>",
Short: "Generate documentation for the CLI",
Use: "docs <output> [flags]",
Short: "Generate documentation for the CLI or config",
Long: ``,
Args: cobra.ExactArgs(1),
Hidden: true,
@ -32,8 +39,18 @@ var docsCmd = &cobra.Command{
return fmt.Errorf("failed to create output directory %q", out)
}
if err := doc.GenMarkdownTreeCustom(rootCmd, out, filePrepender, linkHandler); err != nil {
return fmt.Errorf("failed to generate docs: %w", err)
all := !cliDocs && !configDocs
if cliDocs || all {
if err := doc.GenMarkdownTreeCustom(rootCmd, out, filePrepender, linkHandler); err != nil {
return fmt.Errorf("failed to generate docs: %w", err)
}
}
if configDocs || all {
if err := v1alpha1.GetDoc().Write(out); err != nil {
return fmt.Errorf("failed to generate docs: %w", err)
}
}
return nil
@ -41,5 +58,7 @@ var docsCmd = &cobra.Command{
}
func init() {
docsCmd.Flags().BoolVarP(&configDocs, "config", "c", false, "generate docs for v1alpha1 configs")
docsCmd.Flags().BoolVarP(&cliDocs, "cli", "C", false, "generate docs for CLI commands")
rootCmd.AddCommand(docsCmd)
}

File diff suppressed because it is too large Load Diff

View File

@ -2,4 +2,7 @@ module github.com/talos-systems/docgen
go 1.13
require gopkg.in/yaml.v2 v2.2.5
require (
gopkg.in/yaml.v2 v2.2.5
mvdan.cc/gofumpt v0.0.0-20200927160801-5bfeb2e70dd6
)

View File

@ -1,4 +1,40 @@
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6 h1:qKpj8TpV+LEhel7H/fR788J+KvhWZ3o3V6N2fU/iuLU=
golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
mvdan.cc/gofumpt v0.0.0-20200927160801-5bfeb2e70dd6 h1:z+/YqapuV7VZPvBb3GYmuEJbA88M3PFUxaHilHYVCpQ=
mvdan.cc/gofumpt v0.0.0-20200927160801-5bfeb2e70dd6/go.mod h1:bzrjFmaD6+xqohD3KYP0H2FEuxknnBmyyOxdhLdaIws=

View File

@ -5,6 +5,7 @@
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
@ -16,62 +17,116 @@ import (
"strings"
"text/template"
"gopkg.in/yaml.v2"
yaml "gopkg.in/yaml.v2"
"mvdan.cc/gofumpt/format"
)
var tpl = `// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// DO NOT EDIT: this file is automatically generated by docgen
package {{ .Package }}
import (
"github.com/talos-systems/talos/pkg/machinery/config/encoder"
)
var tpl = `
{{ $tick := "` + "`" + `" -}}
### {{ .Name }}
var (
{{ range $struct := .Structs -}}
{{ $struct.Name }}Doc encoder.Doc
{{ end -}}
)
{{ range $entry := .Entries -}}
#### {{ $entry.Name }}
func init() {
{{ range $struct := .Structs -}}
{{ $docVar := printf "%v%v" $struct.Name "Doc" }}
{{ $docVar }}.Type = "{{ $struct.Name }}"
{{ $docVar }}.Comments[encoder.HeadComment] = "{{ $struct.Text.Comment }}"
{{ $docVar }}.Description = "{{ $struct.Text.Description }}"
{{ range $example := $struct.Text.Examples }}
{{ if $example.Value }}
{{ $docVar }}.AddExample("{{ $example.Name }}", {{ $example.Value }})
{{ end -}}
{{ end -}}
{{ $docVar }}.Fields = make([]encoder.Doc,{{ len $struct.Fields }})
{{ range $index, $field := $struct.Fields -}}
{{ $docVar }}.Fields[{{ $index }}].Name = "{{ $field.Tag }}"
{{ $docVar }}.Fields[{{ $index }}].Type = "{{ $field.Type }}"
{{ $docVar }}.Fields[{{ $index }}].Note = "{{ $field.Note }}"
{{ $docVar }}.Fields[{{ $index }}].Description = "{{ $field.Text.Description }}"
{{ $docVar }}.Fields[{{ $index }}].Comments[encoder.LineComment] = "{{ $field.Text.Comment }}"
{{ range $example := $field.Text.Examples }}
{{ if $example.Value }}
{{ $docVar }}.Fields[{{ $index }}].AddExample("{{ $example.Name }}", {{ $example.Value }})
{{ end -}}
{{ end -}}
{{ if $field.Text.Values -}}
{{ $docVar }}.Fields[{{ $index }}].Values = []string{
{{ range $value := $field.Text.Values -}}
"{{ $value }}",
{{ end -}}
}
{{ end -}}
{{ end -}}
{{ end }}
}
{{ $entry.Text.Description }}
Type: {{ $tick }}{{ $entry.Type }}{{ $tick }}
{{ if $entry.Text.Values -}}
Valid Values:
{{ range $value := $entry.Text.Values -}}
- {{ $tick }}{{ $value }}{{ $tick }}
{{ end }}
{{ range $struct := .Structs -}}
func (_ {{ $struct.Name }}) Doc() *encoder.Doc {
return &{{ $struct.Name }}Doc
}
{{ end -}}
{{ if $entry.Text.Examples -}}
Examples:
{{ range $example := $entry.Text.Examples }}
{{ $tick }}{{ $tick }}{{ $tick }}yaml
{{ $example }}
{{ $tick }}{{ $tick }}{{ $tick }}
{{ end }}
{{ end }}
{{- if $entry.Note -}}
> {{ $entry.Note }}
{{ end }}
{{- end -}}
---
// Get{{ .Name }}Doc returns documentation for the file {{ .File }}.
func Get{{ .Name }}Doc() *encoder.FileDoc {
return &encoder.FileDoc{
Name: "{{ .Package }}{{ if .Name }}.{{ .Name }}{{ end }}",
Description: "---\ntitle: {{ .Package }}{{ if .Name }}.{{ .Name }}{{ end }}\n---\n<!-- markdownlint-disable MD024 -->\n\n{{ .Header }}",
Structs: []*encoder.Doc{
{{ range $struct := .Structs -}}
&{{ $struct.Name }}Doc,
{{ end -}}
},
}
}
`
type Doc struct {
Title string
Sections []*Section
}
type Section struct {
Name string
Entries []*Entry
Package string
Title string
Header string
File string
Structs []*Struct
}
type Entry struct {
type Struct struct {
Name string
Text *Text
Fields []*Field
}
type Example struct {
Name string `yaml:"name"`
Value string `yaml:"value"`
}
type Field struct {
Name string
Type string
Text *Text
Tag string
Note string
}
type Text struct {
Description string `json:"description"`
Values []string `json:"values"`
Examples []string `json:"examples"`
Comment string `json:"-"`
Description string `json:"description"`
Examples []*Example `json:"examples"`
Values []string `json:"values"`
}
func in(p string) (string, error) {
@ -89,6 +144,7 @@ func out(p string) (*os.File, error) {
type structType struct {
name string
text *Text
pos token.Pos
node *ast.StructType
}
@ -127,8 +183,15 @@ func collectStructs(node ast.Node) []*structType {
structName := t.Name.Name
text := &Text{}
if t.Doc != nil {
text = parseComment([]byte(t.Doc.Text()))
}
s := &structType{
name: structName,
text: text,
node: x,
pos: x.Pos(),
}
@ -144,17 +207,40 @@ func collectStructs(node ast.Node) []*structType {
return structs
}
type field struct {
func parseComment(comment []byte) *Text {
text := &Text{}
if err := yaml.Unmarshal(comment, text); err != nil {
// not yaml, fallback
text.Description = string(comment)
// take only the first line from the Description for the comment
text.Comment = strings.Split(text.Description, "\n")[0]
return text
}
text.Description = strings.TrimSpace(text.Description)
// take only the first line from the Description for the comment
text.Comment = strings.Split(text.Description, "\n")[0]
text.Description = escape(text.Description)
for _, example := range text.Examples {
example.Name = escape(example.Name)
example.Value = strings.TrimSpace(example.Value)
}
return text
}
func parseFieldType(p interface{}) string {
if m, ok := p.(*ast.MapType); ok {
return fmt.Sprintf("map[%s]%s", parseFieldType(m.Key), parseFieldType(m.Value))
}
switch t := p.(type) {
case *ast.Ident:
return t.Name
case *ast.ArrayType:
return "array"
case *ast.MapType:
return "map"
return "[]" + parseFieldType(p.(*ast.ArrayType).Elt)
case *ast.StructType:
return "struct"
case *ast.StarExpr:
@ -163,56 +249,84 @@ func parseFieldType(p interface{}) string {
return parseFieldType(t.Sel)
default:
log.Printf("unknown: %#v", t)
return ""
}
}
func collectFields(s *structType) (entries []*Entry) {
entries = []*Entry{}
func escape(value string) string {
return strings.TrimSpace(strings.ReplaceAll(
strings.ReplaceAll(value, "\"", "\\\""),
"\n",
"\\n",
))
}
for _, field := range s.node.Fields.List {
if field.Tag == nil {
if field.Names == nil {
func collectFields(s *structType) (fields []*Field) {
fields = []*Field{}
for _, f := range s.node.Fields.List {
if f.Tag == nil {
if f.Names == nil {
// This is an embedded struct.
continue
}
log.Fatalf("field %q is missing a yaml tag", field.Names[0].Name)
}
if field.Doc == nil {
log.Fatalf("field %q is missing a documentation", field.Names[0].Name)
if f.Doc == nil {
log.Fatalf("field %q is missing a documentation", f.Names[0].Name)
}
tag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
name := tag.Get("yaml")
name = strings.Split(name, ",")[0]
name := f.Names[0].Name
fieldType := parseFieldType(field.Type)
fieldType := parseFieldType(f.Type)
text := &Text{}
if err := yaml.Unmarshal([]byte(field.Doc.Text()), text); err != nil {
log.Fatal(err)
tag := reflect.StructTag(strings.Trim(f.Tag.Value, "`"))
yamlTag := tag.Get("yaml")
yamlTag = strings.Split(yamlTag, ",")[0]
if yamlTag == "" {
yamlTag = strings.ToLower(yamlTag)
}
entry := &Entry{
text := parseComment([]byte(f.Doc.Text()))
field := &Field{
Name: name,
Tag: yamlTag,
Type: fieldType,
Text: text,
}
if field.Comment != nil {
entry.Note = field.Comment.Text()
if f.Comment != nil {
field.Note = escape(f.Comment.Text())
}
entries = append(entries, entry)
fields = append(fields, field)
}
return entries
return fields
}
func render(section *Section, f *os.File) {
t := template.Must(template.New("section.tpl").Parse(tpl))
err := t.Execute(f, section)
func render(doc *Doc, dest string) {
t := template.Must(template.New("docfile.tpl").Parse(tpl))
buf := bytes.Buffer{}
err := t.Execute(&buf, doc)
if err != nil {
panic(err)
}
formatted, err := format.Source(buf.Bytes(), format.Options{})
if err != nil {
log.Printf("data: %s", buf.Bytes())
panic(err)
}
out, err := out(dest)
defer out.Close()
_, err = out.Write(formatted)
if err != nil {
panic(err)
}
@ -228,64 +342,58 @@ func main() {
fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, abs, nil, parser.ParseComments)
node, err := parser.ParseFile(fset, abs, nil, parser.ParseComments)
if err != nil {
log.Fatal(err)
}
var structs []*structType
for _, pkg := range pkgs {
for _, astFile := range pkg.Files {
tokenFile := fset.File(astFile.Pos())
if tokenFile == nil {
continue
}
packageName := node.Name.Name
fmt.Printf("parsing file in package %q: %s\n", pkg.Name, tokenFile.Name())
structs = append(structs, collectStructs(astFile)...)
}
tokenFile := fset.File(node.Pos())
if tokenFile == nil {
log.Fatalf("No token")
}
fmt.Printf("parsing file in package %q: %s\n", packageName, tokenFile.Name())
structs = append(structs, collectStructs(node)...)
if len(structs) == 0 {
log.Fatalf("failed to find types that could be documented in %s", abs)
}
doc := &Doc{
Sections: []*Section{},
Package: packageName,
Structs: []*Struct{},
}
for _, s := range structs {
fmt.Printf("generating docs for type: %q\n", s.name)
entries := collectFields(s)
fields := collectFields(s)
section := &Section{
Name: s.name,
Entries: entries,
s := &Struct{
Name: s.name,
Text: s.text,
Fields: fields,
}
doc.Sections = append(doc.Sections, section)
doc.Structs = append(doc.Structs, s)
}
node, err := parser.ParseFile(fset, filepath.Join(abs, "doc.go"), nil, parser.ParseComments)
if err != nil {
log.Fatal(err)
if err == nil && len(os.Args) > 3 {
doc.Package = node.Name.Name
if os.Args[3] != doc.Package {
doc.Name = os.Args[3]
}
if node.Doc != nil {
doc.Header = escape(node.Doc.Text())
}
}
out, err := out(os.Args[2])
defer out.Close()
out.WriteString("---\n")
out.WriteString("title: " + node.Name.Name + "\n")
out.WriteString("---\n")
out.WriteString("\n")
out.WriteString("<!-- markdownlint-disable MD024 -->")
out.WriteString("\n")
out.WriteString("\n")
out.WriteString(node.Doc.Text())
for _, section := range doc.Sections {
render(section, out)
}
doc.File = os.Args[2]
render(doc, os.Args[2])
}

View File

@ -18,7 +18,7 @@ type Mock struct {
}
func init() {
config.Register("mock", func(verion string) interface{} {
config.Register("mock", func(string) interface{} {
return &Mock{}
})
}

View File

@ -0,0 +1,264 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package encoder
import (
"reflect"
"regexp"
"strings"
yaml "gopkg.in/yaml.v3"
)
const (
// HeadComment populates `yaml.Node` `HeadComment`.
HeadComment = iota
// LineComment populates `yaml.Node` `LineComment`.
LineComment
// FootComment populates `yaml.Node` `FootComment`.
FootComment
)
// Doc represents a struct documentation rendered from comments by docgen.
type Doc struct {
// Comments stores foot, line and head comments.
Comments [3]string
// Fields contains fields documentation if related item is a struct.
Fields []Doc
// Examples list of example values for the item.
Examples []*Example
// Values is only used to render valid values list in the documentation.
Values []string
// Description represents the full description for the item.
Description string
// Name represents struct name or field name.
Name string
// Type represents struct name or field type.
Type string
// Note is rendered as a note for the example in markdown file.
Note string
}
// AddExample adds a new example snippet to the doc.
func (d *Doc) AddExample(name string, value interface{}) {
if d.Examples == nil {
d.Examples = []*Example{}
}
d.Examples = append(d.Examples, &Example{
Name: name,
Value: value,
})
}
// Example represents one example snippet for a type.
type Example struct {
Name string
Value interface{}
}
// Field gets field from the list of fields.
func (d *Doc) Field(i int) *Doc {
if i < len(d.Fields) {
return &d.Fields[i]
}
return nil
}
// Documented is used to check if struct has any documentation defined for it.
type Documented interface {
// Doc requests documentation object.
Doc() *Doc
}
func mergeDoc(a, b *Doc) *Doc {
var res Doc
if a != nil {
res = *a
}
if b == nil {
return &res
}
for i, comment := range b.Comments {
if comment != "" {
res.Comments[i] = comment
}
}
if len(res.Examples) == 0 {
res.Examples = b.Examples
}
return &res
}
func getDoc(in interface{}) *Doc {
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr && v.IsNil() {
in = reflect.New(v.Type().Elem()).Interface()
}
if d, ok := in.(Documented); ok {
return d.Doc()
}
return nil
}
func addComments(node *yaml.Node, doc *Doc, comments ...int) {
if doc != nil {
dest := []*string{
&node.HeadComment,
&node.LineComment,
&node.FootComment,
}
if len(comments) == 0 {
comments = []int{
HeadComment,
LineComment,
FootComment,
}
}
for _, i := range comments {
if doc.Comments[i] != "" {
*dest[i] = doc.Comments[i]
}
}
}
}
// nolint:gocyclo
func renderExample(key string, doc *Doc) string {
if doc == nil {
return ""
}
examples := []string{}
for i, e := range doc.Examples {
v := reflect.ValueOf(e.Value)
if !isSet(v) {
continue
}
if v.Kind() != reflect.Ptr {
v = reflect.Indirect(v)
}
defaultValue := v.Interface()
populateExamples(defaultValue, i)
node, err := toYamlNode(defaultValue)
if err != nil {
continue
}
node, err = toYamlNode(map[string]*yaml.Node{
key: node,
})
if err != nil {
continue
}
if i == 0 {
addComments(node, doc, HeadComment, LineComment)
}
// replace head comment with line comment
if node.HeadComment == "" {
node.HeadComment = node.LineComment
}
node.LineComment = ""
if e.Name != "" {
if node.HeadComment != "" {
node.HeadComment += "\n\n"
}
node.HeadComment = node.HeadComment + e.Name + "\n"
}
data, err := yaml.Marshal(node)
if err != nil {
continue
}
var example string
// don't collapse comment
re := regexp.MustCompile(`(?m)^#`)
data = re.ReplaceAll(data, []byte("# #"))
example += string(data)
examples = append(examples, example)
}
return strings.Join(examples, "")
}
func readExample(v reflect.Value, doc *Doc, index int) {
if doc == nil || len(doc.Examples) == 0 {
return
}
numExamples := len(doc.Examples)
if index >= numExamples {
index = numExamples - 1
}
defaultValue := reflect.ValueOf(doc.Examples[index].Value)
if isSet(defaultValue) {
if v.Kind() != reflect.Ptr && defaultValue.Kind() == reflect.Ptr {
defaultValue = defaultValue.Elem()
}
v.Set(defaultValue.Convert(v.Type()))
}
}
//nolint:gocyclo
func populateExamples(in interface{}, index int) {
doc := getDoc(in)
if reflect.TypeOf(in).Kind() != reflect.Ptr {
return
}
v := reflect.ValueOf(in).Elem()
readExample(v, doc, index)
//nolint:exhaustive
switch v.Kind() {
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
if !field.CanInterface() {
continue
}
if doc != nil && i < len(doc.Fields) {
readExample(field, doc.Field(i), index)
}
value := field.Interface()
populateExamples(value, index)
}
case reflect.Map:
for _, key := range v.MapKeys() {
populateExamples(v.MapIndex(key), index)
}
case reflect.Slice:
for i := 0; i < v.Len(); i++ {
populateExamples(v.Index(i), index)
}
}
}

View File

@ -0,0 +1,292 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package encoder
import (
"reflect"
"sort"
"strings"
yaml "gopkg.in/yaml.v3"
)
// Encoder implements config encoder.
type Encoder struct {
value interface{}
}
// NewEncoder initializes and returns an `Encoder`.
func NewEncoder(value interface{}) *Encoder {
return &Encoder{
value: value,
}
}
// Encode convert value to yaml.
func (e *Encoder) Encode() ([]byte, error) {
node, err := toYamlNode(e.value)
if err != nil {
return nil, err
}
addComments(node, getDoc(e.value), HeadComment, LineComment)
// special handling for case when we get an empty output
if node.Kind == yaml.MappingNode && len(node.Content) == 0 && node.FootComment != "" {
res := ""
if node.HeadComment != "" {
res += node.HeadComment + "\n"
}
lines := strings.Split(res+node.FootComment, "\n")
for i, line := range lines {
if strings.HasPrefix(line, "#") || strings.TrimSpace(line) == "" {
continue
}
lines[i] = "# " + line
}
return []byte(strings.Join(lines, "\n")), nil
}
return yaml.Marshal(node)
}
func isSet(value reflect.Value) bool {
if !value.IsValid() {
return false
}
//nolint:exhaustive
switch value.Kind() {
case reflect.Ptr:
return !value.IsNil() && !value.Elem().IsZero()
case reflect.Map:
return len(value.MapKeys()) != 0
case reflect.Slice:
return value.Len() > 0
default:
return !value.IsZero()
}
}
//nolint:gocyclo
func toYamlNode(in interface{}) (*yaml.Node, error) {
node := &yaml.Node{}
// do not wrap yaml.Node into yaml.Node
if n, ok := in.(*yaml.Node); ok {
return n, nil
}
// if input implements yaml.Marshaler we should use that marshaller instead
// same way as regular yaml marshal does
if m, ok := in.(yaml.Marshaler); ok {
res, err := m.MarshalYAML()
if err != nil {
return nil, err
}
if n, ok := res.(*yaml.Node); ok {
return n, nil
}
in = res
}
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
doc := getDoc(in)
//nolint:exhaustive
switch v.Kind() {
case reflect.Struct:
node.Kind = yaml.MappingNode
t := v.Type()
examples := []string{}
for i := 0; i < v.NumField(); i++ {
// skip unexported fields
if !v.Field(i).CanInterface() {
continue
}
tag := t.Field(i).Tag.Get("yaml")
parts := strings.Split(tag, ",")
fieldName := parts[0]
if fieldName == "" {
fieldName = strings.ToLower(t.Field(i).Name)
}
if fieldName == "-" {
continue
}
var (
defined bool = isSet(v.Field(i))
skip bool
inline bool
flow bool
)
for i, part := range parts {
// always skip the first argument
if i == 0 {
continue
}
if part == "omitempty" && !defined {
skip = true
}
if part == "inline" {
inline = true
}
if part == "flow" {
flow = true
}
}
var value interface{}
if v.Field(i).CanInterface() {
value = v.Field(i).Interface()
}
// get documentation data either from field, or from type
var fieldDoc *Doc
if doc != nil {
fieldDoc = mergeDoc(getDoc(value), doc.Field(i))
} else {
fieldDoc = getDoc(value)
}
if !defined {
example := renderExample(fieldName, fieldDoc)
if example != "" {
examples = append(examples, example)
skip = true
}
}
var style yaml.Style
if flow {
style |= yaml.FlowStyle
}
if !skip {
if inline {
child, err := toYamlNode(value)
if err != nil {
return nil, err
}
if child.Kind == yaml.MappingNode || child.Kind == yaml.SequenceNode {
appendNodes(node, child.Content...)
}
} else if err := addToMap(node, fieldDoc, fieldName, value, style); err != nil {
return nil, err
}
}
}
if len(examples) > 0 {
comment := strings.Join(examples, "\n")
// add rendered example to the foot comment of the last node
// or to the foot comment of parent node
if len(node.Content) > 0 {
node.Content[len(node.Content)-2].FootComment += "\n" + comment
} else {
node.FootComment += comment
}
}
case reflect.Map:
node.Kind = yaml.MappingNode
keys := v.MapKeys()
// always interate keys in alphabetical order to preserve the same output for maps
sort.Slice(keys, func(i, j int) bool {
return keys[i].String() < keys[j].String()
})
for _, k := range keys {
element := v.MapIndex(k)
value := element.Interface()
if err := addToMap(node, getDoc(value), k.Interface(), value, 0); err != nil {
return nil, err
}
}
case reflect.Slice:
node.Kind = yaml.SequenceNode
nodes := make([]*yaml.Node, v.Len())
for i := 0; i < v.Len(); i++ {
element := v.Index(i)
var err error
nodes[i], err = toYamlNode(element.Interface())
if err != nil {
return nil, err
}
}
appendNodes(node, nodes...)
default:
if err := node.Encode(in); err != nil {
return nil, err
}
}
return node, nil
}
func appendNodes(dest *yaml.Node, nodes ...*yaml.Node) {
if dest.Content == nil {
dest.Content = []*yaml.Node{}
}
dest.Content = append(dest.Content, nodes...)
}
func addToMap(dest *yaml.Node, doc *Doc, fieldName, in interface{}, style yaml.Style) error {
key, err := toYamlNode(fieldName)
if err != nil {
return err
}
value, err := toYamlNode(in)
if err != nil {
return err
}
value.Style = style
addComments(key, doc, HeadComment, FootComment)
addComments(value, doc, LineComment)
// override head comment with line comment for non-scalar nodes
if value.Kind != yaml.ScalarNode {
if key.HeadComment == "" {
key.HeadComment = value.LineComment
}
value.LineComment = ""
}
appendNodes(dest, key, value)
return nil
}

View File

@ -0,0 +1,384 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package encoder_test
import (
"testing"
yaml "gopkg.in/yaml.v3"
"github.com/stretchr/testify/suite"
"github.com/talos-systems/talos/pkg/machinery/config/encoder"
)
type Config struct {
Integer int `yaml:"integer"`
Slice []string `yaml:"slice"`
ComplexSlice []*Endpoint `yaml:"complex_slice"`
Map map[string]*Endpoint `yaml:"map"`
Skip string `yaml:"-"`
Omit int `yaml:",omitempty"`
Inline *Mixin `yaml:",inline"`
CustomMarshaller *WithCustomMarshaller `yaml:",omitempty"`
Bytes []byte `yaml:"bytes,flow,omitempty"`
unexported int
}
type FakeConfig struct {
Machine Machine
}
type Mixin struct {
MixedIn string `yaml:"mixed_in"`
}
type Endpoint struct {
Host string
Port int `yaml:",omitempty"`
}
type Machine struct {
State int
Config *MachineConfig
}
type MachineConfig struct {
Version string
Capabilities []string
}
type WithCustomMarshaller struct {
value string
}
// MarshalYAML implements custom marshaller.
func (cm *WithCustomMarshaller) MarshalYAML() (interface{}, error) {
node := &yaml.Node{}
if err := node.Encode(map[string]string{"value": cm.value}); err != nil {
return nil, err
}
node.HeadComment = "completely custom"
return node, nil
}
// This is manually defined documentation data for Config.
// It is intended to be generated by `docgen` command.
var (
configDoc encoder.Doc
endpointDoc encoder.Doc
mixinDoc encoder.Doc
machineDoc encoder.Doc
machineConfigDoc encoder.Doc
)
func init() {
configDoc.Comments[encoder.HeadComment] = "test configuration"
configDoc.Fields = make([]encoder.Doc, 8)
configDoc.Fields[1].Comments[encoder.LineComment] = "<<<"
configDoc.Fields[2].Comments[encoder.HeadComment] = "complex slice"
configDoc.Fields[3].Comments[encoder.FootComment] = "some text example for map"
endpointDoc.Comments[encoder.HeadComment] = "endpoint settings"
endpointDoc.Fields = make([]encoder.Doc, 2)
endpointDoc.Fields[0].Comments[encoder.LineComment] = "endpoint host"
endpointDoc.Fields[1].Comments[encoder.LineComment] = "custom port"
mixinDoc.Fields = make([]encoder.Doc, 1)
mixinDoc.Fields[0].Comments[encoder.LineComment] = "was inlined"
machineDoc.Examples = []*encoder.Example{
{
Name: "uncomment me",
Value: &Machine{
State: 100,
},
},
{
Name: "second example",
Value: &Machine{
State: -1,
},
},
}
machineDoc.Fields = make([]encoder.Doc, 2)
machineDoc.Fields[1].Examples = []*encoder.Example{
{
Name: "",
Value: &MachineConfig{
Version: "0.0.2",
},
},
}
machineConfigDoc.Fields = make([]encoder.Doc, 2)
machineConfigDoc.Fields[0].Comments[encoder.HeadComment] = "this is some version"
machineConfigDoc.Fields[1].Examples = []*encoder.Example{
{
Name: "",
Value: []string{
"reboot", "upgrade",
},
},
}
}
func (c Config) Doc() *encoder.Doc {
return &configDoc
}
func (c Endpoint) Doc() *encoder.Doc {
return &endpointDoc
}
func (c Mixin) Doc() *encoder.Doc {
return &mixinDoc
}
func (c Machine) Doc() *encoder.Doc {
return &machineDoc
}
func (c MachineConfig) Doc() *encoder.Doc {
return &machineConfigDoc
}
// tests
type EncoderSuite struct {
suite.Suite
}
func (suite *EncoderSuite) TestRun() {
e := &Endpoint{
Port: 8080,
}
tests := []struct {
name string
value interface{}
expectedYAML string
incompatible bool
}{
{
name: "default struct",
value: &Config{},
expectedYAML: `# test configuration
integer: 0
# <<<
slice: []
# complex slice
complex_slice: []
map: {}
# some text example for map
`,
},
{
name: "struct with custom marshaller",
value: &Config{
CustomMarshaller: &WithCustomMarshaller{
value: "abcd",
},
},
expectedYAML: `# test configuration
integer: 0
# <<<
slice: []
# complex slice
complex_slice: []
map: {}
# some text example for map
custommarshaller:
# completely custom
value: abcd
`,
},
{
name: "bytes flow",
value: &Config{
Bytes: []byte("..."),
},
expectedYAML: `# test configuration
integer: 0
# <<<
slice: []
# complex slice
complex_slice: []
map: {}
# some text example for map
bytes: [46, 46, 46]
`,
},
{
name: "map check",
value: &Config{
Map: map[string]*Endpoint{
"endpoint": new(Endpoint),
},
unexported: -1,
},
expectedYAML: `# test configuration
integer: 0
# <<<
slice: []
# complex slice
complex_slice: []
map:
# endpoint settings
endpoint:
host: "" # endpoint host
# some text example for map
`,
},
{
name: "nil map element",
value: &Config{
Map: map[string]*Endpoint{
"endpoint": nil,
},
},
expectedYAML: `# test configuration
integer: 0
# <<<
slice: []
# complex slice
complex_slice: []
map:
# endpoint settings
endpoint: null
# some text example for map
`,
},
{
name: "nil map element",
value: &Config{
Map: map[string]*Endpoint{
"endpoint": new(Endpoint),
},
ComplexSlice: []*Endpoint{e},
},
expectedYAML: `# test configuration
integer: 0
# <<<
slice: []
# complex slice
complex_slice:
- host: "" # endpoint host
port: 8080 # custom port
map:
# endpoint settings
endpoint:
host: "" # endpoint host
# some text example for map
`,
},
{
name: "inline",
value: &Config{
Inline: &Mixin{
MixedIn: "a",
},
},
expectedYAML: `# test configuration
integer: 0
# <<<
slice: []
# complex slice
complex_slice: []
map: {}
# some text example for map
mixed_in: a # was inlined
`,
},
{
name: "comment example if zero",
value: &FakeConfig{},
expectedYAML: `# # uncomment me
# machine:
# state: 100
# config:
# # this is some version
# version: 0.0.2
# capabilities:
# - reboot
# - upgrade
# # second example
# machine:
# state: -1
# config:
# # this is some version
# version: 0.0.2
# capabilities:
# - reboot
# - upgrade
`,
incompatible: true,
},
{
name: "comment example if partially set",
value: &FakeConfig{
Machine{
State: 1000,
},
},
expectedYAML: `machine:
state: 1000
# config:
# # this is some version
# version: 0.0.2
# capabilities:
# - reboot
# - upgrade
`,
incompatible: true,
},
}
for _, test := range tests {
encoder := encoder.NewEncoder(test.value)
data, err := encoder.Encode()
suite.Assert().NoError(err)
// compare with expected string output
suite.Assert().EqualValues(test.expectedYAML, string(data), test.name)
// decode into raw map to strip all comments
actualMap, err := decodeToMap(data)
suite.Assert().NoError(err)
// skip if marshaller output is not the same for our encoder and vanilla one
// note: it is only incompatible if config contains nested structs stored as value
// and if these nested structs are documented and you try to load generated yaml into map[interface{}]interface{}
if !test.incompatible {
// compare with regular yaml.Marshal call
expected, err := yaml.Marshal(test.value)
suite.Assert().NoError(err)
expectedMap, err := decodeToMap(expected)
suite.Assert().NoError(err)
suite.Assert().EqualValues(expectedMap, actualMap)
}
}
}
func decodeToMap(data []byte) (map[interface{}]interface{}, error) {
raw := map[interface{}]interface{}{}
err := yaml.Unmarshal(data, &raw)
return raw, err
}
func TestEncoderSuite(t *testing.T) {
suite.Run(t, &EncoderSuite{})
}

View File

@ -0,0 +1,171 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package encoder
import (
"bytes"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"
yaml "gopkg.in/yaml.v3"
)
var markdownTemplate = `{{ .Description }}
{{- $anchors := .Anchors -}}
{{- $tick := "` + "`" + `" -}}
{{ range $struct := .Structs }}
### {{ $struct.Type }}
{{ if $struct.Description -}}
{{ $struct.Description }}
{{ end -}}
{{ if $struct.Examples -}}
Examples:
{{ range $example := $struct.Examples }}
{{ yaml $example.Value "" }}
{{ end -}}
{{ end }}
{{ if $struct.Fields -}}
{{ range $field := $struct.Fields -}}
#### {{ $field.Name }}
Type: <code>{{ encodeType $field.Type }}</code>
{{ $field.Description }}
{{ if $field.Values }}
Valid Values:
{{ range $value := $field.Values -}}
- {{ $tick }}{{ $value }}{{ $tick }}
{{ end -}}
{{ end -}}
{{ if $field.Examples }}
Examples:
{{ range $example := $field.Examples }}
{{ yaml $example.Value $field.Name }}
{{ end -}}
{{ end }}
{{ if $field.Note -}}
> {{ $field.Note }}
{{ end -}}
{{ end -}}
{{ end -}}
{{ if $struct.Values -}}
{{ $struct.Type }} Valid Values:
{{ range $value := $struct.Values -}}
- {{ $tick }}{{ $value }}{{ $tick }}
{{ end -}}
{{- end -}}
---
{{ end -}}`
// FileDoc represents a single go file documentation.
type FileDoc struct {
// Name will be used in md file name pattern.
Name string
// Description file description, supports markdown.
Description string
// Structs structs defined in the file.
Structs []*Doc
Anchors map[string]string
}
// Encode encodes file documentation as MD file.
func (fd *FileDoc) Encode() ([]byte, error) {
anchors := map[string]string{}
for _, t := range fd.Structs {
anchors[t.Type] = strings.ToLower(t.Type)
}
fd.Anchors = anchors
t := template.Must(template.New("file_markdown.tpl").
Funcs(template.FuncMap{
"yaml": encodeYaml,
"encodeType": fd.encodeType,
}).
Parse(markdownTemplate))
buf := bytes.Buffer{}
if err := t.Execute(&buf, fd); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// Write dumps documentation string to folder.
func (fd *FileDoc) Write(path string) error {
data, err := fd.Encode()
if err != nil {
return err
}
if stat, e := os.Stat(path); !os.IsNotExist(e) {
if !stat.IsDir() {
return fmt.Errorf("destination path should be a directory")
}
} else {
if e := os.MkdirAll(path, 0o777); e != nil {
return e
}
}
f, err := os.Create(filepath.Join(path, fmt.Sprintf("%s.%s", fd.Name, "md")))
if err != nil {
return err
}
if _, err := f.Write(data); err != nil {
return err
}
return nil
}
func (fd *FileDoc) encodeType(t string) string {
re := regexp.MustCompile(`\w+`)
for _, s := range re.FindAllString(t, -1) {
if anchor, ok := fd.Anchors[s]; ok {
t = strings.ReplaceAll(t, s, fmt.Sprintf("[%s](#%s)", s, anchor))
}
}
return t
}
func encodeYaml(in interface{}, name string) string {
if name != "" {
in = map[string]interface{}{
name: in,
}
}
node, err := toYamlNode(in)
if err != nil {
return fmt.Sprintf("yaml encoding failed %s", err)
}
data, err := yaml.Marshal(node)
if err != nil {
return fmt.Sprintf("yaml encoding failed %s", err)
}
lines := strings.Split(string(data), "\n")
for i, line := range lines {
lines[i] = strings.TrimRight(line, " ")
}
return fmt.Sprintf("```yaml\n%s```", strings.Join(lines, "\n"))
}

View File

@ -1,61 +0,0 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/*
Package v1alpha1 configuration file contains all the options available for configuring a machine.
We can generate the files using `talosctl`.
This configuration is enough to get started in most cases, however it can be customized as needed.
```bash
talosctl config generate --version v1alpha1 <cluster name> <cluster endpoint>
````
This will generate a machine config for each node type, and a talosconfig.
The following is an example of an `init.yaml`:
```yaml
version: v1alpha1
machine:
type: init
token: 5dt69c.npg6duv71zwqhzbg
ca:
crt: <base64 encoded Ed25519 certificate>
key: <base64 encoded Ed25519 key>
certSANs: []
kubelet: {}
network: {}
install:
disk: /dev/sda
image: ghcr.io/talos-systems/installer:latest
bootloader: true
wipe: false
force: false
cluster:
controlPlane:
endpoint: https://1.2.3.4
clusterName: example
network:
cni: ""
dnsDomain: cluster.local
podSubnets:
- 10.244.0.0/16
serviceSubnets:
- 10.96.0.0/12
token: wlzjyw.bei2zfylhs2by0wd
certificateKey: 20d9aafb46d6db4c0958db5b3fc481c8c14fc9b1abd8ac43194f4246b77131be
aescbcEncryptionSecret: z01mye6j16bspJYtTB/5SFX8j7Ph4JXxM2Xuu4vsBPM=
ca:
crt: <base64 encoded RSA certificate>
key: <base64 encoded RSA key>
apiServer: {}
controllerManager: {}
scheduler: {}
etcd:
ca:
crt: <base64 encoded RSA certificate>
key: <base64 encoded RSA key>
```
*/
package v1alpha1

View File

@ -14,8 +14,6 @@ import (
"strings"
"time"
yaml "gopkg.in/yaml.v3"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/talos-systems/bootkube-plugin/pkg/asset"
@ -23,6 +21,7 @@ import (
"github.com/talos-systems/crypto/x509"
"github.com/talos-systems/talos/pkg/machinery/config"
"github.com/talos-systems/talos/pkg/machinery/config/encoder"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine"
"github.com/talos-systems/talos/pkg/machinery/constants"
)
@ -68,13 +67,10 @@ func (c *Config) String() (string, error) {
}
// Bytes implements the config.Provider interface.
func (c *Config) Bytes() ([]byte, error) {
b, err := yaml.Marshal(c)
if err != nil {
return nil, err
}
func (c *Config) Bytes() (res []byte, err error) {
res, err = encoder.NewEncoder(c).Encode()
return b, nil
return
}
// Install implements the config.Provider interface.

View File

@ -2,9 +2,65 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/*
Package v1alpha1 configuration file contains all the options available for configuring a machine.
We can generate the files using `talosctl`.
This configuration is enough to get started in most cases, however it can be customized as needed.
```bash
talosctl config generate --version v1alpha1 <cluster name> <cluster endpoint>
````
This will generate a machine config for each node type, and a talosconfig.
The following is an example of an `init.yaml`:
```yaml
version: v1alpha1
machine:
type: init
token: 5dt69c.npg6duv71zwqhzbg
ca:
crt: <base64 encoded Ed25519 certificate>
key: <base64 encoded Ed25519 key>
certSANs: []
kubelet: {}
network: {}
install:
disk: /dev/sda
image: ghcr.io/talos-systems/installer:latest
bootloader: true
wipe: false
force: false
cluster:
controlPlane:
endpoint: https://1.2.3.4
clusterName: example
network:
cni: ""
dnsDomain: cluster.local
podSubnets:
- 10.244.0.0/16
serviceSubnets:
- 10.96.0.0/12
token: wlzjyw.bei2zfylhs2by0wd
certificateKey: 20d9aafb46d6db4c0958db5b3fc481c8c14fc9b1abd8ac43194f4246b77131be
aescbcEncryptionSecret: z01mye6j16bspJYtTB/5SFX8j7Ph4JXxM2Xuu4vsBPM=
ca:
crt: <base64 encoded RSA certificate>
key: <base64 encoded RSA key>
apiServer: {}
controllerManager: {}
scheduler: {}
etcd:
ca:
crt: <base64 encoded RSA certificate>
key: <base64 encoded RSA key>
```
*/
package v1alpha1
//go:generate docgen . /tmp/v1alpha1.md
//go:generate docgen ./v1alpha1_types.go ./v1alpha1_types_doc.go v1alpha1
import (
"net/url"
@ -25,6 +81,209 @@ func init() {
})
}
var (
// Examples section.
machineConfigRegistriesExample = &RegistriesConfig{
RegistryMirrors: map[string]*RegistryMirrorConfig{
"docker.io": {
MirrorEndpoints: []string{"https://registry-1.docker.io"},
},
},
RegistryConfig: map[string]*RegistryConfig{
"some.host:123": {
RegistryTLS: &RegistryTLSConfig{
TLSClientIdentity: &x509.PEMEncodedCertificateAndKey{
Crt: []byte("..."),
Key: []byte("..."),
},
},
RegistryAuth: &RegistryAuthConfig{
RegistryUsername: "...",
RegistryPassword: "...",
RegistryAuth: "...",
RegistryIdentityToken: "...",
},
},
},
}
pemEncodedCertificateExample *x509.PEMEncodedCertificateAndKey = &x509.PEMEncodedCertificateAndKey{
Crt: []byte("LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJIekNCMHF..."),
Key: []byte("LS0tLS1CRUdJTiBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0KTUM..."),
}
machineKubeletExample = &KubeletConfig{
KubeletImage: "docker.io/autonomy/kubelet:v1.19.3",
KubeletExtraArgs: map[string]string{
"key": "value",
},
}
machineNetworkConfigExample = &NetworkConfig{
NetworkHostname: "worker-1",
NetworkInterfaces: []*Device{
{},
},
NameServers: []string{"9.8.7.6", "8.7.6.5"},
}
machineDisksExample []*MachineDisk = []*MachineDisk{
{
DeviceName: "/dev/sdb",
DiskPartitions: []*DiskPartition{
{
DiskMountPoint: "//lib/extra",
DiskSize: 100000000,
},
},
},
}
machineInstallExample = &InstallConfig{
InstallDisk: "/dev/sda",
InstallExtraKernelArgs: []string{"option=value"},
InstallImage: "ghcr.io/talos-systems/installer:latest",
InstallBootloader: true,
InstallWipe: false,
}
machineFilesExample = []*MachineFile{
{
FileContent: "...",
FilePermissions: 0o666,
FilePath: "/tmp/file.txt",
FileOp: "append",
},
}
machineEnvExamples = []Env{
{
"GRPC_GO_LOG_VERBOSITY_LEVEL": "99",
"GRPC_GO_LOG_SEVERITY_LEVEL": "info",
"https_proxy": "http://SERVER:PORT/",
},
{
"GRPC_GO_LOG_SEVERITY_LEVEL": "error",
"https_proxy": "https://USERNAME:PASSWORD@SERVER:PORT/",
},
{
"https_proxy": "http://DOMAIN\\USERNAME:PASSWORD@SERVER:PORT/",
},
}
machineTimeExample = &TimeConfig{
TimeServers: []string{"time.cloudflare.com"},
}
machineSysctlsExample map[string]string = map[string]string{
"kernel.domainname": "talos.dev",
"net.ipv4.ip_forward": "0",
}
clusterControlPlaneExample = &ControlPlaneConfig{
Endpoint: &Endpoint{
&url.URL{
Host: "1.2.3.4",
Scheme: "https",
},
},
LocalAPIServerPort: 443,
}
clusterNetworkExample = &ClusterNetworkConfig{
CNI: &CNIConfig{
CNIName: "flannel",
},
DNSDomain: "cluster.local",
PodSubnet: []string{"10.244.0.0/16"},
ServiceSubnet: []string{"10.96.0.0/12"},
}
clusterAPIServerExample = &APIServerConfig{
ContainerImage: "...", // TODO: actual image name
ExtraArgsConfig: map[string]string{
"key": "value", // TODO: add more real live examples
},
CertSANs: []string{
"1.2.3.4",
"4.5.6.7",
},
}
clusterControllerManagerExample = &ControllerManagerConfig{
ContainerImage: "...", // TODO: actual image name
ExtraArgsConfig: map[string]string{
"key": "value", // TODO: add more real live examples
},
}
clusterProxyExample = &ProxyConfig{
ContainerImage: "...", // TODO: actual image name
ExtraArgsConfig: map[string]string{
"key": "value", // TODO: add more real live examples
},
ModeConfig: "ipvs",
}
clusterSchedulerConfig = &SchedulerConfig{
ContainerImage: "...", // TODO: actual image name
ExtraArgsConfig: map[string]string{
"key": "value", // TODO: add more real live examples
},
}
clusterEtcdConfig = &EtcdConfig{
ContainerImage: "...", // TODO: actual image name
EtcdExtraArgs: map[string]string{
"key": "value", // TODO: add more real live examples
},
RootCA: pemEncodedCertificateExample,
}
clusterPodCheckpointerExample = &PodCheckpointer{
PodCheckpointerImage: "...", // TODO: actual image name
}
clusterCoreDNSExample = &CoreDNS{
CoreDNSImage: "...", // TODO: actual image name
}
clusterAdminKubeconfigExample = AdminKubeconfigConfig{
AdminKubeconfigCertLifetime: time.Hour,
}
kubeletExtraMountsExample = []specs.Mount{
{
Source: "/var/lib/example",
Destination: "/var/lib/example",
Type: "bind",
Options: []string{
"rshared",
"ro",
},
},
}
networkConfigExtraHostsExample = []*ExtraHost{
{
HostIP: "192.168.1.100",
HostAliases: []string{
"test",
"test.domain.tld",
},
},
}
clusterCustomCNIExample = &CNIConfig{
CNIName: "custom",
CNIUrls: []string{
"https://www.mysweethttpserver.com/supersecretcni.yaml",
},
}
)
// Config defines the v1alpha1 configuration file.
type Config struct {
// description: |
@ -85,46 +344,34 @@ type MachineConfig struct {
// The `token` is used by a machine to join the PKI of the cluster.
// Using this token, a machine will create a certificate signing request (CSR), and request a certificate that will be used as its' identity.
// examples:
// - "token: 328hom.uqjzh6jnn2eie9oi"
// - name: example token
// value: "\"328hom.uqjzh6jnn2eie9oi\""
MachineToken string `yaml:"token"` // Warning: It is important to ensure that this token is correct since a machine's certificate has a short TTL by default
// description: |
// The root certificate authority of the PKI.
// It is composed of a base64 encoded `crt` and `key`.
// examples:
// - |
// ca:
// crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJIekNCMHF...
// key: LS0tLS1CRUdJTiBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0KTUM...
// - value: pemEncodedCertificateExample
// name: machine CA example
MachineCA *x509.PEMEncodedCertificateAndKey `yaml:"ca,omitempty"`
// description: |
// Extra certificate subject alternative names for the machine's certificate.
// By default, all non-loopback interface IPs are automatically added to the certificate's SANs.
// examples:
// - |
// certSANs:
// - 10.0.0.10
// - 172.16.0.10
// - 192.168.0.10
// - name: Uncomment this to enable SANs.
// value: '[]string{"10.0.0.10", "172.16.0.10", "192.168.0.10"}'
MachineCertSANs []string `yaml:"certSANs"`
// description: |
// Used to provide additional options to the kubelet.
// examples:
// - |
// kubelet:
// image:
// extraArgs:
// key: value
// - name: Kubelet definition example.
// value: machineKubeletExample
MachineKubelet *KubeletConfig `yaml:"kubelet,omitempty"`
// description: |
// Used to configure the machine's network.
// examples:
// - |
// network:
// hostname: worker-1
// interfaces:
// nameservers:
// - 9.8.7.6
// - 8.7.6.5
// - name: Network definition example.
// value: machineNetworkConfigExample
MachineNetwork *NetworkConfig `yaml:"network,omitempty"`
// description: |
// Used to partition, format and mount additional disks.
@ -132,26 +379,14 @@ type MachineConfig struct {
// Note that the partitioning and formating is done only once, if and only if no existing partitions are found.
// If `size:` is omitted, the partition is sized to occupy full disk.
// examples:
// - |
// disks:
// - device: /dev/sdb
// partitions:
// - mountpoint: /var/lib/extra
// size: 10000000000
//
// - name: MachineDisks list example.
// value: machineDisksExample
MachineDisks []*MachineDisk `yaml:"disks,omitempty"` // Note: `size` is in units of bytes.
// description: |
// Used to provide instructions for bare-metal installations.
// examples:
// - |
// install:
// disk: /dev/sda
// extraKernelArgs:
// - option=value
// image: ghcr.io/talos-systems/installer:latest
// bootloader: true
// wipe: false
// force: false
// - name: MachineInstall config usage example.
// value: machineInstallExample
MachineInstall *InstallConfig `yaml:"install,omitempty"`
// description: |
// Allows the addition of user specified files.
@ -161,13 +396,8 @@ type MachineConfig struct {
// If an `op` value of `append` is used, the existing file will be appended.
// Note that the file contents are not required to be base64 encoded.
// examples:
// - |
// files:
// - content: |
// ...
// permissions: 0666
// path: /tmp/file.txt
// op: append
// - name: MachineFiles usage example.
// value: machineFilesExample
MachineFiles []*MachineFile `yaml:"files,omitempty"` // Note: The specified `path` is relative to `/var`.
// description: |
// The `env` field allows for the addition of environment variables to a machine.
@ -179,34 +409,22 @@ type MachineConfig struct {
// - "`https_proxy`"
// - "`no_proxy`"
// examples:
// - |
// env:
// GRPC_GO_LOG_VERBOSITY_LEVEL: "99"
// GRPC_GO_LOG_SEVERITY_LEVEL: info
// https_proxy: http://SERVER:PORT/
// - |
// env:
// GRPC_GO_LOG_SEVERITY_LEVEL: error
// https_proxy: https://USERNAME:PASSWORD@SERVER:PORT/
// - |
// env:
// https_proxy: http://DOMAIN\\USERNAME:PASSWORD@SERVER:PORT/
// - name: Environment variables definition examples.
// value: machineEnvExamples[0]
// - value: machineEnvExamples[1]
// - value: machineEnvExamples[2]
MachineEnv Env `yaml:"env,omitempty"`
// description: |
// Used to configure the machine's time settings.
// examples:
// - |
// time:
// servers:
// - time.cloudflare.com
// - name: Example configuration for cloudflare ntp server.
// value: machineTimeExample
MachineTime *TimeConfig `yaml:"time,omitempty"`
// description: |
// Used to configure the machine's sysctls.
// examples:
// - |
// sysctls:
// kernel.domainname: talos.dev
// net.ipv4.ip_forward: "0"
// - name: MachineSysctls usage example.
// value: machineSysctlsExample
MachineSysctls map[string]string `yaml:"sysctls,omitempty"`
// description: |
// Used to configure the machine's container image registry mirrors.
@ -222,27 +440,7 @@ type MachineConfig struct {
//
// See also matching configuration for [CRI containerd plugin](https://github.com/containerd/cri/blob/master/docs/registry.md).
// examples:
// - |
// registries:
// mirrors:
// docker.io:
// endpoints:
// - https://registry-1.docker.io
// '*':
// endpoints:
// - http://some.host:123/
// config:
// "some.host:123":
// tls:
// ca: ... # base64-encoded CA certificate in PEM format
// clientIdentity:
// cert: ... # base64-encoded client certificate in PEM format
// key: ... # base64-encoded client key in PEM format
// auth:
// username: ...
// password: ...
// auth: ...
// identityToken: ...
// - value: machineConfigRegistriesExample
MachineRegistries RegistriesConfig `yaml:"registries,omitempty"`
}
@ -251,10 +449,8 @@ type ClusterConfig struct {
// description: |
// Provides control plane specific configuration options.
// examples:
// - |
// controlPlane:
// endpoint: https://1.2.3.4
// localAPIServerPort: 443
// - name: Setting controlplain endpoint address to 1.2.3.4 and port to 443 example.
// value: clusterControlPlaneExample
ControlPlane *ControlPlaneConfig `yaml:"controlPlane"`
// description: |
// Configures the cluster's name.
@ -262,121 +458,86 @@ type ClusterConfig struct {
// description: |
// Provides cluster network configuration.
// examples:
// - |
// network:
// cni:
// name: flannel
// dnsDomain: cluster.local
// podSubnets:
// - 10.244.0.0/16
// serviceSubnets:
// - 10.96.0.0/12
// - name: Configuring with flannel cni and setting up subnets.
// value: clusterNetworkExample
ClusterNetwork *ClusterNetworkConfig `yaml:"network,omitempty"`
// description: |
// The [bootstrap token](https://kubernetes.io/docs/reference/access-authn-authz/bootstrap-tokens/).
// examples:
// - wlzjyw.bei2zfylhs2by0wd
// - name: Bootstrap token example (do not use in production!).
// value: '"wlzjyw.bei2zfylhs2by0wd"'
BootstrapToken string `yaml:"token,omitempty"`
// description: |
// The key used for the [encryption of secret data at rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/).
// examples:
// - z01mye6j16bspJYtTB/5SFX8j7Ph4JXxM2Xuu4vsBPM=
// - name: Decryption secret example (do not use in production!).
// value: '"z01mye6j16bspJYtTB/5SFX8j7Ph4JXxM2Xuu4vsBPM="'
ClusterAESCBCEncryptionSecret string `yaml:"aescbcEncryptionSecret"`
// description: |
// The base64 encoded root certificate authority used by Kubernetes.
// examples:
// - |
// ca:
// crt: LS0tLS1CRUdJTiBDRV...
// key: LS0tLS1CRUdJTiBSU0...
// - name: ClusterCA example.
// value: pemEncodedCertificateExample
ClusterCA *x509.PEMEncodedCertificateAndKey `yaml:"ca,omitempty"`
// description: |
// API server specific configuration options.
// examples:
// - |
// apiServer:
// image: ...
// extraArgs:
// key: value
// certSANs:
// - 1.2.3.4
// - 5.6.7.8
// - value: clusterAPIServerExample
APIServerConfig *APIServerConfig `yaml:"apiServer,omitempty"`
// description: |
// Controller manager server specific configuration options.
// examples:
// - |
// controllerManager:
// image: ...
// extraArgs:
// key: value
// - value: clusterControllerManagerExample
ControllerManagerConfig *ControllerManagerConfig `yaml:"controllerManager,omitempty"`
// description: |
// Kube-proxy server-specific configuration options
// examples:
// - |
// proxy:
// mode: ipvs
// extraArgs:
// key: value
// - value: clusterProxyExample
ProxyConfig *ProxyConfig `yaml:"proxy,omitempty"`
// description: |
// Scheduler server specific configuration options.
// examples:
// - |
// scheduler:
// image: ...
// extraArgs:
// key: value
// - value: clusterSchedulerConfig
SchedulerConfig *SchedulerConfig `yaml:"scheduler,omitempty"`
// description: |
// Etcd specific configuration options.
// examples:
// - |
// etcd:
// ca:
// crt: LS0tLS1CRUdJTiBDRV...
// key: LS0tLS1CRUdJTiBSU0...
// image: ...
// - value: clusterEtcdConfig
EtcdConfig *EtcdConfig `yaml:"etcd,omitempty"`
// description: |
// Pod Checkpointer specific configuration options.
// examples:
// - |
// podCheckpointer:
// image: ...
// - value: clusterPodCheckpointerExample
PodCheckpointerConfig *PodCheckpointer `yaml:"podCheckpointer,omitempty"`
// description: |
// Core DNS specific configuration options.
// examples:
// - |
// coreDNS:
// image: ...
// - value: clusterCoreDNSExample
CoreDNSConfig *CoreDNS `yaml:"coreDNS,omitempty"`
// description: |
// A list of urls that point to additional manifests.
// These will get automatically deployed by bootkube.
// examples:
// - |
// extraManifests:
// - "https://www.mysweethttpserver.com/manifest1.yaml"
// - "https://www.mysweethttpserver.com/manifest2.yaml"
// - value: >
// []string{
// "https://www.mysweethttpserver.com/manifest1.yaml",
// "https://www.mysweethttpserver.com/manifest2.yaml",
// }
ExtraManifests []string `yaml:"extraManifests,omitempty"`
// description: |
// A map of key value pairs that will be added while fetching the ExtraManifests.
// examples:
// - |
// extraManifestHeaders:
// Token: "1234567"
// X-ExtraInfo: info
// - value: >
// map[string]string{
// "Token": "1234567",
// "X-ExtraInfo": "info",
// }
ExtraManifestHeaders map[string]string `yaml:"extraManifestHeaders,omitempty"`
// description: |
// Settings for admin kubeconfig generation.
// Certificate lifetime can be configured.
// examples:
// - |
// adminKubeconfig:
// certLifetime: 1h
// - value: clusterAdminKubeconfigExample
AdminKubeconfigConfig AdminKubeconfigConfig `yaml:"adminKubeconfig,omitempty"`
// description: |
// Indicates if master nodes are schedulable.
@ -393,26 +554,20 @@ type KubeletConfig struct {
// description: |
// The `image` field is an optional reference to an alternative kubelet image.
// examples:
// - "image: docker.io/<org>/kubelet:latest"
// - value: '"docker.io/<org>/kubelet:latest"'
KubeletImage string `yaml:"image,omitempty"`
// description: |
// The `extraArgs` field is used to provide additional flags to the kubelet.
// examples:
// - |
// extraArgs:
// key: value
// - value: >
// map[string]string{
// "key": "value",
// }
KubeletExtraArgs map[string]string `yaml:"extraArgs,omitempty"`
// description: |
// The `extraMounts` field is used to add additional mounts to the kubelet container.
// examples:
// - |
// extraMounts:
// - source: /var/lib/example
// destination: /var/lib/example
// type: bind
// options:
// - rshared
// - ro
// - value: kubeletExtraMountsExample
KubeletExtraMounts []specs.Mount `yaml:"extraMounts,omitempty"`
}
@ -478,12 +633,7 @@ type NetworkConfig struct {
// description: |
// Allows for extra entries to be added to /etc/hosts file
// examples:
// - |
// extraHostEntries:
// - ip: 192.168.1.100
// aliases:
// - test
// - test.domain.tld
// - value: networkConfigExtraHostsExample
ExtraHostEntries []*ExtraHost `yaml:"extraHostEntries,omitempty"`
}
@ -492,21 +642,18 @@ type InstallConfig struct {
// description: |
// The disk used to install the bootloader, and ephemeral partitions.
// examples:
// - /dev/sda
// - /dev/nvme0
// - value: '"/dev/sda"'
// - value: '"/dev/nvme0"'
InstallDisk string `yaml:"disk,omitempty"`
// description: |
// Allows for supplying extra kernel args to the bootloader config.
// examples:
// - |
// extraKernelArgs:
// - a=b
// - value: '[]string{"a=b"}'
InstallExtraKernelArgs []string `yaml:"extraKernelArgs,omitempty"`
// description: |
// Allows for supplying the image used to perform the installation.
// examples:
// - |
// image: docker.io/<org>/installer:latest
// - value: '"docker.io/<org>/installer:latest"'
InstallImage string `yaml:"image,omitempty"`
// description: |
// Indicates if a bootloader should be installed.
@ -609,7 +756,7 @@ type ControlPlaneConfig struct {
// Endpoint is the canonical controlplane endpoint, which can be an IP address or a DNS hostname.
// It is single-valued, and may optionally include a port number.
// examples:
// - https://1.2.3.4:443
// - value: '"https://1.2.3.4:443"'
Endpoint *Endpoint `yaml:"endpoint"`
// description: |
// The port that the API server listens on internally.
@ -674,10 +821,7 @@ type EtcdConfig struct {
// The `ca` is the root certificate authority of the PKI.
// It is composed of a base64 encoded `crt` and `key`.
// examples:
// - |
// ca:
// crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJIekNCMHF...
// key: LS0tLS1CRUdJTiBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0KTUM...
// - value: pemEncodedCertificateExample
RootCA *x509.PEMEncodedCertificateAndKey `yaml:"ca"`
// description: |
// Extra arguments to supply to etcd.
@ -696,10 +840,11 @@ type EtcdConfig struct {
// - `peer-trusted-ca-file`
// - `peer-key-file`
// examples:
// - |
// extraArgs:
// initial-cluster: https://1.2.3.4:2380
// advertise-client-urls: https://1.2.3.4:2379
// - values: >
// map[string]string{
// "initial-cluster": "https://1.2.3.4:2380",
// "advertise-client-urls": "https://1.2.3.4:2379",
// }
EtcdExtraArgs map[string]string `yaml:"extraArgs,omitempty"`
}
@ -713,31 +858,26 @@ type ClusterNetworkConfig struct {
// URLs should point to a single yaml file that will get deployed.
// Empty struct or any other name will default to bootkube's flannel.
// examples:
// - |
// cni:
// name: "custom"
// urls:
// - "https://www.mysweethttpserver.com/supersecretcni.yaml"
// - value: clusterCustomCNIExample
CNI *CNIConfig `yaml:"cni,omitempty"`
// description: |
// The domain used by Kubernetes DNS.
// The default is `cluster.local`
// examples:
// - cluser.local
// - value: '"cluser.local"'
DNSDomain string `yaml:"dnsDomain"`
// description: |
// The pod subnet CIDR.
// examples:
// - |
// podSubnets:
// - 10.244.0.0/16
// - value: >
// []string{"10.244.0.0/16"}
PodSubnet []string `yaml:"podSubnets"`
// description: |
// The service subnet CIDR.
// examples:
// - |
// serviceSubnets:
// - 10.96.0.0/12
// examples:
// - value: >
// []string{"10.96.0.0/12"}
ServiceSubnet []string `yaml:"serviceSubnets"`
}
@ -1014,10 +1154,7 @@ type RegistryTLSConfig struct {
// Enable mutual TLS authentication with the registry.
// Client certificate and key should be base64-encoded.
// examples:
// - |
// clientIdentity:
// crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJIekNCMHF...
// key: LS0tLS1CRUdJTiBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0KTUM...
// - value: pemEncodedCertificateExample
TLSClientIdentity *x509.PEMEncodedCertificateAndKey `yaml:"clientIdentity,omitempty"`
// description: |
// CA registry certificate to add the list of trusted certificates.

File diff suppressed because it is too large Load Diff