Skip to content

Commit ad7cd87

Browse files
committedJul 14, 2022
feat: implement --force-yaml-string-quotation
fixes grafana#687 Signed-off-by: Matthias Riegler <me@xvzf.tech>
1 parent f842777 commit ad7cd87

File tree

12 files changed

+63
-16
lines changed

12 files changed

+63
-16
lines changed
 

‎cmd/tk/export.go

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func exportCmd() *cli.Command {
4343
vars := workflowFlags(cmd.Flags())
4444
getJsonnetOpts := jsonnetFlags(cmd.Flags())
4545
getLabelSelector := labelSelectorFlag(cmd.Flags())
46+
getYAMLOpts := yamlStyleFlags(cmd.Flags())
4647

4748
recursive := cmd.Flags().BoolP("recursive", "r", false, "Look recursively for Tanka environments")
4849

@@ -62,6 +63,7 @@ func exportCmd() *cli.Command {
6263
Merge: *merge,
6364
Opts: tanka.Opts{
6465
JsonnetOpts: getJsonnetOpts(),
66+
YamlOpts: getYAMLOpts(),
6567
Filters: filters,
6668
Name: vars.name,
6769
},

‎cmd/tk/flags.go

+10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ func workflowFlags(fs *pflag.FlagSet) *workflowFlagVars {
2424
return &v
2525
}
2626

27+
func yamlStyleFlags(fs *pflag.FlagSet) func() tanka.YAMLOpts {
28+
forceStringQuotation := fs.Bool("force-yaml-string-quotation", false, "enforce YAML quotation for strings")
29+
30+
return func() tanka.YAMLOpts {
31+
return tanka.YAMLOpts{
32+
ForceStringQuotation: *forceStringQuotation,
33+
}
34+
}
35+
}
36+
2737
func labelSelectorFlag(fs *pflag.FlagSet) func() labels.Selector {
2838
labelSelector := fs.StringP("selector", "l", "", "Label selector. Uses the same syntax as kubectl does")
2939

‎cmd/tk/workflow.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ func showCmd() *cli.Command {
198198

199199
vars := workflowFlags(cmd.Flags())
200200
getJsonnetOpts := jsonnetFlags(cmd.Flags())
201+
getYAMLOpts := yamlStyleFlags(cmd.Flags())
201202

202203
cmd.Run = func(cmd *cli.Command, args []string) error {
203204
if !interactive && !*allowRedirect {
@@ -212,17 +213,20 @@ Otherwise run tk show --dangerous-allow-redirect to bypass this check.`)
212213
return err
213214
}
214215

215-
pretty, err := tanka.Show(args[0], tanka.Opts{
216+
opts := tanka.Opts{
216217
JsonnetOpts: getJsonnetOpts(),
218+
YamlOpts: getYAMLOpts(),
217219
Filters: filters,
218220
Name: vars.name,
219-
})
221+
}
222+
223+
pretty, err := tanka.Show(args[0], opts)
220224

221225
if err != nil {
222226
return err
223227
}
224228

225-
return pageln(pretty.String())
229+
return pageln(pretty.String(opts.YamlOpts.ForceStringQuotation))
226230
}
227231
return cmd
228232
}

‎pkg/kubernetes/client/apply.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (k Kubectl) Apply(data manifest.List, opts ApplyOpts) error {
4848
cmd.Stdout = os.Stdout
4949
cmd.Stderr = os.Stderr
5050

51-
cmd.Stdin = strings.NewReader(data.String())
51+
cmd.Stdin = strings.NewReader(data.String(false))
5252

5353
return cmd.Run()
5454
}

‎pkg/kubernetes/client/diff.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (k Kubectl) diff(data manifest.List, serverSide bool) (*string, error) {
5151
cmd.Stdout = &raw
5252
}
5353
cmd.Stderr = &fw
54-
cmd.Stdin = strings.NewReader(data.String())
54+
cmd.Stdin = strings.NewReader(data.String(false))
5555
err := cmd.Run()
5656
if diffErr := parseDiffErr(err, fw.buf, k.Info().ClientVersion); diffErr != nil {
5757
return nil, diffErr

‎pkg/kubernetes/client/get.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func (k Kubectl) GetByLabels(namespace, kind string, labels map[string]string) (
4545
func (k Kubectl) GetByState(data manifest.List, opts GetByStateOpts) (manifest.List, error) {
4646
list, err := k.get("", "", []string{"-f", "-"}, getOpts{
4747
ignoreNotFound: opts.IgnoreNotFound,
48-
stdin: data.String(),
48+
stdin: data.String(false),
4949
})
5050
if err != nil {
5151
return nil, err

‎pkg/kubernetes/diff.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func StaticDiffer(create bool) Differ {
174174
return func(state manifest.List) (*string, error) {
175175
s := ""
176176
for _, m := range state {
177-
is, should := m.String(), ""
177+
is, should := m.String(false), ""
178178
if create {
179179
is, should = should, is
180180
}

‎pkg/kubernetes/manifest/errors.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (s *SchemaError) Error() string {
3838

3939
if s.Manifest != nil {
4040
msg += bluef("\nPlease check below object:\n")
41-
msg += SampleString(s.Manifest.String()).Indent(2)
41+
msg += SampleString(s.Manifest.String(false)).Indent(2)
4242
}
4343

4444
return msg

‎pkg/kubernetes/manifest/manifest.go

+31-5
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,19 @@ import (
99
"github.com/Masterminds/sprig/v3"
1010
"github.com/pkg/errors"
1111
"github.com/stretchr/objx"
12-
yaml "gopkg.in/yaml.v2"
12+
yaml "gopkg.in/yaml.v3"
1313
)
1414

15+
func yamlForceStringQuotationRecursivePatch(n *yaml.Node) {
16+
if n.Tag == "!!str" {
17+
n.Style = yaml.DoubleQuotedStyle
18+
}
19+
for _, cNode := range n.Content {
20+
// for all child nodes
21+
yamlForceStringQuotationRecursivePatch(cNode)
22+
}
23+
}
24+
1525
// Manifest represents a Kubernetes API object. The fields `apiVersion` and
1626
// `kind` are required, `metadata.name` should be present as well
1727
type Manifest map[string]interface{}
@@ -31,8 +41,17 @@ func NewFromObj(raw objx.Map) (Manifest, error) {
3141
}
3242

3343
// String returns the Manifest in yaml representation
34-
func (m Manifest) String() string {
35-
y, err := yaml.Marshal(m)
44+
func (m Manifest) String(forceQuotedStrings bool) string {
45+
46+
yamlNode := &yaml.Node{}
47+
if err := yamlNode.Encode(m); err != nil {
48+
panic(errors.Wrap(err, "converting manifest to yaml.Node"))
49+
}
50+
if forceQuotedStrings {
51+
yamlForceStringQuotationRecursivePatch(yamlNode)
52+
}
53+
54+
y, err := yaml.Marshal(yamlNode)
3655
if err != nil {
3756
// this should never go wrong in normal operations
3857
panic(errors.Wrap(err, "formatting manifest"))
@@ -262,12 +281,19 @@ type List []Manifest
262281

263282
// String returns the List as a yaml stream. In case of an error, it is
264283
// returned as a string instead.
265-
func (m List) String() string {
284+
func (m List) String(forceQuotedStrings bool) string {
266285
buf := bytes.Buffer{}
267286
enc := yaml.NewEncoder(&buf)
268287

269288
for _, d := range m {
270-
if err := enc.Encode(d); err != nil {
289+
yamlNode := &yaml.Node{}
290+
if err := yamlNode.Encode(d); err != nil {
291+
panic(errors.Wrap(err, "converting manifest to yaml.Node"))
292+
}
293+
if forceQuotedStrings {
294+
yamlForceStringQuotationRecursivePatch(yamlNode)
295+
}
296+
if err := enc.Encode(yamlNode); err != nil {
271297
// This should never happen in normal operations
272298
panic(errors.Wrap(err, "formatting manifests"))
273299
}

‎pkg/kubernetes/subsetdiff.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@ func subsetDiff(c client.Client, m manifest.Manifest) (*difference, error) {
9292
return nil, errors.Wrap(err, "getting state from cluster")
9393
}
9494

95-
should := m.String()
95+
should := m.String(false)
9696

9797
sub := subset(m, rawIs)
98-
is := manifest.Manifest(sub).String()
98+
is := manifest.Manifest(sub).String(false)
9999
if is == "{}\n" {
100100
is = ""
101101
}

‎pkg/tanka/export.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func ExportEnvironments(envs []*v1alpha1.Environment, to string, opts *ExportEnv
115115
}
116116

117117
// Write manifest
118-
data := m.String()
118+
data := m.String(opts.Opts.YamlOpts.ForceStringQuotation)
119119
if err := writeExportFile(path, []byte(data)); err != nil {
120120
return err
121121
}

‎pkg/tanka/tanka.go

+5
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ import (
1515

1616
type JsonnetOpts = jsonnet.Opts
1717

18+
type YAMLOpts struct {
19+
ForceStringQuotation bool
20+
}
21+
1822
// Opts specify general, optional properties that apply to all actions
1923
type Opts struct {
2024
JsonnetOpts
25+
YamlOpts YAMLOpts
2126

2227
// Filters are used to optionally select a subset of the resources
2328
Filters process.Matchers

0 commit comments

Comments
 (0)
Please sign in to comment.