Skip to content

Commit 94e7333

Browse files
thockink8s-publishing-bot
authored andcommitted
Add KYAML support to kubectl
KYAML is a strict subset of YAML, which is sort of halfway between YAML and JSON. It has the following properties: * Does not depend on whitespace (easier to text-patch and template). * Always quotes value strings (no ambiguity aroud things like "no"). * Allows quoted keys, but does not require them, and only quotes them if they are not obviously safe (e.g. "no" would always be quoted). * Always uses {} for structs and maps (no more obscure errors about mapping values). * Always uses [] for lists (no more trying to figure out if a dash changes the meaning). * When printing, it includes a header which makes it clear this is YAML and not ill-formed JSON. * Allows trailing commas * Allows comments, * Tries to economize on vertical space by "cuddling" some kinds of brackets together. * Retains comments. Examples: A struct: ```yaml metadata: { creationTimestamp: "2024-12-11T00:10:11Z", labels: { app: "hostnames", }, name: "hostnames", namespace: "default", resourceVersion: "15231643", uid: "f64dbcba-9c58-40b0-bbe7-70495efb5202", } ``` A list of primitves: ```yaml ipFamilies: [ "IPv4", "IPv6", ] ``` A list of structs: ```yaml ports: [{ port: 80, protocol: "TCP", targetPort: 80, }, { port: 443, protocol: "TCP", targetPort: 443, }] ``` A multi-document stream: ```yaml --- { foo: "bar", } --- { qux: "zrb", } ``` Kubernetes-commit: 2cb955d8ccae30167b9610bfe51c2f86e83a1958
1 parent 1466851 commit 94e7333

File tree

4 files changed

+118
-3
lines changed

4 files changed

+118
-3
lines changed

pkg/genericclioptions/json_yaml_flags.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package genericclioptions
1818

1919
import (
20+
"os"
21+
"slices"
2022
"strings"
2123

2224
"github.com/spf13/cobra"
@@ -29,7 +31,12 @@ func (f *JSONYamlPrintFlags) AllowedFormats() []string {
2931
if f == nil {
3032
return []string{}
3133
}
32-
return []string{"json", "yaml"}
34+
formats := []string{"json", "yaml"}
35+
// We can't use the cmdutil pkg directly because of import cycle.
36+
if strings.ToLower(os.Getenv("KUBECTL_KYAML")) == "true" {
37+
formats = append(formats, "kyaml")
38+
}
39+
return formats
3340
}
3441

3542
// JSONYamlPrintFlags provides default flags necessary for json/yaml printing.
@@ -47,11 +54,19 @@ func (f *JSONYamlPrintFlags) ToPrinter(outputFormat string) (printers.ResourcePr
4754
var printer printers.ResourcePrinter
4855

4956
outputFormat = strings.ToLower(outputFormat)
57+
58+
valid := f.AllowedFormats()
59+
if !slices.Contains(valid, outputFormat) {
60+
return nil, NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: valid}
61+
}
62+
5063
switch outputFormat {
5164
case "json":
5265
printer = &printers.JSONPrinter{}
5366
case "yaml":
5467
printer = &printers.YAMLPrinter{}
68+
case "kyaml":
69+
printer = &printers.KYAMLPrinter{}
5570
default:
5671
return nil, NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: f.AllowedFormats()}
5772
}

pkg/printers/json_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,9 @@ func TestPrintersSuccess(t *testing.T) {
105105
om := func(name string) metav1.ObjectMeta { return metav1.ObjectMeta{Name: name} }
106106

107107
genericPrinters := map[string]ResourcePrinter{
108-
"json": NewTypeSetter(scheme.Scheme).ToPrinter(&JSONPrinter{}),
109-
"yaml": NewTypeSetter(scheme.Scheme).ToPrinter(&YAMLPrinter{}),
108+
"json": NewTypeSetter(scheme.Scheme).ToPrinter(&JSONPrinter{}),
109+
"yaml": NewTypeSetter(scheme.Scheme).ToPrinter(&YAMLPrinter{}),
110+
"kyaml": NewTypeSetter(scheme.Scheme).ToPrinter(&KYAMLPrinter{}),
110111
}
111112
objects := map[string]runtime.Object{
112113
"pod": &v1.Pod{ObjectMeta: om("pod")},

pkg/printers/kyaml.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// package kyaml provides a printer for Kubernetes objects that formats them
18+
// as KYAML, a strict subset of YAML that is designed to be explicit and
19+
// unambiguous. KYAML is YAML.
20+
package printers
21+
22+
import (
23+
"bytes"
24+
"errors"
25+
"fmt"
26+
"io"
27+
"reflect"
28+
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/runtime"
31+
"sigs.k8s.io/yaml/kyaml"
32+
)
33+
34+
// KYAMLPrinter is an implementation of ResourcePrinter which formats data into
35+
// a specific dialect of YAML, known as KYAML. KYAML is halfway between YAML
36+
// and JSON, but is a strict subset of YAML, and so it should should be
37+
// readable by any YAML parser. It is designed to be explicit and unambiguous,
38+
// and eschews significant whitespace.
39+
type KYAMLPrinter struct {
40+
encoder kyaml.Encoder
41+
}
42+
43+
// PrintObj prints the data as KYAML to the specified writer.
44+
func (p *KYAMLPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
45+
// We use reflect.Indirect here in order to obtain the actual value from a pointer.
46+
// We need an actual value in order to retrieve the package path for an object.
47+
// Using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers.
48+
if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
49+
return errors.New(InternalObjectPrinterErr)
50+
}
51+
52+
switch obj := obj.(type) {
53+
case *metav1.WatchEvent:
54+
if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj.Object.Object)).Type().PkgPath()) {
55+
return errors.New(InternalObjectPrinterErr)
56+
}
57+
case *runtime.Unknown:
58+
return p.encoder.FromYAML(bytes.NewReader(obj.Raw), w)
59+
}
60+
61+
if obj.GetObjectKind().GroupVersionKind().Empty() {
62+
return fmt.Errorf("missing apiVersion or kind; try GetObjectKind().SetGroupVersionKind() if you know the type")
63+
}
64+
65+
return p.encoder.FromObject(obj, w)
66+
}

pkg/printers/kyaml_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
Copyright 2021 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package printers
18+
19+
import (
20+
"testing"
21+
22+
"sigs.k8s.io/yaml"
23+
24+
"k8s.io/client-go/kubernetes/scheme"
25+
)
26+
27+
func TestKYAMLPrinter(t *testing.T) {
28+
testPrinter(t, NewTypeSetter(scheme.Scheme).ToPrinter(&KYAMLPrinter{}), kyamlUnmarshal)
29+
}
30+
31+
func kyamlUnmarshal(data []byte, v interface{}) error {
32+
return yaml.Unmarshal(data, v)
33+
}

0 commit comments

Comments
 (0)