Skip to content

Commit 850c2a6

Browse files
authored
Merge pull request #7161 from sbueringer/pr-poc-taints-marshalling
🐛 Fix marshaling of taints, so an empty slice is preserved
2 parents adb6819 + 447947c commit 850c2a6

File tree

2 files changed

+107
-4
lines changed

2 files changed

+107
-4
lines changed

bootstrap/kubeadm/api/v1beta1/kubeadm_types.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"encoding/json"
2021
"fmt"
2122
"strings"
2223

@@ -214,6 +215,7 @@ type APIEndpoint struct {
214215
}
215216

216217
// NodeRegistrationOptions holds fields that relate to registering a new control-plane or node to the cluster, either via "kubeadm init" or "kubeadm join".
218+
// Note: The NodeRegistrationOptions struct has to be kept in sync with the structs in MarshalJSON.
217219
type NodeRegistrationOptions struct {
218220

219221
// Name is the `.Metadata.Name` field of the Node API object that will be created in this `kubeadm init` or `kubeadm join` operation.
@@ -243,6 +245,49 @@ type NodeRegistrationOptions struct {
243245
IgnorePreflightErrors []string `json:"ignorePreflightErrors,omitempty"`
244246
}
245247

248+
// MarshalJSON marshals NodeRegistrationOptions in a way that an empty slice in Taints is preserved.
249+
// Taints are then rendered as:
250+
// * nil => omitted from the marshalled JSON
251+
// * [] => rendered as empty array (`[]`)
252+
// * [regular-array] => rendered as usual
253+
// We have to do this as the regular Golang JSON marshalling would just omit
254+
// the empty slice (xref: https://github.com/golang/go/issues/22480).
255+
// Note: We can't re-use the original struct as that would lead to an infinite recursion.
256+
// Note: The structs in this func have to be kept in sync with the NodeRegistrationOptions struct.
257+
func (n *NodeRegistrationOptions) MarshalJSON() ([]byte, error) {
258+
// Marshal an empty Taints slice array without omitempty so it's preserved.
259+
if n.Taints != nil && len(n.Taints) == 0 {
260+
return json.Marshal(struct {
261+
Name string `json:"name,omitempty"`
262+
CRISocket string `json:"criSocket,omitempty"`
263+
Taints []corev1.Taint `json:"taints"`
264+
KubeletExtraArgs map[string]string `json:"kubeletExtraArgs,omitempty"`
265+
IgnorePreflightErrors []string `json:"ignorePreflightErrors,omitempty"`
266+
}{
267+
Name: n.Name,
268+
CRISocket: n.CRISocket,
269+
Taints: n.Taints,
270+
KubeletExtraArgs: n.KubeletExtraArgs,
271+
IgnorePreflightErrors: n.IgnorePreflightErrors,
272+
})
273+
}
274+
275+
// If Taints is nil or not empty we can use omitempty.
276+
return json.Marshal(struct {
277+
Name string `json:"name,omitempty"`
278+
CRISocket string `json:"criSocket,omitempty"`
279+
Taints []corev1.Taint `json:"taints,omitempty"`
280+
KubeletExtraArgs map[string]string `json:"kubeletExtraArgs,omitempty"`
281+
IgnorePreflightErrors []string `json:"ignorePreflightErrors,omitempty"`
282+
}{
283+
Name: n.Name,
284+
CRISocket: n.CRISocket,
285+
Taints: n.Taints,
286+
KubeletExtraArgs: n.KubeletExtraArgs,
287+
IgnorePreflightErrors: n.IgnorePreflightErrors,
288+
})
289+
}
290+
246291
// Networking contains elements describing cluster's networking configuration.
247292
type Networking struct {
248293
// ServiceSubnet is the subnet used by k8s services.

bootstrap/kubeadm/api/v1beta1/kubeadm_types_test.go

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,67 @@ import (
2323

2424
. "github.com/onsi/gomega"
2525
"github.com/pkg/errors"
26+
corev1 "k8s.io/api/core/v1"
2627
)
2728

28-
func TestMarshalJSON(t *testing.T) {
29+
func TestNodeRegistrationOptionsMarshalJSON(t *testing.T) {
30+
var tests = []struct {
31+
name string
32+
opts NodeRegistrationOptions
33+
expected string
34+
}{
35+
{
36+
name: "marshal nil taints",
37+
opts: NodeRegistrationOptions{
38+
Name: "node-1",
39+
CRISocket: "unix:///var/run/containerd/containerd.sock",
40+
Taints: nil,
41+
KubeletExtraArgs: map[string]string{"abc": "def"},
42+
IgnorePreflightErrors: []string{"ignore-1"},
43+
},
44+
expected: `{"name":"node-1","criSocket":"unix:///var/run/containerd/containerd.sock","kubeletExtraArgs":{"abc":"def"},"ignorePreflightErrors":["ignore-1"]}`,
45+
},
46+
{
47+
name: "marshal empty taints",
48+
opts: NodeRegistrationOptions{
49+
Name: "node-1",
50+
CRISocket: "unix:///var/run/containerd/containerd.sock",
51+
Taints: []corev1.Taint{},
52+
KubeletExtraArgs: map[string]string{"abc": "def"},
53+
IgnorePreflightErrors: []string{"ignore-1"},
54+
},
55+
expected: `{"name":"node-1","criSocket":"unix:///var/run/containerd/containerd.sock","taints":[],"kubeletExtraArgs":{"abc":"def"},"ignorePreflightErrors":["ignore-1"]}`,
56+
},
57+
{
58+
name: "marshal regular taints",
59+
opts: NodeRegistrationOptions{
60+
Name: "node-1",
61+
CRISocket: "unix:///var/run/containerd/containerd.sock",
62+
Taints: []corev1.Taint{
63+
{
64+
Key: "key",
65+
Value: "value",
66+
Effect: "effect",
67+
},
68+
},
69+
KubeletExtraArgs: map[string]string{"abc": "def"},
70+
IgnorePreflightErrors: []string{"ignore-1"},
71+
},
72+
expected: `{"name":"node-1","criSocket":"unix:///var/run/containerd/containerd.sock","taints":[{"key":"key","value":"value","effect":"effect"}],"kubeletExtraArgs":{"abc":"def"},"ignorePreflightErrors":["ignore-1"]}`,
73+
},
74+
}
75+
for _, tt := range tests {
76+
t.Run(tt.name, func(t *testing.T) {
77+
g := NewWithT(t)
78+
79+
b, err := tt.opts.MarshalJSON()
80+
g.Expect(err).NotTo(HaveOccurred())
81+
g.Expect(string(b)).To(Equal(tt.expected))
82+
})
83+
}
84+
}
85+
86+
func TestBootstrapTokenStringMarshalJSON(t *testing.T) {
2987
var tests = []struct {
3088
bts BootstrapTokenString
3189
expected string
@@ -45,7 +103,7 @@ func TestMarshalJSON(t *testing.T) {
45103
}
46104
}
47105

48-
func TestUnmarshalJSON(t *testing.T) {
106+
func TestBootstrapTokenStringUnmarshalJSON(t *testing.T) {
49107
var tests = []struct {
50108
input string
51109
bts *BootstrapTokenString
@@ -76,7 +134,7 @@ func TestUnmarshalJSON(t *testing.T) {
76134
}
77135
}
78136

79-
func TestJSONRoundtrip(t *testing.T) {
137+
func TestBootstrapTokenStringJSONRoundtrip(t *testing.T) {
80138
var tests = []struct {
81139
input string
82140
bts *BootstrapTokenString
@@ -130,7 +188,7 @@ func roundtrip(input string, bts *BootstrapTokenString) error {
130188
return nil
131189
}
132190

133-
func TestTokenFromIDAndSecret(t *testing.T) {
191+
func TestBootstrapTokenStringTokenFromIDAndSecret(t *testing.T) {
134192
var tests = []struct {
135193
bts BootstrapTokenString
136194
expected string

0 commit comments

Comments
 (0)