Skip to content

Commit 2d42530

Browse files
authored
Merge pull request kubernetes#89539 from seans3/kubectl-apply-fix
Fixes problem where kubectl apply stops after first error
2 parents edbbb6a + 1083c0f commit 2d42530

File tree

4 files changed

+180
-129
lines changed

4 files changed

+180
-129
lines changed

hack/testdata/multi-resource.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Tests that initial failures to not block subsequent applies.
2+
# Pod must be before namespace, so it initially fails. Second
3+
# apply of pod should succeed, since namespace finally exists.
4+
apiVersion: v1
5+
kind: Pod
6+
metadata:
7+
name: test-pod
8+
namespace: multi-resource-ns
9+
labels:
10+
name: test-pod-label
11+
spec:
12+
containers:
13+
- name: kubernetes-pause
14+
image: k8s.gcr.io/pause:2.0
15+
---
16+
apiVersion: v1
17+
kind: Namespace
18+
metadata:
19+
name: multi-resource-ns

staging/src/k8s.io/kubectl/pkg/cmd/apply/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ go_library(
2222
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
2323
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
2424
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
25+
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
2526
"//staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch:go_default_library",
2627
"//staging/src/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
2728
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",

staging/src/k8s.io/kubectl/pkg/cmd/apply/apply.go

Lines changed: 147 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2929
"k8s.io/apimachinery/pkg/runtime"
3030
"k8s.io/apimachinery/pkg/types"
31+
utilerrors "k8s.io/apimachinery/pkg/util/errors"
3132
"k8s.io/apimachinery/pkg/util/sets"
3233
"k8s.io/cli-runtime/pkg/genericclioptions"
3334
"k8s.io/cli-runtime/pkg/printers"
@@ -371,53 +372,78 @@ func (o *ApplyOptions) Run() error {
371372

372373
// Generates the objects using the resource builder if they have not
373374
// already been stored by calling "SetObjects()" in the pre-processor.
375+
errs := []error{}
374376
infos, err := o.GetObjects()
375377
if err != nil {
376-
return err
378+
errs = append(errs, err)
377379
}
378-
if len(infos) == 0 {
380+
if len(infos) == 0 && len(errs) == 0 {
379381
return fmt.Errorf("no objects passed to apply")
380382
}
383+
// Iterate through all objects, applying each one.
381384
for _, info := range infos {
385+
if err := o.applyOneObject(info); err != nil {
386+
errs = append(errs, err)
387+
}
388+
}
389+
// If any errors occurred during apply, then return error (or
390+
// aggregate of errors).
391+
if len(errs) == 1 {
392+
return errs[0]
393+
}
394+
if len(errs) > 1 {
395+
return utilerrors.NewAggregate(errs)
396+
}
382397

383-
o.MarkNamespaceVisited(info)
398+
if o.PostProcessorFn != nil {
399+
klog.V(4).Infof("Running apply post-processor function")
400+
if err := o.PostProcessorFn(); err != nil {
401+
return err
402+
}
403+
}
404+
405+
return nil
406+
}
407+
408+
func (o *ApplyOptions) applyOneObject(info *resource.Info) error {
409+
o.MarkNamespaceVisited(info)
410+
411+
if err := o.Recorder.Record(info.Object); err != nil {
412+
klog.V(4).Infof("error recording current command: %v", err)
413+
}
384414

385-
if err := o.Recorder.Record(info.Object); err != nil {
386-
klog.V(4).Infof("error recording current command: %v", err)
415+
if o.ServerSideApply {
416+
// Send the full object to be applied on the server side.
417+
data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, info.Object)
418+
if err != nil {
419+
return cmdutil.AddSourceToErr("serverside-apply", info.Source, err)
387420
}
388421

389-
if o.ServerSideApply {
390-
// Send the full object to be applied on the server side.
391-
data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, info.Object)
392-
if err != nil {
393-
return cmdutil.AddSourceToErr("serverside-apply", info.Source, err)
394-
}
422+
options := metav1.PatchOptions{
423+
Force: &o.ForceConflicts,
424+
FieldManager: o.FieldManager,
425+
}
395426

396-
options := metav1.PatchOptions{
397-
Force: &o.ForceConflicts,
398-
FieldManager: o.FieldManager,
427+
helper := resource.NewHelper(info.Client, info.Mapping)
428+
if o.DryRunStrategy == cmdutil.DryRunServer {
429+
if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
430+
return err
399431
}
400-
401-
helper := resource.NewHelper(info.Client, info.Mapping)
402-
if o.DryRunStrategy == cmdutil.DryRunServer {
403-
if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
404-
return err
405-
}
406-
helper.DryRun(true)
432+
helper.DryRun(true)
433+
}
434+
obj, err := helper.Patch(
435+
info.Namespace,
436+
info.Name,
437+
types.ApplyPatchType,
438+
data,
439+
&options,
440+
)
441+
if err != nil {
442+
if isIncompatibleServerError(err) {
443+
err = fmt.Errorf("Server-side apply not available on the server: (%v)", err)
407444
}
408-
obj, err := helper.Patch(
409-
info.Namespace,
410-
info.Name,
411-
types.ApplyPatchType,
412-
data,
413-
&options,
414-
)
415-
if err != nil {
416-
if isIncompatibleServerError(err) {
417-
err = fmt.Errorf("Server-side apply not available on the server: (%v)", err)
418-
}
419-
if errors.IsConflict(err) {
420-
err = fmt.Errorf(`%v
445+
if errors.IsConflict(err) {
446+
err = fmt.Errorf(`%v
421447
Please review the fields above--they currently have other managers. Here
422448
are the ways you can resolve this warning:
423449
* If you intend to manage all of these fields, please re-run the apply
@@ -429,136 +455,128 @@ are the ways you can resolve this warning:
429455
value; in this case, you'll become the manager if the other manager(s)
430456
stop managing the field (remove it from their configuration).
431457
See http://k8s.io/docs/reference/using-api/api-concepts/#conflicts`, err)
432-
}
433-
return err
434-
}
435-
436-
info.Refresh(obj, true)
437-
438-
if err := o.MarkObjectVisited(info); err != nil {
439-
return err
440458
}
459+
return err
460+
}
441461

442-
if o.shouldPrintObject() {
443-
continue
444-
}
462+
info.Refresh(obj, true)
445463

446-
printer, err := o.ToPrinter("serverside-applied")
447-
if err != nil {
448-
return err
449-
}
464+
if err := o.MarkObjectVisited(info); err != nil {
465+
return err
466+
}
450467

451-
if err = printer.PrintObj(info.Object, o.Out); err != nil {
452-
return err
453-
}
454-
continue
468+
if o.shouldPrintObject() {
469+
return nil
455470
}
456471

457-
// Get the modified configuration of the object. Embed the result
458-
// as an annotation in the modified configuration, so that it will appear
459-
// in the patch sent to the server.
460-
modified, err := util.GetModifiedConfiguration(info.Object, true, unstructured.UnstructuredJSONScheme)
472+
printer, err := o.ToPrinter("serverside-applied")
461473
if err != nil {
462-
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving modified configuration from:\n%s\nfor:", info.String()), info.Source, err)
474+
return err
463475
}
464476

465-
if err := info.Get(); err != nil {
466-
if !errors.IsNotFound(err) {
467-
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
468-
}
469-
470-
// Create the resource if it doesn't exist
471-
// First, update the annotation used by kubectl apply
472-
if err := util.CreateApplyAnnotation(info.Object, unstructured.UnstructuredJSONScheme); err != nil {
473-
return cmdutil.AddSourceToErr("creating", info.Source, err)
474-
}
475-
476-
if o.DryRunStrategy != cmdutil.DryRunClient {
477-
// Then create the resource and skip the three-way merge
478-
helper := resource.NewHelper(info.Client, info.Mapping)
479-
if o.DryRunStrategy == cmdutil.DryRunServer {
480-
if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
481-
return cmdutil.AddSourceToErr("creating", info.Source, err)
482-
}
483-
helper.DryRun(true)
484-
}
485-
obj, err := helper.Create(info.Namespace, true, info.Object)
486-
if err != nil {
487-
return cmdutil.AddSourceToErr("creating", info.Source, err)
488-
}
489-
info.Refresh(obj, true)
490-
}
491-
492-
if err := o.MarkObjectVisited(info); err != nil {
493-
return err
494-
}
477+
if err = printer.PrintObj(info.Object, o.Out); err != nil {
478+
return err
479+
}
480+
return nil
481+
}
495482

496-
if o.shouldPrintObject() {
497-
continue
498-
}
483+
// Get the modified configuration of the object. Embed the result
484+
// as an annotation in the modified configuration, so that it will appear
485+
// in the patch sent to the server.
486+
modified, err := util.GetModifiedConfiguration(info.Object, true, unstructured.UnstructuredJSONScheme)
487+
if err != nil {
488+
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving modified configuration from:\n%s\nfor:", info.String()), info.Source, err)
489+
}
499490

500-
printer, err := o.ToPrinter("created")
501-
if err != nil {
502-
return err
503-
}
504-
if err = printer.PrintObj(info.Object, o.Out); err != nil {
505-
return err
506-
}
507-
continue
491+
if err := info.Get(); err != nil {
492+
if !errors.IsNotFound(err) {
493+
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
508494
}
509495

510-
if err := o.MarkObjectVisited(info); err != nil {
511-
return err
496+
// Create the resource if it doesn't exist
497+
// First, update the annotation used by kubectl apply
498+
if err := util.CreateApplyAnnotation(info.Object, unstructured.UnstructuredJSONScheme); err != nil {
499+
return cmdutil.AddSourceToErr("creating", info.Source, err)
512500
}
513501

514502
if o.DryRunStrategy != cmdutil.DryRunClient {
515-
metadata, _ := meta.Accessor(info.Object)
516-
annotationMap := metadata.GetAnnotations()
517-
if _, ok := annotationMap[corev1.LastAppliedConfigAnnotation]; !ok {
518-
fmt.Fprintf(o.ErrOut, warningNoLastAppliedConfigAnnotation, o.cmdBaseName)
519-
}
520-
521-
patcher, err := newPatcher(o, info)
522-
if err != nil {
523-
return err
503+
// Then create the resource and skip the three-way merge
504+
helper := resource.NewHelper(info.Client, info.Mapping)
505+
if o.DryRunStrategy == cmdutil.DryRunServer {
506+
if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
507+
return cmdutil.AddSourceToErr("creating", info.Source, err)
508+
}
509+
helper.DryRun(true)
524510
}
525-
patchBytes, patchedObject, err := patcher.Patch(info.Object, modified, info.Source, info.Namespace, info.Name, o.ErrOut)
511+
obj, err := helper.Create(info.Namespace, true, info.Object)
526512
if err != nil {
527-
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
513+
return cmdutil.AddSourceToErr("creating", info.Source, err)
528514
}
515+
info.Refresh(obj, true)
516+
}
529517

530-
info.Refresh(patchedObject, true)
531-
532-
if string(patchBytes) == "{}" && !o.shouldPrintObject() {
533-
printer, err := o.ToPrinter("unchanged")
534-
if err != nil {
535-
return err
536-
}
537-
if err = printer.PrintObj(info.Object, o.Out); err != nil {
538-
return err
539-
}
540-
continue
541-
}
518+
if err := o.MarkObjectVisited(info); err != nil {
519+
return err
542520
}
543521

544522
if o.shouldPrintObject() {
545-
continue
523+
return nil
546524
}
547525

548-
printer, err := o.ToPrinter("configured")
526+
printer, err := o.ToPrinter("created")
549527
if err != nil {
550528
return err
551529
}
552530
if err = printer.PrintObj(info.Object, o.Out); err != nil {
553531
return err
554532
}
533+
return nil
555534
}
556535

557-
if o.PostProcessorFn != nil {
558-
klog.V(4).Infof("Running apply post-processor function")
559-
if err := o.PostProcessorFn(); err != nil {
536+
if err := o.MarkObjectVisited(info); err != nil {
537+
return err
538+
}
539+
540+
if o.DryRunStrategy != cmdutil.DryRunClient {
541+
metadata, _ := meta.Accessor(info.Object)
542+
annotationMap := metadata.GetAnnotations()
543+
if _, ok := annotationMap[corev1.LastAppliedConfigAnnotation]; !ok {
544+
fmt.Fprintf(o.ErrOut, warningNoLastAppliedConfigAnnotation, o.cmdBaseName)
545+
}
546+
547+
patcher, err := newPatcher(o, info)
548+
if err != nil {
560549
return err
561550
}
551+
patchBytes, patchedObject, err := patcher.Patch(info.Object, modified, info.Source, info.Namespace, info.Name, o.ErrOut)
552+
if err != nil {
553+
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
554+
}
555+
556+
info.Refresh(patchedObject, true)
557+
558+
if string(patchBytes) == "{}" && !o.shouldPrintObject() {
559+
printer, err := o.ToPrinter("unchanged")
560+
if err != nil {
561+
return err
562+
}
563+
if err = printer.PrintObj(info.Object, o.Out); err != nil {
564+
return err
565+
}
566+
return nil
567+
}
568+
}
569+
570+
if o.shouldPrintObject() {
571+
return nil
572+
}
573+
574+
printer, err := o.ToPrinter("configured")
575+
if err != nil {
576+
return err
577+
}
578+
if err = printer.PrintObj(info.Object, o.Out); err != nil {
579+
return err
562580
}
563581

564582
return nil

test/cmd/apply.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,19 @@ __EOF__
274274
# cleanup
275275
kubectl delete --kustomize hack/testdata/kustomize
276276

277+
## kubectl apply multiple resources with initial failure.
278+
# Pre-Condition: no POD exists
279+
kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" ''
280+
# First pass, namespace is created, but pod is not (since namespace does not exist yet).
281+
kubectl apply -f hack/testdata/multi-resource.yaml "${kube_flags[@]:?}"
282+
output_message=$(! kubectl get pods test-pod 2>&1 "${kube_flags[@]:?}")
283+
kube::test::if_has_string "${output_message}" 'pods "test-pod" not found'
284+
# Second pass, pod is created (now that namespace exists).
285+
kubectl apply -f hack/testdata/multi-resource.yaml "${kube_flags[@]:?}"
286+
kube::test::get_object_assert 'pod test-pod' "{{${id_field}}}" 'test-pod'
287+
# cleanup
288+
kubectl delete -f hack/testdata/multi-resource.yaml
289+
277290
set +o nounset
278291
set +o errexit
279292
}

0 commit comments

Comments
 (0)