@ -43,6 +43,8 @@ import (
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/jsonmergepatch"
"k8s.io/apimachinery/pkg/util/mergepatch"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/cli-runtime/pkg/resource"
@ -399,14 +401,7 @@ func (c *Client) BuildTable(reader io.Reader, validate bool) (ResourceList, erro
return result , scrubValidationError ( err )
return result , scrubValidationError ( err )
}
}
// Update takes the current list of objects and target list of objects and
func ( c * Client ) update ( original , target ResourceList , force , threeWayMerge bool ) ( * Result , error ) {
// creates resources that don't already exist, updates resources that have been
// modified in the target configuration, and deletes resources from the current
// configuration that are not present in the target configuration. If an error
// occurs, a Result will still be returned with the error, containing all
// resource updates, creations, and deletions that were attempted. These can be
// used for cleanup or other logging purposes.
func ( c * Client ) Update ( original , target ResourceList , force bool ) ( * Result , error ) {
updateErrors := [ ] string { }
updateErrors := [ ] string { }
res := & Result { }
res := & Result { }
@ -441,7 +436,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
return errors . Errorf ( "no %s with the name %q found" , kind , info . Name )
return errors . Errorf ( "no %s with the name %q found" , kind , info . Name )
}
}
if err := updateResource ( c , info , originalInfo . Object , force ); err != nil {
if err := updateResource ( c , info , originalInfo . Object , force , threeWayMerge ); err != nil {
c . Log ( "error updating the resource %q:\n\t %v" , info . Name , err )
c . Log ( "error updating the resource %q:\n\t %v" , info . Name , err )
updateErrors = append ( updateErrors , err . Error ( ) )
updateErrors = append ( updateErrors , err . Error ( ) )
}
}
@ -482,6 +477,31 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
return res , nil
return res , nil
}
}
// 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
// modified in the target configuration, and deletes resources from the current
// configuration that are not present in the target configuration. If an error
// occurs, a Result will still be returned with the error, containing all
// resource updates, creations, and deletions that were attempted. These can be
// used for cleanup or other logging purposes.
//
// The difference to Update is that UpdateThreeWayMerge does a three-way-merge
// for unstructured objects.
func ( c * Client ) UpdateThreeWayMerge ( original , target ResourceList , force bool ) ( * Result , error ) {
return c . update ( original , target , force , true )
}
// 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
// modified in the target configuration, and deletes resources from the current
// configuration that are not present in the target configuration. If an error
// occurs, a Result will still be returned with the error, containing all
// resource updates, creations, and deletions that were attempted. These can be
// used for cleanup or other logging purposes.
func ( c * Client ) Update ( original , target ResourceList , force bool ) ( * Result , error ) {
return c . update ( original , target , force , false )
}
// Delete deletes Kubernetes resources specified in the resources list with
// Delete deletes Kubernetes resources specified in the resources list with
// background cascade deletion. It will attempt to delete all resources even
// background cascade deletion. It will attempt to delete all resources even
// if one or more fail and collect any errors. All successfully deleted items
// if one or more fail and collect any errors. All successfully deleted items
@ -591,7 +611,7 @@ func deleteResource(info *resource.Info, policy metav1.DeletionPropagation) erro
} )
} )
}
}
func createPatch ( target * resource . Info , current runtime . Object ) ( [ ] byte , types . PatchType , error ) {
func createPatch ( target * resource . Info , current runtime . Object , threeWayMergeForUnstructured bool ) ( [ ] byte , types . PatchType , error ) {
oldData , err := json . Marshal ( current )
oldData , err := json . Marshal ( current )
if err != nil {
if err != nil {
return nil , types . StrategicMergePatchType , errors . Wrap ( err , "serializing current configuration" )
return nil , types . StrategicMergePatchType , errors . Wrap ( err , "serializing current configuration" )
@ -619,7 +639,7 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P
// Unstructured objects, such as CRDs, may not have a not registered error
// Unstructured objects, such as CRDs, may not have a not registered error
// returned from ConvertToVersion. Anything that's unstructured should
// returned from ConvertToVersion. Anything that's unstructured should
// use the jsonpatch.CreateMergeP atch. Strategic Merge Patch is not supported
// use generic JSON merge p atch. Strategic Merge Patch is not supported
// on objects like CRDs.
// on objects like CRDs.
_ , isUnstructured := versionedObject . ( runtime . Unstructured )
_ , isUnstructured := versionedObject . ( runtime . Unstructured )
@ -627,6 +647,19 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P
_ , isCRD := versionedObject . ( * apiextv1beta1 . CustomResourceDefinition )
_ , isCRD := versionedObject . ( * apiextv1beta1 . CustomResourceDefinition )
if isUnstructured || isCRD {
if isUnstructured || isCRD {
if threeWayMergeForUnstructured {
// from https://github.com/kubernetes/kubectl/blob/b83b2ec7d15f286720bccf7872b5c72372cb8e80/pkg/cmd/apply/patcher.go#L129
preconditions := [ ] mergepatch . PreconditionFunc {
mergepatch . RequireKeyUnchanged ( "apiVersion" ) ,
mergepatch . RequireKeyUnchanged ( "kind" ) ,
mergepatch . RequireMetadataKeyUnchanged ( "name" ) ,
}
patch , err := jsonmergepatch . CreateThreeWayJSONMergePatch ( oldData , newData , currentData , preconditions ... )
if err != nil && mergepatch . IsPreconditionFailed ( err ) {
err = fmt . Errorf ( "%w: at least one field was changed: apiVersion, kind or name" , err )
}
return patch , types . MergePatchType , err
}
// fall back to generic JSON merge patch
// fall back to generic JSON merge patch
patch , err := jsonpatch . CreateMergePatch ( oldData , newData )
patch , err := jsonpatch . CreateMergePatch ( oldData , newData )
return patch , types . MergePatchType , err
return patch , types . MergePatchType , err
@ -641,7 +674,7 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P
return patch , types . StrategicMergePatchType , err
return patch , types . StrategicMergePatchType , err
}
}
func updateResource ( c * Client , target * resource . Info , currentObj runtime . Object , force bool ) error {
func updateResource ( c * Client , target * resource . Info , currentObj runtime . Object , force , threeWayMergeForUnstructured bool ) error {
var (
var (
obj runtime . Object
obj runtime . Object
helper = resource . NewHelper ( target . Client , target . Mapping ) . WithFieldManager ( getManagedFieldsManager ( ) )
helper = resource . NewHelper ( target . Client , target . Mapping ) . WithFieldManager ( getManagedFieldsManager ( ) )
@ -657,7 +690,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object,
}
}
c . Log ( "Replaced %q with kind %s for kind %s" , target . Name , currentObj . GetObjectKind ( ) . GroupVersionKind ( ) . Kind , kind )
c . Log ( "Replaced %q with kind %s for kind %s" , target . Name , currentObj . GetObjectKind ( ) . GroupVersionKind ( ) . Kind , kind )
} else {
} else {
patch , patchType , err := createPatch ( target , currentObj )
patch , patchType , err := createPatch ( target , currentObj , threeWayMergeForUnstructured )
if err != nil {
if err != nil {
return errors . Wrap ( err , "failed to create patch" )
return errors . Wrap ( err , "failed to create patch" )
}
}