Merge pull request #88292 from julianvmodesto/add-dry-run

Add --dry-run=server|client|none to more kubectl commands
This commit is contained in:
Kubernetes Prow Robot 2020-02-20 16:30:25 -08:00 committed by GitHub
commit 1591590030
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 191 additions and 12 deletions

View File

@ -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",
],

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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",

View File

@ -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)
})
}

View File

@ -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",

View File

@ -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

View File

@ -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 (<key>=<value>:<effect>)
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 (<key>:<effect>)
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=<no value>: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=<no value>:PreferNoSchedule"
# taint can remove a taint
kubectl taint node 127.0.0.1 dedicated-
# Post-condition: node doesn't have dedicated=foo:PreferNoSchedule taint