Skip to content

Commit 6eea8b3

Browse files
Replace gopkg.in/yaml with sigs.k8s.io/yaml and go.yaml.in/yaml/v3
- Replace gopkg.in/yaml.v2 with sigs.k8s.io/yaml in pkg/genall/genall.go - Replace gopkg.in/yaml.v3 with go.yaml.in/yaml/v3 in pkg/schemapatcher and its internal yaml package - Fix map[any]any → map[string]any type assertions in pkg/crd/gen.go and pkg/genall/genall.go to match JSON-based output - Add depguard rules to block direct imports of gopkg.in/yaml.v2 and gopkg.in/yaml.v3, with links to the unmaintained upstream and the correct replacements - Add unit tests for yamlMarshal and TransformRemoveCreationTimestamp covering number types, booleans, nil, arrays, nested maps, and transform chains Motivation Project are no longer maintained
1 parent 77554a0 commit 6eea8b3

13 files changed

Lines changed: 259 additions & 23 deletions

File tree

.golangci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ linters:
5252
deny:
5353
- pkg: sort
5454
desc: Should be replaced with slices package
55+
forbid-yaml-gopkg:
56+
deny:
57+
- pkg: gopkg.in/yaml.v2
58+
desc: "Use sigs.k8s.io/yaml instead. gopkg.in/yaml.v2 is unmaintained, see github.com/go-yaml/yaml."
59+
- pkg: gopkg.in/yaml.v3
60+
desc: "Use sigs.k8s.io/yaml instead, or go.yaml.in/yaml/v3 for yaml.Node operations. gopkg.in/yaml.v3 is unmaintained, see github.com/go-yaml/yaml."
5561
forbidigo:
5662
forbid:
5763
- pattern: context.Background

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ require (
1010
github.com/onsi/gomega v1.42.0
1111
github.com/spf13/cobra v1.10.2
1212
github.com/spf13/pflag v1.0.10
13+
go.yaml.in/yaml/v3 v3.0.4
1314
golang.org/x/tools v0.46.0
1415
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated
15-
gopkg.in/yaml.v2 v2.4.0
16-
gopkg.in/yaml.v3 v3.0.1
1716
k8s.io/api v0.36.1
1817
k8s.io/apiextensions-apiserver v0.36.1
1918
k8s.io/apimachinery v0.36.1
@@ -79,7 +78,6 @@ require (
7978
go.opentelemetry.io/otel/trace v1.41.0 // indirect
8079
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
8180
go.yaml.in/yaml/v2 v2.4.4 // indirect
82-
go.yaml.in/yaml/v3 v3.0.4 // indirect
8381
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
8482
golang.org/x/mod v0.37.0 // indirect
8583
golang.org/x/net v0.56.0 // indirect

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
296296
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
297297
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
298298
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
299-
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
300-
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
301299
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
302300
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
303301
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

pkg/crd/gen.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func transformRemoveCRDStatus(obj map[string]any) error {
116116
// transformPreserveUnknownFields adds spec.preserveUnknownFields=value.
117117
func transformPreserveUnknownFields(value bool) func(map[string]any) error {
118118
return func(obj map[string]any) error {
119-
if spec, ok := obj["spec"].(map[any]any); ok {
119+
if spec, ok := obj["spec"].(map[string]any); ok {
120120
spec["preserveUnknownFields"] = value
121121
}
122122
return nil

pkg/genall/genall.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ import (
2323
"os"
2424

2525
"golang.org/x/tools/go/packages"
26-
rawyaml "gopkg.in/yaml.v2"
2726
"sigs.k8s.io/controller-tools/pkg/loader"
2827
"sigs.k8s.io/controller-tools/pkg/markers"
28+
sigsyaml "sigs.k8s.io/yaml"
2929
)
3030

3131
// Generators are a list of Generators.
@@ -133,7 +133,10 @@ func WithTransform(transform func(obj map[string]any) error) *WriteYAMLOptions {
133133

134134
// TransformRemoveCreationTimestamp ensures we do not write the metadata.creationTimestamp field.
135135
func TransformRemoveCreationTimestamp(obj map[string]any) error {
136-
metadata := obj["metadata"].(map[any]any)
136+
metadata, ok := obj["metadata"].(map[string]any)
137+
if !ok {
138+
return nil
139+
}
137140
delete(metadata, "creationTimestamp")
138141
return nil
139142
}
@@ -182,14 +185,8 @@ func yamlMarshal(o any, options ...*WriteYAMLOptions) ([]byte, error) {
182185

183186
// yamlJSONToYAMLWithFilter is based on sigs.k8s.io/yaml.JSONToYAML, but allows for transforming the final data before writing.
184187
func yamlJSONToYAMLWithFilter(j []byte, options ...*WriteYAMLOptions) ([]byte, error) {
185-
// Convert the JSON to an object.
186188
var jsonObj map[string]any
187-
// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
188-
// Go JSON library doesn't try to pick the right number type (int, float,
189-
// etc.) when unmarshalling to any, it just picks float64
190-
// universally. go-yaml does go through the effort of picking the right
191-
// number type, so we can preserve number type throughout this process.
192-
if err := rawyaml.Unmarshal(j, &jsonObj); err != nil {
189+
if err := json.Unmarshal(j, &jsonObj); err != nil {
193190
return nil, err
194191
}
195192

@@ -201,8 +198,11 @@ func yamlJSONToYAMLWithFilter(j []byte, options ...*WriteYAMLOptions) ([]byte, e
201198
}
202199
}
203200

204-
// Marshal this object into YAML.
205-
return rawyaml.Marshal(jsonObj)
201+
out, err := json.Marshal(jsonObj)
202+
if err != nil {
203+
return nil, fmt.Errorf("error marshaling into JSON: %w", err)
204+
}
205+
return sigsyaml.JSONToYAML(out)
206206
}
207207

208208
// ReadFile reads the given boilerplate artifact using the context's InputRule.

pkg/genall/genall_suite_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2026 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 genall
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/ginkgo"
23+
g "github.com/onsi/gomega"
24+
)
25+
26+
func TestGenall(t *testing.T) {
27+
g.RegisterFailHandler(Fail)
28+
RunSpecs(t, "Genall Suite")
29+
}

pkg/genall/genall_test.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/*
2+
Copyright 2026 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 genall
18+
19+
import (
20+
. "github.com/onsi/ginkgo"
21+
g "github.com/onsi/gomega"
22+
)
23+
24+
var _ = Describe("yamlMarshal", func() {
25+
It("should preserve integer types without adding decimal points", func() {
26+
type schemaLike struct {
27+
Minimum *float64 `json:"minimum,omitempty"`
28+
Maximum *float64 `json:"maximum,omitempty"`
29+
Port *int32 `json:"port,omitempty"`
30+
Count *int64 `json:"count,omitempty"`
31+
}
32+
33+
minVal := -0.5
34+
maxVal := 1.5
35+
port := int32(8080)
36+
count := int64(2)
37+
38+
out, err := yamlMarshal(schemaLike{Minimum: &minVal, Maximum: &maxVal, Port: &port, Count: &count})
39+
g.Expect(err).NotTo(g.HaveOccurred())
40+
41+
y := string(out)
42+
g.Expect(y).To(g.ContainSubstring("minimum: -0.5"))
43+
g.Expect(y).To(g.ContainSubstring("maximum: 1.5"))
44+
g.Expect(y).To(g.ContainSubstring("port: 8080"))
45+
g.Expect(y).To(g.ContainSubstring("count: 2"))
46+
g.Expect(y).NotTo(g.ContainSubstring("2.0"))
47+
g.Expect(y).NotTo(g.ContainSubstring("8080.0"))
48+
})
49+
50+
It("should marshal nil as null", func() {
51+
out, err := yamlMarshal(nil)
52+
g.Expect(err).NotTo(g.HaveOccurred())
53+
g.Expect(string(out)).To(g.ContainSubstring("null"))
54+
})
55+
56+
It("should marshal string values correctly", func() {
57+
obj := map[string]any{
58+
"name": "hello-world",
59+
"special": "value: with colon",
60+
"empty": "",
61+
}
62+
out, err := yamlMarshal(obj)
63+
g.Expect(err).NotTo(g.HaveOccurred())
64+
65+
y := string(out)
66+
g.Expect(y).To(g.ContainSubstring("name: hello-world"))
67+
g.Expect(y).To(g.Or(
68+
g.ContainSubstring("value: with colon"),
69+
g.ContainSubstring("'value: with colon'"),
70+
g.ContainSubstring(`"value: with colon"`),
71+
))
72+
})
73+
74+
It("should marshal arrays correctly", func() {
75+
obj := map[string]any{
76+
"items": []any{"a", "b", "c"},
77+
}
78+
out, err := yamlMarshal(obj)
79+
g.Expect(err).NotTo(g.HaveOccurred())
80+
81+
y := string(out)
82+
g.Expect(y).To(g.ContainSubstring("- a"))
83+
g.Expect(y).To(g.ContainSubstring("- b"))
84+
g.Expect(y).To(g.ContainSubstring("- c"))
85+
})
86+
87+
It("should marshal deeply nested maps correctly", func() {
88+
obj := map[string]any{
89+
"spec": map[string]any{
90+
"template": map[string]any{
91+
"metadata": map[string]any{
92+
"labels": map[string]any{
93+
"app": "myapp",
94+
},
95+
},
96+
},
97+
},
98+
}
99+
out, err := yamlMarshal(obj)
100+
g.Expect(err).NotTo(g.HaveOccurred())
101+
g.Expect(string(out)).To(g.ContainSubstring("app: myapp"))
102+
})
103+
104+
It("should marshal booleans correctly", func() {
105+
obj := map[string]any{"enabled": true, "disabled": false}
106+
out, err := yamlMarshal(obj)
107+
g.Expect(err).NotTo(g.HaveOccurred())
108+
109+
y := string(out)
110+
g.Expect(y).To(g.ContainSubstring("enabled: true"))
111+
g.Expect(y).To(g.ContainSubstring("disabled: false"))
112+
})
113+
114+
It("should apply multiple transforms in order", func() {
115+
obj := map[string]any{
116+
"metadata": map[string]any{
117+
"name": "example",
118+
"creationTimestamp": "2024-01-01T00:00:00Z",
119+
},
120+
"extra": "keep",
121+
}
122+
123+
removeExtra := func(o map[string]any) error {
124+
delete(o, "extra")
125+
return nil
126+
}
127+
128+
out, err := yamlMarshal(obj,
129+
WithTransform(TransformRemoveCreationTimestamp),
130+
WithTransform(removeExtra),
131+
)
132+
g.Expect(err).NotTo(g.HaveOccurred())
133+
134+
y := string(out)
135+
g.Expect(y).NotTo(g.ContainSubstring("creationTimestamp"))
136+
g.Expect(y).NotTo(g.ContainSubstring("extra"))
137+
g.Expect(y).To(g.ContainSubstring("name: example"))
138+
})
139+
140+
It("should apply transforms and produce valid YAML for a CRD-like object", func() {
141+
obj := map[string]any{
142+
"apiVersion": "apiextensions.k8s.io/v1",
143+
"kind": "CustomResourceDefinition",
144+
"metadata": map[string]any{
145+
"name": "foos.example.com",
146+
"creationTimestamp": "2024-01-01T00:00:00Z",
147+
},
148+
"spec": map[string]any{
149+
"group": "example.com",
150+
"versions": []any{
151+
map[string]any{"name": "v1", "served": true, "storage": true},
152+
},
153+
},
154+
}
155+
156+
out, err := yamlMarshal(obj, WithTransform(TransformRemoveCreationTimestamp))
157+
g.Expect(err).NotTo(g.HaveOccurred())
158+
159+
y := string(out)
160+
g.Expect(y).NotTo(g.ContainSubstring("creationTimestamp"))
161+
g.Expect(y).To(g.ContainSubstring("name: foos.example.com"))
162+
g.Expect(y).To(g.ContainSubstring("group: example.com"))
163+
})
164+
})
165+
166+
var _ = Describe("TransformRemoveCreationTimestamp", func() {
167+
It("should remove creationTimestamp when present", func() {
168+
obj := map[string]any{
169+
"metadata": map[string]any{
170+
"name": "test",
171+
"creationTimestamp": "2024-01-01T00:00:00Z",
172+
},
173+
}
174+
g.Expect(TransformRemoveCreationTimestamp(obj)).To(g.Succeed())
175+
176+
meta := obj["metadata"].(map[string]any)
177+
g.Expect(meta).NotTo(g.HaveKey("creationTimestamp"))
178+
g.Expect(meta).To(g.HaveKey("name"))
179+
})
180+
181+
It("should be a no-op when metadata is absent", func() {
182+
obj := map[string]any{"kind": "CronJob"}
183+
g.Expect(TransformRemoveCreationTimestamp(obj)).To(g.Succeed())
184+
})
185+
186+
It("should be a no-op when creationTimestamp is already absent", func() {
187+
obj := map[string]any{
188+
"metadata": map[string]any{"name": "test"},
189+
}
190+
g.Expect(TransformRemoveCreationTimestamp(obj)).To(g.Succeed())
191+
192+
meta := obj["metadata"].(map[string]any)
193+
g.Expect(meta).To(g.HaveKey("name"))
194+
})
195+
196+
It("should be a no-op when metadata is a non-map type", func() {
197+
obj := map[string]any{"metadata": "not-a-map"}
198+
g.Expect(TransformRemoveCreationTimestamp(obj)).To(g.Succeed())
199+
})
200+
201+
It("should be a no-op when metadata is nil", func() {
202+
obj := map[string]any{"metadata": nil}
203+
g.Expect(TransformRemoveCreationTimestamp(obj)).To(g.Succeed())
204+
})
205+
})

pkg/schemapatcher/gen.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
"os"
2222
"path/filepath"
2323

24-
"gopkg.in/yaml.v3"
24+
"go.yaml.in/yaml/v3"
2525
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2626
"k8s.io/apimachinery/pkg/api/equality"
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

pkg/schemapatcher/internal/yaml/convert.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
"encoding/json"
2121
"fmt"
2222

23-
"gopkg.in/yaml.v3"
23+
"go.yaml.in/yaml/v3"
2424
)
2525

2626
// ToYAML converts some object that serializes to JSON into a YAML node tree.

pkg/schemapatcher/internal/yaml/delete_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package yaml
1919
import (
2020
. "github.com/onsi/ginkgo"
2121
. "github.com/onsi/gomega"
22-
"gopkg.in/yaml.v3"
22+
"go.yaml.in/yaml/v3"
2323
)
2424

2525
var _ = Describe("DeleteNode", func() {

0 commit comments

Comments
 (0)