let patch use local file content to mutate

pull/6/head
deads2k 2016-06-02 13:50:52 -04:00
parent 2f4309d270
commit ac64404d86
2 changed files with 92 additions and 17 deletions

View File

@ -627,6 +627,9 @@ runTests() {
# Post-condition: valid-pod POD is created # Post-condition: valid-pod POD is created
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:' kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:'
## Patch can modify a local object
kubectl patch --local -f pkg/api/validation/testdata/v1/validPod.yaml --patch='{"spec": {"restartPolicy":"Never"}}' -o jsonpath='{.spec.restartPolicy}' | grep -q "Never"
## Patch pod can change image ## Patch pod can change image
# Command # Command
kubectl patch "${kube_flags[@]}" pod valid-pod --record -p='{"spec":{"containers":[{"name": "kubernetes-serve-hostname", "image": "nginx"}]}}' kubectl patch "${kube_flags[@]}" pod valid-pod --record -p='{"spec":{"containers":[{"name": "kubernetes-serve-hostname", "image": "nginx"}]}}'

View File

@ -21,13 +21,16 @@ import (
"io" "io"
"strings" "strings"
"github.com/evanphx/json-patch"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/util/strategicpatch"
"k8s.io/kubernetes/pkg/util/yaml" "k8s.io/kubernetes/pkg/util/yaml"
) )
@ -38,6 +41,9 @@ var patchTypes = map[string]api.PatchType{"json": api.JSONPatchType, "merge": ap
type PatchOptions struct { type PatchOptions struct {
Filenames []string Filenames []string
Recursive bool Recursive bool
Local bool
OutputFormat string
} }
const ( const (
@ -78,9 +84,8 @@ func NewCmdPatch(f *cmdutil.Factory, out io.Writer) *cobra.Command {
Long: patch_long, Long: patch_long,
Example: patch_example, Example: patch_example,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) options.OutputFormat = cmdutil.GetFlagString(cmd, "output")
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" err := RunPatch(f, out, cmd, args, options)
err := RunPatch(f, out, cmd, args, shortOutput, options)
cmdutil.CheckErr(err) cmdutil.CheckErr(err)
}, },
ValidArgs: validArgs, ValidArgs: validArgs,
@ -89,17 +94,25 @@ func NewCmdPatch(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.") cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.")
cmd.MarkFlagRequired("patch") cmd.MarkFlagRequired("patch")
cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List())) cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List()))
cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddPrinterFlags(cmd)
cmdutil.AddRecordFlag(cmd) cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd) cmdutil.AddInclude3rdPartyFlags(cmd)
usage := "Filename, directory, or URL to a file identifying the resource to update" usage := "Filename, directory, or URL to a file identifying the resource to update"
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
cmdutil.AddRecursiveFlag(cmd, &options.Recursive) cmdutil.AddRecursiveFlag(cmd, &options.Recursive)
cmd.Flags().BoolVar(&options.Local, "local", false, "If true, patch will operate on the content of the file, not the server-side resource.")
return cmd return cmd
} }
func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool, options *PatchOptions) error { func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *PatchOptions) error {
switch {
case options.Local && len(args) != 0:
return fmt.Errorf("cannot specify --local and server resources")
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace() cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil { if err != nil {
return err return err
@ -149,6 +162,7 @@ func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
return err return err
} }
if !options.Local {
helper := resource.NewHelper(client, mapping) helper := resource.NewHelper(client, mapping)
patchedObject, err := helper.Patch(namespace, name, patchType, patchBytes) patchedObject, err := helper.Patch(namespace, name, patchType, patchBytes)
if err != nil { if err != nil {
@ -164,7 +178,44 @@ func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
} }
} }
count++ count++
cmdutil.PrintSuccess(mapper, shortOutput, out, "", name, "patched")
if options.OutputFormat == "name" || len(options.OutputFormat) == 0 {
cmdutil.PrintSuccess(mapper, options.OutputFormat == "name", out, "", name, "patched")
}
return nil
}
count++
patchedObj, err := api.Scheme.DeepCopy(info.VersionedObject)
if err != nil {
return err
}
originalObjJS, err := runtime.Encode(api.Codecs.LegacyCodec(), info.VersionedObject.(runtime.Object))
if err != nil {
return err
}
originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, patchedObj.(runtime.Object))
if err != nil {
return err
}
targetObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), originalPatchedObjJS)
if err != nil {
return err
}
// TODO: if we ever want to go generic, this allows a clean -o yaml without trying to print columns or anything
// rawExtension := &runtime.Unknown{
// Raw: originalPatchedObjJS,
// }
printer, err := f.PrinterForMapping(cmd, mapping, false)
if err != nil {
return err
}
if err := printer.PrintObj(targetObj, out); err != nil {
return err
}
return nil return nil
}) })
if err != nil { if err != nil {
@ -175,3 +226,24 @@ func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
} }
return nil return nil
} }
func getPatchedJSON(patchType api.PatchType, originalJS, patchJS []byte, obj runtime.Object) ([]byte, error) {
switch patchType {
case api.JSONPatchType:
patchObj, err := jsonpatch.DecodePatch(patchJS)
if err != nil {
return nil, err
}
return patchObj.Apply(originalJS)
case api.MergePatchType:
return jsonpatch.MergePatch(originalJS, patchJS)
case api.StrategicMergePatchType:
return strategicpatch.StrategicMergePatchData(originalJS, patchJS, obj)
default:
// only here as a safety net - go-restful filters content-type
return nil, fmt.Errorf("unknown Content-Type header for patch: %v", patchType)
}
}