Skip to content

Commit

Permalink
Merge pull request #4 from Kuadrant/json-patch
Browse files Browse the repository at this point in the history
New example JSON patch merge strategy
  • Loading branch information
guicassolato authored Jul 2, 2024
2 parents 547d4cc + 6a13cbd commit 66032b1
Show file tree
Hide file tree
Showing 5 changed files with 562 additions and 0 deletions.
143 changes: 143 additions & 0 deletions examples/json_patch/color_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package json_patch

import (
"encoding/json"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2"

jsonpatch "github.com/evanphx/json-patch"
"github.com/kuadrant/policy-machinery/machinery"
)

type ColorPolicy struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ColorSpec `json:"spec"`
}

var _ machinery.Policy = &ColorPolicy{}

func (p *ColorPolicy) GetURL() string {
return machinery.UrlFromObject(p)
}

func (p *ColorPolicy) GetTargetRefs() []machinery.PolicyTargetReference {
return []machinery.PolicyTargetReference{
machinery.LocalPolicyTargetReferenceWithSectionName{
LocalPolicyTargetReferenceWithSectionName: p.Spec.TargetRef,
PolicyNamespace: p.Namespace,
},
}
}

func (p *ColorPolicy) GetMergeStrategy() machinery.MergeStrategy {
return func(parent, child machinery.Policy) machinery.Policy {
colorParent, okSource := parent.(*ColorPolicy)
colorChild, okTarget := child.(*ColorPolicy)

if !okSource || !okTarget {
return nil
}

parentSpecJSON, err := json.Marshal(colorParent.Spec.Proper())
if err != nil {
return nil
}

childSpecJSON, err := json.Marshal(colorChild.Spec.Proper())
if err != nil {
return nil
}

var resultSpecJSON []byte

if overrides := colorParent.Spec.Overrides; overrides != nil {
resultSpecJSON, err = jsonpatch.MergePatch(childSpecJSON, parentSpecJSON)
} else {
resultSpecJSON, err = jsonpatch.MergePatch(parentSpecJSON, childSpecJSON)
}

if err != nil {
return nil
}

result := ColorSpecProper{}
if err := json.Unmarshal(resultSpecJSON, &result); err != nil {
return nil
}

return &ColorPolicy{
TypeMeta: colorChild.TypeMeta,
ObjectMeta: colorChild.ObjectMeta,
Spec: ColorSpec{
TargetRef: colorChild.Spec.TargetRef,
ColorSpecProper: result,
},
}
}
}

func (p *ColorPolicy) Merge(policy machinery.Policy) machinery.Policy {
source := policy.(*ColorPolicy)
return source.GetMergeStrategy()(source, p)
}

func (p *ColorPolicy) DeepCopy() *ColorPolicy {
spec := p.Spec.DeepCopy()
return &ColorPolicy{
TypeMeta: p.TypeMeta,
ObjectMeta: p.ObjectMeta,
Spec: *spec,
}
}

type ColorSpec struct {
TargetRef gwapi.LocalPolicyTargetReferenceWithSectionName `json:"targetRef"`

Defaults *ColorSpecProper `json:"defaults,omitempty"`
Overrides *ColorSpecProper `json:"overrides,omitempty"`

ColorSpecProper `json:""`
}

func (s *ColorSpec) Proper() *ColorSpecProper {
if s.Defaults != nil {
return s.Defaults
}
if s.Overrides != nil {
return s.Overrides
}
return &s.ColorSpecProper
}

func (s *ColorSpec) DeepCopy() *ColorSpec {
rules := make(map[string]ColorValue, len(s.Proper().Rules))
for k, v := range s.Proper().Rules {
rules[k] = v
}
return &ColorSpec{
TargetRef: s.TargetRef,
ColorSpecProper: ColorSpecProper{
Rules: rules,
},
}
}

type ColorSpecProper struct {
Rules map[string]ColorValue `json:"rules,omitempty"`
}

type ColorValue string

const (
Black ColorValue = "black"
Blue ColorValue = "blue"
Green ColorValue = "green"
Orange ColorValue = "orange"
Purple ColorValue = "purple"
Red ColorValue = "red"
White ColorValue = "white"
Yellow ColorValue = "yellow"
)
180 changes: 180 additions & 0 deletions examples/json_patch/color_policy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//go:build unit

package json_patch

import (
"testing"

"github.com/kuadrant/policy-machinery/machinery"
)

func TestMerge(t *testing.T) {
testCases := []struct {
name string
source machinery.Policy
target machinery.Policy
expected map[string]ColorValue
}{
{
name: "JSON patch defaults into empty policy",
source: &ColorPolicy{
Spec: ColorSpec{
Defaults: &ColorSpecProper{
Rules: map[string]ColorValue{
"rule-1": Blue,
"rule-2": Red,
},
},
},
},
target: &ColorPolicy{},
expected: map[string]ColorValue{
"rule-1": Blue,
"rule-2": Red,
},
},
{
name: "JSON patch defaults into policy without conflicting rules",
source: &ColorPolicy{
Spec: ColorSpec{
Defaults: &ColorSpecProper{
Rules: map[string]ColorValue{
"rule-1": Blue,
"rule-2": Red,
},
},
},
},
target: &ColorPolicy{
Spec: ColorSpec{
ColorSpecProper: ColorSpecProper{
Rules: map[string]ColorValue{
"rule-3": Green,
},
},
},
},
expected: map[string]ColorValue{
"rule-1": Blue,
"rule-2": Red,
"rule-3": Green,
},
},
{
name: "JSON patch defaults into policy with conflicting rules",
source: &ColorPolicy{
Spec: ColorSpec{
Defaults: &ColorSpecProper{
Rules: map[string]ColorValue{
"rule-1": Blue,
"rule-2": Red,
},
},
},
},
target: &ColorPolicy{
Spec: ColorSpec{
ColorSpecProper: ColorSpecProper{
Rules: map[string]ColorValue{
"rule-1": Yellow,
"rule-3": Green,
},
},
},
},
expected: map[string]ColorValue{
"rule-1": Yellow,
"rule-2": Red,
"rule-3": Green,
},
},
{
name: "JSON patch overrides into empty policy",
source: &ColorPolicy{
Spec: ColorSpec{
Overrides: &ColorSpecProper{
Rules: map[string]ColorValue{
"rule-1": Blue,
"rule-2": Red,
},
},
},
},
target: &ColorPolicy{},
expected: map[string]ColorValue{
"rule-1": Blue,
"rule-2": Red,
},
},
{
name: "JSON patch overrides into policy without conflicting rules",
source: &ColorPolicy{
Spec: ColorSpec{
Overrides: &ColorSpecProper{
Rules: map[string]ColorValue{
"rule-1": Blue,
"rule-2": Red,
},
},
},
},
target: &ColorPolicy{
Spec: ColorSpec{
ColorSpecProper: ColorSpecProper{
Rules: map[string]ColorValue{
"rule-3": Green,
},
},
},
},
expected: map[string]ColorValue{
"rule-1": Blue,
"rule-2": Red,
"rule-3": Green,
},
},
{
name: "JSON patch overrides into policy with conflicting rules",
source: &ColorPolicy{
Spec: ColorSpec{
Overrides: &ColorSpecProper{
Rules: map[string]ColorValue{
"rule-1": Blue,
"rule-2": Red,
},
},
},
},
target: &ColorPolicy{
Spec: ColorSpec{
ColorSpecProper: ColorSpecProper{
Rules: map[string]ColorValue{
"rule-1": Yellow,
"rule-3": Green,
},
},
},
},
expected: map[string]ColorValue{
"rule-1": Blue,
"rule-2": Red,
"rule-3": Green,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
merged := tc.target.Merge(tc.source)
mergedColorPolicy := merged.(*ColorPolicy)
mergedRules := mergedColorPolicy.Spec.Proper().Rules
if len(mergedRules) != len(tc.expected) {
t.Errorf("Expected %d rules, but got %d", len(tc.expected), len(mergedRules))
}
for id, color := range mergedRules {
if tc.expected[id] != color {
t.Errorf("Expected rule %s to have color %s, but got %s", id, tc.expected[id], color)
}
}
})
}
}
Loading

0 comments on commit 66032b1

Please sign in to comment.