dev-tools/mage/config.go (141 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. package mage import ( "bytes" "fmt" "os" "path/filepath" "regexp" "strings" "text/template" "github.com/magefile/mage/mg" ) // ConfigFileType is a bitset that indicates what types of config files to // generate. type ConfigFileType uint8 // Config file types. const ( ShortConfigType ConfigFileType = 1 << iota ReferenceConfigType DockerConfigType AllConfigTypes ConfigFileType = 0xFF ) // IsShort return true if ShortConfigType is set. func (t ConfigFileType) IsShort() bool { return t&ShortConfigType > 0 } // IsReference return true if ReferenceConfigType is set. func (t ConfigFileType) IsReference() bool { return t&ReferenceConfigType > 0 } // IsDocker return true if DockerConfigType is set. func (t ConfigFileType) IsDocker() bool { return t&DockerConfigType > 0 } // ConfigFileParams defines the files that make up each config file. type ConfigFileParams struct { Templates []string // List of files or globs to load. ExtraVars map[string]interface{} Short, Reference, Docker ConfigParams } // ConfigParams defines config param template type ConfigParams struct { Template string Deps []interface{} } // Config generates config files. Set DEV_OS and DEV_ARCH to change the target // host for the generated configs. Defaults to linux/amd64. func Config(types ConfigFileType, args ConfigFileParams, targetDir string) error { // Short if types.IsShort() { file := filepath.Join(targetDir, BeatName+".yml") if err := makeConfigTemplate(file, 0600, args, ShortConfigType); err != nil { return fmt.Errorf("failed making short config: %w", err) } } // Reference if types.IsReference() { file := filepath.Join(targetDir, BeatName+".reference.yml") if err := makeConfigTemplate(file, 0644, args, ReferenceConfigType); err != nil { return fmt.Errorf("failed making reference config: %w", err) } } // Docker if types.IsDocker() { file := filepath.Join(targetDir, BeatName+".docker.yml") if err := makeConfigTemplate(file, 0600, args, DockerConfigType); err != nil { return fmt.Errorf("failed making docker config: %w", err) } } return nil } func makeConfigTemplate(destination string, mode os.FileMode, confParams ConfigFileParams, typ ConfigFileType) error { // Determine what type to build and set some parameters. var confFile ConfigParams var tmplParams map[string]interface{} switch typ { case ShortConfigType: confFile = confParams.Short tmplParams = map[string]interface{}{} case ReferenceConfigType: confFile = confParams.Reference tmplParams = map[string]interface{}{"Reference": true} case DockerConfigType: confFile = confParams.Docker tmplParams = map[string]interface{}{"Docker": true} default: panic(fmt.Errorf("invalid config file type: %v", typ)) } // Build the dependencies. mg.SerialDeps(confFile.Deps...) // Set variables that are available in templates. // Rather than adding more "ExcludeX"/"UseX" options consider overwriting // one of the libbeat templates in your project by adding a file with the // same name to your _meta/config directory. params := map[string]interface{}{ "GOOS": EnvOr("DEV_OS", "linux"), "GOARCH": EnvOr("DEV_ARCH", "amd64"), "BeatLicense": BeatLicense, "Reference": false, "Docker": false, "ExcludeConsole": false, "ExcludeFileOutput": false, "ExcludeKafka": false, "ExcludeLogstash": false, "ExcludeRedis": false, "UseObserverProcessor": false, "UseDockerMetadataProcessor": true, "UseKubernetesMetadataProcessor": false, "ExcludeDashboards": false, } params = joinMaps(params, confParams.ExtraVars, tmplParams) tmpl := template.New("config").Option("missingkey=error") funcs := joinMaps(FuncMap, template.FuncMap{ "header": header, "subheader": subheader, "indent": indent, // include is necessary because you cannot pipe 'template' to a function // since 'template' is an action. This allows you to include a // template and indent it (e.g. {{ include "x.tmpl" . | indent 4 }}). "include": func(name string, data interface{}) (string, error) { buf := bytes.NewBuffer(nil) if err := tmpl.ExecuteTemplate(buf, name, data); err != nil { return "", err } return buf.String(), nil }, }) tmpl = tmpl.Funcs(funcs) fmt.Printf(">> Building %v for %v/%v\n", destination, params["GOOS"], params["GOARCH"]) var err error for _, templateGlob := range confParams.Templates { if tmpl, err = tmpl.ParseGlob(templateGlob); err != nil { return fmt.Errorf("failed to parse config templates in %q: %w", templateGlob, err) } } data, err := os.ReadFile(confFile.Template) if err != nil { return fmt.Errorf("failed to read config template %q: %w", confFile.Template, err) } tmpl, err = tmpl.Parse(string(data)) if err != nil { return fmt.Errorf("failed to parse template: %w", err) } out, err := os.OpenFile(CreateDir(destination), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode) if err != nil { return err } defer out.Close() if err = tmpl.Execute(out, EnvMap(params)); err != nil { return fmt.Errorf("failed building %v: %w", destination, err) } return nil } func header(title string) string { return makeHeading(title, "=") } func subheader(title string) string { return makeHeading(title, "-") } var nonWhitespaceRegex = regexp.MustCompile(`(?m)(^.*\S.*$)`) // indent pads all non-whitespace lines with the number of spaces specified. func indent(spaces int, content string) string { pad := strings.Repeat(" ", spaces) return nonWhitespaceRegex.ReplaceAllString(content, pad+"$1") } func makeHeading(title, separator string) string { const line = 80 leftEquals := (line - len("# ") - len(title) - 2*len(" ")) / 2 rightEquals := leftEquals + len(title)%2 return "# " + strings.Repeat(separator, leftEquals) + " " + title + " " + strings.Repeat(separator, rightEquals) }