Skip to content

Commit 670369f

Browse files
committed
Move patch functionality for apply into its own file.
1 parent e38e320 commit 670369f

File tree

3 files changed

+267
-228
lines changed

3 files changed

+267
-228
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ go_library(
77
"apply_edit_last_applied.go",
88
"apply_set_last_applied.go",
99
"apply_view_last_applied.go",
10+
"patcher.go",
1011
"prune.go",
1112
],
1213
importmap = "k8s.io/kubernetes/vendor/k8s.io/kubectl/pkg/cmd/apply",

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

Lines changed: 1 addition & 228 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,9 @@ limitations under the License.
1717
package apply
1818

1919
import (
20-
"encoding/json"
2120
"fmt"
22-
"io"
2321
"net/http"
24-
"time"
2522

26-
"github.com/jonboulle/clockwork"
2723
"github.com/spf13/cobra"
2824
corev1 "k8s.io/api/core/v1"
2925
"k8s.io/apimachinery/pkg/api/errors"
@@ -33,18 +29,13 @@ import (
3329
"k8s.io/apimachinery/pkg/runtime"
3430
"k8s.io/apimachinery/pkg/runtime/schema"
3531
"k8s.io/apimachinery/pkg/types"
36-
"k8s.io/apimachinery/pkg/util/jsonmergepatch"
37-
"k8s.io/apimachinery/pkg/util/mergepatch"
3832
"k8s.io/apimachinery/pkg/util/sets"
39-
"k8s.io/apimachinery/pkg/util/strategicpatch"
40-
"k8s.io/apimachinery/pkg/util/wait"
4133
"k8s.io/cli-runtime/pkg/genericclioptions"
4234
"k8s.io/cli-runtime/pkg/printers"
4335
"k8s.io/cli-runtime/pkg/resource"
4436
"k8s.io/client-go/discovery"
4537
"k8s.io/client-go/dynamic"
4638
"k8s.io/klog"
47-
oapi "k8s.io/kube-openapi/pkg/util/proto"
4839
"k8s.io/kubectl/pkg/cmd/delete"
4940
cmdutil "k8s.io/kubectl/pkg/cmd/util"
5041
"k8s.io/kubectl/pkg/scheme"
@@ -103,15 +94,6 @@ type ApplyOptions struct {
10394
objectsCached bool
10495
}
10596

106-
const (
107-
// maxPatchRetry is the maximum number of conflicts retry for during a patch operation before returning failure
108-
maxPatchRetry = 5
109-
// backOffPeriod is the period to back off when apply patch results in error.
110-
backOffPeriod = 1 * time.Second
111-
// how many times we can retry before back off
112-
triesBeforeBackOff = 1
113-
)
114-
11597
var (
11698
applyLong = templates.LongDesc(i18n.T(`
11799
Apply a configuration to a resource by filename or stdin.
@@ -352,10 +334,6 @@ func (o *ApplyOptions) GetObjects() ([]*resource.Info, error) {
352334

353335
// Run executes the `apply` command.
354336
func (o *ApplyOptions) Run() error {
355-
var openapiSchema openapi.Resources
356-
if o.OpenAPIPatch {
357-
openapiSchema = o.OpenAPISchema
358-
}
359337

360338
dryRunVerifier := &DryRunVerifier{
361339
Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(o.DynamicClient)),
@@ -521,22 +499,7 @@ See http://k8s.io/docs/reference/using-api/api-concepts/#conflicts`, err)
521499
fmt.Fprintf(o.ErrOut, warningNoLastAppliedConfigAnnotation, o.cmdBaseName)
522500
}
523501

524-
helper := resource.NewHelper(info.Client, info.Mapping)
525-
patcher := &Patcher{
526-
Mapping: info.Mapping,
527-
Helper: helper,
528-
DynamicClient: o.DynamicClient,
529-
Overwrite: o.Overwrite,
530-
BackOff: clockwork.NewRealClock(),
531-
Force: o.DeleteOptions.ForceDeletion,
532-
Cascade: o.DeleteOptions.Cascade,
533-
Timeout: o.DeleteOptions.Timeout,
534-
GracePeriod: o.DeleteOptions.GracePeriod,
535-
ServerDryRun: o.ServerDryRun,
536-
OpenapiSchema: openapiSchema,
537-
Retries: maxPatchRetry,
538-
}
539-
502+
patcher := newPatcher(o, info)
540503
patchBytes, patchedObject, err := patcher.Patch(info.Object, modified, info.Source, info.Namespace, info.Name, o.ErrOut)
541504
if err != nil {
542505
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
@@ -636,34 +599,6 @@ func (o *ApplyOptions) printObjects() error {
636599
return nil
637600
}
638601

639-
func (p *Patcher) delete(namespace, name string) error {
640-
return runDelete(namespace, name, p.Mapping, p.DynamicClient, p.Cascade, p.GracePeriod, p.ServerDryRun)
641-
}
642-
643-
// Patcher defines options to patch OpenAPI objects.
644-
type Patcher struct {
645-
Mapping *meta.RESTMapping
646-
Helper *resource.Helper
647-
DynamicClient dynamic.Interface
648-
649-
Overwrite bool
650-
BackOff clockwork.Clock
651-
652-
Force bool
653-
Cascade bool
654-
Timeout time.Duration
655-
GracePeriod int
656-
ServerDryRun bool
657-
658-
// If set, forces the patch against a specific resourceVersion
659-
ResourceVersion *string
660-
661-
// Number of retries to make if the patch fails with conflict
662-
Retries int
663-
664-
OpenapiSchema openapi.Resources
665-
}
666-
667602
// DryRunVerifier verifies if a given group-version-kind supports DryRun
668603
// against the current server. Sending dryRun requests to apiserver that
669604
// don't support it will result in objects being unwillingly persisted.
@@ -702,165 +637,3 @@ func (v *DryRunVerifier) HasSupport(gvk schema.GroupVersionKind) error {
702637
}
703638
return nil
704639
}
705-
706-
func addResourceVersion(patch []byte, rv string) ([]byte, error) {
707-
var patchMap map[string]interface{}
708-
err := json.Unmarshal(patch, &patchMap)
709-
if err != nil {
710-
return nil, err
711-
}
712-
u := unstructured.Unstructured{Object: patchMap}
713-
a, err := meta.Accessor(&u)
714-
if err != nil {
715-
return nil, err
716-
}
717-
a.SetResourceVersion(rv)
718-
719-
return json.Marshal(patchMap)
720-
}
721-
722-
func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
723-
// Serialize the current configuration of the object from the server.
724-
current, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
725-
if err != nil {
726-
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("serializing current configuration from:\n%v\nfor:", obj), source, err)
727-
}
728-
729-
// Retrieve the original configuration of the object from the annotation.
730-
original, err := util.GetOriginalConfiguration(obj)
731-
if err != nil {
732-
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("retrieving original configuration from:\n%v\nfor:", obj), source, err)
733-
}
734-
735-
var patchType types.PatchType
736-
var patch []byte
737-
var lookupPatchMeta strategicpatch.LookupPatchMeta
738-
var schema oapi.Schema
739-
createPatchErrFormat := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
740-
741-
// Create the versioned struct from the type defined in the restmapping
742-
// (which is the API version we'll be submitting the patch to)
743-
versionedObject, err := scheme.Scheme.New(p.Mapping.GroupVersionKind)
744-
switch {
745-
case runtime.IsNotRegisteredError(err):
746-
// fall back to generic JSON merge patch
747-
patchType = types.MergePatchType
748-
preconditions := []mergepatch.PreconditionFunc{mergepatch.RequireKeyUnchanged("apiVersion"),
749-
mergepatch.RequireKeyUnchanged("kind"), mergepatch.RequireMetadataKeyUnchanged("name")}
750-
patch, err = jsonmergepatch.CreateThreeWayJSONMergePatch(original, modified, current, preconditions...)
751-
if err != nil {
752-
if mergepatch.IsPreconditionFailed(err) {
753-
return nil, nil, fmt.Errorf("%s", "At least one of apiVersion, kind and name was changed")
754-
}
755-
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
756-
}
757-
case err != nil:
758-
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("getting instance of versioned object for %v:", p.Mapping.GroupVersionKind), source, err)
759-
case err == nil:
760-
// Compute a three way strategic merge patch to send to server.
761-
patchType = types.StrategicMergePatchType
762-
763-
// Try to use openapi first if the openapi spec is available and can successfully calculate the patch.
764-
// Otherwise, fall back to baked-in types.
765-
if p.OpenapiSchema != nil {
766-
if schema = p.OpenapiSchema.LookupResource(p.Mapping.GroupVersionKind); schema != nil {
767-
lookupPatchMeta = strategicpatch.PatchMetaFromOpenAPI{Schema: schema}
768-
if openapiPatch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.Overwrite); err != nil {
769-
fmt.Fprintf(errOut, "warning: error calculating patch from openapi spec: %v\n", err)
770-
} else {
771-
patchType = types.StrategicMergePatchType
772-
patch = openapiPatch
773-
}
774-
}
775-
}
776-
777-
if patch == nil {
778-
lookupPatchMeta, err = strategicpatch.NewPatchMetaFromStruct(versionedObject)
779-
if err != nil {
780-
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
781-
}
782-
patch, err = strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.Overwrite)
783-
if err != nil {
784-
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
785-
}
786-
}
787-
}
788-
789-
if string(patch) == "{}" {
790-
return patch, obj, nil
791-
}
792-
793-
if p.ResourceVersion != nil {
794-
patch, err = addResourceVersion(patch, *p.ResourceVersion)
795-
if err != nil {
796-
return nil, nil, cmdutil.AddSourceToErr("Failed to insert resourceVersion in patch", source, err)
797-
}
798-
}
799-
800-
options := metav1.PatchOptions{}
801-
if p.ServerDryRun {
802-
options.DryRun = []string{metav1.DryRunAll}
803-
}
804-
805-
patchedObj, err := p.Helper.Patch(namespace, name, patchType, patch, &options)
806-
return patch, patchedObj, err
807-
}
808-
809-
// Patch tries to patch an OpenAPI resource. On success, returns the merge patch as well
810-
// the final patched object. On failure, returns an error.
811-
func (p *Patcher) Patch(current runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
812-
var getErr error
813-
patchBytes, patchObject, err := p.patchSimple(current, modified, source, namespace, name, errOut)
814-
if p.Retries == 0 {
815-
p.Retries = maxPatchRetry
816-
}
817-
for i := 1; i <= p.Retries && errors.IsConflict(err); i++ {
818-
if i > triesBeforeBackOff {
819-
p.BackOff.Sleep(backOffPeriod)
820-
}
821-
current, getErr = p.Helper.Get(namespace, name, false)
822-
if getErr != nil {
823-
return nil, nil, getErr
824-
}
825-
patchBytes, patchObject, err = p.patchSimple(current, modified, source, namespace, name, errOut)
826-
}
827-
if err != nil && (errors.IsConflict(err) || errors.IsInvalid(err)) && p.Force {
828-
patchBytes, patchObject, err = p.deleteAndCreate(current, modified, namespace, name)
829-
}
830-
return patchBytes, patchObject, err
831-
}
832-
833-
func (p *Patcher) deleteAndCreate(original runtime.Object, modified []byte, namespace, name string) ([]byte, runtime.Object, error) {
834-
if err := p.delete(namespace, name); err != nil {
835-
return modified, nil, err
836-
}
837-
// TODO: use wait
838-
if err := wait.PollImmediate(1*time.Second, p.Timeout, func() (bool, error) {
839-
if _, err := p.Helper.Get(namespace, name, false); !errors.IsNotFound(err) {
840-
return false, err
841-
}
842-
return true, nil
843-
}); err != nil {
844-
return modified, nil, err
845-
}
846-
versionedObject, _, err := unstructured.UnstructuredJSONScheme.Decode(modified, nil, nil)
847-
if err != nil {
848-
return modified, nil, err
849-
}
850-
options := metav1.CreateOptions{}
851-
if p.ServerDryRun {
852-
options.DryRun = []string{metav1.DryRunAll}
853-
}
854-
createdObject, err := p.Helper.Create(namespace, true, versionedObject, &options)
855-
if err != nil {
856-
// restore the original object if we fail to create the new one
857-
// but still propagate and advertise error to user
858-
recreated, recreateErr := p.Helper.Create(namespace, true, original, &options)
859-
if recreateErr != nil {
860-
err = fmt.Errorf("An error occurred force-replacing the existing object with the newly provided one:\n\n%v.\n\nAdditionally, an error occurred attempting to restore the original object:\n\n%v", err, recreateErr)
861-
} else {
862-
createdObject = recreated
863-
}
864-
}
865-
return modified, createdObject, err
866-
}

0 commit comments

Comments
 (0)