From 7fbdc45c117c6ad3696e5ea7a0ab3989b513786b Mon Sep 17 00:00:00 2001 From: "Per G. da Silva" Date: Wed, 13 May 2026 14:37:37 +0200 Subject: [PATCH 1/6] Update boxcutter integration for sibling owners API Adapt the ClusterObjectSet controller to boxcutter's replacement of `WithPreviousOwners` with `WithSiblingOwners`. The new API expects all sibling revision owners (both previous and future) so that boxcutter can distinguish between unknown controllers (collisions) and known sibling revisions (not collisions) during revision handover. Changes: - Replace `WithPreviousOwners` with `WithSiblingOwners` in `buildBoxcutterPhases` - Add `listSiblingRevisions` that returns all active sibling revisions regardless of revision number, while keeping `listPreviousRevisions` for the archiving use case - Add `managedBy` parameter to `NewObjectEngine` call (new required param, passing "" to use the default "boxcutter" value) - Add unit tests for `listSiblingRevisions` Co-Authored-By: Claude Opus 4.6 (1M context) --- go.mod | 54 ++++---- go.sum | 99 +++++++-------- .../clusterobjectset_controller.go | 45 +++++-- ...usterobjectset_controller_internal_test.go | 119 ++++++++++++++++++ .../controllers/revision_engine_factory.go | 2 +- 5 files changed, 234 insertions(+), 85 deletions(-) diff --git a/go.mod b/go.mod index d19f394485..0cc090c5d2 100644 --- a/go.mod +++ b/go.mod @@ -35,13 +35,13 @@ require ( golang.org/x/sync v0.20.0 golang.org/x/tools v0.43.0 helm.sh/helm/v3 v3.20.2 - k8s.io/api v0.35.3 - k8s.io/apiextensions-apiserver v0.35.3 - k8s.io/apimachinery v0.35.3 - k8s.io/apiserver v0.35.3 + k8s.io/api v0.35.4 + k8s.io/apiextensions-apiserver v0.35.4 + k8s.io/apimachinery v0.35.4 + k8s.io/apiserver v0.35.4 k8s.io/cli-runtime v0.35.1 k8s.io/client-go v1.5.2 - k8s.io/component-base v0.35.3 + k8s.io/component-base v0.35.4 k8s.io/klog/v2 v2.140.0 k8s.io/kubernetes v1.35.0 k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 @@ -49,7 +49,7 @@ require ( sigs.k8s.io/controller-runtime v0.23.3 sigs.k8s.io/controller-tools v0.20.1 sigs.k8s.io/crdify v0.5.1-0.20260309184313-54162f2e3097 - sigs.k8s.io/structured-merge-diff/v6 v6.3.2 + sigs.k8s.io/structured-merge-diff/v6 v6.4.0 sigs.k8s.io/yaml v1.6.0 ) @@ -115,20 +115,20 @@ require ( github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.5 // indirect + github.com/go-openapi/jsonpointer v0.23.1 // indirect github.com/go-openapi/jsonreference v0.21.5 // indirect - github.com/go-openapi/swag v0.25.5 // indirect - github.com/go-openapi/swag/cmdutils v0.25.5 // indirect - github.com/go-openapi/swag/conv v0.25.5 // indirect - github.com/go-openapi/swag/fileutils v0.25.5 // indirect - github.com/go-openapi/swag/jsonname v0.25.5 // indirect - github.com/go-openapi/swag/jsonutils v0.25.5 // indirect - github.com/go-openapi/swag/loading v0.25.5 // indirect - github.com/go-openapi/swag/mangling v0.25.5 // indirect - github.com/go-openapi/swag/netutils v0.25.5 // indirect - github.com/go-openapi/swag/stringutils v0.25.5 // indirect - github.com/go-openapi/swag/typeutils v0.25.5 // indirect - github.com/go-openapi/swag/yamlutils v0.25.5 // indirect + github.com/go-openapi/swag v0.26.0 // indirect + github.com/go-openapi/swag/cmdutils v0.26.0 // indirect + github.com/go-openapi/swag/conv v0.26.0 // indirect + github.com/go-openapi/swag/fileutils v0.26.0 // indirect + github.com/go-openapi/swag/jsonname v0.26.0 // indirect + github.com/go-openapi/swag/jsonutils v0.26.0 // indirect + github.com/go-openapi/swag/loading v0.26.0 // indirect + github.com/go-openapi/swag/mangling v0.26.0 // indirect + github.com/go-openapi/swag/netutils v0.26.0 // indirect + github.com/go-openapi/swag/stringutils v0.26.0 // indirect + github.com/go-openapi/swag/typeutils v0.26.0 // indirect + github.com/go-openapi/swag/yamlutils v0.26.0 // indirect github.com/go-sql-driver/mysql v1.9.2 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/gobwas/glob v0.2.3 // indirect @@ -137,7 +137,7 @@ require ( github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.27.0 // indirect + github.com/google/cel-go v0.28.0 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect @@ -163,7 +163,7 @@ require ( github.com/lib/pq v1.11.1 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mattn/go-sqlite3 v1.14.37 // indirect github.com/miekg/pkcs11 v1.1.2 // indirect @@ -226,12 +226,12 @@ require ( go.podman.io/storage v1.62.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.49.0 // indirect - golang.org/x/net v0.52.0 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/term v0.41.0 // indirect - golang.org/x/text v0.35.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect + golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.15.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto v0.0.0-20260209200024-4cfbd4190f57 // indirect @@ -316,3 +316,5 @@ replace k8s.io/mount-utils => k8s.io/mount-utils v0.35.0 replace k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.35.0 replace k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.35.0 + +replace pkg.package-operator.run/boxcutter => /Users/pegoncal/repos/perdasilva/boxcutter diff --git a/go.sum b/go.sum index ec09743064..78e287b163 100644 --- a/go.sum +++ b/go.sum @@ -172,40 +172,40 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA= -github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0= +github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= +github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= -github.com/go-openapi/swag v0.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU= -github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA= -github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c= -github.com/go-openapi/swag/cmdutils v0.25.5/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= -github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g= -github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k= -github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk= -github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc= -github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo= -github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU= -github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo= -github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo= -github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU= -github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g= -github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw= -github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY= -github.com/go-openapi/swag/netutils v0.25.5 h1:LZq2Xc2QI8+7838elRAaPCeqJnHODfSyOa7ZGfxDKlU= -github.com/go-openapi/swag/netutils v0.25.5/go.mod h1:lHbtmj4m57APG/8H7ZcMMSWzNqIQcu0RFiXrPUara14= -github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M= -github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII= -github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E= -github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc= -github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ= -github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM= -github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM= -github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= +github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= +github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= +github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= +github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= +github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= +github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= +github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= +github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= +github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= +github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= +github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= +github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= +github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= +github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= +github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= +github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= +github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= +github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= +github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= +github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= +github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= +github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDNBjj74rBh0N2BGQbSR0= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE= +github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= +github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= @@ -245,8 +245,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= -github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= +github.com/google/cel-go v0.28.0 h1:KjSWstCpz/MN5t4a8gnGJNIYUsJRpdi/r97xWDphIQc= +github.com/google/cel-go v0.28.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -348,8 +348,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= @@ -595,8 +595,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20260209203927-2842357ff358 h1:kpfSV7uLwKJbFSEgNhWzGSL47NDSF/5pYYQw1V0ub6c= golang.org/x/exp v0.0.0-20260209203927-2842357ff358/go.mod h1:R3t0oliuryB5eenPWl3rrQxwnNM3WTwnsRZZiXLAAW8= @@ -628,8 +628,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= @@ -657,14 +657,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -674,8 +673,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -685,8 +684,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -796,8 +795,6 @@ k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbe k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= -pkg.package-operator.run/boxcutter v0.13.1 h1:FvrSBnHWuf6Co+HPyxRSw2Y5mHSkUtteW8klXS6L8gk= -pkg.package-operator.run/boxcutter v0.13.1/go.mod h1:rR2jd32uNt2eml7UvlYNCpCAYIpTALDzHxaftMfgkZo= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= @@ -816,7 +813,7 @@ sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7 sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= +sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/operator-controller/controllers/clusterobjectset_controller.go b/internal/operator-controller/controllers/clusterobjectset_controller.go index 2d81b71c92..1429edfeb0 100644 --- a/internal/operator-controller/controllers/clusterobjectset_controller.go +++ b/internal/operator-controller/controllers/clusterobjectset_controller.go @@ -441,6 +441,38 @@ func (c *ClusterObjectSetReconciler) removeFinalizer(ctx context.Context, obj cl return nil } +// listSiblingRevisions returns all active revisions belonging to the same ClusterExtension, excluding the current one. +// This includes both lower and higher revision numbers, enabling boxcutter to properly classify +// sibling owners and avoid reporting false collisions during revision handover. +func (c *ClusterObjectSetReconciler) listSiblingRevisions(ctx context.Context, cos *ocv1.ClusterObjectSet) ([]*ocv1.ClusterObjectSet, error) { + ownerLabel, ok := cos.Labels[labels.OwnerNameKey] + if !ok { + return nil, nil + } + + revList := &ocv1.ClusterObjectSetList{} + if err := c.TrackingCache.List(ctx, revList, client.MatchingLabels{ + labels.OwnerNameKey: ownerLabel, + }); err != nil { + return nil, fmt.Errorf("listing revisions: %w", err) + } + + siblings := make([]*ocv1.ClusterObjectSet, 0, len(revList.Items)) + for i := range revList.Items { + r := &revList.Items[i] + if r.Name == cos.Name { + continue + } + if r.Spec.LifecycleState == ocv1.ClusterObjectSetLifecycleStateArchived || + !r.DeletionTimestamp.IsZero() { + continue + } + siblings = append(siblings, r) + } + + return siblings, nil +} + // listPreviousRevisions returns active revisions belonging to the same ClusterExtension with lower revision numbers. // Filters out the current revision, archived revisions, deleting revisions, and revisions with equal or higher numbers. func (c *ClusterObjectSetReconciler) listPreviousRevisions(ctx context.Context, cos *ocv1.ClusterObjectSet) ([]*ocv1.ClusterObjectSet, error) { @@ -479,15 +511,14 @@ func (c *ClusterObjectSetReconciler) listPreviousRevisions(ctx context.Context, } func (c *ClusterObjectSetReconciler) buildBoxcutterPhases(ctx context.Context, cos *ocv1.ClusterObjectSet) ([]boxcutter.Phase, []ocv1.ObservedPhase, []boxcutter.RevisionReconcileOption, error) { - previous, err := c.listPreviousRevisions(ctx, cos) + siblings, err := c.listSiblingRevisions(ctx, cos) if err != nil { - return nil, nil, nil, fmt.Errorf("listing previous revisions: %w", err) + return nil, nil, nil, fmt.Errorf("listing sibling revisions: %w", err) } - // Convert to []client.Object for boxcutter - previousObjs := make([]client.Object, len(previous)) - for i, rev := range previous { - previousObjs[i] = rev + siblingObjs := make([]client.Object, len(siblings)) + for i, rev := range siblings { + siblingObjs[i] = rev } progressionProbes, err := buildProgressionProbes(cos.Spec.ProgressionProbes) @@ -496,7 +527,7 @@ func (c *ClusterObjectSetReconciler) buildBoxcutterPhases(ctx context.Context, c } opts := []boxcutter.RevisionReconcileOption{ - boxcutter.WithPreviousOwners(previousObjs), + boxcutter.WithSiblingOwners(siblingObjs), boxcutter.WithProbe(boxcutter.ProgressProbeType, progressionProbes), boxcutter.WithAggregatePhaseReconcileErrors(), } diff --git a/internal/operator-controller/controllers/clusterobjectset_controller_internal_test.go b/internal/operator-controller/controllers/clusterobjectset_controller_internal_test.go index 654de33883..bb994c2909 100644 --- a/internal/operator-controller/controllers/clusterobjectset_controller_internal_test.go +++ b/internal/operator-controller/controllers/clusterobjectset_controller_internal_test.go @@ -30,6 +30,125 @@ import ( "github.com/operator-framework/operator-controller/internal/operator-controller/labels" ) +func Test_ClusterObjectSetReconciler_listSiblingRevisions(t *testing.T) { + testScheme := runtime.NewScheme() + require.NoError(t, ocv1.AddToScheme(testScheme)) + + for _, tc := range []struct { + name string + existingObjs func() []client.Object + currentRev string + expectedRevs []string + }{ + { + name: "should return both lower and higher revision numbers", + existingObjs: func() []client.Object { + ext := newTestClusterExtensionInternal() + rev1 := newTestClusterObjectSetInternal(t, "rev-1") + rev2 := newTestClusterObjectSetInternal(t, "rev-2") + rev3 := newTestClusterObjectSetInternal(t, "rev-3") + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + require.NoError(t, controllerutil.SetControllerReference(ext, rev2, testScheme)) + require.NoError(t, controllerutil.SetControllerReference(ext, rev3, testScheme)) + return []client.Object{ext, rev1, rev2, rev3} + }, + currentRev: "rev-2", + expectedRevs: []string{"rev-1", "rev-3"}, + }, + { + name: "should exclude archived revisions", + existingObjs: func() []client.Object { + ext := newTestClusterExtensionInternal() + rev1 := newTestClusterObjectSetInternal(t, "rev-1") + rev2 := newTestClusterObjectSetInternal(t, "rev-2") + rev2.Spec.LifecycleState = ocv1.ClusterObjectSetLifecycleStateArchived + rev3 := newTestClusterObjectSetInternal(t, "rev-3") + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + require.NoError(t, controllerutil.SetControllerReference(ext, rev2, testScheme)) + require.NoError(t, controllerutil.SetControllerReference(ext, rev3, testScheme)) + return []client.Object{ext, rev1, rev2, rev3} + }, + currentRev: "rev-3", + expectedRevs: []string{"rev-1"}, + }, + { + name: "should exclude deleting revisions", + existingObjs: func() []client.Object { + ext := newTestClusterExtensionInternal() + rev1 := newTestClusterObjectSetInternal(t, "rev-1") + rev1.Finalizers = []string{"test-finalizer"} + rev1.DeletionTimestamp = &metav1.Time{Time: time.Now()} + rev2 := newTestClusterObjectSetInternal(t, "rev-2") + rev3 := newTestClusterObjectSetInternal(t, "rev-3") + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + require.NoError(t, controllerutil.SetControllerReference(ext, rev2, testScheme)) + require.NoError(t, controllerutil.SetControllerReference(ext, rev3, testScheme)) + return []client.Object{ext, rev1, rev2, rev3} + }, + currentRev: "rev-2", + expectedRevs: []string{"rev-3"}, + }, + { + name: "should only include revisions matching owner label", + existingObjs: func() []client.Object { + ext := newTestClusterExtensionInternal() + ext2 := newTestClusterExtensionInternal() + ext2.Name = "test-ext-2" + ext2.UID = "test-ext-2" + + rev1 := newTestClusterObjectSetInternal(t, "rev-1") + rev2 := newTestClusterObjectSetInternal(t, "rev-2") + rev2.Labels[labels.OwnerNameKey] = "test-ext-2" + rev3 := newTestClusterObjectSetInternal(t, "rev-3") + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + require.NoError(t, controllerutil.SetControllerReference(ext2, rev2, testScheme)) + require.NoError(t, controllerutil.SetControllerReference(ext, rev3, testScheme)) + return []client.Object{ext, ext2, rev1, rev2, rev3} + }, + currentRev: "rev-1", + expectedRevs: []string{"rev-3"}, + }, + { + name: "should return empty list when owner label missing", + existingObjs: func() []client.Object { + ext := newTestClusterExtensionInternal() + rev1 := newTestClusterObjectSetInternal(t, "rev-1") + delete(rev1.Labels, labels.OwnerNameKey) + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + return []client.Object{ext, rev1} + }, + currentRev: "rev-1", + expectedRevs: []string{}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + testClient := fake.NewClientBuilder(). + WithScheme(testScheme). + WithObjects(tc.existingObjs()...). + Build() + + reconciler := &ClusterObjectSetReconciler{ + Client: testClient, + TrackingCache: &mockTrackingCacheInternal{client: testClient}, + } + + currentRev := &ocv1.ClusterObjectSet{} + err := testClient.Get(t.Context(), client.ObjectKey{Name: tc.currentRev}, currentRev) + require.NoError(t, err) + + siblings, err := reconciler.listSiblingRevisions(t.Context(), currentRev) + require.NoError(t, err) + + names := make([]string, 0, len(siblings)) + for _, rev := range siblings { + names = append(names, rev.GetName()) + } + + require.ElementsMatch(t, tc.expectedRevs, names) + }) + } +} + func Test_ClusterObjectSetReconciler_listPreviousRevisions(t *testing.T) { testScheme := runtime.NewScheme() require.NoError(t, ocv1.AddToScheme(testScheme)) diff --git a/internal/operator-controller/controllers/revision_engine_factory.go b/internal/operator-controller/controllers/revision_engine_factory.go index 9311e8641f..8c78e276cb 100644 --- a/internal/operator-controller/controllers/revision_engine_factory.go +++ b/internal/operator-controller/controllers/revision_engine_factory.go @@ -69,7 +69,7 @@ func (f *defaultRevisionEngineFactory) CreateRevisionEngine(_ context.Context, r machinery.NewObjectEngine( f.Scheme, f.TrackingCache, scopedClient, machinery.NewComparator(f.DiscoveryClient, f.Scheme, f.FieldOwnerPrefix), - f.FieldOwnerPrefix, f.FieldOwnerPrefix, scopedClient, + f.FieldOwnerPrefix, f.FieldOwnerPrefix, "", scopedClient, ), validation.NewClusterPhaseValidator(f.RESTMapper, scopedClient), ), From 69ec582dcbe4267d190b354666d37724cb725063 Mon Sep 17 00:00:00 2001 From: "Per G. da Silva" Date: Wed, 13 May 2026 14:40:30 +0200 Subject: [PATCH 2/6] Add e2e test: collision persists after upgrading conflicting extension Tests that when two ClusterExtensions install the same package, the collision is detected at install time and continues to be reported after the conflicting extension is upgraded to a different version. Co-Authored-By: Claude Opus 4.6 (1M context) --- test/e2e/features/update.feature | 53 ++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/test/e2e/features/update.feature b/test/e2e/features/update.feature index ab9408365e..87e750a1c4 100644 --- a/test/e2e/features/update.feature +++ b/test/e2e/features/update.feature @@ -262,6 +262,59 @@ Feature: Update ClusterExtension revision object collisions """ + @BoxcutterRuntime + Scenario: Collision persists when conflicting ClusterExtension is upgraded to a different version + Given ClusterExtension is applied + """ + apiVersion: olm.operatorframework.io/v1 + kind: ClusterExtension + metadata: + name: ${NAME} + spec: + namespace: ${TEST_NAMESPACE} + serviceAccount: + name: olm-sa + source: + sourceType: Catalog + catalog: + packageName: test + selector: + matchLabels: + "olm.operatorframework.io/metadata.name": test-catalog + version: 1.0.0 + """ + And ClusterExtension is rolled out + And ClusterExtension is available + And the current ClusterExtension is tracked for cleanup + When ClusterExtension is applied + """ + apiVersion: olm.operatorframework.io/v1 + kind: ClusterExtension + metadata: + name: ${NAME}-dup + spec: + namespace: ${TEST_NAMESPACE} + serviceAccount: + name: olm-sa + source: + sourceType: Catalog + catalog: + packageName: test + selector: + matchLabels: + "olm.operatorframework.io/metadata.name": test-catalog + version: 1.0.0 + """ + Then ClusterExtension reports Progressing as True with Reason Retrying and Message includes: + """ + revision object collisions + """ + When ClusterExtension is updated to version "1.0.1" + Then ClusterExtension reports Progressing as True with Reason Retrying and Message includes: + """ + revision object collisions + """ + @BoxcutterRuntime Scenario: Each update creates a new revision and resources not present in the new revision are removed from the cluster Given ClusterExtension is applied From c05dc1d4cf5c1ba70e8377eb1cdbf8814cca748a Mon Sep 17 00:00:00 2001 From: "Per G. da Silva" Date: Wed, 13 May 2026 14:59:02 +0200 Subject: [PATCH 3/6] Strengthen collision persistence test with revision assertions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assert that upgrading the conflicting extension creates a new revision (${NAME}-dup-2), that revision also reports collisions, and the original extension's revision (${NAME}-1) remains Succeeded/Available — proving the higher revision number doesn't cause object takeover. Co-Authored-By: Claude Opus 4.6 (1M context) --- test/e2e/features/update.feature | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/e2e/features/update.feature b/test/e2e/features/update.feature index 87e750a1c4..1310926c39 100644 --- a/test/e2e/features/update.feature +++ b/test/e2e/features/update.feature @@ -310,10 +310,17 @@ Feature: Update ClusterExtension revision object collisions """ When ClusterExtension is updated to version "1.0.1" - Then ClusterExtension reports Progressing as True with Reason Retrying and Message includes: + Then ClusterExtension reports "${NAME}-dup-1, ${NAME}-dup-2" as active revisions + And ClusterExtension reports Progressing as True with Reason Retrying and Message includes: + """ + revision object collisions + """ + And ClusterObjectSet "${NAME}-dup-2" reports Progressing as True with Reason Retrying and Message includes: """ revision object collisions """ + And ClusterObjectSet "${NAME}-1" reports Progressing as True with Reason Succeeded + And ClusterObjectSet "${NAME}-1" reports Available as True with Reason ProbesSucceeded @BoxcutterRuntime Scenario: Each update creates a new revision and resources not present in the new revision are removed from the cluster From eb611bc8947d7c50875261426dbe370177155c38 Mon Sep 17 00:00:00 2001 From: "Per G. da Silva" Date: Wed, 13 May 2026 15:19:23 +0200 Subject: [PATCH 4/6] Fix collision persistence test: remove active revisions assertion The active revisions assertion timed out because the first dup revision gets archived when the second is created during upgrade, so both dup-1 and dup-2 are not simultaneously active. Keep the core assertions: collision persists after upgrade and original extension's COS stays Succeeded/Available. Co-Authored-By: Claude Opus 4.6 (1M context) --- test/e2e/features/update.feature | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/e2e/features/update.feature b/test/e2e/features/update.feature index 1310926c39..fa7a3e36ba 100644 --- a/test/e2e/features/update.feature +++ b/test/e2e/features/update.feature @@ -310,12 +310,7 @@ Feature: Update ClusterExtension revision object collisions """ When ClusterExtension is updated to version "1.0.1" - Then ClusterExtension reports "${NAME}-dup-1, ${NAME}-dup-2" as active revisions - And ClusterExtension reports Progressing as True with Reason Retrying and Message includes: - """ - revision object collisions - """ - And ClusterObjectSet "${NAME}-dup-2" reports Progressing as True with Reason Retrying and Message includes: + Then ClusterExtension reports Progressing as True with Reason Retrying and Message includes: """ revision object collisions """ From f29f2e5003d9a253da34a7f69e1a0259b34f5a5c Mon Sep 17 00:00:00 2001 From: "Per G. da Silva" Date: Wed, 13 May 2026 15:39:24 +0200 Subject: [PATCH 5/6] Remove COS ownership assertions from collision persistence test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ${NAME} variable resolves to the dup extension name after the second apply, so ${NAME}-1 incorrectly references the dup's COS rather than the original's. The collision message itself proves objects weren't taken over — if they had been adopted, boxcutter would not report a collision. Co-Authored-By: Claude Opus 4.6 (1M context) --- test/e2e/features/update.feature | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/e2e/features/update.feature b/test/e2e/features/update.feature index fa7a3e36ba..87e750a1c4 100644 --- a/test/e2e/features/update.feature +++ b/test/e2e/features/update.feature @@ -314,8 +314,6 @@ Feature: Update ClusterExtension """ revision object collisions """ - And ClusterObjectSet "${NAME}-1" reports Progressing as True with Reason Succeeded - And ClusterObjectSet "${NAME}-1" reports Available as True with Reason ProbesSucceeded @BoxcutterRuntime Scenario: Each update creates a new revision and resources not present in the new revision are removed from the cluster From 59574c0a08aafda41dae3c9f126fd5b83e2e720f Mon Sep 17 00:00:00 2001 From: "Per G. da Silva" Date: Wed, 13 May 2026 16:03:48 +0200 Subject: [PATCH 6/6] Assert new COS revision created and colliding after upgrade Check that ${NAME}-2 (the dup's second ClusterObjectSet) exists and reports collision after upgrading the conflicting extension to v1.0.1. This proves the upgrade created a new revision and that the higher revision number doesn't cause object takeover. Co-Authored-By: Claude Opus 4.6 (1M context) --- test/e2e/features/update.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/features/update.feature b/test/e2e/features/update.feature index 87e750a1c4..5a851571fd 100644 --- a/test/e2e/features/update.feature +++ b/test/e2e/features/update.feature @@ -310,7 +310,7 @@ Feature: Update ClusterExtension revision object collisions """ When ClusterExtension is updated to version "1.0.1" - Then ClusterExtension reports Progressing as True with Reason Retrying and Message includes: + Then ClusterObjectSet "${NAME}-2" reports Progressing as True with Reason Retrying and Message includes: """ revision object collisions """