-
Notifications
You must be signed in to change notification settings - Fork 326
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Net-5510][Net-5455]: CRD controller should only patch the finalizer …
…in the metadata of a CRD, rather than the whole object (#3362) Finalizer patcher Co-authored-by: Derek Menteer <[email protected]>
- Loading branch information
1 parent
8716b2f
commit 3047e16
Showing
17 changed files
with
235 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:bug-fix | ||
crd-controllers: When the CRD controller reconciles a config entry CRD, only patch finalizers in the metadata of the CRD, rather than updating the entire entry, which causes changes to the CRD spec (such as adding in unspecified zero values) when using a Kubernetes client such as kubectl with `replace` mode. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
control-plane/config-entries/controllers/finalizer_patch.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package controllers | ||
|
||
import ( | ||
"encoding/json" | ||
|
||
jsonpatch "github.com/evanphx/json-patch" | ||
"k8s.io/apimachinery/pkg/types" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
type FinalizerPatcher struct{} | ||
|
||
type FinalizerPatch struct { | ||
NewFinalizers []string | ||
} | ||
|
||
// Type implements client.Patch. Since this patch is used for a custom CRD, Kubernetes does not allow the more advanced | ||
// StrategicMergePatch. Therefore, this patcher will replace the entire list of finalizers with the new list, rather | ||
// than adding/removing individual entries. | ||
// | ||
// This can result in a small race condition where we could overwrite recently modified finalizers (either modified by a | ||
// user or another controller process). Before the addition of this finalizer patcher implementation, this race | ||
// condition still existed, but applied to the entirety of the CRD because we used to update the entire CRD rather than | ||
// just the finalizer, so this reduces the surface area of the race condition. Generally we should not expect users or | ||
// other controllers to be touching the finalizers of consul-k8s managed CRDs. | ||
func (fp *FinalizerPatch) Type() types.PatchType { | ||
return types.MergePatchType | ||
} | ||
|
||
var _ client.Patch = (*FinalizerPatch)(nil) | ||
|
||
func (f *FinalizerPatcher) AddFinalizersPatch(oldObj client.Object, addFinalizers ...string) *FinalizerPatch { | ||
output := make([]string, 0, len(addFinalizers)) | ||
existing := make(map[string]bool) | ||
for _, f := range oldObj.GetFinalizers() { | ||
existing[f] = true | ||
output = append(output, f) | ||
} | ||
for _, f := range addFinalizers { | ||
if !existing[f] { | ||
output = append(output, f) | ||
} | ||
} | ||
return &FinalizerPatch{ | ||
NewFinalizers: output, | ||
} | ||
} | ||
|
||
func (f *FinalizerPatcher) RemoveFinalizersPatch(oldObj client.Object, removeFinalizers ...string) *FinalizerPatch { | ||
output := make([]string, 0) | ||
remove := make(map[string]bool) | ||
for _, f := range removeFinalizers { | ||
remove[f] = true | ||
} | ||
for _, f := range oldObj.GetFinalizers() { | ||
if !remove[f] { | ||
output = append(output, f) | ||
} | ||
} | ||
return &FinalizerPatch{ | ||
NewFinalizers: output, | ||
} | ||
} | ||
|
||
// Data implements client.Patch. | ||
func (fp *FinalizerPatch) Data(obj client.Object) ([]byte, error) { | ||
newData, err := json.Marshal(map[string]any{ | ||
"metadata": map[string]any{ | ||
"finalizers": fp.NewFinalizers, | ||
}, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
p, err := jsonpatch.CreateMergePatch([]byte(`{}`), newData) | ||
return p, err | ||
} |
97 changes: 97 additions & 0 deletions
97
control-plane/config-entries/controllers/finalizer_patch_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package controllers | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
"github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" | ||
) | ||
|
||
func TestFinalizersPatcher(t *testing.T) { | ||
cases := []struct { | ||
name string | ||
oldObject client.Object | ||
addFinalizers []string | ||
removeFinalizers []string | ||
expectedFinalizerPatch *FinalizerPatch | ||
op string | ||
}{ | ||
{ | ||
name: "adds finalizers at the end and keeps the original list in order", | ||
oldObject: &v1alpha1.ServiceResolver{ | ||
ObjectMeta: v1.ObjectMeta{ | ||
Finalizers: []string{ | ||
"a", | ||
"b", | ||
"c", | ||
}, | ||
}, | ||
}, | ||
addFinalizers: []string{"d", "e"}, | ||
expectedFinalizerPatch: &FinalizerPatch{ | ||
NewFinalizers: []string{"a", "b", "c", "d", "e"}, | ||
}, | ||
}, | ||
{ | ||
name: "adds finalizers when original list is empty", | ||
oldObject: &v1alpha1.ServiceResolver{ | ||
ObjectMeta: v1.ObjectMeta{ | ||
Finalizers: []string{}, | ||
}, | ||
}, | ||
addFinalizers: []string{"d", "e"}, | ||
expectedFinalizerPatch: &FinalizerPatch{ | ||
NewFinalizers: []string{"d", "e"}, | ||
}, | ||
}, | ||
{ | ||
name: "removes finalizers keeping the original list in order", | ||
oldObject: &v1alpha1.ServiceResolver{ | ||
ObjectMeta: v1.ObjectMeta{ | ||
Finalizers: []string{ | ||
"a", | ||
"b", | ||
"c", | ||
"d", | ||
}, | ||
}, | ||
}, | ||
removeFinalizers: []string{"b"}, | ||
expectedFinalizerPatch: &FinalizerPatch{ | ||
NewFinalizers: []string{"a", "c", "d"}, | ||
}, | ||
}, | ||
{ | ||
name: "removes all finalizers if specified", | ||
oldObject: &v1alpha1.ServiceResolver{ | ||
ObjectMeta: v1.ObjectMeta{ | ||
Finalizers: []string{ | ||
"a", | ||
"b", | ||
}, | ||
}, | ||
}, | ||
removeFinalizers: []string{"a", "b"}, | ||
expectedFinalizerPatch: &FinalizerPatch{ | ||
NewFinalizers: []string{}, | ||
}, | ||
}, | ||
} | ||
for _, c := range cases { | ||
t.Run(c.name, func(t *testing.T) { | ||
f := FinalizerPatcher{} | ||
var patch *FinalizerPatch | ||
|
||
if len(c.addFinalizers) > 0 { | ||
patch = f.AddFinalizersPatch(c.oldObject, c.addFinalizers...) | ||
} else if len(c.removeFinalizers) > 0 { | ||
patch = f.RemoveFinalizersPatch(c.oldObject, c.removeFinalizers...) | ||
} | ||
|
||
require.Equal(t, c.expectedFinalizerPatch, patch) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.