From 381c30ea4a9a3d87f9164b34d6109b29a81baf6c Mon Sep 17 00:00:00 2001 From: Gijs Kunze Date: Sat, 5 May 2018 10:51:02 +0200 Subject: [PATCH 1/4] Added the ability to set individual values for the toolbox template command --- cmd/kops/toolbox_template.go | 24 ++++++++++++++++++++++-- docs/cli/kops_toolbox_template.md | 3 +++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/cmd/kops/toolbox_template.go b/cmd/kops/toolbox_template.go index aece4f2a86261..775683b7eec6b 100644 --- a/cmd/kops/toolbox_template.go +++ b/cmd/kops/toolbox_template.go @@ -27,6 +27,7 @@ import ( "github.com/ghodss/yaml" "github.com/spf13/cobra" + "k8s.io/helm/pkg/strvals" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/util/i18n" @@ -45,6 +46,7 @@ var ( kops toolbox template \ --values values.yaml --values=another.yaml \ + --set var=value --set-string othervar=true \ --snippets file_or_directory --snippets=another.dir \ --template file_or_directory --template=directory \ --output cluster.yaml @@ -63,6 +65,8 @@ type toolboxTemplateOption struct { outputPath string snippetsPath []string templatePath []string + values []string + stringValues []string } // NewCmdToolboxTemplate returns a new templating command @@ -87,6 +91,8 @@ func NewCmdToolboxTemplate(f *util.Factory, out io.Writer) *cobra.Command { } cmd.Flags().StringSliceVar(&options.configPath, "values", options.configPath, "Path to a configuration file containing values to include in template") + cmd.Flags().StringSliceVar(&options.values, "set", options.values, "Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + cmd.Flags().StringSliceVar(&options.stringValues, "set-string", options.stringValues, "Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") 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().StringVar(&options.outputPath, "output", options.outputPath, "Path to output file, otherwise defaults to stdout") @@ -100,7 +106,7 @@ func NewCmdToolboxTemplate(f *util.Factory, out io.Writer) *cobra.Command { // runToolBoxTemplate is the action for the command func runToolBoxTemplate(f *util.Factory, out io.Writer, options *toolboxTemplateOption) error { // @step: read in the configuration if any - context, err := newTemplateContext(options.configPath) + context, err := newTemplateContext(options.configPath, options.values, options.stringValues) if err != nil { return err } @@ -204,7 +210,7 @@ func runToolBoxTemplate(f *util.Factory, out io.Writer, options *toolboxTemplate } // newTemplateContext is responsible for loadding the --values and build a context for the template -func newTemplateContext(files []string) (map[string]interface{}, error) { +func newTemplateContext(files []string, values []string, stringValues []string) (map[string]interface{}, error) { context := make(map[string]interface{}, 0) for _, x := range files { @@ -227,6 +233,20 @@ func newTemplateContext(files []string) (map[string]interface{}, error) { } } + // User specified a value via --set + for _, value := range values { + if err := strvals.ParseInto(value, context); err != nil { + return nil, fmt.Errorf("failed parsing --set data: %s", err) + } + } + + // User specified a value via --set-string + for _, value := range stringValues { + if err := strvals.ParseIntoString(value, context); err != nil { + return nil, fmt.Errorf("failed parsing --set-string data: %s", err) + } + } + return context, nil } diff --git a/docs/cli/kops_toolbox_template.md b/docs/cli/kops_toolbox_template.md index f615a9f98da43..eaa5b86196b78 100644 --- a/docs/cli/kops_toolbox_template.md +++ b/docs/cli/kops_toolbox_template.md @@ -20,6 +20,7 @@ kops toolbox template [flags] kops toolbox template \ --values values.yaml --values=another.yaml \ + --set var=value --set-string othervar=true \ --snippets file_or_directory --snippets=another.dir \ --template file_or_directory --template=directory \ --output cluster.yaml @@ -33,6 +34,8 @@ kops toolbox template [flags] --format-yaml Attempt to format the generated yaml content before output -h, --help help for template --output string Path to output file, otherwise defaults to stdout + --set strings Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --set-string strings Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) --snippets strings Path to directory containing snippets used for templating --template strings Path to template file or directory of templates to render --values strings Path to a configuration file containing values to include in template From 295aeb9197229bf48d82fba4ed8dc04947bd4894 Mon Sep 17 00:00:00 2001 From: Gijs Kunze Date: Tue, 8 May 2018 21:52:15 +0200 Subject: [PATCH 2/4] Added dependency on k8sio/helm/pkg/strvals --- Gopkg.lock | 6 + cmd/kops/BUILD.bazel | 1 + vendor/k8s.io/helm/LICENSE | 202 +++++++++++ .../testdata/dependent-chart-alias/LICENSE | 1 + .../LICENSE | 1 + .../LICENSE | 1 + .../LICENSE | 1 + .../pkg/chartutil/testdata/frobnitz/LICENSE | 1 + .../testdata/frobnitz_backslash/LICENSE | 1 + .../chartutil/testdata/joonix/charts/frobnitz | 1 + .../repository/cache/local-index.yaml | 1 + .../repository/cache/local-index.yaml | 1 + vendor/k8s.io/helm/pkg/strvals/BUILD.bazel | 13 + vendor/k8s.io/helm/pkg/strvals/doc.go | 32 ++ vendor/k8s.io/helm/pkg/strvals/parser.go | 340 ++++++++++++++++++ 15 files changed, 603 insertions(+) create mode 100644 vendor/k8s.io/helm/LICENSE create mode 100644 vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-alias/LICENSE create mode 100644 vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE create mode 100644 vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE create mode 100644 vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE create mode 100644 vendor/k8s.io/helm/pkg/chartutil/testdata/frobnitz/LICENSE create mode 100755 vendor/k8s.io/helm/pkg/chartutil/testdata/frobnitz_backslash/LICENSE create mode 120000 vendor/k8s.io/helm/pkg/chartutil/testdata/joonix/charts/frobnitz create mode 120000 vendor/k8s.io/helm/pkg/downloader/testdata/helmhome/repository/cache/local-index.yaml create mode 120000 vendor/k8s.io/helm/pkg/getter/testdata/repository/cache/local-index.yaml create mode 100644 vendor/k8s.io/helm/pkg/strvals/BUILD.bazel create mode 100644 vendor/k8s.io/helm/pkg/strvals/doc.go create mode 100644 vendor/k8s.io/helm/pkg/strvals/parser.go diff --git a/Gopkg.lock b/Gopkg.lock index 8db91ee40fa4a..2251509cd21c6 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1476,6 +1476,12 @@ ] revision = "01a732e01d00cb9a81bb0ca050d3e6d2b947927b" +[[projects]] + name = "k8s.io/helm" + packages = ["pkg/strvals"] + revision = "f6025bb9ee7daf9fee0026541c90a6f557a3e0bc" + version = "v2.9.0" + [[projects]] name = "k8s.io/kube-openapi" packages = [ diff --git a/cmd/kops/BUILD.bazel b/cmd/kops/BUILD.bazel index f10a181b38886..50af0e0d20c84 100644 --- a/cmd/kops/BUILD.bazel +++ b/cmd/kops/BUILD.bazel @@ -113,6 +113,7 @@ go_library( "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", "//vendor/k8s.io/client-go/util/homedir:go_default_library", + "//vendor/k8s.io/helm/pkg/strvals:go_default_library", "//vendor/k8s.io/kubernetes/pkg/kubectl/cmd/templates:go_default_library", "//vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util:go_default_library", "//vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/editor:go_default_library", diff --git a/vendor/k8s.io/helm/LICENSE b/vendor/k8s.io/helm/LICENSE new file mode 100644 index 0000000000000..21c57fae21f7e --- /dev/null +++ b/vendor/k8s.io/helm/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 The Kubernetes Authors All Rights Reserved + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-alias/LICENSE b/vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-alias/LICENSE new file mode 100644 index 0000000000000..6121943b10a68 --- /dev/null +++ b/vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-alias/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE b/vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE new file mode 100644 index 0000000000000..6121943b10a68 --- /dev/null +++ b/vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE b/vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE new file mode 100644 index 0000000000000..6121943b10a68 --- /dev/null +++ b/vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE b/vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE new file mode 100644 index 0000000000000..6121943b10a68 --- /dev/null +++ b/vendor/k8s.io/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/vendor/k8s.io/helm/pkg/chartutil/testdata/frobnitz/LICENSE b/vendor/k8s.io/helm/pkg/chartutil/testdata/frobnitz/LICENSE new file mode 100644 index 0000000000000..6121943b10a68 --- /dev/null +++ b/vendor/k8s.io/helm/pkg/chartutil/testdata/frobnitz/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/vendor/k8s.io/helm/pkg/chartutil/testdata/frobnitz_backslash/LICENSE b/vendor/k8s.io/helm/pkg/chartutil/testdata/frobnitz_backslash/LICENSE new file mode 100755 index 0000000000000..6121943b10a68 --- /dev/null +++ b/vendor/k8s.io/helm/pkg/chartutil/testdata/frobnitz_backslash/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/vendor/k8s.io/helm/pkg/chartutil/testdata/joonix/charts/frobnitz b/vendor/k8s.io/helm/pkg/chartutil/testdata/joonix/charts/frobnitz new file mode 120000 index 0000000000000..fde1b78ac5f38 --- /dev/null +++ b/vendor/k8s.io/helm/pkg/chartutil/testdata/joonix/charts/frobnitz @@ -0,0 +1 @@ +../../frobnitz \ No newline at end of file diff --git a/vendor/k8s.io/helm/pkg/downloader/testdata/helmhome/repository/cache/local-index.yaml b/vendor/k8s.io/helm/pkg/downloader/testdata/helmhome/repository/cache/local-index.yaml new file mode 120000 index 0000000000000..ed068e99e4db9 --- /dev/null +++ b/vendor/k8s.io/helm/pkg/downloader/testdata/helmhome/repository/cache/local-index.yaml @@ -0,0 +1 @@ +repository/local/index.yaml \ No newline at end of file diff --git a/vendor/k8s.io/helm/pkg/getter/testdata/repository/cache/local-index.yaml b/vendor/k8s.io/helm/pkg/getter/testdata/repository/cache/local-index.yaml new file mode 120000 index 0000000000000..ed068e99e4db9 --- /dev/null +++ b/vendor/k8s.io/helm/pkg/getter/testdata/repository/cache/local-index.yaml @@ -0,0 +1 @@ +repository/local/index.yaml \ No newline at end of file diff --git a/vendor/k8s.io/helm/pkg/strvals/BUILD.bazel b/vendor/k8s.io/helm/pkg/strvals/BUILD.bazel new file mode 100644 index 0000000000000..e8ec288ef36c7 --- /dev/null +++ b/vendor/k8s.io/helm/pkg/strvals/BUILD.bazel @@ -0,0 +1,13 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "parser.go", + ], + importmap = "vendor/k8s.io/helm/pkg/strvals", + importpath = "k8s.io/helm/pkg/strvals", + visibility = ["//visibility:public"], + deps = ["//vendor/github.com/ghodss/yaml:go_default_library"], +) diff --git a/vendor/k8s.io/helm/pkg/strvals/doc.go b/vendor/k8s.io/helm/pkg/strvals/doc.go new file mode 100644 index 0000000000000..d2b859e67d5a9 --- /dev/null +++ b/vendor/k8s.io/helm/pkg/strvals/doc.go @@ -0,0 +1,32 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/*Package strvals provides tools for working with strval lines. + +Helm supports a compressed format for YAML settings which we call strvals. +The format is roughly like this: + + name=value,topname.subname=value + +The above is equivalent to the YAML document + + name: value + topname: + subname: value + +This package provides a parser and utilities for converting the strvals format +to other formats. +*/ +package strvals diff --git a/vendor/k8s.io/helm/pkg/strvals/parser.go b/vendor/k8s.io/helm/pkg/strvals/parser.go new file mode 100644 index 0000000000000..aa3d15904d5fc --- /dev/null +++ b/vendor/k8s.io/helm/pkg/strvals/parser.go @@ -0,0 +1,340 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package strvals + +import ( + "bytes" + "errors" + "fmt" + "io" + "strconv" + "strings" + + "github.com/ghodss/yaml" +) + +// ErrNotList indicates that a non-list was treated as a list. +var ErrNotList = errors.New("not a list") + +// ToYAML takes a string of arguments and converts to a YAML document. +func ToYAML(s string) (string, error) { + m, err := Parse(s) + if err != nil { + return "", err + } + d, err := yaml.Marshal(m) + return strings.TrimSuffix(string(d), "\n"), err +} + +// Parse parses a set line. +// +// A set line is of the form name1=value1,name2=value2 +func Parse(s string) (map[string]interface{}, error) { + vals := map[string]interface{}{} + scanner := bytes.NewBufferString(s) + t := newParser(scanner, vals, false) + err := t.parse() + return vals, err +} + +// ParseString parses a set line and forces a string value. +// +// A set line is of the form name1=value1,name2=value2 +func ParseString(s string) (map[string]interface{}, error) { + vals := map[string]interface{}{} + scanner := bytes.NewBufferString(s) + t := newParser(scanner, vals, true) + err := t.parse() + return vals, err +} + +// ParseInto parses a strvals line and merges the result into dest. +// +// If the strval string has a key that exists in dest, it overwrites the +// dest version. +func ParseInto(s string, dest map[string]interface{}) error { + scanner := bytes.NewBufferString(s) + t := newParser(scanner, dest, false) + return t.parse() +} + +// ParseIntoString parses a strvals line nad merges the result into dest. +// +// This method always returns a string as the value. +func ParseIntoString(s string, dest map[string]interface{}) error { + scanner := bytes.NewBufferString(s) + t := newParser(scanner, dest, true) + return t.parse() +} + +// parser is a simple parser that takes a strvals line and parses it into a +// map representation. +type parser struct { + sc *bytes.Buffer + data map[string]interface{} + st bool +} + +func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser { + return &parser{sc: sc, data: data, st: stringBool} +} + +func (t *parser) parse() error { + for { + err := t.key(t.data) + if err == nil { + continue + } + if err == io.EOF { + return nil + } + return err + } +} + +func runeSet(r []rune) map[rune]bool { + s := make(map[rune]bool, len(r)) + for _, rr := range r { + s[rr] = true + } + return s +} + +func (t *parser) key(data map[string]interface{}) error { + stop := runeSet([]rune{'=', '[', ',', '.'}) + for { + switch k, last, err := runesUntil(t.sc, stop); { + case err != nil: + if len(k) == 0 { + return err + } + return fmt.Errorf("key %q has no value", string(k)) + //set(data, string(k), "") + //return err + case last == '[': + // We are in a list index context, so we need to set an index. + i, err := t.keyIndex() + if err != nil { + return fmt.Errorf("error parsing index: %s", err) + } + kk := string(k) + // Find or create target list + list := []interface{}{} + if _, ok := data[kk]; ok { + list = data[kk].([]interface{}) + } + + // Now we need to get the value after the ]. + list, err = t.listItem(list, i) + set(data, kk, list) + return err + case last == '=': + //End of key. Consume =, Get value. + // FIXME: Get value list first + vl, e := t.valList() + switch e { + case nil: + set(data, string(k), vl) + return nil + case io.EOF: + set(data, string(k), "") + return e + case ErrNotList: + v, e := t.val() + set(data, string(k), typedVal(v, t.st)) + return e + default: + return e + } + + case last == ',': + // No value given. Set the value to empty string. Return error. + set(data, string(k), "") + return fmt.Errorf("key %q has no value (cannot end with ,)", string(k)) + case last == '.': + // First, create or find the target map. + inner := map[string]interface{}{} + if _, ok := data[string(k)]; ok { + inner = data[string(k)].(map[string]interface{}) + } + + // Recurse + e := t.key(inner) + if len(inner) == 0 { + return fmt.Errorf("key map %q has no value", string(k)) + } + set(data, string(k), inner) + return e + } + } +} + +func set(data map[string]interface{}, key string, val interface{}) { + // If key is empty, don't set it. + if len(key) == 0 { + return + } + data[key] = val +} + +func setIndex(list []interface{}, index int, val interface{}) []interface{} { + if len(list) <= index { + newlist := make([]interface{}, index+1) + copy(newlist, list) + list = newlist + } + list[index] = val + return list +} + +func (t *parser) keyIndex() (int, error) { + // First, get the key. + stop := runeSet([]rune{']'}) + v, _, err := runesUntil(t.sc, stop) + if err != nil { + return 0, err + } + // v should be the index + return strconv.Atoi(string(v)) + +} +func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { + stop := runeSet([]rune{'[', '.', '='}) + switch k, last, err := runesUntil(t.sc, stop); { + case len(k) > 0: + return list, fmt.Errorf("unexpected data at end of array index: %q", k) + case err != nil: + return list, err + case last == '=': + vl, e := t.valList() + switch e { + case nil: + return setIndex(list, i, vl), nil + case io.EOF: + return setIndex(list, i, ""), err + case ErrNotList: + v, e := t.val() + return setIndex(list, i, typedVal(v, t.st)), e + default: + return list, e + } + case last == '[': + // now we have a nested list. Read the index and handle. + i, err := t.keyIndex() + if err != nil { + return list, fmt.Errorf("error parsing index: %s", err) + } + // Now we need to get the value after the ]. + list2, err := t.listItem(list, i) + return setIndex(list, i, list2), err + case last == '.': + // We have a nested object. Send to t.key + inner := map[string]interface{}{} + if len(list) > i { + inner = list[i].(map[string]interface{}) + } + + // Recurse + e := t.key(inner) + return setIndex(list, i, inner), e + default: + return nil, fmt.Errorf("parse error: unexpected token %v", last) + } +} + +func (t *parser) val() ([]rune, error) { + stop := runeSet([]rune{','}) + v, _, err := runesUntil(t.sc, stop) + return v, err +} + +func (t *parser) valList() ([]interface{}, error) { + r, _, e := t.sc.ReadRune() + if e != nil { + return []interface{}{}, e + } + + if r != '{' { + t.sc.UnreadRune() + return []interface{}{}, ErrNotList + } + + list := []interface{}{} + stop := runeSet([]rune{',', '}'}) + for { + switch v, last, err := runesUntil(t.sc, stop); { + case err != nil: + if err == io.EOF { + err = errors.New("list must terminate with '}'") + } + return list, err + case last == '}': + // If this is followed by ',', consume it. + if r, _, e := t.sc.ReadRune(); e == nil && r != ',' { + t.sc.UnreadRune() + } + list = append(list, typedVal(v, t.st)) + return list, nil + case last == ',': + list = append(list, typedVal(v, t.st)) + } + } +} + +func runesUntil(in io.RuneReader, stop map[rune]bool) ([]rune, rune, error) { + v := []rune{} + for { + switch r, _, e := in.ReadRune(); { + case e != nil: + return v, r, e + case inMap(r, stop): + return v, r, nil + case r == '\\': + next, _, e := in.ReadRune() + if e != nil { + return v, next, e + } + v = append(v, next) + default: + v = append(v, r) + } + } +} + +func inMap(k rune, m map[rune]bool) bool { + _, ok := m[k] + return ok +} + +func typedVal(v []rune, st bool) interface{} { + val := string(v) + if strings.EqualFold(val, "true") { + return true + } + + if strings.EqualFold(val, "false") { + return false + } + + // If this value does not start with zero, and not returnString, try parsing it to an int + if !st && len(val) != 0 && val[0] != '0' { + if iv, err := strconv.ParseInt(val, 10, 64); err == nil { + return iv + } + } + + return val +} From 0ccaf4fd8e17a216fb5c189b5810b95d39abe9c0 Mon Sep 17 00:00:00 2001 From: Gijs Kunze Date: Wed, 9 May 2018 18:28:12 +0200 Subject: [PATCH 3/4] Fixes complex --set parsing --- cmd/kops/toolbox_template.go | 4 ++-- docs/cli/kops_toolbox_template.md | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cmd/kops/toolbox_template.go b/cmd/kops/toolbox_template.go index 775683b7eec6b..9af8c26e90adf 100644 --- a/cmd/kops/toolbox_template.go +++ b/cmd/kops/toolbox_template.go @@ -91,8 +91,8 @@ func NewCmdToolboxTemplate(f *util.Factory, out io.Writer) *cobra.Command { } cmd.Flags().StringSliceVar(&options.configPath, "values", options.configPath, "Path to a configuration file containing values to include in template") - cmd.Flags().StringSliceVar(&options.values, "set", options.values, "Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - cmd.Flags().StringSliceVar(&options.stringValues, "set-string", options.stringValues, "Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + cmd.Flags().StringArrayVar(&options.values, "set", options.values, "Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + cmd.Flags().StringArrayVar(&options.stringValues, "set-string", options.stringValues, "Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") 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().StringVar(&options.outputPath, "output", options.outputPath, "Path to output file, otherwise defaults to stdout") diff --git a/docs/cli/kops_toolbox_template.md b/docs/cli/kops_toolbox_template.md index eaa5b86196b78..84cafbdd700d9 100644 --- a/docs/cli/kops_toolbox_template.md +++ b/docs/cli/kops_toolbox_template.md @@ -29,16 +29,16 @@ kops toolbox template [flags] ### Options ``` - --config-value string Show the value of a specific configuration value - --fail-on-missing Fail on referencing unset variables in templates (default true) - --format-yaml Attempt to format the generated yaml content before output - -h, --help help for template - --output string Path to output file, otherwise defaults to stdout - --set strings Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) - --set-string strings Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) - --snippets strings Path to directory containing snippets used for templating - --template strings Path to template file or directory of templates to render - --values strings Path to a configuration file containing values to include in template + --config-value string Show the value of a specific configuration value + --fail-on-missing Fail on referencing unset variables in templates (default true) + --format-yaml Attempt to format the generated yaml content before output + -h, --help help for template + --output string Path to output file, otherwise defaults to stdout + --set stringArray Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --set-string stringArray Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --snippets strings Path to directory containing snippets used for templating + --template strings Path to template file or directory of templates to render + --values strings Path to a configuration file containing values to include in template ``` ### Options inherited from parent commands From 2f036847c79962c0d10d55945426bfaf04e53c75 Mon Sep 17 00:00:00 2001 From: Gijs Kunze Date: Wed, 9 May 2018 18:28:21 +0200 Subject: [PATCH 4/4] Added documentation for #5108 and #4668 --- docs/cluster_template.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/cluster_template.md b/docs/cluster_template.md index be811014ef6eb..c3a775483c16d 100644 --- a/docs/cluster_template.md +++ b/docs/cluster_template.md @@ -41,6 +41,42 @@ dnsZone: k8s.example.com awsRegion: eu-west-1 ``` +When multiple environment files are passed using `--values` Kops performs a deep merge, for example given the following two files: +```yaml +# File values-a.yaml +instanceGroups: + foo: + ami: ami-1234567 + type: m4.large + +# File values-b.yaml +instanceGroups: + foo: + type: t2.large +``` + +Would result in the `instanceGroups.foo` object having two properties: `{"ami": "ami-1234567", "type": "t2.large"}`. + +Besides specifying values through an environment file it is also possible to pass variables directly on the command line using the `--set` and `--set-string` command line options. The difference between the two options is that `--set-string` will always yield a string value while `--set` will cause the value to be parsed as a YAML value, for example the value `true` would turn into a boolean with `--set` while with `--set-string` it will be the literal string `"true"`. The format for specifying a variable is as follows: + +```shell +kops toolbox template --template mytemplate.tpl --set 'version=1.0,foo.bar=baz' --set-string 'foo.myArray={1,2,3}' --set 'foo.myArray[1]=false,foo.myArray[3]=4' +``` + +which would yield the same values as using the `--values` option with the following file + +```yaml +version: 1.0 +foo: + bar: baz + myArray: + - "1" + - false + - "3" + - 4 +``` + + Running `kops toolbox template` replaces the placeholders in the template by values and generates the file output.yaml, which can then be used to replace the desired cluster configuration with `kops replace -f cluster.yaml`. Note: when creating a cluster desired configuration template, you can