2019-07-08 09:00:04 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-01-16 09:32:05 +00:00
|
|
|
"bytes"
|
2019-07-08 09:00:04 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
2019-11-28 20:56:04 +00:00
|
|
|
"path"
|
2024-01-16 09:32:05 +00:00
|
|
|
"path/filepath"
|
|
|
|
"reflect"
|
2019-11-28 20:56:04 +00:00
|
|
|
"sort"
|
|
|
|
"strconv"
|
2019-07-08 09:00:04 +00:00
|
|
|
"strings"
|
|
|
|
|
2019-11-28 20:56:04 +00:00
|
|
|
"github.com/BurntSushi/toml"
|
2022-11-21 17:36:05 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
2020-08-17 16:04:03 +00:00
|
|
|
"github.com/traefik/paerser/env"
|
|
|
|
"github.com/traefik/paerser/flag"
|
|
|
|
"github.com/traefik/paerser/generator"
|
|
|
|
"github.com/traefik/paerser/parser"
|
2023-02-03 14:24:05 +00:00
|
|
|
"github.com/traefik/traefik/v3/cmd"
|
2024-01-16 14:41:57 +00:00
|
|
|
"github.com/traefik/traefik/v3/pkg/collector/hydratation"
|
|
|
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
|
|
|
"github.com/traefik/traefik/v3/pkg/config/static"
|
2024-01-16 09:32:05 +00:00
|
|
|
"gopkg.in/yaml.v3"
|
2019-07-08 09:00:04 +00:00
|
|
|
)
|
|
|
|
|
2024-01-16 09:32:05 +00:00
|
|
|
var commentGenerated = `## CODE GENERATED AUTOMATICALLY
|
|
|
|
## THIS FILE MUST NOT BE EDITED BY HAND
|
|
|
|
`
|
|
|
|
|
2019-07-08 09:00:04 +00:00
|
|
|
func main() {
|
2024-01-16 14:41:57 +00:00
|
|
|
logger := log.With().Logger()
|
2024-01-16 09:32:05 +00:00
|
|
|
|
|
|
|
dynConf := &dynamic.Configuration{}
|
|
|
|
|
|
|
|
err := hydratation.Hydrate(dynConf)
|
|
|
|
if err != nil {
|
2024-01-16 14:41:57 +00:00
|
|
|
logger.Fatal().Err(err).Send()
|
2024-01-16 09:32:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dynConf.HTTP.Models = map[string]*dynamic.Model{}
|
|
|
|
clean(dynConf.HTTP.Middlewares)
|
|
|
|
clean(dynConf.TCP.Middlewares)
|
|
|
|
clean(dynConf.HTTP.Services)
|
|
|
|
clean(dynConf.TCP.Services)
|
|
|
|
clean(dynConf.UDP.Services)
|
|
|
|
|
|
|
|
err = tomlWrite("./docs/content/reference/dynamic-configuration/file.toml", dynConf)
|
|
|
|
if err != nil {
|
2024-01-16 14:41:57 +00:00
|
|
|
logger.Fatal().Err(err).Send()
|
2024-01-16 09:32:05 +00:00
|
|
|
}
|
|
|
|
err = yamlWrite("./docs/content/reference/dynamic-configuration/file.yaml", dynConf)
|
|
|
|
if err != nil {
|
2024-01-16 14:41:57 +00:00
|
|
|
logger.Fatal().Err(err).Send()
|
2024-01-16 09:32:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err = labelsWrite("./docs/content/reference/dynamic-configuration", dynConf)
|
|
|
|
if err != nil {
|
2024-01-16 14:41:57 +00:00
|
|
|
logger.Fatal().Err(err).Send()
|
2024-01-16 09:32:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
staticConf := &static.Configuration{}
|
|
|
|
|
|
|
|
err = hydratation.Hydrate(staticConf)
|
|
|
|
if err != nil {
|
2024-01-16 14:41:57 +00:00
|
|
|
logger.Fatal().Err(err).Send()
|
2024-01-16 09:32:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
delete(staticConf.EntryPoints, "EntryPoint1")
|
|
|
|
|
|
|
|
err = tomlWrite("./docs/content/reference/static-configuration/file.toml", staticConf)
|
|
|
|
if err != nil {
|
2024-01-16 14:41:57 +00:00
|
|
|
logger.Fatal().Err(err).Send()
|
2024-01-16 09:32:05 +00:00
|
|
|
}
|
|
|
|
err = yamlWrite("./docs/content/reference/static-configuration/file.yaml", staticConf)
|
|
|
|
if err != nil {
|
2024-01-16 14:41:57 +00:00
|
|
|
logger.Fatal().Err(err).Send()
|
2024-01-16 09:32:05 +00:00
|
|
|
}
|
|
|
|
|
2020-08-17 16:04:03 +00:00
|
|
|
genStaticConfDoc("./docs/content/reference/static-configuration/env-ref.md", "", func(i interface{}) ([]parser.Flat, error) {
|
|
|
|
return env.Encode(env.DefaultNamePrefix, i)
|
|
|
|
})
|
2019-07-08 09:00:04 +00:00
|
|
|
genStaticConfDoc("./docs/content/reference/static-configuration/cli-ref.md", "--", flag.Encode)
|
2019-11-28 20:56:04 +00:00
|
|
|
genKVDynConfDoc("./docs/content/reference/dynamic-configuration/kv-ref.md")
|
2019-07-08 09:00:04 +00:00
|
|
|
}
|
|
|
|
|
2024-01-16 09:32:05 +00:00
|
|
|
func labelsWrite(outputDir string, element *dynamic.Configuration) error {
|
|
|
|
cleanServers(element)
|
|
|
|
|
|
|
|
etnOpts := parser.EncoderToNodeOpts{OmitEmpty: true, TagName: parser.TagLabel, AllowSliceAsStruct: true}
|
|
|
|
node, err := parser.EncodeToNode(element, parser.DefaultRootName, etnOpts)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
metaOpts := parser.MetadataOpts{TagName: parser.TagLabel, AllowSliceAsStruct: true}
|
|
|
|
err = parser.AddMetadata(element, node, metaOpts)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
labels := make(map[string]string)
|
|
|
|
encodeNode(labels, node.Name, node)
|
|
|
|
|
|
|
|
var keys []string
|
|
|
|
for k := range labels {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
dockerLabels, err := os.Create(filepath.Join(outputDir, "docker-labels.yml"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer dockerLabels.Close()
|
|
|
|
|
|
|
|
// Write the comment at the beginning of the file
|
|
|
|
if _, err := dockerLabels.WriteString(commentGenerated); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-01-17 13:54:05 +00:00
|
|
|
for _, k := range keys {
|
2024-01-16 09:32:05 +00:00
|
|
|
v := labels[k]
|
|
|
|
if v != "" {
|
|
|
|
if v == "42000000000" {
|
|
|
|
v = "42s"
|
|
|
|
}
|
|
|
|
fmt.Fprintln(dockerLabels, `- "`+strings.ToLower(k)+`=`+v+`"`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func cleanServers(element *dynamic.Configuration) {
|
|
|
|
for _, svc := range element.HTTP.Services {
|
|
|
|
if svc.LoadBalancer != nil {
|
|
|
|
server := svc.LoadBalancer.Servers[0]
|
|
|
|
svc.LoadBalancer.Servers = nil
|
|
|
|
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, server)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, svc := range element.TCP.Services {
|
|
|
|
if svc.LoadBalancer != nil {
|
|
|
|
server := svc.LoadBalancer.Servers[0]
|
|
|
|
svc.LoadBalancer.Servers = nil
|
|
|
|
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, server)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, svc := range element.UDP.Services {
|
|
|
|
if svc.LoadBalancer != nil {
|
|
|
|
server := svc.LoadBalancer.Servers[0]
|
|
|
|
svc.LoadBalancer.Servers = nil
|
|
|
|
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, server)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func yamlWrite(outputFile string, element any) error {
|
|
|
|
file, err := os.OpenFile(outputFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
// Write the comment at the beginning of the file
|
|
|
|
if _, err := file.WriteString(commentGenerated); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
encoder := yaml.NewEncoder(buf)
|
|
|
|
encoder.SetIndent(2)
|
|
|
|
err = encoder.Encode(element)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = file.Write(buf.Bytes())
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func tomlWrite(outputFile string, element any) error {
|
|
|
|
file, err := os.OpenFile(outputFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
// Write the comment at the beginning of the file
|
|
|
|
if _, err := file.WriteString(commentGenerated); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return toml.NewEncoder(file).Encode(element)
|
|
|
|
}
|
|
|
|
|
|
|
|
func clean(element any) {
|
|
|
|
valSvcs := reflect.ValueOf(element)
|
|
|
|
|
|
|
|
key := valSvcs.MapKeys()[0]
|
|
|
|
valueSvcRoot := valSvcs.MapIndex(key).Elem()
|
|
|
|
|
|
|
|
var svcFieldNames []string
|
|
|
|
for i := 0; i < valueSvcRoot.NumField(); i++ {
|
|
|
|
svcFieldNames = append(svcFieldNames, valueSvcRoot.Type().Field(i).Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(svcFieldNames)
|
|
|
|
|
|
|
|
for i, fieldName := range svcFieldNames {
|
|
|
|
v := reflect.New(valueSvcRoot.Type())
|
|
|
|
v.Elem().FieldByName(fieldName).Set(valueSvcRoot.FieldByName(fieldName))
|
|
|
|
|
|
|
|
valSvcs.SetMapIndex(reflect.ValueOf(fmt.Sprintf("%s%.2d", valueSvcRoot.Type().Name(), i+1)), v)
|
|
|
|
}
|
|
|
|
|
|
|
|
valSvcs.SetMapIndex(reflect.ValueOf(fmt.Sprintf("%s0", valueSvcRoot.Type().Name())), reflect.Value{})
|
|
|
|
valSvcs.SetMapIndex(reflect.ValueOf(fmt.Sprintf("%s1", valueSvcRoot.Type().Name())), reflect.Value{})
|
|
|
|
}
|
|
|
|
|
2020-07-07 12:42:03 +00:00
|
|
|
func genStaticConfDoc(outputFile, prefix string, encodeFn func(interface{}) ([]parser.Flat, error)) {
|
2022-11-21 17:36:05 +00:00
|
|
|
logger := log.With().Str("file", outputFile).Logger()
|
2019-07-08 09:00:04 +00:00
|
|
|
|
2021-02-26 09:20:03 +00:00
|
|
|
element := &cmd.NewTraefikConfiguration().Configuration
|
2019-07-08 09:00:04 +00:00
|
|
|
|
|
|
|
generator.Generate(element)
|
|
|
|
|
|
|
|
flats, err := encodeFn(element)
|
|
|
|
if err != nil {
|
2022-11-21 17:36:05 +00:00
|
|
|
logger.Fatal().Err(err).Send()
|
2019-07-08 09:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err = os.RemoveAll(outputFile)
|
|
|
|
if err != nil {
|
2022-11-21 17:36:05 +00:00
|
|
|
logger.Fatal().Err(err).Send()
|
2019-07-08 09:00:04 +00:00
|
|
|
}
|
|
|
|
|
2020-07-07 12:42:03 +00:00
|
|
|
file, err := os.OpenFile(outputFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666)
|
2019-07-08 09:00:04 +00:00
|
|
|
if err != nil {
|
2022-11-21 17:36:05 +00:00
|
|
|
logger.Fatal().Err(err).Send()
|
2019-07-08 09:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
w := errWriter{w: file}
|
|
|
|
|
|
|
|
w.writeln(`<!--
|
|
|
|
CODE GENERATED AUTOMATICALLY
|
|
|
|
THIS FILE MUST NOT BE EDITED BY HAND
|
2019-07-10 07:26:04 +00:00
|
|
|
-->`)
|
|
|
|
w.writeln()
|
2019-07-08 09:00:04 +00:00
|
|
|
|
|
|
|
for i, flat := range flats {
|
2020-07-13 09:44:38 +00:00
|
|
|
// TODO must be move into the flats creation.
|
|
|
|
if flat.Name == "experimental.plugins.<name>" || flat.Name == "TRAEFIK_EXPERIMENTAL_PLUGINS_<NAME>" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-09-14 08:56:08 +00:00
|
|
|
if strings.HasPrefix(flat.Name, "pilot.") || strings.HasPrefix(flat.Name, "TRAEFIK_PILOT_") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-02-02 16:10:03 +00:00
|
|
|
if prefix == "" {
|
|
|
|
w.writeln("`" + prefix + strings.ReplaceAll(flat.Name, "[0]", "_n") + "`: ")
|
|
|
|
} else {
|
|
|
|
w.writeln("`" + prefix + strings.ReplaceAll(flat.Name, "[0]", "[n]") + "`: ")
|
|
|
|
}
|
|
|
|
|
2019-07-08 09:00:04 +00:00
|
|
|
if flat.Default == "" {
|
|
|
|
w.writeln(flat.Description)
|
|
|
|
} else {
|
|
|
|
w.writeln(flat.Description + " (Default: ```" + flat.Default + "```)")
|
|
|
|
}
|
|
|
|
|
|
|
|
if i < len(flats)-1 {
|
|
|
|
w.writeln()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if w.err != nil {
|
2022-11-21 17:36:05 +00:00
|
|
|
logger.Fatal().Err(err).Send()
|
2019-07-08 09:00:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type errWriter struct {
|
|
|
|
w io.Writer
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ew *errWriter) writeln(a ...interface{}) {
|
|
|
|
if ew.err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_, ew.err = fmt.Fprintln(ew.w, a...)
|
|
|
|
}
|
2019-11-28 20:56:04 +00:00
|
|
|
|
|
|
|
func genKVDynConfDoc(outputFile string) {
|
|
|
|
dynConfPath := "./docs/content/reference/dynamic-configuration/file.toml"
|
|
|
|
conf := map[string]interface{}{}
|
|
|
|
_, err := toml.DecodeFile(dynConfPath, &conf)
|
|
|
|
if err != nil {
|
2022-11-21 17:36:05 +00:00
|
|
|
log.Fatal().Err(err).Send()
|
2019-11-28 20:56:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
file, err := os.Create(outputFile)
|
|
|
|
if err != nil {
|
2022-11-21 17:36:05 +00:00
|
|
|
log.Fatal().Err(err).Send()
|
2019-11-28 20:56:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
store := storeWriter{data: map[string]string{}}
|
2024-01-16 09:32:05 +00:00
|
|
|
|
2019-11-28 20:56:04 +00:00
|
|
|
c := client{store: store}
|
|
|
|
err = c.load("traefik", conf)
|
|
|
|
if err != nil {
|
2022-11-21 17:36:05 +00:00
|
|
|
log.Fatal().Err(err).Send()
|
2019-11-28 20:56:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var keys []string
|
|
|
|
for k := range store.data {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
2024-01-16 09:32:05 +00:00
|
|
|
_, _ = fmt.Fprintf(file, `<!--
|
|
|
|
CODE GENERATED AUTOMATICALLY
|
|
|
|
THIS FILE MUST NOT BE EDITED BY HAND
|
|
|
|
-->
|
|
|
|
`)
|
|
|
|
|
2019-11-28 20:56:04 +00:00
|
|
|
for _, k := range keys {
|
|
|
|
_, _ = fmt.Fprintf(file, "| `%s` | `%s` |\n", k, store.data[k])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type storeWriter struct {
|
|
|
|
data map[string]string
|
|
|
|
}
|
|
|
|
|
2022-08-31 06:24:08 +00:00
|
|
|
func (f storeWriter) Put(key string, value []byte, _ []string) error {
|
2019-11-28 20:56:04 +00:00
|
|
|
f.data[key] = string(value)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type client struct {
|
|
|
|
store storeWriter
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c client) load(parentKey string, conf map[string]interface{}) error {
|
|
|
|
for k, v := range conf {
|
|
|
|
switch entry := v.(type) {
|
|
|
|
case map[string]interface{}:
|
|
|
|
key := path.Join(parentKey, k)
|
|
|
|
|
|
|
|
if len(entry) == 0 {
|
|
|
|
err := c.store.Put(key, nil, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err := c.load(key, entry)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case []map[string]interface{}:
|
|
|
|
for i, o := range entry {
|
|
|
|
key := path.Join(parentKey, k, strconv.Itoa(i))
|
|
|
|
|
|
|
|
if err := c.load(key, o); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case []interface{}:
|
|
|
|
for i, o := range entry {
|
|
|
|
key := path.Join(parentKey, k, strconv.Itoa(i))
|
|
|
|
|
|
|
|
err := c.store.Put(key, []byte(fmt.Sprintf("%v", o)), nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
key := path.Join(parentKey, k)
|
|
|
|
|
|
|
|
err := c.store.Put(key, []byte(fmt.Sprintf("%v", v)), nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|