Merge pull request #3706 from gambol99/format

Automatic merge from submit-queue.

Kops Template YAML Formatting

Adding an extra option to the toolbox templating to format the YAML before writing out; which is usefull to cleanup formating issues and as detecting errors in the template

- added a formating options --format-yaml to the toolbox template
- updated the cli documentation
- the option is off by default to retain behavior
This commit is contained in:
Kubernetes Submit Queue 2017-10-30 06:59:28 -07:00 committed by GitHub
commit e0d8eef16b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 37 deletions

View File

@ -17,19 +17,21 @@ limitations under the License.
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"k8s.io/kops/cmd/kops/util" "github.com/ghodss/yaml"
"k8s.io/kops/pkg/util/templater"
"k8s.io/kops/upup/pkg/fi/utils"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kops/cmd/kops/util"
"k8s.io/kops/pkg/util/templater"
"k8s.io/kops/upup/pkg/fi/utils"
) )
var ( var (
@ -54,7 +56,9 @@ var (
type toolboxTemplateOption struct { type toolboxTemplateOption struct {
clusterName string clusterName string
configPath []string configPath []string
configValue string
failOnMissing bool failOnMissing bool
formatYAML bool
outputPath string outputPath string
snippetsPath []string snippetsPath []string
templatePath []string templatePath []string
@ -85,7 +89,9 @@ func NewCmdToolboxTemplate(f *util.Factory, out io.Writer) *cobra.Command {
cmd.Flags().StringSliceVar(&options.templatePath, "template", options.templatePath, "Path to template file or directory of templates to render") cmd.Flags().StringSliceVar(&options.templatePath, "template", options.templatePath, "Path to template file or directory of templates to render")
cmd.Flags().StringSliceVar(&options.snippetsPath, "snippets", options.snippetsPath, "Path to directory containing snippets used for templating") cmd.Flags().StringSliceVar(&options.snippetsPath, "snippets", options.snippetsPath, "Path to directory containing snippets used for templating")
cmd.Flags().StringVar(&options.outputPath, "output", options.outputPath, "Path to output file, otherwise defaults to stdout") cmd.Flags().StringVar(&options.outputPath, "output", options.outputPath, "Path to output file, otherwise defaults to stdout")
cmd.Flags().StringVar(&options.configValue, "config-value", "", "Show the value of a specific configuration value")
cmd.Flags().BoolVar(&options.failOnMissing, "fail-on-missing", true, "Fail on referencing unset variables in templates") cmd.Flags().BoolVar(&options.failOnMissing, "fail-on-missing", true, "Fail on referencing unset variables in templates")
cmd.Flags().BoolVar(&options.formatYAML, "format-yaml", false, "Attempt to format the generated yaml content before output")
return cmd return cmd
} }
@ -93,30 +99,24 @@ func NewCmdToolboxTemplate(f *util.Factory, out io.Writer) *cobra.Command {
// runToolBoxTemplate is the action for the command // runToolBoxTemplate is the action for the command
func runToolBoxTemplate(f *util.Factory, out io.Writer, options *toolboxTemplateOption) error { func runToolBoxTemplate(f *util.Factory, out io.Writer, options *toolboxTemplateOption) error {
// @step: read in the configuration if any // @step: read in the configuration if any
context := make(map[string]interface{}, 0) context, err := newTemplateContext(options.configPath)
for _, x := range options.configPath { if err != nil {
list, err := expandFiles(utils.ExpandPath(x)) return err
if err != nil {
return err
}
for _, j := range list {
content, err := ioutil.ReadFile(j)
if err != nil {
return fmt.Errorf("unable to configuration file: %s, error: %s", j, err)
}
ctx := make(map[string]interface{}, 0)
if err := utils.YamlUnmarshal(content, &ctx); err != nil {
return fmt.Errorf("unable decode the configuration file: %s, error: %v", j, err)
}
// @step: merge the maps together
for k, v := range ctx {
context[k] = v
}
}
} }
context["clusterName"] = options.clusterName context["clusterName"] = options.clusterName
// @check if we are just rendering the config value
if options.configValue != "" {
v, found := context[options.configValue]
switch found {
case true:
fmt.Fprintf(out, "%s\n", v)
default:
fmt.Fprintf(out, "null\n")
}
return nil
}
// @step: expand the list of templates into a list of files to render // @step: expand the list of templates into a list of files to render
var templates []string var templates []string
for _, x := range options.templatePath { for _, x := range options.templatePath {
@ -143,15 +143,7 @@ func runToolBoxTemplate(f *util.Factory, out io.Writer, options *toolboxTemplate
} }
} }
// @step: get the output io.Writer b := new(bytes.Buffer)
writer := out
if options.outputPath != "" {
w, err := os.OpenFile(utils.ExpandPath(options.outputPath), os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0660)
if err != nil {
return fmt.Errorf("unable to open file: %s, error: %v", options.outputPath, err)
}
writer = w
}
// @step: render each of the template and write to location // @step: render each of the template and write to location
r := templater.NewTemplater() r := templater.NewTemplater()
@ -166,18 +158,83 @@ func runToolBoxTemplate(f *util.Factory, out io.Writer, options *toolboxTemplate
if err != nil { if err != nil {
return fmt.Errorf("unable to render template: %s, error: %s", x, err) return fmt.Errorf("unable to render template: %s, error: %s", x, err)
} }
// @check if we have a zero length string and if so, forgo it
if len(rendered) <= 0 {
continue
}
io.WriteString(writer, rendered) // @check if we need to format the yaml
if options.formatYAML {
var data interface{}
if err := utils.YamlUnmarshal([]byte(rendered), &data); err != nil {
return fmt.Errorf("unable to unmarshall content from template: %s, error: %s", x, err)
}
// @TODO: i'm not sure how this could happen but best to heck none the less
formatted, err := yaml.Marshal(&data)
if err != nil {
return fmt.Errorf("unable to marhshal formated content to yaml: %s", err)
}
rendered = string(formatted)
}
if _, err := b.WriteString(rendered); err != nil {
return fmt.Errorf("unable to write template: %s, error: %s", x, err)
}
// @check if we should need to add document separator // @check if we should need to add document separator
if i < size { if i < size {
io.WriteString(writer, "---\n") if _, err := b.WriteString("---\n"); err != nil {
return fmt.Errorf("unable to write to template: %s, error: %s", x, err)
}
} }
} }
iowriter := out
// @check if we are writing to a file rather than stdout
if options.outputPath != "" {
w, err := os.OpenFile(utils.ExpandPath(options.outputPath), os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0660)
if err != nil {
return fmt.Errorf("unable to open file: %s, error: %v", options.outputPath, err)
}
defer w.Close()
iowriter = w
}
if _, err := iowriter.Write(b.Bytes()); err != nil {
return fmt.Errorf("unable to write template: %s", err)
}
return nil return nil
} }
// newTemplateContext is responsible for loadding the --values and build a context for the template
func newTemplateContext(files []string) (map[string]interface{}, error) {
context := make(map[string]interface{}, 0)
for _, x := range files {
list, err := expandFiles(utils.ExpandPath(x))
if err != nil {
return nil, err
}
for _, j := range list {
content, err := ioutil.ReadFile(j)
if err != nil {
return nil, fmt.Errorf("unable to configuration file: %s, error: %s", j, err)
}
ctx := make(map[string]interface{}, 0)
if err := utils.YamlUnmarshal(content, &ctx); err != nil {
return nil, fmt.Errorf("unable decode the configuration file: %s, error: %v", j, err)
}
for k, v := range ctx {
context[k] = v
}
}
}
return context, nil
}
// expandFiles is responsible for resolving any references to directories // expandFiles is responsible for resolving any references to directories
func expandFiles(path string) ([]string, error) { func expandFiles(path string) ([]string, error) {
// @check if the the path is a directory, if not we can return straight away // @check if the the path is a directory, if not we can return straight away
@ -185,7 +242,7 @@ func expandFiles(path string) ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// @check if no a directory and return as is // @check if not a directory and return as is
if !stat.IsDir() { if !stat.IsDir() {
return []string{path}, nil return []string{path}, nil
} }

View File

@ -29,7 +29,9 @@ kops toolbox template
### Options ### Options
``` ```
--config-value string Show the value of a specific configuration value
--fail-on-missing Fail on referencing unset variables in templates (default true) --fail-on-missing Fail on referencing unset variables in templates (default true)
--format-yaml Attempt to format the generated yaml content before output
--output string Path to output file, otherwise defaults to stdout --output string Path to output file, otherwise defaults to stdout
--snippets stringSlice Path to directory containing snippets used for templating --snippets stringSlice Path to directory containing snippets used for templating
--template stringSlice Path to template file or directory of templates to render --template stringSlice Path to template file or directory of templates to render