Skip to content

Commit a2d0f87

Browse files
committed
Add shared Kubernetes decode and patch helpers
1 parent f26c07b commit a2d0f87

3 files changed

Lines changed: 112 additions & 0 deletions

File tree

k8s/patch.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package k8s
2+
3+
import "encoding/json"
4+
5+
// MergePatch marshals a payload as a Kubernetes merge patch document.
6+
func MergePatch(payload any) ([]byte, error) {
7+
return json.Marshal(payload)
8+
}
9+
10+
// StatusMergePatch builds a merge patch for a status subresource update.
11+
func StatusMergePatch(status any) ([]byte, error) {
12+
return MergePatch(map[string]any{"status": status})
13+
}
14+
15+
// MetadataAnnotationsMergePatch builds a merge patch for annotation updates.
16+
func MetadataAnnotationsMergePatch(annotations map[string]any) ([]byte, error) {
17+
return MergePatch(map[string]any{
18+
"metadata": map[string]any{
19+
"annotations": annotations,
20+
},
21+
})
22+
}

k8s/unstructured.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package k8s
2+
3+
import (
4+
"fmt"
5+
6+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
7+
"k8s.io/apimachinery/pkg/runtime"
8+
)
9+
10+
// DecodeUnstructured converts an unstructured Kubernetes object into a typed value.
11+
func DecodeUnstructured[T any](u *unstructured.Unstructured) (*T, error) {
12+
var out T
13+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &out); err != nil {
14+
return nil, fmt.Errorf("decode %T: %w", out, err)
15+
}
16+
return &out, nil
17+
}

k8s/unstructured_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package k8s
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
8+
)
9+
10+
func TestDecodeUnstructured(t *testing.T) {
11+
type spec struct {
12+
Name string `json:"name"`
13+
}
14+
type sample struct {
15+
Spec spec `json:"spec"`
16+
}
17+
18+
obj := &unstructured.Unstructured{
19+
Object: map[string]any{
20+
"spec": map[string]any{
21+
"name": "demo",
22+
},
23+
},
24+
}
25+
26+
got, err := DecodeUnstructured[sample](obj)
27+
if err != nil {
28+
t.Fatalf("DecodeUnstructured returned error: %v", err)
29+
}
30+
if got.Spec.Name != "demo" {
31+
t.Fatalf("decoded name mismatch: got %q", got.Spec.Name)
32+
}
33+
}
34+
35+
func TestStatusMergePatch(t *testing.T) {
36+
data, err := StatusMergePatch(map[string]string{"phase": "Ready"})
37+
if err != nil {
38+
t.Fatalf("StatusMergePatch returned error: %v", err)
39+
}
40+
41+
var patch map[string]map[string]string
42+
if err := json.Unmarshal(data, &patch); err != nil {
43+
t.Fatalf("unmarshal patch: %v", err)
44+
}
45+
if got := patch["status"]["phase"]; got != "Ready" {
46+
t.Fatalf("status phase mismatch: got %q", got)
47+
}
48+
}
49+
50+
func TestMetadataAnnotationsMergePatch(t *testing.T) {
51+
data, err := MetadataAnnotationsMergePatch(map[string]any{
52+
"cocoon.cis/hibernate": nil,
53+
"cocoon.cis/vm-name": "vk-demo-0",
54+
})
55+
if err != nil {
56+
t.Fatalf("MetadataAnnotationsMergePatch returned error: %v", err)
57+
}
58+
59+
var patch struct {
60+
Metadata struct {
61+
Annotations map[string]any `json:"annotations"`
62+
} `json:"metadata"`
63+
}
64+
if err := json.Unmarshal(data, &patch); err != nil {
65+
t.Fatalf("unmarshal patch: %v", err)
66+
}
67+
if got := patch.Metadata.Annotations["cocoon.cis/vm-name"]; got != "vk-demo-0" {
68+
t.Fatalf("vm-name mismatch: got %#v", got)
69+
}
70+
if got := patch.Metadata.Annotations["cocoon.cis/hibernate"]; got != nil {
71+
t.Fatalf("expected nil patch value, got %#v", got)
72+
}
73+
}

0 commit comments

Comments
 (0)