Merge pull request #31518 from gjenkins8/gjenkins/fix_update_create

fix: Use server-side apply for object create during update
pull/31560/head
Matt Farina 10 months ago committed by GitHub
commit 61d289c119
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -419,6 +419,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
if err != nil { if err != nil {
return nil, err return nil, err
} }
if _, err := i.cfg.KubeClient.Create( if _, err := i.cfg.KubeClient.Create(
resourceList, resourceList,
kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false)); err != nil && !apierrors.IsAlreadyExists(err) { kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false)); err != nil && !apierrors.IsAlreadyExists(err) {

@ -125,6 +125,9 @@ const (
FieldValidationDirectiveStrict FieldValidationDirective = "Strict" FieldValidationDirectiveStrict FieldValidationDirective = "Strict"
) )
type CreateApplyFunc func(target *resource.Info) error
type UpdateApplyFunc func(original, target *resource.Info) error
func init() { func init() {
// Add CRDs to the scheme. They are missing by default. // Add CRDs to the scheme. They are missing by default.
if err := apiextv1.AddToScheme(scheme.Scheme); err != nil { if err := apiextv1.AddToScheme(scheme.Scheme); err != nil {
@ -281,39 +284,27 @@ func ClientCreateOptionFieldValidationDirective(fieldValidationDirective FieldVa
} }
} }
// Create creates Kubernetes resources specified in the resource list. func (c *Client) makeCreateApplyFunc(serverSideApply, forceConflicts, dryRun bool, fieldValidationDirective FieldValidationDirective) CreateApplyFunc {
func (c *Client) Create(resources ResourceList, options ...ClientCreateOption) (*Result, error) { if serverSideApply {
c.Logger().Debug("creating resource(s)", "resources", len(resources)) c.Logger().Debug(
"using server-side apply for resource creation",
createOptions := clientCreateOptions{ slog.Bool("forceConflicts", forceConflicts),
serverSideApply: true, // Default to server-side apply slog.Bool("dryRun", dryRun),
fieldValidationDirective: FieldValidationDirectiveStrict, slog.String("fieldValidationDirective", string(fieldValidationDirective)))
}
errs := make([]error, 0, len(options))
for _, o := range options {
errs = append(errs, o(&createOptions))
}
if err := errors.Join(errs...); err != nil {
return nil, fmt.Errorf("invalid client create option(s): %w", err)
}
makeCreateApplyFunc := func() func(target *resource.Info) error {
if createOptions.serverSideApply {
c.Logger().Debug("using server-side apply for resource creation", slog.Bool("forceConflicts", createOptions.forceConflicts), slog.Bool("dryRun", createOptions.dryRun), slog.String("fieldValidationDirective", string(createOptions.fieldValidationDirective)))
return func(target *resource.Info) error { return func(target *resource.Info) error {
err := patchResourceServerSide(target, createOptions.dryRun, createOptions.forceConflicts, createOptions.fieldValidationDirective) err := patchResourceServerSide(target, dryRun, forceConflicts, fieldValidationDirective)
logger := c.Logger().With( logger := c.Logger().With(
slog.String("namespace", target.Namespace), slog.String("namespace", target.Namespace),
slog.String("name", target.Name), slog.String("name", target.Name),
slog.String("gvk", target.Mapping.GroupVersionKind.String())) slog.String("gvk", target.Mapping.GroupVersionKind.String()))
if err != nil { if err != nil {
logger.Debug("Error patching resource", slog.Any("error", err)) logger.Debug("Error creating resource via patch", slog.Any("error", err))
return err return err
} }
logger.Debug("Patched resource") logger.Debug("Created resource via patch")
return nil return nil
} }
@ -323,7 +314,29 @@ func (c *Client) Create(resources ResourceList, options ...ClientCreateOption) (
return createResource return createResource
} }
if err := perform(resources, makeCreateApplyFunc()); err != nil { // Create creates Kubernetes resources specified in the resource list.
func (c *Client) Create(resources ResourceList, options ...ClientCreateOption) (*Result, error) {
c.Logger().Debug("creating resource(s)", "resources", len(resources))
createOptions := clientCreateOptions{
serverSideApply: true, // Default to server-side apply
fieldValidationDirective: FieldValidationDirectiveStrict,
}
errs := make([]error, 0, len(options))
for _, o := range options {
errs = append(errs, o(&createOptions))
}
if err := errors.Join(errs...); err != nil {
return nil, fmt.Errorf("invalid client create option(s): %w", err)
}
createApplyFunc := c.makeCreateApplyFunc(
createOptions.serverSideApply,
createOptions.forceConflicts,
createOptions.dryRun,
createOptions.fieldValidationDirective)
if err := perform(resources, createApplyFunc); err != nil {
return nil, err return nil, err
} }
return &Result{Created: resources}, nil return &Result{Created: resources}, nil
@ -525,7 +538,7 @@ func (c *Client) BuildTable(reader io.Reader, validate bool) (ResourceList, erro
transformRequests) transformRequests)
} }
func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateApplyFunc) (*Result, error) { func (c *Client) update(originals, targets ResourceList, createApplyFunc CreateApplyFunc, updateApplyFunc UpdateApplyFunc) (*Result, error) {
updateErrors := []error{} updateErrors := []error{}
res := &Result{} res := &Result{}
@ -545,7 +558,7 @@ func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateA
res.Created = append(res.Created, target) res.Created = append(res.Created, target)
// Since the resource does not exist, create it. // Since the resource does not exist, create it.
if err := createResource(target); err != nil { if err := createApplyFunc(target); err != nil {
return fmt.Errorf("failed to create resource: %w", err) return fmt.Errorf("failed to create resource: %w", err)
} }
@ -699,8 +712,6 @@ func ClientUpdateOptionUpgradeClientSideFieldManager(upgradeClientSideFieldManag
} }
} }
type UpdateApplyFunc func(original, target *resource.Info) error
// Update takes the current list of objects and target list of objects and // Update takes the current list of objects and target list of objects and
// creates resources that don't already exist, updates resources that have been // creates resources that don't already exist, updates resources that have been
// modified in the target configuration, and deletes resources from the current // modified in the target configuration, and deletes resources from the current
@ -736,6 +747,12 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate
return &Result{}, fmt.Errorf("invalid operation: cannot use server-side apply and force replace together") return &Result{}, fmt.Errorf("invalid operation: cannot use server-side apply and force replace together")
} }
createApplyFunc := c.makeCreateApplyFunc(
updateOptions.serverSideApply,
updateOptions.forceConflicts,
updateOptions.dryRun,
updateOptions.fieldValidationDirective)
makeUpdateApplyFunc := func() UpdateApplyFunc { makeUpdateApplyFunc := func() UpdateApplyFunc {
if updateOptions.forceReplace { if updateOptions.forceReplace {
c.Logger().Debug( c.Logger().Debug(
@ -796,7 +813,7 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate
} }
} }
return c.update(originals, targets, makeUpdateApplyFunc()) return c.update(originals, targets, createApplyFunc, makeUpdateApplyFunc())
} }
// Delete deletes Kubernetes resources specified in the resources list with // Delete deletes Kubernetes resources specified in the resources list with

@ -350,9 +350,7 @@ func TestUpdate(t *testing.T) {
"/namespaces/default/pods/otter:GET", "/namespaces/default/pods/otter:GET",
"/namespaces/default/pods/otter:PATCH", "/namespaces/default/pods/otter:PATCH",
"/namespaces/default/pods/dolphin:GET", "/namespaces/default/pods/dolphin:GET",
"/namespaces/default/pods:POST", // create dolphin "/namespaces/default/pods/dolphin:PATCH", // create dolphin
"/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:GET",
@ -464,6 +462,8 @@ func TestUpdate(t *testing.T) {
return newResponseJSON(http.StatusConflict, resourceQuotaConflict) return newResponseJSON(http.StatusConflict, resourceQuotaConflict)
} }
return newResponse(http.StatusOK, &listTarget.Items[1])
case p == "/namespaces/default/pods/dolphin" && m == http.MethodPatch:
return newResponse(http.StatusOK, &listTarget.Items[1]) return newResponse(http.StatusOK, &listTarget.Items[1])
case p == "/namespaces/default/pods/squid" && m == http.MethodDelete: case p == "/namespaces/default/pods/squid" && m == http.MethodDelete:
return newResponse(http.StatusOK, &listTarget.Items[1]) return newResponse(http.StatusOK, &listTarget.Items[1])
@ -485,10 +485,9 @@ func TestUpdate(t *testing.T) {
Reason: metav1.StatusReasonForbidden, Reason: metav1.StatusReasonForbidden,
Code: http.StatusForbidden, Code: http.StatusForbidden,
}) })
default:
} }
t.Fail() t.FailNow()
return nil, nil return nil, nil
} }

Loading…
Cancel
Save