Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func (pl *proxyLogger) Write(p []byte) (int, error) {

logMap, ok := line.(map[string]interface{})
defer func() {
pl.buf = []byte{}
pl.buf = pl.buf[:0]
}()

if !ok {
Expand Down
46 changes: 38 additions & 8 deletions pkg/filter/jq/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package jq
import (
"encoding/json"
"errors"
"fmt"

"github.com/itchyny/gojq"

Expand Down Expand Up @@ -91,19 +92,48 @@ func (c *CompiledJqFilter) String() string {
return c.originalStr
}

// deepCopyAny recursively copies JSON-compatible values (maps, slices, and
// primitives) without going through json.Marshal/Unmarshal. This is
// significantly faster and allocates only the final structure.
func deepCopyAny(input any) (any, error) {
if input == nil {
return nil, nil
}
data, err := json.Marshal(input)
if err != nil {
return nil, err
}
var output any
if err := json.Unmarshal(data, &output); err != nil {
return nil, err
return deepCopyValue(input)
}

func deepCopyValue(v any) (any, error) {
switch val := v.(type) {
case map[string]any:
m := make(map[string]any, len(val))
for k, v := range val {
copied, err := deepCopyValue(v)
if err != nil {
return nil, err
}
m[k] = copied
}
return m, nil
case []any:
s := make([]any, len(val))
for i, v := range val {
copied, err := deepCopyValue(v)
if err != nil {
return nil, err
}
s[i] = copied
}
return s, nil
case string, bool, json.Number,
int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64,
float32, float64:
return val, nil
case nil:
return nil, nil
default:
return nil, fmt.Errorf("deepCopyValue: unsupported type %T", v)
}
return output, nil
}

// collectResults drains a gojq iterator and serialises the results to JSON.
Expand Down
92 changes: 87 additions & 5 deletions pkg/filter/jq/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,32 +175,114 @@ func Test_ApplyFilter_PanicSafety(t *testing.T) {
func Test_deepCopyAny(t *testing.T) {
g := NewWithT(t)

// Test copying a map
// Test copying a map preserves types
inputMap := map[string]any{"foo": "bar", "num": 42}
copyMap, err := deepCopyAny(inputMap)
g.Expect(err).Should(BeNil())
g.Expect(copyMap).Should(Equal(map[string]any{"foo": "bar", "num": float64(42)}))
g.Expect(copyMap).Should(Equal(map[string]any{"foo": "bar", "num": 42}))
g.Expect(copyMap).ShouldNot(BeIdenticalTo(inputMap))

// Test copying a slice
// Test copying a slice preserves types
inputSlice := []any{"a", 1, true}
copySlice, err := deepCopyAny(inputSlice)
g.Expect(err).Should(BeNil())
g.Expect(copySlice).Should(Equal([]any{"a", float64(1), true}))
g.Expect(copySlice).Should(Equal([]any{"a", 1, true}))
g.Expect(copySlice).ShouldNot(BeIdenticalTo(inputSlice))

// Test copying nil
copyNil, err := deepCopyAny(nil)
g.Expect(err).Should(BeNil())
g.Expect(copyNil).Should(BeNil())

// Test copying a value that cannot be marshaled to JSON
// Test copying a value with unsupported type
inputInvalid := map[string]any{"ch": make(chan int)}
copyInvalid, err := deepCopyAny(inputInvalid)
g.Expect(err).ShouldNot(BeNil())
g.Expect(copyInvalid).Should(BeNil())
}

func Test_deepCopyAny_NestedMap(t *testing.T) {
g := NewWithT(t)

input := map[string]any{
"metadata": map[string]any{
"name": "my-pod",
"namespace": "default",
"labels": map[string]any{"app": "test"},
},
"spec": map[string]any{
"replicas": float64(3),
},
}

result, err := deepCopyAny(input)
g.Expect(err).Should(BeNil())

resultMap := result.(map[string]any)
g.Expect(resultMap["metadata"]).Should(Equal(input["metadata"]))

// Verify it's a true deep copy: mutating the copy must not affect the original.
resultMeta := resultMap["metadata"].(map[string]any)
resultMeta["name"] = "mutated"
g.Expect(input["metadata"].(map[string]any)["name"]).Should(Equal("my-pod"))
}

func Test_deepCopyAny_NestedSlice(t *testing.T) {
g := NewWithT(t)

input := []any{
map[string]any{"name": "a"},
map[string]any{"name": "b"},
}

result, err := deepCopyAny(input)
g.Expect(err).Should(BeNil())

resultSlice := result.([]any)
g.Expect(resultSlice).Should(HaveLen(2))

// Mutate copy, verify original is untouched.
resultSlice[0].(map[string]any)["name"] = "mutated"
g.Expect(input[0].(map[string]any)["name"]).Should(Equal("a"))
}

func Test_deepCopyAny_NumericTypes(t *testing.T) {
g := NewWithT(t)

input := map[string]any{
"int": 42,
"int64": int64(100),
"float64": 3.14,
"bool": true,
"string": "hello",
}

result, err := deepCopyAny(input)
g.Expect(err).Should(BeNil())
g.Expect(result).Should(Equal(input))
}

func BenchmarkDeepCopyAny(b *testing.B) {
input := map[string]any{
"metadata": map[string]any{
"name": "my-pod",
"namespace": "default",
"labels": map[string]any{"app": "test", "env": "prod"},
},
"spec": map[string]any{
"containers": []any{
map[string]any{"name": "main", "image": "nginx:latest"},
map[string]any{"name": "sidecar", "image": "envoy:latest"},
},
"replicas": float64(3),
},
}
b.ResetTimer()
for range b.N {
_, _ = deepCopyAny(input)
}
}

// ---- Compile / CompiledJqFilter tests ----

func Test_Compile_ValidExpression(t *testing.T) {
Expand Down
12 changes: 10 additions & 2 deletions pkg/hook/binding_context/binding_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bindingcontext

import (
"encoding/json"
"io"

"github.com/deckhouse/deckhouse/pkg/log"
v1 "k8s.io/api/admission/v1"
Expand Down Expand Up @@ -182,6 +183,13 @@ func ConvertBindingContextList(version string, contexts []BindingContext) Bindin
}

func (b BindingContextList) Json() ([]byte, error) {
data, err := json.MarshalIndent(b, "", " ")
return data, err
return json.Marshal(b)
}

// WriteJson streams the JSON-encoded binding context list directly to w,
// avoiding an intermediate []byte allocation that can be very large for
// synchronization events with many objects.
func (b BindingContextList) WriteJson(w io.Writer) error {
enc := json.NewEncoder(w)
return enc.Encode(b)
}
Loading
Loading