honor dry-run when pruning removed resources

Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com>
pull/31616/head
MrJack 3 weeks ago
parent 1b39a855c3
commit 84b1ec1cd4

@ -572,7 +572,7 @@ func (c *Client) BuildTable(reader io.Reader, validate bool) (ResourceList, erro
transformRequests) transformRequests)
} }
func (c *Client) update(originals, targets ResourceList, createApplyFunc CreateApplyFunc, updateApplyFunc UpdateApplyFunc) (*Result, error) { func (c *Client) update(originals, targets ResourceList, createApplyFunc CreateApplyFunc, updateApplyFunc UpdateApplyFunc, dryRun bool) (*Result, error) {
updateErrors := []error{} updateErrors := []error{}
res := &Result{} res := &Result{}
@ -655,7 +655,7 @@ func (c *Client) update(originals, targets ResourceList, createApplyFunc CreateA
} }
for _, info := range originals.Difference(targets) { for _, info := range originals.Difference(targets) {
c.Logger().Debug("deleting resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind) c.Logger().Debug("deleting resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, "dryRun", dryRun)
if err := info.Get(); err != nil { if err := info.Get(); err != nil {
c.Logger().Debug( c.Logger().Debug(
@ -681,7 +681,7 @@ func (c *Client) update(originals, targets ResourceList, createApplyFunc CreateA
c.Logger().Debug("skipping delete due to annotation", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, "annotation", ResourcePolicyAnno, "value", KeepPolicy) c.Logger().Debug("skipping delete due to annotation", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, "annotation", ResourcePolicyAnno, "value", KeepPolicy)
continue continue
} }
if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil { if err := deleteResource(info, metav1.DeletePropagationBackground, dryRun); err != nil {
c.Logger().Debug( c.Logger().Debug(
"failed to delete resource", "failed to delete resource",
slog.String("namespace", info.Namespace), slog.String("namespace", info.Namespace),
@ -902,7 +902,7 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate
} }
} }
return c.update(originals, targets, createApplyFunc, makeUpdateApplyFunc()) return c.update(originals, targets, createApplyFunc, makeUpdateApplyFunc(), updateOptions.dryRun)
} }
// Delete deletes Kubernetes resources specified in the resources list with // Delete deletes Kubernetes resources specified in the resources list with
@ -915,7 +915,7 @@ func (c *Client) Delete(resources ResourceList, policy metav1.DeletionPropagatio
mtx := sync.Mutex{} mtx := sync.Mutex{}
err := perform(resources, func(target *resource.Info) error { err := perform(resources, func(target *resource.Info) error {
c.Logger().Debug("starting delete resource", "namespace", target.Namespace, "name", target.Name, "kind", target.Mapping.GroupVersionKind.Kind) c.Logger().Debug("starting delete resource", "namespace", target.Namespace, "name", target.Name, "kind", target.Mapping.GroupVersionKind.Kind)
err := deleteResource(target, policy) err := deleteResource(target, policy, false)
if err == nil || apierrors.IsNotFound(err) { if err == nil || apierrors.IsNotFound(err) {
if err != nil { if err != nil {
c.Logger().Debug( c.Logger().Debug(
@ -1062,11 +1062,14 @@ func createResource(info *resource.Info) error {
}) })
} }
func deleteResource(info *resource.Info, policy metav1.DeletionPropagation) error { func deleteResource(info *resource.Info, policy metav1.DeletionPropagation, dryRun bool) error {
return retry.RetryOnConflict( return retry.RetryOnConflict(
retry.DefaultRetry, retry.DefaultRetry,
func() error { func() error {
opts := &metav1.DeleteOptions{PropagationPolicy: &policy} opts := &metav1.DeleteOptions{PropagationPolicy: &policy}
if dryRun {
opts.DryRun = []string{metav1.DryRunAll}
}
_, err := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()).DeleteWithOptions(info.Namespace, info.Name, opts) _, err := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()).DeleteWithOptions(info.Namespace, info.Name, opts)
return err return err
}) })

@ -19,6 +19,7 @@ package kube
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/json"
"errors" "errors"
"io" "io"
"net/http" "net/http"
@ -593,6 +594,87 @@ func TestUpdate(t *testing.T) {
} }
} }
// TestUpdateDryRunDelete verifies that the dry-run option is propagated to the
// deletions Update performs when a resource present in the original release is
// absent from the target. Without this, a server-side dry-run upgrade would
// really remove pruned resources from the cluster.
func TestUpdateDryRunDelete(t *testing.T) {
for name, dryRun := range map[string]bool{"dry run": true, "normal": false} {
t.Run(name, func(t *testing.T) {
listOriginal := newPodList("starfish", "squid")
listTarget := newPodList("starfish")
expectedDryRun := ""
var expectedDeleteDryRun []string
if dryRun {
expectedDryRun = "All"
expectedDeleteDryRun = []string{metav1.DryRunAll}
}
c := newTestClient(t)
cb := func(_ []RequestResponseAction, req *http.Request) (*http.Response, error) {
p, m := req.URL.Path, req.Method
switch {
case p == "/namespaces/default/pods/starfish" && m == http.MethodGet:
return newResponse(http.StatusOK, &listOriginal.Items[0])
case p == "/namespaces/default/pods/starfish" && m == http.MethodPatch:
assert.Equal(t, expectedDryRun, req.URL.Query().Get("dryRun"))
return newResponse(http.StatusOK, &listTarget.Items[0])
case p == "/namespaces/default/pods/squid" && m == http.MethodGet:
return newResponse(http.StatusOK, &listOriginal.Items[1])
case p == "/namespaces/default/pods/squid" && m == http.MethodDelete:
// DeleteOptions are sent in the request body, not the query string
defer req.Body.Close()
data, err := io.ReadAll(req.Body)
require.NoError(t, err)
opts := metav1.DeleteOptions{}
require.NoError(t, json.Unmarshal(data, &opts))
assert.Equal(t, expectedDeleteDryRun, opts.DryRun)
return newResponse(http.StatusOK, &listOriginal.Items[1])
}
t.FailNow()
return nil, nil
}
client := NewRequestResponseLogClient(t, cb)
c.Factory.(*cmdtesting.TestFactory).UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(client.Do),
}
first, err := c.Build(objBody(&listOriginal), false)
require.NoError(t, err)
second, err := c.Build(objBody(&listTarget), false)
require.NoError(t, err)
result, err := c.Update(
first,
second,
ClientUpdateOptionServerSideApply(true, false),
ClientUpdateOptionDryRun(dryRun))
require.NoError(t, err)
assert.Len(t, result.Deleted, 1, "expected 1 resource deleted")
actions := []string{}
for _, action := range client.Actions {
actions = append(actions, action.Request.URL.Path+":"+action.Request.Method)
}
assert.Equal(t, []string{
"/namespaces/default/pods/starfish:GET",
"/namespaces/default/pods/starfish:PATCH",
"/namespaces/default/pods/squid:GET",
"/namespaces/default/pods/squid:DELETE",
}, actions)
})
}
}
func TestBuild(t *testing.T) { func TestBuild(t *testing.T) {
tests := []struct { tests := []struct {
name string name string

Loading…
Cancel
Save