Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit fcd3deb

Browse files
committed
chore: Add missing files
1 parent a89e67d commit fcd3deb

2 files changed

Lines changed: 200 additions & 0 deletions

File tree

pkg/object/secret.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package object
2+
3+
import (
4+
"encoding/base64"
5+
)
6+
7+
// DecodeSecretData decodes base64-encoded data fields in a Kubernetes Secret object.
8+
// This function modifies the object in-place, converting the base64-encoded strings
9+
// in the "data" field to their decoded plaintext equivalents.
10+
//
11+
// This is useful when Secret objects enter the controller pipeline from watches,
12+
// allowing pipeline expressions to access the decoded secret values directly
13+
// without manual base64 decoding.
14+
//
15+
// If the object is not a Secret (kind != "Secret"), this function does nothing.
16+
// If decoding fails for any field, the original base64 value is retained.
17+
func DecodeSecretData(obj Object) Object {
18+
if obj == nil {
19+
return obj
20+
}
21+
22+
// Check if this is a Secret object.
23+
if obj.GetKind() != "Secret" {
24+
return obj
25+
}
26+
27+
// Get the data map.
28+
content := obj.UnstructuredContent()
29+
dataField, found := content["data"]
30+
if !found {
31+
return obj
32+
}
33+
34+
dataMap, ok := dataField.(map[string]any)
35+
if !ok {
36+
return obj
37+
}
38+
39+
// Decode each base64-encoded field.
40+
for key, value := range dataMap {
41+
if strValue, ok := value.(string); ok {
42+
decoded, err := base64.StdEncoding.DecodeString(strValue)
43+
if err != nil {
44+
// If decoding fails, keep the original value.
45+
// This handles cases where data is malformed or already decoded.
46+
continue
47+
}
48+
dataMap[key] = string(decoded)
49+
}
50+
}
51+
52+
return obj
53+
}

pkg/object/secret_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package object
2+
3+
import (
4+
"testing"
5+
6+
corev1 "k8s.io/api/core/v1"
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
)
9+
10+
func TestDecodeSecretData(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
input *corev1.Secret
14+
expected map[string]string
15+
}{
16+
{
17+
name: "decode simple secret with multiple keys",
18+
input: &corev1.Secret{
19+
ObjectMeta: metav1.ObjectMeta{
20+
Namespace: "testnamespace",
21+
Name: "testsecret",
22+
},
23+
Type: corev1.SecretTypeOpaque,
24+
Data: map[string][]byte{
25+
"username": []byte("admin"),
26+
"password": []byte("s3cr3t"),
27+
"config": []byte(`{"key":"value"}`),
28+
},
29+
},
30+
expected: map[string]string{
31+
"username": "admin",
32+
"password": "s3cr3t",
33+
"config": `{"key":"value"}`,
34+
},
35+
},
36+
{
37+
name: "decode secret with binary data",
38+
input: &corev1.Secret{
39+
ObjectMeta: metav1.ObjectMeta{
40+
Namespace: "testnamespace",
41+
Name: "binarysecret",
42+
},
43+
Type: corev1.SecretTypeOpaque,
44+
Data: map[string][]byte{
45+
"binarykey": {0x00, 0x01, 0x02, 0xFF},
46+
},
47+
},
48+
expected: map[string]string{
49+
"binarykey": "\x00\x01\x02\xff",
50+
},
51+
},
52+
{
53+
name: "decode secret with empty data",
54+
input: &corev1.Secret{
55+
ObjectMeta: metav1.ObjectMeta{
56+
Namespace: "testnamespace",
57+
Name: "emptysecret",
58+
},
59+
Type: corev1.SecretTypeOpaque,
60+
Data: map[string][]byte{},
61+
},
62+
expected: map[string]string{},
63+
},
64+
}
65+
66+
for _, tt := range tests {
67+
t.Run(tt.name, func(t *testing.T) {
68+
// Convert Secret to unstructured object.
69+
obj, err := NewViewObjectFromNativeObject("test", "Secret", tt.input)
70+
if err != nil {
71+
t.Fatalf("failed to create object: %v", err)
72+
}
73+
74+
// Decode the secret data.
75+
DecodeSecretData(obj)
76+
77+
// Verify decoded values.
78+
content := obj.UnstructuredContent()
79+
dataField, found := content["data"]
80+
if !found && len(tt.expected) > 0 {
81+
t.Fatal("data field not found in object")
82+
}
83+
84+
if len(tt.expected) == 0 {
85+
return // Empty data, nothing to check.
86+
}
87+
88+
dataMap, ok := dataField.(map[string]any)
89+
if !ok {
90+
t.Fatalf("data field is not a map: %T", dataField)
91+
}
92+
93+
for key, expectedValue := range tt.expected {
94+
actualValue, found := dataMap[key]
95+
if !found {
96+
t.Errorf("key %q not found in decoded data", key)
97+
continue
98+
}
99+
100+
actualStr, ok := actualValue.(string)
101+
if !ok {
102+
t.Errorf("value for key %q is not a string: %T", key, actualValue)
103+
continue
104+
}
105+
106+
if actualStr != expectedValue {
107+
t.Errorf("key %q: got %q, want %q", key, actualStr, expectedValue)
108+
}
109+
}
110+
})
111+
}
112+
}
113+
114+
func TestDecodeSecretData_NonSecret(t *testing.T) {
115+
// Create a non-Secret object (e.g., ConfigMap)
116+
cm := NewViewObject("test", "ConfigMap")
117+
SetName(cm, "default", "testconfigmap")
118+
SetContent(cm, map[string]any{
119+
"data": map[string]any{
120+
"key": "value",
121+
},
122+
})
123+
124+
// Call DecodeSecretData on a non-Secret object.
125+
DecodeSecretData(cm)
126+
127+
// Verify data is unchanged.
128+
content := cm.UnstructuredContent()
129+
dataField, found := content["data"]
130+
if !found {
131+
t.Fatal("data field not found")
132+
}
133+
134+
dataMap, ok := dataField.(map[string]any)
135+
if !ok {
136+
t.Fatalf("data field is not a map: %T", dataField)
137+
}
138+
139+
if value, ok := dataMap["key"]; !ok || value != "value" {
140+
t.Errorf("non-Secret data was modified: got %v", dataMap)
141+
}
142+
}
143+
144+
func TestDecodeSecretData_NilObject(t *testing.T) {
145+
// Should not panic on nil object.
146+
DecodeSecretData(nil)
147+
}

0 commit comments

Comments
 (0)