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
7 changes: 7 additions & 0 deletions api/v1/composition.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ type InputRevisions struct {
Revision *int `json:"revision,omitempty"`
SynthesizerGeneration *int64 `json:"synthesizerGeneration,omitempty"`
CompositionGeneration *int64 `json:"compositionGeneration,omitempty"`
IgnoreSideEffects *bool `json:"ignoreSideEffects,omitempty"`
}

func NewInputRevisions(obj client.Object, refKey string) *InputRevisions {
Expand All @@ -151,6 +152,9 @@ func NewInputRevisions(obj client.Object, refKey string) *InputRevisions {
if rev, _ := strconv.ParseInt(obj.GetAnnotations()["eno.azure.io/composition-generation"], 10, 64); rev != 0 {
ir.CompositionGeneration = &rev
}
if val, err := strconv.ParseBool(obj.GetAnnotations()["eno.azure.io/ignore-side-effects"]); err == nil {
ir.IgnoreSideEffects = &val
}
return &ir
}

Expand All @@ -162,6 +166,9 @@ func (i *InputRevisions) Less(b InputRevisions) bool {
return *i.Revision < *b.Revision
}
if i.ResourceVersion == b.ResourceVersion {
if i.IgnoreSideEffects != nil && b.IgnoreSideEffects != nil && *i.IgnoreSideEffects != *b.IgnoreSideEffects {
return *i.IgnoreSideEffects && !*b.IgnoreSideEffects
}
return false
}
iInt, iErr := strconv.Atoi(i.ResourceVersion)
Expand Down
86 changes: 86 additions & 0 deletions api/v1/composition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
func TestInputRevisionsLess(t *testing.T) {
revision1 := 1
revision2 := 2
trueVal := true
falseVal := false
tests := []struct {
Name string
A InputRevisions
Expand Down Expand Up @@ -171,6 +173,90 @@ func TestInputRevisionsLess(t *testing.T) {
},
Expectation: false,
},
{
Name: "same ResourceVersion with IgnoreSideEffects true vs false",
A: InputRevisions{
Key: "key7",
ResourceVersion: "7",
IgnoreSideEffects: &trueVal,
},
B: InputRevisions{
Key: "key7",
ResourceVersion: "7",
IgnoreSideEffects: &falseVal,
},
Expectation: true,
},
{
Name: "same ResourceVersion with IgnoreSideEffects false vs true",
A: InputRevisions{
Key: "key8",
ResourceVersion: "8",
IgnoreSideEffects: &falseVal,
},
B: InputRevisions{
Key: "key8",
ResourceVersion: "8",
IgnoreSideEffects: &trueVal,
},
Expectation: false,
},
{
Name: "same ResourceVersion with both IgnoreSideEffects true",
A: InputRevisions{
Key: "key9",
ResourceVersion: "9",
IgnoreSideEffects: &trueVal,
},
B: InputRevisions{
Key: "key9",
ResourceVersion: "9",
IgnoreSideEffects: &trueVal,
},
Expectation: false,
},
{
Name: "same ResourceVersion with both IgnoreSideEffects false",
A: InputRevisions{
Key: "key10",
ResourceVersion: "10",
IgnoreSideEffects: &falseVal,
},
B: InputRevisions{
Key: "key10",
ResourceVersion: "10",
IgnoreSideEffects: &falseVal,
},
Expectation: false,
},
{
Name: "same ResourceVersion with one nil IgnoreSideEffects",
A: InputRevisions{
Key: "key11",
ResourceVersion: "11",
IgnoreSideEffects: &trueVal,
},
B: InputRevisions{
Key: "key11",
ResourceVersion: "11",
IgnoreSideEffects: nil,
},
Expectation: false,
},
{
Name: "same ResourceVersion with both nil IgnoreSideEffects",
A: InputRevisions{
Key: "key12",
ResourceVersion: "12",
IgnoreSideEffects: nil,
},
B: InputRevisions{
Key: "key12",
ResourceVersion: "12",
IgnoreSideEffects: nil,
},
Expectation: false,
},
}

for _, tt := range tests {
Expand Down
8 changes: 8 additions & 0 deletions api/v1/config/crd/eno.azure.io_compositions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ spec:
compositionGeneration:
format: int64
type: integer
ignoreSideEffects:
type: boolean
key:
type: string
resourceVersion:
Expand Down Expand Up @@ -248,6 +250,8 @@ spec:
compositionGeneration:
format: int64
type: integer
ignoreSideEffects:
type: boolean
key:
type: string
resourceVersion:
Expand Down Expand Up @@ -323,6 +327,8 @@ spec:
compositionGeneration:
format: int64
type: integer
ignoreSideEffects:
type: boolean
key:
type: string
resourceVersion:
Expand Down Expand Up @@ -367,6 +373,8 @@ spec:
compositionGeneration:
format: int64
type: integer
ignoreSideEffects:
type: boolean
key:
type: string
resourceVersion:
Expand Down
4 changes: 4 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2175,6 +2175,7 @@ golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -2565,6 +2566,7 @@ golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
Expand Down Expand Up @@ -2891,6 +2893,7 @@ k8s.io/code-generator v0.33.1/go.mod h1:HUKT7Ubp6bOgIbbaPIs9lpd2Q02uqkMCMx9/GjDr
k8s.io/code-generator v0.33.2 h1:PCJ0Y6viTCxxJHMOyGqYwWEteM4q6y1Hqo2rNpl6jF4=
k8s.io/code-generator v0.33.2/go.mod h1:hBjCA9kPMpjLWwxcr75ReaQfFXY8u+9bEJJ7kRw3J8c=
k8s.io/code-generator v0.33.3/go.mod h1:6Y02+HQJYgNphv9z3wJB5w+sjYDIEBQW7sh62PkufvA=
k8s.io/code-generator v0.34.0 h1:Ze2i1QsvUprIlX3oHiGv09BFQRLCz+StA8qKwwFzees=
k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI=
k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM=
Expand Down Expand Up @@ -2925,6 +2928,7 @@ k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0
k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4=
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
Expand Down
162 changes: 162 additions & 0 deletions internal/controllers/reconciliation/crud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1072,3 +1072,165 @@ func TestResourceSelector(t *testing.T) {
})
assert.True(t, errors.IsNotFound(mgr.DownstreamClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "test-obj-1"}, &corev1.ConfigMap{})))
}

func TestIgnoreSideEffectsInputAnnotationOverrideFalse(t *testing.T) {
ctx := testutil.NewContext(t)
mgr := testutil.NewManager(t)
upstream := mgr.GetClient()

registerControllers(t, mgr)
testutil.WithFakeExecutor(t, mgr, func(ctx context.Context, s *apiv1.Synthesizer, input *krmv1.ResourceList) (*krmv1.ResourceList, error) {
output := &krmv1.ResourceList{}
output.Items = []*unstructured.Unstructured{{
Object: map[string]any{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]any{
"name": "test-obj",
"namespace": "default",
},
"data": map[string]any{"foo": "bar"},
},
}}
return output, nil
})

setupTestSubject(t, mgr)
mgr.Start(t)

input := &corev1.ConfigMap{}
input.Name = "test-input"
input.Namespace = "default"
input.Data = map[string]string{"replicas": "1"}
input.Annotations = map[string]string{"eno.azure.io/ignore-side-effects": "false"}
require.NoError(t, upstream.Create(ctx, input))

synth := &apiv1.Synthesizer{}
synth.Name = "test-syn"
synth.Spec.Image = "test-image"
synth.Spec.Refs = []apiv1.Ref{{
Key: "config",
Resource: apiv1.ResourceRef{
Version: "v1",
Kind: "ConfigMap",
},
}}
require.NoError(t, upstream.Create(ctx, synth))

comp := &apiv1.Composition{}
comp.Name = "test-comp"
comp.Namespace = "default"
comp.Annotations = map[string]string{"eno.azure.io/ignore-side-effects": "true"}
comp.Spec.Synthesizer.Name = synth.Name
comp.Spec.Bindings = []apiv1.Binding{{
Key: "config",
Resource: apiv1.ResourceBinding{
Name: input.Name,
Namespace: input.Namespace,
},
}}
require.NoError(t, upstream.Create(ctx, comp))

testutil.Eventually(t, func() bool {
upstream.Get(ctx, client.ObjectKeyFromObject(comp), comp)
return len(comp.Status.InputRevisions) == 1 &&
comp.Status.InputRevisions[0].IgnoreSideEffects != nil &&
*comp.Status.InputRevisions[0].IgnoreSideEffects == false
})

waitForReadiness(t, mgr, comp, synth, nil)

initialUUID := comp.Status.CurrentSynthesis.UUID

err := retry.RetryOnConflict(testutil.Backoff, func() error {
err := upstream.Get(ctx, client.ObjectKeyFromObject(input), input)
if err != nil {
return err
}
input.Data["replicas"] = "3"
return upstream.Update(ctx, input)
})
require.NoError(t, err)

testutil.Eventually(t, func() bool {
upstream.Get(ctx, client.ObjectKeyFromObject(comp), comp)
return comp.Status.CurrentSynthesis != nil && comp.Status.CurrentSynthesis.UUID != initialUUID
})
}

func TestIgnoreSideEffectsInputAnnotationOverrideTrue(t *testing.T) {
ctx := testutil.NewContext(t)
mgr := testutil.NewManager(t)
upstream := mgr.GetClient()

registerControllers(t, mgr)
testutil.WithFakeExecutor(t, mgr, func(ctx context.Context, s *apiv1.Synthesizer, input *krmv1.ResourceList) (*krmv1.ResourceList, error) {
output := &krmv1.ResourceList{}
output.Items = []*unstructured.Unstructured{{
Object: map[string]any{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]any{
"name": "test-obj",
"namespace": "default",
},
"data": map[string]any{"foo": "bar"},
},
}}
return output, nil
})

setupTestSubject(t, mgr)
mgr.Start(t)

input := &corev1.ConfigMap{}
input.Name = "test-input"
input.Namespace = "default"
input.Data = map[string]string{"replicas": "1"}
input.Annotations = map[string]string{"eno.azure.io/ignore-side-effects": "true"}
require.NoError(t, upstream.Create(ctx, input))

synth := &apiv1.Synthesizer{}
synth.Name = "test-syn"
synth.Spec.Image = "test-image"
synth.Spec.Refs = []apiv1.Ref{{
Key: "config",
Resource: apiv1.ResourceRef{
Version: "v1",
Kind: "ConfigMap",
},
}}
require.NoError(t, upstream.Create(ctx, synth))

comp := &apiv1.Composition{}
comp.Name = "test-comp"
comp.Namespace = "default"
comp.Spec.Synthesizer.Name = synth.Name
comp.Spec.Bindings = []apiv1.Binding{{
Key: "config",
Resource: apiv1.ResourceBinding{
Name: input.Name,
Namespace: input.Namespace,
},
}}
require.NoError(t, upstream.Create(ctx, comp))

waitForReadiness(t, mgr, comp, synth, nil)

initialUUID := comp.Status.CurrentSynthesis.UUID

err := retry.RetryOnConflict(testutil.Backoff, func() error {
err := upstream.Get(ctx, client.ObjectKeyFromObject(input), input)
if err != nil {
return err
}
input.Data["replicas"] = "3"
return upstream.Update(ctx, input)
})
require.NoError(t, err)

time.Sleep(time.Millisecond * 500)

require.NoError(t, upstream.Get(ctx, client.ObjectKeyFromObject(comp), comp))
assert.Equal(t, initialUUID, comp.Status.CurrentSynthesis.UUID)
}
Loading
Loading