Merge pull request #31393 from benoittgt/12299

Return errors during upgrade when the deletion of resources fails
pull/31412/head
Matt Farina 11 months ago committed by GitHub
commit 752354074c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -569,10 +569,17 @@ func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateA
} }
if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil { if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil {
slog.Debug("failed to delete resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) slog.Debug("failed to delete resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err))
if !apierrors.IsNotFound(err) {
updateErrors = append(updateErrors, fmt.Errorf("failed to delete resource %s: %w", info.Name, err))
}
continue continue
} }
res.Deleted = append(res.Deleted, info) res.Deleted = append(res.Deleted, info)
} }
if len(updateErrors) != 0 {
return res, joinErrors(updateErrors, " && ")
}
return res, nil return res, nil
} }

@ -321,6 +321,7 @@ func TestUpdate(t *testing.T) {
ThreeWayMergeForUnstructured bool ThreeWayMergeForUnstructured bool
ServerSideApply bool ServerSideApply bool
ExpectedActions []string ExpectedActions []string
ExpectedError string
} }
expectedActionsClientSideApply := []string{ expectedActionsClientSideApply := []string{
@ -336,6 +337,8 @@ func TestUpdate(t *testing.T) {
"/namespaces/default/pods:POST", // retry due to 409 "/namespaces/default/pods:POST", // retry due to 409
"/namespaces/default/pods/squid:GET", "/namespaces/default/pods/squid:GET",
"/namespaces/default/pods/squid:DELETE", "/namespaces/default/pods/squid:DELETE",
"/namespaces/default/pods/notfound:GET",
"/namespaces/default/pods/notfound:DELETE",
} }
expectedActionsServerSideApply := []string{ expectedActionsServerSideApply := []string{
@ -351,11 +354,13 @@ func TestUpdate(t *testing.T) {
"/namespaces/default/pods:POST", // retry due to 409 "/namespaces/default/pods:POST", // retry due to 409
"/namespaces/default/pods/squid:GET", "/namespaces/default/pods/squid:GET",
"/namespaces/default/pods/squid:DELETE", "/namespaces/default/pods/squid:DELETE",
"/namespaces/default/pods/notfound:GET",
"/namespaces/default/pods/notfound:DELETE",
} }
testCases := map[string]testCase{ testCases := map[string]testCase{
"client-side apply": { "client-side apply": {
OriginalPods: newPodList("starfish", "otter", "squid"), OriginalPods: newPodList("starfish", "otter", "squid", "notfound"),
TargetPods: func() v1.PodList { TargetPods: func() v1.PodList {
listTarget := newPodList("starfish", "otter", "dolphin") listTarget := newPodList("starfish", "otter", "dolphin")
listTarget.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}} listTarget.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}}
@ -365,9 +370,10 @@ func TestUpdate(t *testing.T) {
ThreeWayMergeForUnstructured: false, ThreeWayMergeForUnstructured: false,
ServerSideApply: false, ServerSideApply: false,
ExpectedActions: expectedActionsClientSideApply, ExpectedActions: expectedActionsClientSideApply,
ExpectedError: "",
}, },
"client-side apply (three-way merge for unstructured)": { "client-side apply (three-way merge for unstructured)": {
OriginalPods: newPodList("starfish", "otter", "squid"), OriginalPods: newPodList("starfish", "otter", "squid", "notfound"),
TargetPods: func() v1.PodList { TargetPods: func() v1.PodList {
listTarget := newPodList("starfish", "otter", "dolphin") listTarget := newPodList("starfish", "otter", "dolphin")
listTarget.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}} listTarget.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}}
@ -377,9 +383,10 @@ func TestUpdate(t *testing.T) {
ThreeWayMergeForUnstructured: true, ThreeWayMergeForUnstructured: true,
ServerSideApply: false, ServerSideApply: false,
ExpectedActions: expectedActionsClientSideApply, ExpectedActions: expectedActionsClientSideApply,
ExpectedError: "",
}, },
"serverSideApply": { "serverSideApply": {
OriginalPods: newPodList("starfish", "otter", "squid"), OriginalPods: newPodList("starfish", "otter", "squid", "notfound"),
TargetPods: func() v1.PodList { TargetPods: func() v1.PodList {
listTarget := newPodList("starfish", "otter", "dolphin") listTarget := newPodList("starfish", "otter", "dolphin")
listTarget.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}} listTarget.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}}
@ -389,6 +396,23 @@ func TestUpdate(t *testing.T) {
ThreeWayMergeForUnstructured: false, ThreeWayMergeForUnstructured: false,
ServerSideApply: true, ServerSideApply: true,
ExpectedActions: expectedActionsServerSideApply, ExpectedActions: expectedActionsServerSideApply,
ExpectedError: "",
},
"serverSideApply with forbidden deletion": {
OriginalPods: newPodList("starfish", "otter", "squid", "notfound", "forbidden"),
TargetPods: func() v1.PodList {
listTarget := newPodList("starfish", "otter", "dolphin")
listTarget.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}}
return listTarget
}(),
ThreeWayMergeForUnstructured: false,
ServerSideApply: true,
ExpectedActions: append(expectedActionsServerSideApply,
"/namespaces/default/pods/forbidden:GET",
"/namespaces/default/pods/forbidden:DELETE",
),
ExpectedError: "failed to delete resource forbidden:",
}, },
} }
@ -444,6 +468,22 @@ func TestUpdate(t *testing.T) {
return newResponse(http.StatusOK, &listTarget.Items[1]) return newResponse(http.StatusOK, &listTarget.Items[1])
case p == "/namespaces/default/pods/squid" && m == http.MethodGet: case p == "/namespaces/default/pods/squid" && m == http.MethodGet:
return newResponse(http.StatusOK, &listTarget.Items[2]) return newResponse(http.StatusOK, &listTarget.Items[2])
case p == "/namespaces/default/pods/notfound" && m == http.MethodGet:
// Resource exists in original but will simulate not found on delete
return newResponse(http.StatusOK, &listOriginal.Items[3])
case p == "/namespaces/default/pods/notfound" && m == http.MethodDelete:
// Simulate a not found during deletion; should not cause update to fail
return newResponse(http.StatusNotFound, notFoundBody())
case p == "/namespaces/default/pods/forbidden" && m == http.MethodGet:
return newResponse(http.StatusOK, &listOriginal.Items[4])
case p == "/namespaces/default/pods/forbidden" && m == http.MethodDelete:
// Simulate RBAC forbidden that should cause update to fail
return newResponse(http.StatusForbidden, &metav1.Status{
Status: metav1.StatusFailure,
Message: "pods \"forbidden\" is forbidden: User \"test-user\" cannot delete resource \"pods\" in API group \"\" in the namespace \"default\"",
Reason: metav1.StatusReasonForbidden,
Code: http.StatusForbidden,
})
default: default:
} }
@ -471,7 +511,13 @@ func TestUpdate(t *testing.T) {
ClientUpdateOptionForceReplace(false), ClientUpdateOptionForceReplace(false),
ClientUpdateOptionServerSideApply(tc.ServerSideApply, false), ClientUpdateOptionServerSideApply(tc.ServerSideApply, false),
ClientUpdateOptionUpgradeClientSideFieldManager(true)) ClientUpdateOptionUpgradeClientSideFieldManager(true))
require.NoError(t, err)
if tc.ExpectedError != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tc.ExpectedError)
} else {
require.NoError(t, err)
}
assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created))
assert.Len(t, result.Updated, 2, "expected 2 resource updated, got %d", len(result.Updated)) assert.Len(t, result.Updated, 2, "expected 2 resource updated, got %d", len(result.Updated))

Loading…
Cancel
Save