mirror of
				https://github.com/traefik/traefik.git
				synced 2025-10-31 16:31:16 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			422 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			422 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"path/filepath"
 | |
| 	"reflect"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/BurntSushi/toml"
 | |
| 	"github.com/traefik/paerser/env"
 | |
| 	"github.com/traefik/paerser/flag"
 | |
| 	"github.com/traefik/paerser/generator"
 | |
| 	"github.com/traefik/paerser/parser"
 | |
| 	"github.com/traefik/traefik/v2/cmd"
 | |
| 	"github.com/traefik/traefik/v2/pkg/collector/hydratation"
 | |
| 	"github.com/traefik/traefik/v2/pkg/config/dynamic"
 | |
| 	"github.com/traefik/traefik/v2/pkg/config/static"
 | |
| 	"github.com/traefik/traefik/v2/pkg/log"
 | |
| 	"gopkg.in/yaml.v3"
 | |
| )
 | |
| 
 | |
| var commentGenerated = `## CODE GENERATED AUTOMATICALLY
 | |
| ## THIS FILE MUST NOT BE EDITED BY HAND
 | |
| `
 | |
| 
 | |
| func main() {
 | |
| 	logger := log.WithoutContext()
 | |
| 
 | |
| 	dynConf := &dynamic.Configuration{}
 | |
| 
 | |
| 	err := hydratation.Hydrate(dynConf)
 | |
| 	if err != nil {
 | |
| 		logger.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	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 {
 | |
| 		logger.Fatal(err)
 | |
| 	}
 | |
| 	err = yamlWrite("./docs/content/reference/dynamic-configuration/file.yaml", dynConf)
 | |
| 	if err != nil {
 | |
| 		logger.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	err = labelsWrite("./docs/content/reference/dynamic-configuration", dynConf)
 | |
| 	if err != nil {
 | |
| 		logger.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	staticConf := &static.Configuration{}
 | |
| 
 | |
| 	err = hydratation.Hydrate(staticConf)
 | |
| 	if err != nil {
 | |
| 		logger.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	delete(staticConf.EntryPoints, "EntryPoint1")
 | |
| 
 | |
| 	err = tomlWrite("./docs/content/reference/static-configuration/file.toml", staticConf)
 | |
| 	if err != nil {
 | |
| 		logger.Fatal(err)
 | |
| 	}
 | |
| 	err = yamlWrite("./docs/content/reference/static-configuration/file.yaml", staticConf)
 | |
| 	if err != nil {
 | |
| 		logger.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	genStaticConfDoc("./docs/content/reference/static-configuration/env-ref.md", "", func(i interface{}) ([]parser.Flat, error) {
 | |
| 		return env.Encode(env.DefaultNamePrefix, i)
 | |
| 	})
 | |
| 	genStaticConfDoc("./docs/content/reference/static-configuration/cli-ref.md", "--", flag.Encode)
 | |
| 	genKVDynConfDoc("./docs/content/reference/dynamic-configuration/kv-ref.md")
 | |
| }
 | |
| 
 | |
| 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
 | |
| 	}
 | |
| 
 | |
| 	marathonLabels, err := os.Create(filepath.Join(outputDir, "marathon-labels.json"))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer marathonLabels.Close()
 | |
| 
 | |
| 	// Write the comment at the beginning of the file
 | |
| 	if _, err := marathonLabels.WriteString(strings.ReplaceAll(commentGenerated, "##", "//")); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	for i, k := range keys {
 | |
| 		v := labels[k]
 | |
| 		if v != "" {
 | |
| 			if v == "42000000000" {
 | |
| 				v = "42s"
 | |
| 			}
 | |
| 			fmt.Fprintln(dockerLabels, `- "`+strings.ToLower(k)+`=`+v+`"`)
 | |
| 
 | |
| 			if i == len(keys)-1 {
 | |
| 				fmt.Fprintln(marathonLabels, `"`+strings.ToLower(k)+`": "`+v+`"`)
 | |
| 			} else {
 | |
| 				fmt.Fprintln(marathonLabels, `"`+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 := range valueSvcRoot.NumField() {
 | |
| 		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{})
 | |
| }
 | |
| 
 | |
| func genStaticConfDoc(outputFile, prefix string, encodeFn func(interface{}) ([]parser.Flat, error)) {
 | |
| 	logger := log.WithoutContext().WithField("file", outputFile)
 | |
| 
 | |
| 	element := &cmd.NewTraefikConfiguration().Configuration
 | |
| 
 | |
| 	generator.Generate(element)
 | |
| 
 | |
| 	flats, err := encodeFn(element)
 | |
| 	if err != nil {
 | |
| 		logger.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	err = os.RemoveAll(outputFile)
 | |
| 	if err != nil {
 | |
| 		logger.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	file, err := os.OpenFile(outputFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666)
 | |
| 	if err != nil {
 | |
| 		logger.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	defer file.Close()
 | |
| 
 | |
| 	w := errWriter{w: file}
 | |
| 
 | |
| 	w.writeln(`<!--
 | |
| CODE GENERATED AUTOMATICALLY
 | |
| THIS FILE MUST NOT BE EDITED BY HAND
 | |
| -->`)
 | |
| 	w.writeln()
 | |
| 
 | |
| 	for i, flat := range flats {
 | |
| 		// TODO must be move into the flats creation.
 | |
| 		if flat.Name == "experimental.plugins.<name>" || flat.Name == "TRAEFIK_EXPERIMENTAL_PLUGINS_<NAME>" {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if strings.HasPrefix(flat.Name, "pilot.") || strings.HasPrefix(flat.Name, "TRAEFIK_PILOT_") {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if prefix == "" {
 | |
| 			w.writeln("`" + prefix + strings.ReplaceAll(flat.Name, "[0]", "_n") + "`:  ")
 | |
| 		} else {
 | |
| 			w.writeln("`" + prefix + strings.ReplaceAll(flat.Name, "[0]", "[n]") + "`:  ")
 | |
| 		}
 | |
| 
 | |
| 		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 {
 | |
| 		logger.Fatal(err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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...)
 | |
| }
 | |
| 
 | |
| func genKVDynConfDoc(outputFile string) {
 | |
| 	dynConfPath := "./docs/content/reference/dynamic-configuration/file.toml"
 | |
| 	conf := map[string]interface{}{}
 | |
| 	_, err := toml.DecodeFile(dynConfPath, &conf)
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	file, err := os.Create(outputFile)
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	store := storeWriter{data: map[string]string{}}
 | |
| 
 | |
| 	c := client{store: store}
 | |
| 	err = c.load("traefik", conf)
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	var keys []string
 | |
| 	for k := range store.data {
 | |
| 		keys = append(keys, k)
 | |
| 	}
 | |
| 
 | |
| 	sort.Strings(keys)
 | |
| 
 | |
| 	_, _ = fmt.Fprintf(file, `<!--
 | |
| CODE GENERATED AUTOMATICALLY
 | |
| THIS FILE MUST NOT BE EDITED BY HAND
 | |
| -->
 | |
| `)
 | |
| 
 | |
| 	_, _ = fmt.Fprintf(file, `
 | |
| | Key (Path) | Value |
 | |
| |------------|-------|
 | |
| `)
 | |
| 
 | |
| 	for _, k := range keys {
 | |
| 		_, _ = fmt.Fprintf(file, "| `%s` | `%s` |\n", k, store.data[k])
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type storeWriter struct {
 | |
| 	data map[string]string
 | |
| }
 | |
| 
 | |
| func (f storeWriter) Put(key string, value []byte, _ []string) error {
 | |
| 	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
 | |
| }
 |