diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/delete/BUILD b/staging/src/k8s.io/kubectl/pkg/cmd/delete/BUILD index 108d231c382..ed90898d7f4 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/delete/BUILD +++ b/staging/src/k8s.io/kubectl/pkg/cmd/delete/BUILD @@ -41,6 +41,7 @@ go_test( "//staging/src/k8s.io/cli-runtime/pkg/resource:go_default_library", "//staging/src/k8s.io/client-go/rest/fake:go_default_library", "//staging/src/k8s.io/kubectl/pkg/cmd/testing:go_default_library", + "//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library", "//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", ], diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/delete/delete.go b/staging/src/k8s.io/kubectl/pkg/cmd/delete/delete.go index feb751bc6e2..d5fa6e8d514 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/delete/delete.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/delete/delete.go @@ -115,6 +115,9 @@ type DeleteOptions struct { GracePeriod int Timeout time.Duration + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.DryRunVerifier + Output string DynamicClient dynamic.Interface @@ -143,6 +146,7 @@ func NewCmdDelete(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra } deleteFlags.AddFlags(cmd) + cmdutil.AddDryRunFlag(cmd) return cmd } @@ -173,6 +177,20 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Co o.GracePeriod = 1 } + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + dynamicClient, err := f.DynamicClient() + if err != nil { + return err + } + discoveryClient, err := f.ToDiscoveryClient() + if err != nil { + return err + } + o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient) + if len(o.Raw) == 0 { r := f.NewBuilder(). Unstructured(). @@ -291,6 +309,18 @@ func (o *DeleteOptions) DeleteResult(r *resource.Result) error { fmt.Fprintf(o.ErrOut, "warning: deleting cluster-scoped resources, not scoped to the provided namespace\n") warnClusterScope = false } + + if o.DryRunStrategy == cmdutil.DryRunClient { + if !o.Quiet { + o.PrintObj(info) + } + return nil + } + if o.DryRunStrategy == cmdutil.DryRunServer { + if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil { + return err + } + } response, err := o.deleteResource(info, options) if err != nil { return err @@ -330,6 +360,11 @@ func (o *DeleteOptions) DeleteResult(r *resource.Result) error { return nil } + // If we are dry-running, then we don't want to wait + if o.DryRunStrategy != cmdutil.DryRunNone { + return nil + } + effectiveTimeout := o.Timeout if effectiveTimeout == 0 { // if we requested to wait forever, set it to a week. @@ -356,7 +391,10 @@ func (o *DeleteOptions) DeleteResult(r *resource.Result) error { } func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) (runtime.Object, error) { - deleteResponse, err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions) + deleteResponse, err := resource. + NewHelper(info.Client, info.Mapping). + DryRun(o.DryRunStrategy == cmdutil.DryRunServer). + DeleteWithOptions(info.Namespace, info.Name, deleteOptions) if err != nil { return nil, cmdutil.AddSourceToErr("deleting", info.Source, err) } @@ -381,6 +419,13 @@ func (o *DeleteOptions) PrintObj(info *resource.Info) { operation = "force deleted" } + switch o.DryRunStrategy { + case cmdutil.DryRunClient: + operation = fmt.Sprintf("%s (dry run)", operation) + case cmdutil.DryRunServer: + operation = fmt.Sprintf("%s (server dry run)", operation) + } + if o.Output == "name" { // -o name: prints resource/name fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/delete/delete_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/delete/delete_test.go index 305e4e162c2..e9f7a04696a 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/delete/delete_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/delete/delete_test.go @@ -33,6 +33,7 @@ import ( "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/rest/fake" cmdtesting "k8s.io/kubectl/pkg/cmd/testing" + cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/scheme" ) @@ -42,6 +43,7 @@ func fakecmd() *cobra.Command { DisableFlagsInUseLine: true, Run: func(cmd *cobra.Command, args []string) {}, } + cmdutil.AddDryRunFlag(cmd) return cmd } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/replace/replace.go b/staging/src/k8s.io/kubectl/pkg/cmd/replace/replace.go index e5d2881143a..7b514f82ad7 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/replace/replace.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/replace/replace.go @@ -74,6 +74,9 @@ type ReplaceOptions struct { DeleteFlags *delete.DeleteFlags DeleteOptions *delete.DeleteOptions + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.DryRunVerifier + PrintObj func(obj runtime.Object) error createAnnotation bool @@ -123,6 +126,7 @@ func NewCmdReplace(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobr cmdutil.AddValidateFlags(cmd) cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddDryRunFlag(cmd) cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to PUT to the server. Uses the transport specified by the kubeconfig file.") @@ -141,6 +145,21 @@ func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] o.validate = cmdutil.GetFlagBool(cmd, "validate") o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + dynamicClient, err := f.DynamicClient() + if err != nil { + return err + } + discoveryClient, err := f.ToDiscoveryClient() + if err != nil { + return err + } + o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient) + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + printer, err := o.PrintFlags.ToPrinter() if err != nil { return err @@ -149,10 +168,6 @@ func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] return printer.PrintObj(obj, o.Out) } - dynamicClient, err := f.DynamicClient() - if err != nil { - return err - } deleteOpts := o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams) //Replace will create a resource if it doesn't exist already, so ignore not found error @@ -264,8 +279,20 @@ func (o *ReplaceOptions) Run(f cmdutil.Factory) error { klog.V(4).Infof("error recording current command: %v", err) } + if o.DryRunStrategy == cmdutil.DryRunClient { + return o.PrintObj(info.Object) + } + if o.DryRunStrategy == cmdutil.DryRunServer { + if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil { + return err + } + } + // Serialize the object with the annotation applied. - obj, err := resource.NewHelper(info.Client, info.Mapping).Replace(info.Namespace, info.Name, true, info.Object) + obj, err := resource. + NewHelper(info.Client, info.Mapping). + DryRun(o.DryRunStrategy == cmdutil.DryRunServer). + Replace(info.Namespace, info.Name, true, info.Object) if err != nil { return cmdutil.AddSourceToErr("replacing", info.Source, err) } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/taint/BUILD b/staging/src/k8s.io/kubectl/pkg/cmd/taint/BUILD index bc632caf807..8409bfd7848 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/taint/BUILD +++ b/staging/src/k8s.io/kubectl/pkg/cmd/taint/BUILD @@ -12,6 +12,7 @@ go_library( deps = [ "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/taint/taint.go b/staging/src/k8s.io/kubectl/pkg/cmd/taint/taint.go index 498ff9ea312..9be64c5de27 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/taint/taint.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/taint/taint.go @@ -24,8 +24,9 @@ import ( "github.com/spf13/cobra" "k8s.io/klog" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/strategicpatch" @@ -44,6 +45,9 @@ type TaintOptions struct { PrintFlags *genericclioptions.PrintFlags ToPrinter func(string) (printers.ResourcePrinter, error) + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.DryRunVerifier + resources []string taintsToAdd []v1.Taint taintsToRemove []v1.Taint @@ -109,6 +113,7 @@ func NewCmdTaint(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra. } options.PrintFlags.AddFlags(cmd) + cmdutil.AddDryRunFlag(cmd) cmdutil.AddValidateFlags(cmd) cmd.Flags().StringVarP(&options.selector, "selector", "l", options.selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") @@ -124,6 +129,21 @@ func (o *TaintOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st return err } + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + dynamicClient, err := f.DynamicClient() + if err != nil { + return err + } + discoveryClient, err := f.ToDiscoveryClient() + if err != nil { + return err + } + o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient) + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + // retrieves resource and taint args from args // also checks args to verify that all resources are specified before taints taintArgs := []string{} @@ -261,12 +281,54 @@ func (o TaintOptions) RunTaint() error { klog.V(2).Infof("couldn't compute patch: %v", err) } + printer, err := o.ToPrinter(operation) + if err != nil { + return err + } + if o.DryRunStrategy == cmdutil.DryRunClient { + if createdPatch { + typedObj, err := scheme.Scheme.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion()) + if err != nil { + return err + } + + nodeObj, ok := typedObj.(*v1.Node) + if !ok { + return fmt.Errorf("unexpected type %T", typedObj) + } + + originalObjJS, err := json.Marshal(nodeObj) + if err != nil { + return err + } + + originalPatchedObjJS, err := strategicpatch.StrategicMergePatch(originalObjJS, patchBytes, nodeObj) + if err != nil { + return err + } + + targetObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalPatchedObjJS) + if err != nil { + return err + } + return printer.PrintObj(targetObj, o.Out) + } + return printer.PrintObj(obj, o.Out) + } + mapping := info.ResourceMapping() + if o.DryRunStrategy == cmdutil.DryRunServer { + if err := o.DryRunVerifier.HasSupport(mapping.GroupVersionKind); err != nil { + return err + } + } client, err := o.ClientForMapping(mapping) if err != nil { return err } - helper := resource.NewHelper(client, mapping) + helper := resource. + NewHelper(client, mapping). + DryRun(o.DryRunStrategy == cmdutil.DryRunServer) var outputObj runtime.Object if createdPatch { @@ -278,10 +340,6 @@ func (o TaintOptions) RunTaint() error { return err } - printer, err := o.ToPrinter(operation) - if err != nil { - return err - } return printer.PrintObj(outputObj, o.Out) }) } diff --git a/test/cmd/core.sh b/test/cmd/core.sh index f52267ddb90..893f5bf9b57 100755 --- a/test/cmd/core.sh +++ b/test/cmd/core.sh @@ -583,6 +583,34 @@ run_pod_tests() { } } __EOF__ + kube::test::get_object_assert "node node-v1-test" "{{range.items}}{{if .metadata.annotations.a}}found{{end}}{{end}}:" ':' + + # Dry-run command + kubectl replace --dry-run=server -f - "${kube_flags[@]}" << __EOF__ +{ + "kind": "Node", + "apiVersion": "v1", + "metadata": { + "name": "node-v1-test", + "annotations": {"a":"b"}, + "resourceVersion": "0" + } +} +__EOF__ + kubectl replace --dry-run=client -f - "${kube_flags[@]}" << __EOF__ +{ + "kind": "Node", + "apiVersion": "v1", + "metadata": { + "name": "node-v1-test", + "annotations": {"a":"b"}, + "resourceVersion": "0" + } +} +__EOF__ + kube::test::get_object_assert "node node-v1-test" "{{range.items}}{{if .metadata.annotations.a}}found{{end}}{{end}}:" ':' + + # Command kubectl replace -f - "${kube_flags[@]}" << __EOF__ { "kind": "Node", diff --git a/test/cmd/delete.sh b/test/cmd/delete.sh index 0b8a723a474..d16b01ed8ed 100755 --- a/test/cmd/delete.sh +++ b/test/cmd/delete.sh @@ -32,6 +32,15 @@ run_kubectl_delete_allnamespaces_tests() { kubectl create configmap "two" --namespace="${ns_two}" kubectl label configmap "one" --namespace="${ns_one}" deletetest=true kubectl label configmap "two" --namespace="${ns_two}" deletetest=true + + # dry-run + kubectl delete configmap --dry-run=client -l deletetest=true --all-namespaces + kubectl delete configmap --dry-run=server -l deletetest=true --all-namespaces + kubectl config set-context "${CONTEXT}" --namespace="${ns_one}" + kube::test::get_object_assert configmap "{{range.items}}{{${id_field:?}}}:{{end}}" 'one:' + kubectl config set-context "${CONTEXT}" --namespace="${ns_two}" + kube::test::get_object_assert configmap "{{range.items}}{{${id_field:?}}}:{{end}}" 'two:' + kubectl delete configmap -l deletetest=true --all-namespaces # no configmaps should be in either of those namespaces diff --git a/test/cmd/node-management.sh b/test/cmd/node-management.sh index 74db489d047..1a12af304cb 100755 --- a/test/cmd/node-management.sh +++ b/test/cmd/node-management.sh @@ -75,6 +75,10 @@ __EOF__ # taint/untaint # Pre-condition: node doesn't have dedicated=foo:PreferNoSchedule taint kube::test::get_object_assert "nodes 127.0.0.1" '{{range .spec.taints}}{{if eq .key \"dedicated\"}}{{.key}}={{.value}}:{{.effect}}{{end}}{{end}}' "" # expect no output + # Dry-run + kubectl taint node 127.0.0.1 --dry-run=client dedicated=foo:PreferNoSchedule + kubectl taint node 127.0.0.1 --dry-run=server dedicated=foo:PreferNoSchedule + kube::test::get_object_assert "nodes 127.0.0.1" '{{range .spec.taints}}{{if eq .key \"dedicated\"}}{{.key}}={{.value}}:{{.effect}}{{end}}{{end}}' "" # expect no output # taint can add a taint (=:) kubectl taint node 127.0.0.1 dedicated=foo:PreferNoSchedule kube::test::get_object_assert "nodes 127.0.0.1" '{{range .spec.taints}}{{if eq .key \"dedicated\"}}{{.key}}={{.value}}:{{.effect}}{{end}}{{end}}' "dedicated=foo:PreferNoSchedule" @@ -83,6 +87,10 @@ __EOF__ # taint can add a taint (:) kubectl taint node 127.0.0.1 dedicated:PreferNoSchedule kube::test::get_object_assert "nodes 127.0.0.1" '{{range .spec.taints}}{{if eq .key \"dedicated\"}}{{.key}}={{.value}}:{{.effect}}{{end}}{{end}}' "dedicated=:PreferNoSchedule" + # Dry-run remove a taint + kubectl taint node 127.0.0.1 --dry-run=client dedicated- + kubectl taint node 127.0.0.1 --dry-run=server dedicated- + kube::test::get_object_assert "nodes 127.0.0.1" '{{range .spec.taints}}{{if eq .key \"dedicated\"}}{{.key}}={{.value}}:{{.effect}}{{end}}{{end}}' "dedicated=:PreferNoSchedule" # taint can remove a taint kubectl taint node 127.0.0.1 dedicated- # Post-condition: node doesn't have dedicated=foo:PreferNoSchedule taint