Bumping k8s dependencies to 1.13

This commit is contained in:
Cheng Xing
2018-11-16 14:08:25 -08:00
parent 305407125c
commit b4c0b68ec7
8002 changed files with 884099 additions and 276228 deletions

View File

@@ -57,12 +57,7 @@ go_library(
],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/validation:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/apply/parse:go_default_library",
"//pkg/kubectl/apply/strategy:go_default_library",
@@ -79,11 +74,7 @@ go_library(
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/cmd/wait:go_default_library",
"//pkg/kubectl/explain:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/metricsutil:go_default_library",
"//pkg/kubectl/plugins:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//pkg/kubectl/proxy:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
@@ -95,6 +86,56 @@ go_library(
"//pkg/util/interrupt:go_default_library",
"//pkg/util/taints:go_default_library",
"//pkg/version:go_default_library",
"//staging/src/k8s.io/api/autoscaling/v1:go_default_library",
"//staging/src/k8s.io/api/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/policy/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/version:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/printers:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library",
"//staging/src/k8s.io/client-go/discovery:go_default_library",
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/apps/v1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/autoscaling/v1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/batch/v1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/scale:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/tools/portforward:go_default_library",
"//staging/src/k8s.io/client-go/tools/remotecommand:go_default_library",
"//staging/src/k8s.io/client-go/tools/watch:go_default_library",
"//staging/src/k8s.io/client-go/transport/spdy:go_default_library",
"//staging/src/k8s.io/metrics/pkg/apis/metrics:go_default_library",
"//staging/src/k8s.io/metrics/pkg/apis/metrics/v1beta1:go_default_library",
"//staging/src/k8s.io/metrics/pkg/client/clientset/versioned:go_default_library",
"//vendor/github.com/daviddengcn/go-colortext:go_default_library",
"//vendor/github.com/docker/distribution/reference:go_default_library",
"//vendor/github.com/docker/docker/pkg/term:go_default_library",
@@ -105,50 +146,7 @@ go_library(
"//vendor/github.com/renstrom/dedent:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/autoscaling/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/jsonmergepatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/autoscaling/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/scale:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/portforward:go_default_library",
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/k8s.io/client-go/transport/spdy:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
"//vendor/k8s.io/metrics/pkg/apis/metrics:go_default_library",
"//vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1:go_default_library",
"//vendor/k8s.io/metrics/pkg/client/clientset_generated/clientset:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],
)
@@ -174,7 +172,6 @@ go_test(
"label_test.go",
"logs_test.go",
"patch_test.go",
"plugin_test.go",
"portforward_test.go",
"replace_test.go",
"rollingupdate_test.go",
@@ -198,48 +195,46 @@ go_test(
"//pkg/apis/batch:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/kubectl/cmd/create:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/plugins:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/kubectl/util/term:go_default_library",
"//pkg/printers:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/policy/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1: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/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testing:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/version:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/printers:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library",
"//staging/src/k8s.io/client-go/dynamic/fake:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
"//staging/src/k8s.io/client-go/testing:go_default_library",
"//staging/src/k8s.io/client-go/tools/remotecommand:go_default_library",
"//staging/src/k8s.io/metrics/pkg/apis/metrics/v1alpha1:go_default_library",
"//staging/src/k8s.io/metrics/pkg/apis/metrics/v1beta1:go_default_library",
"//staging/src/k8s.io/metrics/pkg/client/clientset/versioned/fake:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch/testing:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/client-go/dynamic/fake:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/k8s.io/metrics/pkg/apis/metrics/v1alpha1:go_default_library",
"//vendor/k8s.io/metrics/pkg/apis/metrics/v1beta1:go_default_library",
"//vendor/k8s.io/metrics/pkg/client/clientset_generated/clientset/fake:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],
)

View File

@@ -19,9 +19,9 @@ package cmd
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -31,12 +31,12 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@@ -294,7 +294,7 @@ func (o AnnotateOptions) RunAnnotate() error {
helper := resource.NewHelper(client, mapping)
if createdPatch {
outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes)
outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes, nil)
} else {
outputObj, err = helper.Replace(namespace, name, false, obj)
}

View File

@@ -26,10 +26,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -455,7 +454,7 @@ func TestAnnotateObject(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
@@ -508,7 +507,7 @@ func TestAnnotateObjectFromFile(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
@@ -593,7 +592,7 @@ func TestAnnotateMultipleObjects(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
NegotiatedSerializer: unstructuredSerializer,

View File

@@ -27,9 +27,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/printers"
)

View File

@@ -23,10 +23,10 @@ import (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/discovery"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -39,6 +39,9 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/client-go/dynamic"
oapi "k8s.io/kube-openapi/pkg/util/proto"
api "k8s.io/kubernetes/pkg/apis/core"
@@ -46,9 +49,6 @@ import (
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/kubectl/validation"
@@ -66,6 +66,7 @@ type ApplyOptions struct {
Selector string
DryRun bool
ServerDryRun bool
Prune bool
PruneResources []pruneResource
cmdBaseName string
@@ -172,6 +173,7 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions
cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types.")
cmd.Flags().StringArrayVar(&o.PruneWhitelist, "prune-whitelist", o.PruneWhitelist, "Overwrite the default whitelist with <group/version/kind> for --prune")
cmd.Flags().BoolVar(&o.OpenApiPatch, "openapi-patch", o.OpenApiPatch, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.")
cmd.Flags().BoolVar(&o.ServerDryRun, "server-dry-run", o.ServerDryRun, "If true, request will be sent to server with dry-run flag, which means the modifications won't be persisted. This is an alpha feature and flag.")
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd)
@@ -186,13 +188,19 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions
func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
o.DryRun = cmdutil.GetDryRunFlag(cmd)
if o.DryRun && o.ServerDryRun {
return fmt.Errorf("--dry-run and --server-dry-run can't be used together")
}
// allow for a success message operation to be specified at print time
o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
o.PrintFlags.NamePrintFlags.Operation = operation
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
if o.ServerDryRun {
o.PrintFlags.Complete("%s (server dry run)")
}
return o.PrintFlags.ToPrinter()
}
@@ -354,7 +362,11 @@ func (o *ApplyOptions) Run() error {
if !o.DryRun {
// Then create the resource and skip the three-way merge
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
options := metav1.CreateOptions{}
if o.ServerDryRun {
options.DryRun = []string{metav1.DryRunAll}
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, &options)
if err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
@@ -402,6 +414,7 @@ func (o *ApplyOptions) Run() error {
cascade: o.DeleteOptions.Cascade,
timeout: o.DeleteOptions.Timeout,
gracePeriod: o.DeleteOptions.GracePeriod,
serverDryRun: o.ServerDryRun,
openapiSchema: openapiSchema,
}
@@ -483,9 +496,10 @@ func (o *ApplyOptions) Run() error {
labelSelector: o.Selector,
visitedUids: visitedUids,
cascade: o.DeleteOptions.Cascade,
dryRun: o.DryRun,
gracePeriod: o.DeleteOptions.GracePeriod,
cascade: o.DeleteOptions.Cascade,
dryRun: o.DryRun,
serverDryRun: o.ServerDryRun,
gracePeriod: o.DeleteOptions.GracePeriod,
toPrinter: o.ToPrinter,
@@ -572,9 +586,10 @@ type pruner struct {
labelSelector string
fieldSelector string
cascade bool
dryRun bool
gracePeriod int
cascade bool
serverDryRun bool
dryRun bool
gracePeriod int
toPrinter func(string) (printers.ResourcePrinter, error)
@@ -629,14 +644,17 @@ func (p *pruner) prune(namespace string, mapping *meta.RESTMapping, includeUnini
}
func (p *pruner) delete(namespace, name string, mapping *meta.RESTMapping) error {
return runDelete(namespace, name, mapping, p.dynamicClient, p.cascade, p.gracePeriod)
return runDelete(namespace, name, mapping, p.dynamicClient, p.cascade, p.gracePeriod, p.serverDryRun)
}
func runDelete(namespace, name string, mapping *meta.RESTMapping, c dynamic.Interface, cascade bool, gracePeriod int) error {
func runDelete(namespace, name string, mapping *meta.RESTMapping, c dynamic.Interface, cascade bool, gracePeriod int, serverDryRun bool) error {
options := &metav1.DeleteOptions{}
if gracePeriod >= 0 {
options = metav1.NewDeleteOptions(int64(gracePeriod))
}
if serverDryRun {
options.DryRun = []string{metav1.DryRunAll}
}
policy := metav1.DeletePropagationForeground
if !cascade {
policy = metav1.DeletePropagationOrphan
@@ -646,7 +664,7 @@ func runDelete(namespace, name string, mapping *meta.RESTMapping, c dynamic.Inte
}
func (p *patcher) delete(namespace, name string) error {
return runDelete(namespace, name, p.mapping, p.dynamicClient, p.cascade, p.gracePeriod)
return runDelete(namespace, name, p.mapping, p.dynamicClient, p.cascade, p.gracePeriod, p.serverDryRun)
}
type patcher struct {
@@ -657,10 +675,11 @@ type patcher struct {
overwrite bool
backOff clockwork.Clock
force bool
cascade bool
timeout time.Duration
gracePeriod int
force bool
cascade bool
timeout time.Duration
gracePeriod int
serverDryRun bool
openapiSchema openapi.Resources
}
@@ -736,7 +755,12 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names
return patch, obj, nil
}
patchedObj, err := p.helper.Patch(namespace, name, patchType, patch)
options := metav1.UpdateOptions{}
if p.serverDryRun {
options.DryRun = []string{metav1.DryRunAll}
}
patchedObj, err := p.helper.Patch(namespace, name, patchType, patch, &options)
return patch, patchedObj, err
}
@@ -753,7 +777,7 @@ func (p *patcher) patch(current runtime.Object, modified []byte, source, namespa
}
patchBytes, patchObject, err = p.patchSimple(current, modified, source, namespace, name, errOut)
}
if err != nil && errors.IsConflict(err) && p.force {
if err != nil && (errors.IsConflict(err) || errors.IsInvalid(err)) && p.force {
patchBytes, patchObject, err = p.deleteAndCreate(current, modified, namespace, name)
}
return patchBytes, patchObject, err
@@ -776,11 +800,15 @@ func (p *patcher) deleteAndCreate(original runtime.Object, modified []byte, name
if err != nil {
return modified, nil, err
}
createdObject, err := p.helper.Create(namespace, true, versionedObject)
options := metav1.CreateOptions{}
if p.serverDryRun {
options.DryRun = []string{metav1.DryRunAll}
}
createdObject, err := p.helper.Create(namespace, true, versionedObject, &options)
if err != nil {
// restore the original object if we fail to create the new one
// but still propagate and advertise error to user
recreated, recreateErr := p.helper.Create(namespace, true, original)
recreated, recreateErr := p.helper.Create(namespace, true, original, &options)
if recreateErr != nil {
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\n", err, recreateErr)
} else {

View File

@@ -19,10 +19,10 @@ package cmd
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
var (
@@ -76,10 +76,10 @@ func NewCmdApplyEditLastApplied(f cmdutil.Factory, ioStreams genericclioptions.I
// bind flag structs
o.RecordFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
usage := "to use to edit the resource"
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "Output format. One of: yaml|json.")
cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings,
"Defaults to the line ending native to your platform.")
cmdutil.AddIncludeUninitializedFlag(cmd)

View File

@@ -26,13 +26,13 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@@ -200,7 +200,7 @@ func (o *SetLastAppliedOptions) RunSetLastApplied() error {
return err
}
helper := resource.NewHelper(client, mapping)
finalObj, err = helper.Patch(o.namespace, info.Name, patch.PatchType, patch.Patch)
finalObj, err = helper.Patch(o.namespace, info.Name, patch.PatchType, patch.Patch, nil)
if err != nil {
return err
}

View File

@@ -38,18 +38,16 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
sptest "k8s.io/apimachinery/pkg/util/strategicpatch/testing"
"k8s.io/cli-runtime/pkg/genericclioptions"
dynamicfakeclient "k8s.io/client-go/dynamic/fake"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
clienttesting "k8s.io/client-go/testing"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/extensions"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -147,9 +145,9 @@ func readReplicationController(t *testing.T, filenameRC string) (string, []byte)
return metaAccessor.GetName(), rcBytes
}
func readReplicationControllerFromFile(t *testing.T, filename string) *api.ReplicationController {
func readReplicationControllerFromFile(t *testing.T, filename string) *corev1.ReplicationController {
data := readBytesFromFile(t, filename)
rc := api.ReplicationController{}
rc := corev1.ReplicationController{}
if err := runtime.DecodeInto(testapi.Default.Codec(), data, &rc); err != nil {
t.Fatal(err)
}
@@ -166,9 +164,9 @@ func readUnstructuredFromFile(t *testing.T, filename string) *unstructured.Unstr
return &unst
}
func readServiceFromFile(t *testing.T, filename string) *api.Service {
func readServiceFromFile(t *testing.T, filename string) *corev1.Service {
data := readBytesFromFile(t, filename)
svc := api.Service{}
svc := corev1.Service{}
if err := runtime.DecodeInto(testapi.Default.Codec(), data, &svc); err != nil {
t.Fatal(err)
}
@@ -205,7 +203,7 @@ func annotateRuntimeObject(t *testing.T, originalObj, currentObj runtime.Object,
if currentAnnotations == nil {
currentAnnotations = make(map[string]string)
}
currentAnnotations[api.LastAppliedConfigAnnotation] = string(original)
currentAnnotations[corev1.LastAppliedConfigAnnotation] = string(original)
currentAccessor.SetAnnotations(currentAnnotations)
current, err := runtime.Encode(unstructured.JSONFallbackEncoder{Encoder: testapi.Default.Codec()}, currentObj)
if err != nil {
@@ -245,7 +243,7 @@ func validatePatchApplication(t *testing.T, req *http.Request) {
}
annotationsMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"})
if _, ok := annotationsMap[api.LastAppliedConfigAnnotation]; !ok {
if _, ok := annotationsMap[corev1.LastAppliedConfigAnnotation]; !ok {
t.Fatalf("patch does not contain annotation:\n%s\n", patch)
}
@@ -395,7 +393,7 @@ func TestRunApplyViewLastApplied(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
@@ -409,9 +407,9 @@ func TestRunApplyViewLastApplied(t *testing.T) {
bodyRC := ioutil.NopCloser(bytes.NewReader(test.respBytes))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case p == "/namespaces/test/replicationcontrollers/no-match" && m == "GET":
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &api.Pod{})}, nil
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &corev1.Pod{})}, nil
case p == "/api/v1/namespaces/test" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &corev1.Namespace{})}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
@@ -896,7 +894,7 @@ func TestApplyNULLPreservation(t *testing.T) {
t.Fatal(err)
}
annotationMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"})
if _, ok := annotationMap[api.LastAppliedConfigAnnotation]; !ok {
if _, ok := annotationMap[corev1.LastAppliedConfigAnnotation]; !ok {
t.Fatalf("patch does not contain annotation:\n%s\n", patch)
}
strategy := walkMapPath(t, patchMap, []string{"spec", "strategy"})
@@ -1122,7 +1120,7 @@ func TestRunApplySetLastApplied(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
@@ -1136,13 +1134,13 @@ func TestRunApplySetLastApplied(t *testing.T) {
bodyRC := ioutil.NopCloser(bytes.NewReader(noAnnotationRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case p == noExistPath && m == "GET":
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &api.Pod{})}, nil
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &corev1.Pod{})}, nil
case p == pathRC && m == "PATCH":
checkPatchString(t, req)
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case p == "/api/v1/namespaces/test" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &corev1.Namespace{})}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
@@ -1184,7 +1182,7 @@ func checkPatchString(t *testing.T, req *http.Request) {
}
annotationsMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"})
if _, ok := annotationsMap[api.LastAppliedConfigAnnotation]; !ok {
if _, ok := annotationsMap[corev1.LastAppliedConfigAnnotation]; !ok {
t.Fatalf("patch does not contain annotation:\n%s\n", patch)
}
@@ -1231,7 +1229,7 @@ func TestForceApply(t *testing.T) {
var bodyRC io.ReadCloser
if isScaledDownToZero {
rcObj := readReplicationControllerFromFile(t, filenameRC)
rcObj.Spec.Replicas = 0
rcObj.Spec.Replicas = int32ptr(0)
rcBytes, err := runtime.Encode(testapi.Default.Codec(), rcObj)
if err != nil {
t.Fatal(err)

View File

@@ -23,11 +23,11 @@ import (
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -17,7 +17,6 @@ limitations under the License.
package cmd
import (
"errors"
"fmt"
"io"
"net/url"
@@ -26,17 +25,17 @@ import (
"github.com/golang/glog"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@@ -62,14 +61,41 @@ const (
defaultPodLogsTimeout = 20 * time.Second
)
func NewCmdAttach(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := &AttachOptions{
// AttachOptions declare the arguments accepted by the Exec command
type AttachOptions struct {
StreamOptions
// whether to disable use of standard error when streaming output from tty
DisableStderr bool
CommandName string
SuggestedCmdUsage string
Pod *corev1.Pod
AttachFunc func(*AttachOptions, *corev1.Container, bool, remotecommand.TerminalSizeQueue) func() error
Resources []string
Builder func() *resource.Builder
AttachablePodFn polymorphichelpers.AttachableLogsForObjectFunc
restClientGetter genericclioptions.RESTClientGetter
Attach RemoteAttach
GetPodTimeout time.Duration
Config *restclient.Config
}
func NewAttachOptions(streams genericclioptions.IOStreams) *AttachOptions {
return &AttachOptions{
StreamOptions: StreamOptions{
IOStreams: streams,
},
Attach: &DefaultRemoteAttach{},
Attach: &DefaultRemoteAttach{},
AttachFunc: defaultAttachFunc,
}
}
func NewCmdAttach(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewAttachOptions(streams)
cmd := &cobra.Command{
Use: "attach (POD | TYPE/NAME) -c CONTAINER",
DisableFlagsInUseLine: true,
@@ -94,6 +120,29 @@ type RemoteAttach interface {
Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error
}
func defaultAttachFunc(o *AttachOptions, containerToAttach *corev1.Container, raw bool, sizeQueue remotecommand.TerminalSizeQueue) func() error {
return func() error {
restClient, err := restclient.RESTClientFor(o.Config)
if err != nil {
return err
}
req := restClient.Post().
Resource("pods").
Name(o.Pod.Name).
Namespace(o.Pod.Namespace).
SubResource("attach")
req.VersionedParams(&corev1.PodAttachOptions{
Container: containerToAttach.Name,
Stdin: o.Stdin,
Stdout: o.Out != nil,
Stderr: !o.DisableStderr,
TTY: raw,
}, legacyscheme.ParameterCodec)
return o.Attach.Attach("POST", req.URL(), o.Config, o.In, o.Out, o.ErrOut, raw, sizeQueue)
}
}
// DefaultRemoteAttach is the standard implementation of attaching
type DefaultRemoteAttach struct{}
@@ -111,63 +160,24 @@ func (*DefaultRemoteAttach) Attach(method string, url *url.URL, config *restclie
})
}
// AttachOptions declare the arguments accepted by the Exec command
type AttachOptions struct {
StreamOptions
CommandName string
SuggestedCmdUsage string
Pod *api.Pod
Attach RemoteAttach
PodClient coreclient.PodsGetter
GetPodTimeout time.Duration
Config *restclient.Config
}
// Complete verifies command line arguments and loads data from the command environment
func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []string) error {
if len(argsIn) == 0 {
return cmdutil.UsageErrorf(cmd, "at least 1 argument is required for attach")
}
if len(argsIn) > 2 {
return cmdutil.UsageErrorf(cmd, "expected POD, TYPE/NAME, or TYPE NAME, (at most 2 arguments) saw %d: %v", len(argsIn), argsIn)
}
namespace, _, err := f.ToRawKubeConfigLoader().Namespace()
func (o *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
p.GetPodTimeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
o.AttachablePodFn = polymorphichelpers.AttachablePodForObjectFn
o.GetPodTimeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
if err != nil {
return cmdutil.UsageErrorf(cmd, err.Error())
}
builder := f.NewBuilder().
WithScheme(legacyscheme.Scheme).
NamespaceParam(namespace).DefaultNamespace()
switch len(argsIn) {
case 1:
builder.ResourceNames("pods", argsIn[0])
case 2:
builder.ResourceNames(argsIn[0], argsIn[1])
}
obj, err := builder.Do().Object()
if err != nil {
return err
}
attachablePod, err := polymorphichelpers.AttachablePodForObjectFn(f, obj, p.GetPodTimeout)
if err != nil {
return err
}
p.PodName = attachablePod.Name
p.Namespace = namespace
o.Builder = f.NewBuilder
o.Resources = args
o.restClientGetter = f
fullCmdName := ""
cmdParent := cmd.Parent()
@@ -175,82 +185,85 @@ func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn [
fullCmdName = cmdParent.CommandPath()
}
if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "describe") {
p.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe pod/%s -n %s' to see all of the containers in this pod.", fullCmdName, p.PodName, p.Namespace)
o.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe pod/%s -n %s' to see all of the containers in this pod.", fullCmdName, o.PodName, o.Namespace)
}
config, err := f.ToRESTConfig()
if err != nil {
return err
}
p.Config = config
o.Config = config
clientset, err := f.ClientSet()
if err != nil {
return err
}
p.PodClient = clientset.Core()
if p.CommandName == "" {
p.CommandName = cmd.CommandPath()
if o.CommandName == "" {
o.CommandName = cmd.CommandPath()
}
return nil
}
// Validate checks that the provided attach options are specified.
func (p *AttachOptions) Validate() error {
allErrs := []error{}
if len(p.PodName) == 0 {
allErrs = append(allErrs, errors.New("pod name must be specified"))
func (o *AttachOptions) Validate() error {
if len(o.Resources) == 0 {
return fmt.Errorf("at least 1 argument is required for attach")
}
if p.Out == nil || p.ErrOut == nil {
allErrs = append(allErrs, errors.New("both output and error output must be provided"))
if len(o.Resources) > 2 {
return fmt.Errorf("expected POD, TYPE/NAME, or TYPE NAME, (at most 2 arguments) saw %d: %v", len(o.Resources), o.Resources)
}
if p.Attach == nil || p.PodClient == nil || p.Config == nil {
allErrs = append(allErrs, errors.New("client, client config, and attach must be provided"))
if o.GetPodTimeout <= 0 {
return fmt.Errorf("--pod-running-timeout must be higher than zero")
}
return utilerrors.NewAggregate(allErrs)
return nil
}
// Run executes a validated remote execution against a pod.
func (p *AttachOptions) Run() error {
if p.Pod == nil {
pod, err := p.PodClient.Pods(p.Namespace).Get(p.PodName, metav1.GetOptions{})
func (o *AttachOptions) Run() error {
if o.Pod == nil {
b := o.Builder().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
NamespaceParam(o.Namespace).DefaultNamespace()
switch len(o.Resources) {
case 1:
b.ResourceNames("pods", o.Resources[0])
case 2:
b.ResourceNames(o.Resources[0], o.Resources[1])
}
obj, err := b.Do().Object()
if err != nil {
return err
}
if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed {
return fmt.Errorf("cannot attach a container in a completed pod; current phase is %s", pod.Status.Phase)
o.Pod, err = o.findAttachablePod(obj)
if err != nil {
return err
}
p.Pod = pod
if o.Pod.Status.Phase == corev1.PodSucceeded || o.Pod.Status.Phase == corev1.PodFailed {
return fmt.Errorf("cannot attach a container in a completed pod; current phase is %s", o.Pod.Status.Phase)
}
// TODO: convert this to a clean "wait" behavior
}
pod := p.Pod
// check for TTY
containerToAttach, err := p.containerToAttachTo(pod)
containerToAttach, err := o.containerToAttachTo(o.Pod)
if err != nil {
return fmt.Errorf("cannot attach to the container: %v", err)
}
if p.TTY && !containerToAttach.TTY {
p.TTY = false
if p.ErrOut != nil {
fmt.Fprintf(p.ErrOut, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name)
if o.TTY && !containerToAttach.TTY {
o.TTY = false
if o.ErrOut != nil {
fmt.Fprintf(o.ErrOut, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name)
}
} else if !p.TTY && containerToAttach.TTY {
} else if !o.TTY && containerToAttach.TTY {
// the container was launched with a TTY, so we have to force a TTY here, otherwise you'll get
// an error "Unrecognized input header"
p.TTY = true
o.TTY = true
}
// ensure we can recover the terminal while attached
t := p.setupTTY()
// save p.Err so we can print the command prompt message below
stderr := p.ErrOut
t := o.setupTTY()
var sizeQueue remotecommand.TerminalSizeQueue
if t.Raw {
@@ -265,66 +278,52 @@ func (p *AttachOptions) Run() error {
sizeQueue = t.MonitorSize(&sizePlusOne, size)
}
// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
// true
p.ErrOut = nil
o.DisableStderr = true
}
fn := func() error {
restClient, err := restclient.RESTClientFor(p.Config)
if err != nil {
return err
}
// TODO: consider abstracting into a client invocation or client helper
req := restClient.Post().
Resource("pods").
Name(pod.Name).
Namespace(pod.Namespace).
SubResource("attach")
req.VersionedParams(&api.PodAttachOptions{
Container: containerToAttach.Name,
Stdin: p.Stdin,
Stdout: p.Out != nil,
Stderr: p.ErrOut != nil,
TTY: t.Raw,
}, legacyscheme.ParameterCodec)
return p.Attach.Attach("POST", req.URL(), p.Config, p.In, p.Out, p.ErrOut, t.Raw, sizeQueue)
if !o.Quiet {
fmt.Fprintln(o.ErrOut, "If you don't see a command prompt, try pressing enter.")
}
if !p.Quiet && stderr != nil {
fmt.Fprintln(stderr, "If you don't see a command prompt, try pressing enter.")
}
if err := t.Safe(fn); err != nil {
if err := t.Safe(o.AttachFunc(o, containerToAttach, t.Raw, sizeQueue)); err != nil {
return err
}
if p.Stdin && t.Raw && pod.Spec.RestartPolicy == api.RestartPolicyAlways {
fmt.Fprintf(p.Out, "Session ended, resume using '%s %s -c %s -i -t' command when the pod is running\n", p.CommandName, pod.Name, containerToAttach.Name)
if o.Stdin && t.Raw && o.Pod.Spec.RestartPolicy == corev1.RestartPolicyAlways {
fmt.Fprintf(o.Out, "Session ended, resume using '%s %s -c %s -i -t' command when the pod is running\n", o.CommandName, o.Pod.Name, containerToAttach.Name)
}
return nil
}
func (o *AttachOptions) findAttachablePod(obj runtime.Object) (*corev1.Pod, error) {
attachablePod, err := o.AttachablePodFn(o.restClientGetter, obj, o.GetPodTimeout)
if err != nil {
return nil, err
}
o.StreamOptions.PodName = attachablePod.Name
return attachablePod, nil
}
// containerToAttach returns a reference to the container to attach to, given
// by name or the first container if name is empty.
func (p *AttachOptions) containerToAttachTo(pod *api.Pod) (*api.Container, error) {
if len(p.ContainerName) > 0 {
func (o *AttachOptions) containerToAttachTo(pod *corev1.Pod) (*corev1.Container, error) {
if len(o.ContainerName) > 0 {
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == p.ContainerName {
if pod.Spec.Containers[i].Name == o.ContainerName {
return &pod.Spec.Containers[i], nil
}
}
for i := range pod.Spec.InitContainers {
if pod.Spec.InitContainers[i].Name == p.ContainerName {
if pod.Spec.InitContainers[i].Name == o.ContainerName {
return &pod.Spec.InitContainers[i], nil
}
}
return nil, fmt.Errorf("container not found (%s)", p.ContainerName)
return nil, fmt.Errorf("container not found (%s)", o.ContainerName)
}
if len(p.SuggestedCmdUsage) > 0 {
fmt.Fprintf(p.ErrOut, "Defaulting container name to %s.\n", pod.Spec.Containers[0].Name)
fmt.Fprintf(p.ErrOut, "%s\n", p.SuggestedCmdUsage)
if len(o.SuggestedCmdUsage) > 0 {
fmt.Fprintf(o.ErrOut, "Defaulting container name to %s.\n", pod.Spec.Containers[0].Name)
fmt.Fprintf(o.ErrOut, "%s\n", o.SuggestedCmdUsage)
}
glog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name)
@@ -332,8 +331,8 @@ func (p *AttachOptions) containerToAttachTo(pod *api.Pod) (*api.Container, error
}
// GetContainerName returns the name of the container to attach to, with a fallback.
func (p *AttachOptions) GetContainerName(pod *api.Pod) (string, error) {
c, err := p.containerToAttachTo(pod)
func (o *AttachOptions) GetContainerName(pod *corev1.Pod) (string, error) {
c, err := o.containerToAttachTo(pod)
if err != nil {
return "", err
}

View File

@@ -25,19 +25,16 @@ import (
"testing"
"time"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -53,130 +50,136 @@ func (f *fakeRemoteAttach) Attach(method string, url *url.URL, config *restclien
return f.err
}
func fakeAttachablePodFn(pod *corev1.Pod) polymorphichelpers.AttachableLogsForObjectFunc {
return func(getter genericclioptions.RESTClientGetter, obj runtime.Object, timeout time.Duration) (*corev1.Pod, error) {
return pod, nil
}
}
func TestPodAndContainerAttach(t *testing.T) {
tests := []struct {
args []string
p *AttachOptions
name string
expectError bool
expectedPod string
expectedContainer string
timeout time.Duration
obj runtime.Object
name string
args []string
options *AttachOptions
expectError string
expectedPodName string
expectedContainerName string
obj *corev1.Pod
}{
{
p: &AttachOptions{},
expectError: true,
name: "empty",
timeout: 1,
options: &AttachOptions{GetPodTimeout: 1},
expectError: "at least 1 argument is required",
},
{
p: &AttachOptions{},
args: []string{"one", "two", "three"},
expectError: true,
name: "too many args",
timeout: 2,
options: &AttachOptions{GetPodTimeout: 2},
args: []string{"one", "two", "three"},
expectError: "at most 2 arguments",
},
{
p: &AttachOptions{},
args: []string{"foo"},
expectedPod: "foo",
name: "no container, no flags",
name: "no container, no flags",
options: &AttachOptions{GetPodTimeout: defaultPodLogsTimeout},
args: []string{"foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: attachPod(),
},
{
name: "container in flag",
options: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "bar"}, GetPodTimeout: 10000000},
args: []string{"foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: attachPod(),
},
{
name: "init container in flag",
options: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "initfoo"}, GetPodTimeout: 30},
args: []string{"foo"},
expectedPodName: "foo",
expectedContainerName: "initfoo",
obj: attachPod(),
},
{
name: "non-existing container",
options: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "wrong"}, GetPodTimeout: 10},
args: []string{"foo"},
expectedPodName: "foo",
expectError: "container not found",
obj: attachPod(),
},
{
name: "no container, no flags, pods and name",
options: &AttachOptions{GetPodTimeout: 10000},
args: []string{"pods", "foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: attachPod(),
},
{
name: "invalid get pod timeout value",
options: &AttachOptions{GetPodTimeout: 0},
args: []string{"pod/foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: attachPod(),
timeout: defaultPodLogsTimeout,
},
{
p: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
args: []string{"foo"},
expectedPod: "foo",
expectedContainer: "bar",
name: "container in flag",
obj: attachPod(),
timeout: 10000000,
},
{
p: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "initfoo"}},
args: []string{"foo"},
expectedPod: "foo",
expectedContainer: "initfoo",
name: "init container in flag",
obj: attachPod(),
timeout: 30,
},
{
p: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
args: []string{"foo", "-c", "wrong"},
expectError: true,
name: "non-existing container in flag",
obj: attachPod(),
timeout: 10,
},
{
p: &AttachOptions{},
args: []string{"pods", "foo"},
expectedPod: "foo",
name: "no container, no flags, pods and name",
obj: attachPod(),
timeout: 10000,
},
{
p: &AttachOptions{},
args: []string{"pod/foo"},
expectedPod: "foo",
name: "no container, no flags, pod/name",
obj: attachPod(),
timeout: 1,
},
{
p: &AttachOptions{},
args: []string{"pod/foo"},
expectedPod: "foo",
name: "invalid get pod timeout value",
obj: attachPod(),
expectError: true,
timeout: 0,
expectError: "must be higher than zero",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
// setup opts to fetch our test pod
test.options.AttachablePodFn = fakeAttachablePodFn(test.obj)
test.options.Resources = test.args
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
if test.obj != nil {
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, test.obj)}, nil
}
return nil, nil
}),
}
tf.ClientConfigVal = defaultClientConfig()
cmd := &cobra.Command{}
options := test.p
cmdutil.AddPodRunningTimeoutFlag(cmd, test.timeout)
err := options.Complete(tf, cmd, test.args)
if test.expectError && err == nil {
t.Errorf("%s: unexpected non-error", test.name)
}
if !test.expectError && err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
}
if err != nil {
if err := test.options.Validate(); err != nil {
if !strings.Contains(err.Error(), test.expectError) {
t.Errorf("unexpected error: expected %q, got %q", test.expectError, err)
}
return
}
if options.PodName != test.expectedPod {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedPod, options.PodName)
pod, err := test.options.findAttachablePod(&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "test"},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foobar",
},
},
},
})
if err != nil {
if !strings.Contains(err.Error(), test.expectError) {
t.Errorf("unexpected error: expected %q, got %q", err, test.expectError)
}
return
}
if options.ContainerName != test.expectedContainer {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedContainer, options.ContainerName)
if pod.Name != test.expectedPodName {
t.Errorf("unexpected pod name: expected %q, got %q", test.expectedContainerName, pod.Name)
}
container, err := test.options.containerToAttachTo(attachPod())
if err != nil {
if !strings.Contains(err.Error(), test.expectError) {
t.Errorf("unexpected error: expected %q, got %q", err, test.expectError)
}
return
}
if container.Name != test.expectedContainerName {
t.Errorf("unexpected container name: expected %q, got %q", test.expectedContainerName, container.Name)
}
if test.options.PodName != test.expectedPodName {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedPodName, test.options.PodName)
}
if len(test.expectError) > 0 {
t.Fatalf("expected error %q, but saw none", test.expectError)
}
})
}
@@ -186,7 +189,7 @@ func TestAttach(t *testing.T) {
version := "v1"
tests := []struct {
name, version, podPath, fetchPodPath, attachPath, container string
pod *api.Pod
pod *corev1.Pod
remoteAttachErr bool
exepctedErr string
}{
@@ -226,8 +229,8 @@ func TestAttach(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
@@ -246,12 +249,13 @@ func TestAttach(t *testing.T) {
}
}),
}
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: scheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
remoteAttach := &fakeRemoteAttach{}
if test.remoteAttachErr {
remoteAttach.err = fmt.Errorf("attach error")
}
params := &AttachOptions{
options := &AttachOptions{
StreamOptions: StreamOptions{
ContainerName: test.container,
IOStreams: genericclioptions.NewTestIOStreamsDiscard(),
@@ -259,12 +263,24 @@ func TestAttach(t *testing.T) {
Attach: remoteAttach,
GetPodTimeout: 1000,
}
cmd := &cobra.Command{}
cmdutil.AddPodRunningTimeoutFlag(cmd, 1000)
if err := params.Complete(tf, cmd, []string{"foo"}); err != nil {
t.Fatal(err)
options.restClientGetter = tf
options.Namespace = "test"
options.Resources = []string{"foo"}
options.Builder = tf.NewBuilder
options.AttachablePodFn = fakeAttachablePodFn(test.pod)
options.AttachFunc = func(opts *AttachOptions, containerToAttach *corev1.Container, raw bool, sizeQueue remotecommand.TerminalSizeQueue) func() error {
return func() error {
u, err := url.Parse(fmt.Sprintf("%s?container=%s", test.attachPath, containerToAttach.Name))
if err != nil {
return err
}
return options.Attach.Attach("POST", u, nil, nil, nil, nil, raw, sizeQueue)
}
}
err := params.Run()
err := options.Run()
if test.exepctedErr != "" && err.Error() != test.exepctedErr {
t.Errorf("%s: Unexpected exec error: %v", test.name, err)
return
@@ -294,7 +310,7 @@ func TestAttachWarnings(t *testing.T) {
version := "v1"
tests := []struct {
name, container, version, podPath, fetchPodPath, expectedErr string
pod *api.Pod
pod *corev1.Pod
stdin, tty bool
}{
{
@@ -313,8 +329,10 @@ func TestAttachWarnings(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
streams, _, _, bufErr := genericclioptions.NewTestIOStreams()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
@@ -328,30 +346,42 @@ func TestAttachWarnings(t *testing.T) {
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)
t.Errorf("%s: unexpected request: %s %#v\n%#v", p, req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
}),
}
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
streams, _, _, bufErr := genericclioptions.NewTestIOStreams()
ex := &fakeRemoteAttach{}
params := &AttachOptions{
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: scheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
options := &AttachOptions{
StreamOptions: StreamOptions{
ContainerName: test.container,
IOStreams: streams,
Stdin: test.stdin,
TTY: test.tty,
ContainerName: test.container,
IOStreams: streams,
},
Attach: ex,
Attach: &fakeRemoteAttach{},
GetPodTimeout: 1000,
}
cmd := &cobra.Command{}
cmdutil.AddPodRunningTimeoutFlag(cmd, 1000)
if err := params.Complete(tf, cmd, []string{"foo"}); err != nil {
t.Fatal(err)
options.restClientGetter = tf
options.Namespace = "test"
options.Resources = []string{"foo"}
options.Builder = tf.NewBuilder
options.AttachablePodFn = fakeAttachablePodFn(test.pod)
options.AttachFunc = func(opts *AttachOptions, containerToAttach *corev1.Container, raw bool, sizeQueue remotecommand.TerminalSizeQueue) func() error {
return func() error {
u, err := url.Parse("http://foo.bar")
if err != nil {
return err
}
return options.Attach.Attach("POST", u, nil, nil, nil, nil, raw, sizeQueue)
}
}
if err := params.Run(); err != nil {
if err := options.Run(); err != nil {
t.Fatal(err)
}
@@ -367,25 +397,25 @@ func TestAttachWarnings(t *testing.T) {
}
}
func attachPod() *api.Pod {
return &api.Pod{
func attachPod() *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyAlways,
DNSPolicy: corev1.DNSClusterFirst,
Containers: []corev1.Container{
{
Name: "bar",
},
},
InitContainers: []api.Container{
InitContainers: []corev1.Container{
{
Name: "initfoo",
},
},
},
Status: api.PodStatus{
Phase: api.PodRunning,
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
},
}
}

View File

@@ -16,22 +16,24 @@ go_library(
"//build/visible_to:pkg_kubectl_cmd_auth_CONSUMERS",
],
deps = [
"//pkg/apis/authorization:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion:go_default_library",
"//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//pkg/registry/rbac/reconciliation:go_default_library",
"//staging/src/k8s.io/api/authorization/v1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1alpha1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/printers:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/authorization/v1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
],
)
@@ -57,8 +59,8 @@ go_test(
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
],
)

View File

@@ -18,7 +18,7 @@ package auth
import (
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

View File

@@ -24,12 +24,12 @@ import (
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
authorizationv1 "k8s.io/api/authorization/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
internalauthorizationclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion"
"k8s.io/cli-runtime/pkg/genericclioptions"
authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
@@ -40,7 +40,7 @@ type CanIOptions struct {
AllNamespaces bool
Quiet bool
Namespace string
SelfSARClient internalauthorizationclient.SelfSubjectAccessReviewsGetter
SelfSARClient authorizationv1client.SelfSubjectAccessReviewsGetter
Verb string
Resource schema.GroupVersionResource
@@ -138,11 +138,11 @@ func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
}
var err error
client, err := f.ClientSet()
client, err := f.KubernetesClientSet()
if err != nil {
return err
}
o.SelfSARClient = client.Authorization()
o.SelfSARClient = client.AuthorizationV1()
o.Namespace = ""
if !o.AllNamespaces {
@@ -168,11 +168,11 @@ func (o *CanIOptions) Validate() error {
}
func (o *CanIOptions) RunAccessCheck() (bool, error) {
var sar *authorizationapi.SelfSubjectAccessReview
var sar *authorizationv1.SelfSubjectAccessReview
if o.NonResourceURL == "" {
sar = &authorizationapi.SelfSubjectAccessReview{
Spec: authorizationapi.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
sar = &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: o.Namespace,
Verb: o.Verb,
Group: o.Resource.Group,
@@ -183,9 +183,9 @@ func (o *CanIOptions) RunAccessCheck() (bool, error) {
},
}
} else {
sar = &authorizationapi.SelfSubjectAccessReview{
Spec: authorizationapi.SelfSubjectAccessReviewSpec{
NonResourceAttributes: &authorizationapi.NonResourceAttributes{
sar = &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
NonResourceAttributes: &authorizationv1.NonResourceAttributes{
Verb: o.Verb,
Path: o.NonResourceURL,
},

View File

@@ -18,18 +18,21 @@ package auth
import (
"errors"
"fmt"
"github.com/golang/glog"
"github.com/spf13/cobra"
rbacv1 "k8s.io/api/rbac/v1"
rbacv1alpha1 "k8s.io/api/rbac/v1alpha1"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
rbacv1client "k8s.io/client-go/kubernetes/typed/rbac/v1"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/registry/rbac/reconciliation"
)
@@ -37,9 +40,11 @@ import (
// ReconcileOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
// referencing the cmd.Flags()
type ReconcileOptions struct {
PrintFlags *genericclioptions.PrintFlags
FilenameOptions *resource.FilenameOptions
DryRun bool
PrintFlags *genericclioptions.PrintFlags
FilenameOptions *resource.FilenameOptions
DryRun bool
RemoveExtraPermissions bool
RemoveExtraSubjects bool
Visitor resource.Visitor
RBACClient rbacv1client.RbacV1Interface
@@ -89,6 +94,8 @@ func NewCmdReconcile(f cmdutil.Factory, streams genericclioptions.IOStreams) *co
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to reconcile.")
cmd.Flags().BoolVar(&o.DryRun, "dry-run", o.DryRun, "If true, display results but do not submit changes")
cmd.Flags().BoolVar(&o.RemoveExtraPermissions, "remove-extra-permissions", o.RemoveExtraPermissions, "If true, removes extra permissions added to roles")
cmd.Flags().BoolVar(&o.RemoveExtraSubjects, "remove-extra-subjects", o.RemoveExtraSubjects, "If true, removes extra subjects added to rolebindings")
cmd.MarkFlagRequired("filename")
return cmd
@@ -174,7 +181,7 @@ func (o *ReconcileOptions) RunReconcile() error {
case *rbacv1.Role:
reconcileOptions := reconciliation.ReconcileRoleOptions{
Confirm: !o.DryRun,
RemoveExtraPermissions: false,
RemoveExtraPermissions: o.RemoveExtraPermissions,
Role: reconciliation.RoleRuleOwner{Role: t},
Client: reconciliation.RoleModifier{
NamespaceClient: o.NamespaceClient.Namespaces(),
@@ -190,7 +197,7 @@ func (o *ReconcileOptions) RunReconcile() error {
case *rbacv1.ClusterRole:
reconcileOptions := reconciliation.ReconcileRoleOptions{
Confirm: !o.DryRun,
RemoveExtraPermissions: false,
RemoveExtraPermissions: o.RemoveExtraPermissions,
Role: reconciliation.ClusterRoleRuleOwner{ClusterRole: t},
Client: reconciliation.ClusterRoleModifier{
Client: o.RBACClient.ClusterRoles(),
@@ -205,7 +212,7 @@ func (o *ReconcileOptions) RunReconcile() error {
case *rbacv1.RoleBinding:
reconcileOptions := reconciliation.ReconcileRoleBindingOptions{
Confirm: !o.DryRun,
RemoveExtraSubjects: false,
RemoveExtraSubjects: o.RemoveExtraSubjects,
RoleBinding: reconciliation.RoleBindingAdapter{RoleBinding: t},
Client: reconciliation.RoleBindingClientAdapter{
Client: o.RBACClient,
@@ -221,7 +228,7 @@ func (o *ReconcileOptions) RunReconcile() error {
case *rbacv1.ClusterRoleBinding:
reconcileOptions := reconciliation.ReconcileRoleBindingOptions{
Confirm: !o.DryRun,
RemoveExtraSubjects: false,
RemoveExtraSubjects: o.RemoveExtraSubjects,
RoleBinding: reconciliation.ClusterRoleBindingAdapter{ClusterRoleBinding: t},
Client: reconciliation.ClusterRoleBindingClientAdapter{
Client: o.RBACClient.ClusterRoleBindings(),
@@ -233,6 +240,16 @@ func (o *ReconcileOptions) RunReconcile() error {
}
o.PrintObject(result.RoleBinding.GetObject(), o.Out)
case *rbacv1beta1.Role,
*rbacv1beta1.RoleBinding,
*rbacv1beta1.ClusterRole,
*rbacv1beta1.ClusterRoleBinding,
*rbacv1alpha1.Role,
*rbacv1alpha1.RoleBinding,
*rbacv1alpha1.ClusterRole,
*rbacv1alpha1.ClusterRoleBinding:
return fmt.Errorf("only rbac.authorization.k8s.io/v1 is supported: not %T", t)
default:
glog.V(1).Infof("skipping %#v", info.Object.GetObjectKind())
// skip ignored resources

View File

@@ -24,14 +24,14 @@ import (
autoscalingv1 "k8s.io/api/autoscaling/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"

View File

@@ -20,21 +20,20 @@ import (
"fmt"
"io"
"github.com/spf13/cobra"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/certificates"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
certificatesv1beta1client "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"github.com/spf13/cobra"
)
func NewCmdCertificate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
@@ -63,7 +62,7 @@ type CertificateOptions struct {
csrNames []string
outputStyle string
clientSet internalclientset.Interface
clientSet certificatesv1beta1client.CertificatesV1beta1Interface
builder *resource.Builder
genericclioptions.IOStreams
@@ -83,7 +82,12 @@ func (o *CertificateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg
}
o.builder = f.NewBuilder()
o.clientSet, err = f.ClientSet()
clientConfig, err := f.ToRESTConfig()
if err != nil {
return err
}
o.clientSet, err = certificatesv1beta1client.NewForConfig(clientConfig)
if err != nil {
return err
}
@@ -135,18 +139,18 @@ func NewCmdCertificateApprove(f cmdutil.Factory, ioStreams genericclioptions.IOS
}
func (o *CertificateOptions) RunCertificateApprove(force bool) error {
return o.modifyCertificateCondition(o.builder, o.clientSet, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool) {
return o.modifyCertificateCondition(o.builder, o.clientSet, force, func(csr *certificatesv1beta1.CertificateSigningRequest) (*certificatesv1beta1.CertificateSigningRequest, bool) {
var alreadyApproved bool
for _, c := range csr.Status.Conditions {
if c.Type == certificates.CertificateApproved {
if c.Type == certificatesv1beta1.CertificateApproved {
alreadyApproved = true
}
}
if alreadyApproved {
return csr, true
}
csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateApproved,
csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
Type: certificatesv1beta1.CertificateApproved,
Reason: "KubectlApprove",
Message: "This CSR was approved by kubectl certificate approve.",
LastUpdateTime: metav1.Now(),
@@ -187,30 +191,30 @@ func NewCmdCertificateDeny(f cmdutil.Factory, ioStreams genericclioptions.IOStre
}
func (o *CertificateOptions) RunCertificateDeny(force bool) error {
return o.modifyCertificateCondition(o.builder, o.clientSet, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool) {
return o.modifyCertificateCondition(o.builder, o.clientSet, force, func(csr *certificatesv1beta1.CertificateSigningRequest) (*certificatesv1beta1.CertificateSigningRequest, bool) {
var alreadyDenied bool
for _, c := range csr.Status.Conditions {
if c.Type == certificates.CertificateDenied {
if c.Type == certificatesv1beta1.CertificateDenied {
alreadyDenied = true
}
}
if alreadyDenied {
return csr, true
}
csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateDenied,
csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
Type: certificatesv1beta1.CertificateDenied,
Reason: "KubectlDeny",
Message: "This CSR was approved by kubectl certificate deny.",
Message: "This CSR was denied by kubectl certificate deny.",
LastUpdateTime: metav1.Now(),
})
return csr, false
})
}
func (options *CertificateOptions) modifyCertificateCondition(builder *resource.Builder, clientSet internalclientset.Interface, force bool, modify func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool)) error {
func (options *CertificateOptions) modifyCertificateCondition(builder *resource.Builder, clientSet certificatesv1beta1client.CertificatesV1beta1Interface, force bool, modify func(csr *certificatesv1beta1.CertificateSigningRequest) (*certificatesv1beta1.CertificateSigningRequest, bool)) error {
var found int
r := builder.
WithScheme(legacyscheme.Scheme).
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
ContinueOnError().
FilenameParam(false, &options.FilenameOptions).
ResourceNames("certificatesigningrequest", options.csrNames...).
@@ -223,12 +227,10 @@ func (options *CertificateOptions) modifyCertificateCondition(builder *resource.
return err
}
for i := 0; ; i++ {
csr := info.Object.(*certificates.CertificateSigningRequest)
csr := info.Object.(*certificatesv1beta1.CertificateSigningRequest)
csr, hasCondition := modify(csr)
if !hasCondition || force {
csr, err = clientSet.Certificates().
CertificateSigningRequests().
UpdateApproval(csr)
csr, err = clientSet.CertificateSigningRequests().UpdateApproval(csr)
if errors.IsConflict(err) && i < 10 {
if err := info.Get(); err != nil {
return err

View File

@@ -23,13 +23,13 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
ct "github.com/daviddengcn/go-colortext"
@@ -92,8 +92,6 @@ func (o *ClusterInfoOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) err
}
func (o *ClusterInfoOptions) Run() error {
printService(o.Out, "Kubernetes master", o.Client.Host)
// TODO use generalized labels once they are implemented (#341)
b := o.Builder.
WithScheme(legacyscheme.Scheme).
@@ -105,6 +103,8 @@ func (o *ClusterInfoOptions) Run() error {
if err != nil {
return err
}
printService(o.Out, "Kubernetes master", o.Client.Host)
services := r.Object.(*api.ServiceList).Items
for _, service := range services {
var link string

View File

@@ -25,13 +25,14 @@ import (
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
@@ -46,7 +47,8 @@ type ClusterInfoDumpOptions struct {
Namespaces []string
Timeout time.Duration
Clientset internalclientset.Interface
AppsClient appsv1client.AppsV1Interface
CoreClient corev1client.CoreV1Interface
Namespace string
RESTClientGetter genericclioptions.RESTClientGetter
LogsForObject polymorphichelpers.LogsForObjectFunc
@@ -57,7 +59,7 @@ type ClusterInfoDumpOptions struct {
// NewCmdCreateSecret groups subcommands to create various types of secrets
func NewCmdClusterInfoDump(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := &ClusterInfoDumpOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme),
PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme).WithDefaultOutput("json"),
IOStreams: ioStreams,
}
@@ -72,6 +74,9 @@ func NewCmdClusterInfoDump(f cmdutil.Factory, ioStreams genericclioptions.IOStre
cmdutil.CheckErr(o.Run())
},
}
o.PrintFlags.AddFlags(cmd)
cmd.Flags().StringVar(&o.OutputDir, "output-directory", o.OutputDir, i18n.T("Where to output the files. If empty or '-' uses stdout, otherwise creates a directory hierarchy in that directory"))
cmd.Flags().StringSliceVar(&o.Namespaces, "namespaces", o.Namespaces, "A comma separated list of namespaces to dump.")
cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", o.AllNamespaces, "If true, dump all namespaces. If true, --namespaces is ignored.")
@@ -122,18 +127,28 @@ func (o *ClusterInfoDumpOptions) Complete(f cmdutil.Factory, cmd *cobra.Command)
return err
}
jsonOutputFmt := "json"
o.PrintFlags.OutputFormat = &jsonOutputFmt
o.PrintObj = printer.PrintObj
config, err := f.ToRESTConfig()
if err != nil {
return err
}
o.CoreClient, err = corev1client.NewForConfig(config)
if err != nil {
return err
}
o.AppsClient, err = appsv1client.NewForConfig(config)
if err != nil {
return err
}
o.Timeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
if err != nil {
return err
}
o.Clientset, err = f.ClientSet()
if err != nil {
return err
}
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
@@ -146,7 +161,7 @@ func (o *ClusterInfoDumpOptions) Complete(f cmdutil.Factory, cmd *cobra.Command)
}
func (o *ClusterInfoDumpOptions) Run() error {
nodes, err := o.Clientset.Core().Nodes().List(metav1.ListOptions{})
nodes, err := o.CoreClient.Nodes().List(metav1.ListOptions{})
if err != nil {
return err
}
@@ -157,7 +172,7 @@ func (o *ClusterInfoDumpOptions) Run() error {
var namespaces []string
if o.AllNamespaces {
namespaceList, err := o.Clientset.Core().Namespaces().List(metav1.ListOptions{})
namespaceList, err := o.CoreClient.Namespaces().List(metav1.ListOptions{})
if err != nil {
return err
}
@@ -175,7 +190,7 @@ func (o *ClusterInfoDumpOptions) Run() error {
for _, namespace := range namespaces {
// TODO: this is repetitive in the extreme. Use reflection or
// something to make this a for loop.
events, err := o.Clientset.Core().Events(namespace).List(metav1.ListOptions{})
events, err := o.CoreClient.Events(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
@@ -183,7 +198,7 @@ func (o *ClusterInfoDumpOptions) Run() error {
return err
}
rcs, err := o.Clientset.Core().ReplicationControllers(namespace).List(metav1.ListOptions{})
rcs, err := o.CoreClient.ReplicationControllers(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
@@ -191,7 +206,7 @@ func (o *ClusterInfoDumpOptions) Run() error {
return err
}
svcs, err := o.Clientset.Core().Services(namespace).List(metav1.ListOptions{})
svcs, err := o.CoreClient.Services(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
@@ -199,7 +214,7 @@ func (o *ClusterInfoDumpOptions) Run() error {
return err
}
sets, err := o.Clientset.Extensions().DaemonSets(namespace).List(metav1.ListOptions{})
sets, err := o.AppsClient.DaemonSets(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
@@ -207,7 +222,7 @@ func (o *ClusterInfoDumpOptions) Run() error {
return err
}
deps, err := o.Clientset.Extensions().Deployments(namespace).List(metav1.ListOptions{})
deps, err := o.AppsClient.Deployments(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
@@ -215,7 +230,7 @@ func (o *ClusterInfoDumpOptions) Run() error {
return err
}
rps, err := o.Clientset.Extensions().ReplicaSets(namespace).List(metav1.ListOptions{})
rps, err := o.AppsClient.ReplicaSets(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
@@ -223,7 +238,7 @@ func (o *ClusterInfoDumpOptions) Run() error {
return err
}
pods, err := o.Clientset.Core().Pods(namespace).List(metav1.ListOptions{})
pods, err := o.CoreClient.Pods(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
@@ -232,24 +247,26 @@ func (o *ClusterInfoDumpOptions) Run() error {
return err
}
printContainer := func(writer io.Writer, container api.Container, pod *api.Pod) {
printContainer := func(writer io.Writer, container corev1.Container, pod *corev1.Pod) {
writer.Write([]byte(fmt.Sprintf("==== START logs for container %s of pod %s/%s ====\n", container.Name, pod.Namespace, pod.Name)))
defer writer.Write([]byte(fmt.Sprintf("==== END logs for container %s of pod %s/%s ====\n", container.Name, pod.Namespace, pod.Name)))
request, err := o.LogsForObject(o.RESTClientGetter, pod, &api.PodLogOptions{Container: container.Name}, timeout)
requests, err := o.LogsForObject(o.RESTClientGetter, pod, &corev1.PodLogOptions{Container: container.Name}, timeout, false)
if err != nil {
// Print error and return.
writer.Write([]byte(fmt.Sprintf("Create log request error: %s\n", err.Error())))
return
}
data, err := request.DoRaw()
if err != nil {
// Print error and return.
writer.Write([]byte(fmt.Sprintf("Request log error: %s\n", err.Error())))
return
for _, request := range requests {
data, err := request.DoRaw()
if err != nil {
// Print error and return.
writer.Write([]byte(fmt.Sprintf("Request log error: %s\n", err.Error())))
return
}
writer.Write(data)
}
writer.Write(data)
}
for ix := range pods.Items {
@@ -262,8 +279,13 @@ func (o *ClusterInfoDumpOptions) Run() error {
}
}
}
if o.OutputDir != "-" {
fmt.Fprintf(o.Out, "Cluster info dumped to %s\n", o.OutputDir)
dest := o.OutputDir
if len(dest) == 0 {
dest = "standard output"
}
if dest != "-" {
fmt.Fprintf(o.Out, "Cluster info dumped to %s\n", dest)
}
return nil
}

View File

@@ -22,8 +22,8 @@ import (
"path"
"testing"
"k8s.io/cli-runtime/pkg/genericclioptions"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestSetupOutputWriterNoOp(t *testing.T) {

View File

@@ -21,6 +21,12 @@ import (
"fmt"
"io"
"os"
"os/exec"
"runtime"
"strings"
"syscall"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
utilflag "k8s.io/apiserver/pkg/util/flag"
@@ -36,8 +42,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl/cmd/wait"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
const (
@@ -257,7 +262,100 @@ var (
)
func NewDefaultKubectlCommand() *cobra.Command {
return NewKubectlCommand(os.Stdin, os.Stdout, os.Stderr)
return NewDefaultKubectlCommandWithArgs(&defaultPluginHandler{}, os.Args, os.Stdin, os.Stdout, os.Stderr)
}
func NewDefaultKubectlCommandWithArgs(pluginHandler PluginHandler, args []string, in io.Reader, out, errout io.Writer) *cobra.Command {
cmd := NewKubectlCommand(in, out, errout)
if pluginHandler == nil {
return cmd
}
if len(args) > 1 {
cmdPathPieces := args[1:]
// only look for suitable extension executables if
// the specified command does not already exist
if _, _, err := cmd.Find(cmdPathPieces); err != nil {
if err := handleEndpointExtensions(pluginHandler, cmdPathPieces); err != nil {
fmt.Fprintf(errout, "%v\n", err)
os.Exit(1)
}
}
}
return cmd
}
// PluginHandler is capable of parsing command line arguments
// and performing executable filename lookups to search
// for valid plugin files, and execute found plugins.
type PluginHandler interface {
// Lookup receives a potential filename and returns
// a full or relative path to an executable, if one
// exists at the given filename, or an error.
Lookup(filename string) (string, error)
// Execute receives an executable's filepath, a slice
// of arguments, and a slice of environment variables
// to relay to the executable.
Execute(executablePath string, cmdArgs, environment []string) error
}
type defaultPluginHandler struct{}
// Lookup implements PluginHandler
func (h *defaultPluginHandler) Lookup(filename string) (string, error) {
// if on Windows, append the "exe" extension
// to the filename that we are looking up.
if runtime.GOOS == "windows" {
filename = filename + ".exe"
}
return exec.LookPath(filename)
}
// Execute implements PluginHandler
func (h *defaultPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error {
return syscall.Exec(executablePath, cmdArgs, environment)
}
func handleEndpointExtensions(pluginHandler PluginHandler, cmdArgs []string) error {
remainingArgs := []string{} // all "non-flag" arguments
for idx := range cmdArgs {
if strings.HasPrefix(cmdArgs[idx], "-") {
break
}
remainingArgs = append(remainingArgs, strings.Replace(cmdArgs[idx], "-", "_", -1))
}
foundBinaryPath := ""
// attempt to find binary, starting at longest possible name with given cmdArgs
for len(remainingArgs) > 0 {
path, err := pluginHandler.Lookup(fmt.Sprintf("kubectl-%s", strings.Join(remainingArgs, "-")))
if err != nil || len(path) == 0 {
remainingArgs = remainingArgs[:len(remainingArgs)-1]
continue
}
foundBinaryPath = path
break
}
if len(foundBinaryPath) == 0 {
return nil
}
// invoke cmd binary relaying the current environment and args given
// remainingArgs will always have at least one element.
// execve will make remainingArgs[0] the "binary name".
if err := pluginHandler.Execute(foundBinaryPath, append([]string{foundBinaryPath}, cmdArgs[len(remainingArgs):]...), os.Environ()); err != nil {
return err
}
return nil
}
// NewKubectlCommand creates the `kubectl` command and its nested children.

View File

@@ -23,10 +23,10 @@ import (
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
genericprinters "k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
genericprinters "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/printers"
)
@@ -83,54 +83,56 @@ func TestIllegalPackageSourceCheckerThroughPrintFlags(t *testing.T) {
}
for _, tc := range testCases {
printFlags := genericclioptions.NewPrintFlags("succeeded").WithTypeSetter(scheme.Scheme)
printFlags.OutputFormat = &tc.output
t.Run(tc.name, func(t *testing.T) {
printFlags := genericclioptions.NewPrintFlags("succeeded").WithTypeSetter(legacyscheme.Scheme)
printFlags.OutputFormat = &tc.output
printer, err := printFlags.ToPrinter()
if err != nil {
t.Fatalf("unexpected error %v", err)
}
output := bytes.NewBuffer([]byte{})
err = printer.PrintObj(tc.obj, output)
if err != nil {
if !tc.expectInternalObjErr {
printer, err := printFlags.ToPrinter()
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if !genericprinters.IsInternalObjectError(err) {
t.Fatalf("unexpected error - expecting internal object printer error, got %q", err)
output := bytes.NewBuffer([]byte{})
err = printer.PrintObj(tc.obj, output)
if err != nil {
if !tc.expectInternalObjErr {
t.Fatalf("unexpected error %v", err)
}
if !genericprinters.IsInternalObjectError(err) {
t.Fatalf("unexpected error - expecting internal object printer error, got %q", err)
}
return
}
continue
}
if tc.expectInternalObjErr {
t.Fatalf("expected internal object printer error, but got no error")
}
if tc.expectInternalObjErr {
t.Fatalf("expected internal object printer error, but got no error")
}
if len(tc.expectedOutput) == 0 {
continue
}
if len(tc.expectedOutput) == 0 {
return
}
if tc.expectedOutput != output.String() {
t.Fatalf("unexpected output: expecting %q, got %q", tc.expectedOutput, output.String())
}
if tc.expectedOutput != output.String() {
t.Fatalf("unexpected output: expecting %q, got %q", tc.expectedOutput, output.String())
}
})
}
}
func TestIllegalPackageSourceCheckerDirectlyThroughPrinters(t *testing.T) {
jsonPathPrinter, err := printers.NewJSONPathPrinter("{ .metadata.name }")
jsonPathPrinter, err := genericprinters.NewJSONPathPrinter("{ .metadata.name }")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
goTemplatePrinter, err := printers.NewGoTemplatePrinter([]byte("{{ .metadata.name }}"))
goTemplatePrinter, err := genericprinters.NewGoTemplatePrinter([]byte("{{ .metadata.name }}"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
customColumns, err := printers.NewCustomColumnsPrinterFromSpec("NAME:.metadata.name", scheme.Codecs.UniversalDecoder(), true)
customColumns, err := printers.NewCustomColumnsPrinterFromSpec("NAME:.metadata.name", legacyscheme.Codecs.UniversalDecoder(), true)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -138,7 +140,7 @@ func TestIllegalPackageSourceCheckerDirectlyThroughPrinters(t *testing.T) {
testCases := []struct {
name string
expectInternalObjErr bool
printer printers.ResourcePrinter
printer genericprinters.ResourcePrinter
obj runtime.Object
expectedOutput string
}{
@@ -181,31 +183,33 @@ func TestIllegalPackageSourceCheckerDirectlyThroughPrinters(t *testing.T) {
}
for _, tc := range testCases {
output := bytes.NewBuffer([]byte{})
t.Run(tc.name, func(t *testing.T) {
output := bytes.NewBuffer([]byte{})
err := tc.printer.PrintObj(tc.obj, output)
if err != nil {
if !tc.expectInternalObjErr {
t.Fatalf("unexpected error %v", err)
err := tc.printer.PrintObj(tc.obj, output)
if err != nil {
if !tc.expectInternalObjErr {
t.Fatalf("unexpected error %v", err)
}
if !genericprinters.IsInternalObjectError(err) {
t.Fatalf("unexpected error - expecting internal object printer error, got %q", err)
}
return
}
if !genericprinters.IsInternalObjectError(err) {
t.Fatalf("unexpected error - expecting internal object printer error, got %q", err)
if tc.expectInternalObjErr {
t.Fatalf("expected internal object printer error, but got no error")
}
continue
}
if tc.expectInternalObjErr {
t.Fatalf("expected internal object printer error, but got no error")
}
if len(tc.expectedOutput) == 0 {
return
}
if len(tc.expectedOutput) == 0 {
continue
}
if tc.expectedOutput != output.String() {
t.Fatalf("unexpected output: expecting %q, got %q", tc.expectedOutput, output.String())
}
if tc.expectedOutput != output.String() {
t.Fatalf("unexpected output: expecting %q, got %q", tc.expectedOutput, output.String())
}
})
}
}

View File

@@ -19,6 +19,7 @@ package cmd
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
@@ -26,26 +27,21 @@ import (
"reflect"
stdstrings "strings"
"testing"
"time"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api/testapi"
apitesting "k8s.io/kubernetes/pkg/api/testing"
api "k8s.io/kubernetes/pkg/apis/core"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
// This init should be removed after switching this command and its tests to user external types.
func init() {
api.AddToScheme(scheme.Scheme)
}
func initTestErrorHandler(t *testing.T) {
cmdutil.BehaviorOnFatal(func(str string, code int) {
t.Errorf("Error running command (exit code %d): %s", code, str)
@@ -69,45 +65,45 @@ func defaultClientConfig() *restclient.Config {
}
}
func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList) {
pods := &api.PodList{
func testData() (*corev1.PodList, *corev1.ServiceList, *corev1.ReplicationControllerList) {
pods := &corev1.PodList{
ListMeta: metav1.ListMeta{
ResourceVersion: "15",
},
Items: []api.Pod{
Items: []corev1.Pod{
{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: apitesting.DeepEqualSafePodSpec(),
Spec: apitesting.V1DeepEqualSafePodSpec(),
},
{
ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"},
Spec: apitesting.DeepEqualSafePodSpec(),
Spec: apitesting.V1DeepEqualSafePodSpec(),
},
},
}
svc := &api.ServiceList{
svc := &corev1.ServiceList{
ListMeta: metav1.ListMeta{
ResourceVersion: "16",
},
Items: []api.Service{
Items: []corev1.Service{
{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Spec: corev1.ServiceSpec{
SessionAffinity: "None",
Type: api.ServiceTypeClusterIP,
Type: corev1.ServiceTypeClusterIP,
},
},
},
}
rc := &api.ReplicationControllerList{
rc := &corev1.ReplicationControllerList{
ListMeta: metav1.ListMeta{
ResourceVersion: "17",
},
Items: []api.ReplicationController{
Items: []corev1.ReplicationController{
{
ObjectMeta: metav1.ObjectMeta{Name: "rc1", Namespace: "test", ResourceVersion: "18"},
Spec: api.ReplicationControllerSpec{
Replicas: 1,
Spec: corev1.ReplicationControllerSpec{
Replicas: int32ptr(1),
},
},
},
@@ -115,6 +111,11 @@ func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList)
return pods, svc, rc
}
func int32ptr(val int) *int32 {
t := int32(val)
return &t
}
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
}
@@ -131,98 +132,6 @@ func stringBody(body string) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(body)))
}
func newAllPhasePodList() *api.PodList {
nodeName := "kubernetes-node-abcd"
return &api.PodList{
Items: []api.Pod{
{
ObjectMeta: metav1.ObjectMeta{
Name: "test1",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodPending,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "test2",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodRunning,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "test3",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodSucceeded,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "test4",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodFailed,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "test5",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodUnknown,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
}},
}
}
func TestNormalizationFuncGlobalExistence(t *testing.T) {
// This test can be safely deleted when we will not support multiple flag formats
root := NewKubectlCommand(os.Stdin, os.Stdout, os.Stderr)
@@ -306,3 +215,106 @@ func Test_deprecatedAlias(t *testing.T) {
t.Errorf("original function doesn't appear to have been called by alias")
}
}
func TestKubectlCommandHandlesPlugins(t *testing.T) {
tests := []struct {
name string
args []string
expectPlugin string
expectPluginArgs []string
expectError string
}{
{
name: "test that normal commands are able to be executed, when no plugin overshadows them",
args: []string{"kubectl", "get", "foo"},
expectPlugin: "",
expectPluginArgs: []string{},
},
{
name: "test that a plugin executable is found based on command args",
args: []string{"kubectl", "foo", "--bar"},
expectPlugin: "testdata/plugin/kubectl-foo",
expectPluginArgs: []string{"foo", "--bar"},
},
{
name: "test that a plugin does not execute over an existing command by the same name",
args: []string{"kubectl", "version"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pluginsHandler := &testPluginHandler{
pluginsDirectory: "testdata/plugin",
}
_, in, out, errOut := genericclioptions.NewTestIOStreams()
cmdutil.BehaviorOnFatal(func(str string, code int) {
errOut.Write([]byte(str))
})
root := NewDefaultKubectlCommandWithArgs(pluginsHandler, test.args, in, out, errOut)
if err := root.Execute(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if pluginsHandler.err != nil && pluginsHandler.err.Error() != test.expectError {
t.Fatalf("unexpected error: expected %q to occur, but got %q", test.expectError, pluginsHandler.err)
}
if pluginsHandler.executedPlugin != test.expectPlugin {
t.Fatalf("unexpected plugin execution: expedcted %q, got %q", test.expectPlugin, pluginsHandler.executedPlugin)
}
if len(pluginsHandler.withArgs) != len(test.expectPluginArgs) {
t.Fatalf("unexpected plugin execution args: expedcted %q, got %q", test.expectPluginArgs, pluginsHandler.withArgs)
}
})
}
}
type testPluginHandler struct {
pluginsDirectory string
// execution results
executedPlugin string
withArgs []string
withEnv []string
err error
}
func (h *testPluginHandler) Lookup(filename string) (string, error) {
dir, err := os.Stat(h.pluginsDirectory)
if err != nil {
h.err = err
return "", err
}
if !dir.IsDir() {
h.err = fmt.Errorf("expected %q to be a directory", h.pluginsDirectory)
return "", h.err
}
plugins, err := ioutil.ReadDir(h.pluginsDirectory)
if err != nil {
h.err = err
return "", err
}
for _, p := range plugins {
if p.Name() == filename {
return fmt.Sprintf("%s/%s", h.pluginsDirectory, p.Name()), nil
}
}
h.err = fmt.Errorf("unable to find a plugin executable %q", filename)
return "", h.err
}
func (h *testPluginHandler) Execute(executablePath string, cmdArgs, env []string) error {
h.executedPlugin = executablePath
h.withArgs = cmdArgs
h.withEnv = env
return nil
}

View File

@@ -67,6 +67,8 @@ var (
# Installing bash completion on Linux
## If bash-completion is not installed on Linux, please install the 'bash-completion' package
## via your distribution's package manager.
## Load the kubectl completion code for bash into the current shell
source <(kubectl completion bash)
## Write bash completion code to a file and source if from .bash_profile

View File

@@ -14,7 +14,6 @@ go_library(
"current_context.go",
"delete_cluster.go",
"delete_context.go",
"flags.go",
"get_clusters.go",
"get_contexts.go",
"navigation_step_parser.go",
@@ -32,18 +31,16 @@ go_library(
"//pkg/api/legacyscheme:go_default_library",
"//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/printers:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api/latest:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api/latest:go_default_library",
],
)
@@ -69,12 +66,12 @@ go_test(
embed = [":go_default_library"],
deps = [
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
],
)

View File

@@ -23,10 +23,10 @@ import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@@ -45,8 +45,8 @@ func NewCmdConfig(f cmdutil.Factory, pathOptions *clientcmd.PathOptions, streams
The loading order follows these rules:
1. If the --` + pathOptions.ExplicitFileFlag + ` flag is set, then only that file is loaded. The flag may only be set once and no merging takes place.
2. If $` + pathOptions.EnvVar + ` environment variable is set, then it is used a list of paths (normal path delimitting rules for your system). These paths are merged. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list.
1. If the --` + pathOptions.ExplicitFileFlag + ` flag is set, then only that file is loaded. The flag may only be set once and no merging takes place.
2. If $` + pathOptions.EnvVar + ` environment variable is set, then it is used as a list of paths (normal path delimitting rules for your system). These paths are merged. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list.
3. Otherwise, ` + path.Join("${HOME}", pathOptions.GlobalFileSubpath) + ` is used and no merging takes place.`),
Run: cmdutil.DefaultSubCommandRun(streams.ErrOut),
}

View File

@@ -27,10 +27,10 @@ import (
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func newRedFederalCowHammerConfig() clientcmdapi.Config {

View File

@@ -36,6 +36,7 @@ func stringFlagFor(s string) flag.StringFlag {
func TestCreateAuthInfoOptions(t *testing.T) {
tests := []struct {
name string
flags []string
wantParseErr bool
wantCompleteErr bool
@@ -44,6 +45,7 @@ func TestCreateAuthInfoOptions(t *testing.T) {
wantOptions *createAuthInfoOptions
}{
{
name: "test1",
flags: []string{
"me",
},
@@ -52,6 +54,7 @@ func TestCreateAuthInfoOptions(t *testing.T) {
},
},
{
name: "test2",
flags: []string{
"me",
"--token=foo",
@@ -62,6 +65,7 @@ func TestCreateAuthInfoOptions(t *testing.T) {
},
},
{
name: "test3",
flags: []string{
"me",
"--username=jane",
@@ -74,6 +78,7 @@ func TestCreateAuthInfoOptions(t *testing.T) {
},
},
{
name: "test4",
// Cannot provide both token and basic auth.
flags: []string{
"me",
@@ -84,6 +89,7 @@ func TestCreateAuthInfoOptions(t *testing.T) {
wantValidateErr: true,
},
{
name: "test5",
flags: []string{
"--auth-provider=oidc",
"--auth-provider-arg=client-id=foo",
@@ -101,6 +107,7 @@ func TestCreateAuthInfoOptions(t *testing.T) {
},
},
{
name: "test6",
flags: []string{
"--auth-provider=oidc",
"--auth-provider-arg=client-id-",
@@ -118,6 +125,7 @@ func TestCreateAuthInfoOptions(t *testing.T) {
},
},
{
name: "test7",
flags: []string{
"--auth-provider-arg=client-id-", // auth provider name not required
"--auth-provider-arg=client-secret-",
@@ -133,6 +141,7 @@ func TestCreateAuthInfoOptions(t *testing.T) {
},
},
{
name: "test8",
flags: []string{
"--auth-provider=oidc",
"--auth-provider-arg=client-id", // values must be of form 'key=value' or 'key-'
@@ -141,6 +150,7 @@ func TestCreateAuthInfoOptions(t *testing.T) {
wantCompleteErr: true,
},
{
name: "test9",
flags: []string{
// No name for authinfo provided.
},
@@ -148,48 +158,50 @@ func TestCreateAuthInfoOptions(t *testing.T) {
},
}
for i, test := range tests {
buff := new(bytes.Buffer)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buff := new(bytes.Buffer)
opts := new(createAuthInfoOptions)
cmd := newCmdConfigSetAuthInfo(buff, opts)
if err := cmd.ParseFlags(test.flags); err != nil {
if !test.wantParseErr {
t.Errorf("case %d: parsing error for flags %q: %v: %s", i, test.flags, err, buff)
opts := new(createAuthInfoOptions)
cmd := newCmdConfigSetAuthInfo(buff, opts)
if err := cmd.ParseFlags(tt.flags); err != nil {
if !tt.wantParseErr {
t.Errorf("case %s: parsing error for flags %q: %v: %s", tt.name, tt.flags, err, buff)
}
return
}
continue
}
if test.wantParseErr {
t.Errorf("case %d: expected parsing error for flags %q: %s", i, test.flags, buff)
continue
}
if err := opts.complete(cmd, buff); err != nil {
if !test.wantCompleteErr {
t.Errorf("case %d: complete() error for flags %q: %s", i, test.flags, buff)
if tt.wantParseErr {
t.Errorf("case %s: expected parsing error for flags %q: %s", tt.name, tt.flags, buff)
return
}
continue
}
if test.wantCompleteErr {
t.Errorf("case %d: complete() expected errors for flags %q: %s", i, test.flags, buff)
continue
}
if err := opts.validate(); err != nil {
if !test.wantValidateErr {
t.Errorf("case %d: flags %q: validate failed: %v", i, test.flags, err)
if err := opts.complete(cmd, buff); err != nil {
if !tt.wantCompleteErr {
t.Errorf("case %s: complete() error for flags %q: %s", tt.name, tt.flags, buff)
}
return
}
if tt.wantCompleteErr {
t.Errorf("case %s: complete() expected errors for flags %q: %s", tt.name, tt.flags, buff)
return
}
continue
}
if test.wantValidateErr {
t.Errorf("case %d: flags %q: expected validate to fail", i, test.flags)
continue
}
if err := opts.validate(); err != nil {
if !tt.wantValidateErr {
t.Errorf("case %s: flags %q: validate failed: %v", tt.name, tt.flags, err)
}
return
}
if !reflect.DeepEqual(opts, test.wantOptions) {
t.Errorf("case %d: flags %q: mis-matched options,\nwanted=%#v\ngot= %#v", i, test.flags, test.wantOptions, opts)
}
if tt.wantValidateErr {
t.Errorf("case %s: flags %q: expected validate to fail", tt.name, tt.flags)
return
}
if !reflect.DeepEqual(opts, tt.wantOptions) {
t.Errorf("case %s: flags %q: mis-matched options,\nwanted=%#v\ngot= %#v", tt.name, tt.flags, tt.wantOptions, opts)
}
})
}
}

View File

@@ -34,6 +34,7 @@ import (
type createContextOptions struct {
configAccess clientcmd.ConfigAccess
name string
currContext bool
cluster flag.StringFlag
authInfo flag.StringFlag
namespace flag.StringFlag
@@ -54,23 +55,24 @@ func NewCmdConfigSetContext(out io.Writer, configAccess clientcmd.ConfigAccess)
options := &createContextOptions{configAccess: configAccess}
cmd := &cobra.Command{
Use: fmt.Sprintf("set-context NAME [--%v=cluster_nickname] [--%v=user_nickname] [--%v=namespace]", clientcmd.FlagClusterName, clientcmd.FlagAuthInfoName, clientcmd.FlagNamespace),
Use: fmt.Sprintf("set-context [NAME | --current] [--%v=cluster_nickname] [--%v=user_nickname] [--%v=namespace]", clientcmd.FlagClusterName, clientcmd.FlagAuthInfoName, clientcmd.FlagNamespace),
DisableFlagsInUseLine: true,
Short: i18n.T("Sets a context entry in kubeconfig"),
Long: create_context_long,
Example: create_context_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.complete(cmd))
exists, err := options.run()
name, exists, err := options.run()
cmdutil.CheckErr(err)
if exists {
fmt.Fprintf(out, "Context %q modified.\n", options.name)
fmt.Fprintf(out, "Context %q modified.\n", name)
} else {
fmt.Fprintf(out, "Context %q created.\n", options.name)
fmt.Fprintf(out, "Context %q created.\n", name)
}
},
}
cmd.Flags().BoolVar(&options.currContext, "current", options.currContext, "Modify the current context")
cmd.Flags().Var(&options.cluster, clientcmd.FlagClusterName, clientcmd.FlagClusterName+" for the context entry in kubeconfig")
cmd.Flags().Var(&options.authInfo, clientcmd.FlagAuthInfoName, clientcmd.FlagAuthInfoName+" for the context entry in kubeconfig")
cmd.Flags().Var(&options.namespace, clientcmd.FlagNamespace, clientcmd.FlagNamespace+" for the context entry in kubeconfig")
@@ -78,29 +80,37 @@ func NewCmdConfigSetContext(out io.Writer, configAccess clientcmd.ConfigAccess)
return cmd
}
func (o createContextOptions) run() (bool, error) {
func (o createContextOptions) run() (string, bool, error) {
err := o.validate()
if err != nil {
return false, err
return "", false, err
}
config, err := o.configAccess.GetStartingConfig()
if err != nil {
return false, err
return "", false, err
}
startingStanza, exists := config.Contexts[o.name]
name := o.name
if o.currContext {
if len(config.CurrentContext) == 0 {
return "", false, errors.New("no current context is set")
}
name = config.CurrentContext
}
startingStanza, exists := config.Contexts[name]
if !exists {
startingStanza = clientcmdapi.NewContext()
}
context := o.modifyContext(*startingStanza)
config.Contexts[o.name] = &context
config.Contexts[name] = &context
if err := clientcmd.ModifyConfig(o.configAccess, *config, true); err != nil {
return exists, err
return name, exists, err
}
return exists, nil
return name, exists, nil
}
func (o *createContextOptions) modifyContext(existingContext clientcmdapi.Context) clientcmdapi.Context {
@@ -121,17 +131,21 @@ func (o *createContextOptions) modifyContext(existingContext clientcmdapi.Contex
func (o *createContextOptions) complete(cmd *cobra.Command) error {
args := cmd.Flags().Args()
if len(args) != 1 {
if len(args) > 1 {
return helpErrorf(cmd, "Unexpected args: %v", args)
}
o.name = args[0]
if len(args) == 1 {
o.name = args[0]
}
return nil
}
func (o createContextOptions) validate() error {
if len(o.name) == 0 {
return errors.New("you must specify a non-empty context name")
if len(o.name) == 0 && !o.currContext {
return errors.New("you must specify a non-empty context name or --current-context")
}
if len(o.name) > 0 && o.currContext {
return errors.New("you cannot specify a context name and --current-context")
}
return nil

View File

@@ -28,6 +28,7 @@ import (
type createContextTest struct {
description string
testContext string // name of the context being modified
config clientcmdapi.Config //initiate kubectl config
args []string //kubectl set-context args
flags []string //kubectl set-context flags
@@ -38,6 +39,7 @@ type createContextTest struct {
func TestCreateContext(t *testing.T) {
conf := clientcmdapi.Config{}
test := createContextTest{
testContext: "shaker-context",
description: "Testing for create a new context",
config: conf,
args: []string{"shaker-context"},
@@ -60,6 +62,7 @@ func TestModifyContext(t *testing.T) {
"shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"},
"not-this": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}}
test := createContextTest{
testContext: "shaker-context",
description: "Testing for modify a already exist context",
config: conf,
args: []string{"shaker-context"},
@@ -77,6 +80,32 @@ func TestModifyContext(t *testing.T) {
test.run(t)
}
func TestModifyCurrentContext(t *testing.T) {
conf := clientcmdapi.Config{
CurrentContext: "shaker-context",
Contexts: map[string]*clientcmdapi.Context{
"shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"},
"not-this": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}}
test := createContextTest{
testContext: "shaker-context",
description: "Testing for modify a current context",
config: conf,
args: []string{},
flags: []string{
"--current",
"--cluster=cluster_nickname",
"--user=user_nickname",
"--namespace=namespace",
},
expected: `Context "shaker-context" modified.` + "\n",
expectedConfig: clientcmdapi.Config{
Contexts: map[string]*clientcmdapi.Context{
"shaker-context": {AuthInfo: "user_nickname", Cluster: "cluster_nickname", Namespace: "namespace"},
"not-this": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}},
}
test.run(t)
}
func (test createContextTest) run(t *testing.T) {
fakeKubeFile, err := ioutil.TempFile(os.TempDir(), "")
if err != nil {
@@ -108,8 +137,8 @@ func (test createContextTest) run(t *testing.T) {
}
}
if test.expectedConfig.Contexts != nil {
expectContext := test.expectedConfig.Contexts[test.args[0]]
actualContext := config.Contexts[test.args[0]]
expectContext := test.expectedConfig.Contexts[test.testContext]
actualContext := config.Contexts[test.testContext]
if expectContext.AuthInfo != actualContext.AuthInfo || expectContext.Cluster != actualContext.Cluster ||
expectContext.Namespace != actualContext.Namespace {
t.Errorf("Fail in %q:\n expected Context %v\n but found %v in kubeconfig\n", test.description, expectContext, actualContext)

View File

@@ -1,101 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
genericprinters "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/printers"
)
// kubectlConfigPrintFlags composes common printer flag structs
// used across all config commands, and provides a method
// of retrieving a known printer based on flag values provided.
type kubectlConfigPrintFlags struct {
JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags
NamePrintFlags *genericclioptions.NamePrintFlags
TemplateFlags *printers.KubeTemplatePrintFlags
TypeSetter *genericprinters.TypeSetterPrinter
OutputFormat *string
}
func (f *kubectlConfigPrintFlags) Complete(successTemplate string) error {
return f.NamePrintFlags.Complete(successTemplate)
}
func (f *kubectlConfigPrintFlags) AllowedFormats() []string {
formats := f.JSONYamlPrintFlags.AllowedFormats()
formats = append(formats, f.NamePrintFlags.AllowedFormats()...)
formats = append(formats, f.TemplateFlags.AllowedFormats()...)
return formats
}
func (f *kubectlConfigPrintFlags) ToPrinter() (printers.ResourcePrinter, error) {
outputFormat := ""
if f.OutputFormat != nil {
outputFormat = *f.OutputFormat
}
if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
return f.TypeSetter.WrapToPrinter(p, err)
}
if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
return f.TypeSetter.WrapToPrinter(p, err)
}
if p, err := f.TemplateFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
return f.TypeSetter.WrapToPrinter(p, err)
}
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: f.AllowedFormats()}
}
func (f *kubectlConfigPrintFlags) AddFlags(cmd *cobra.Command) {
f.JSONYamlPrintFlags.AddFlags(cmd)
f.NamePrintFlags.AddFlags(cmd)
f.TemplateFlags.AddFlags(cmd)
if f.OutputFormat != nil {
cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, "Output format. One of: json|yaml|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See custom columns [http://kubernetes.io/docs/user-guide/kubectl-overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].")
}
}
// WithDefaultOutput sets a default output format if one is not provided through a flag value
func (f *kubectlConfigPrintFlags) WithDefaultOutput(output string) *kubectlConfigPrintFlags {
f.OutputFormat = &output
return f
}
func newKubeConfigPrintFlags(scheme runtime.ObjectTyper) *kubectlConfigPrintFlags {
outputFormat := ""
return &kubectlConfigPrintFlags{
OutputFormat: &outputFormat,
JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(),
NamePrintFlags: genericclioptions.NewNamePrintFlags(""),
TemplateFlags: printers.NewKubeTemplatePrintFlags(),
TypeSetter: genericprinters.NewTypeSetter(scheme),
}
}

View File

@@ -27,11 +27,11 @@ import (
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
)

View File

@@ -21,9 +21,9 @@ import (
"os"
"testing"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
type getContextsTest struct {
@@ -61,7 +61,7 @@ func TestGetContextsAllNoHeader(t *testing.T) {
names: []string{},
noHeader: true,
nameOnly: false,
expectedOut: "* shaker-context big-cluster blue-user saw-ns\n",
expectedOut: "* shaker-context big-cluster blue-user saw-ns\n",
}
test.run(t)
}
@@ -171,7 +171,7 @@ func (test getContextsTest) run(t *testing.T) {
cmd.Run(cmd, test.names)
if len(test.expectedOut) != 0 {
if buf.String() != test.expectedOut {
t.Errorf("Expected %v, but got %v", test.expectedOut, buf.String())
t.Errorf("Expected\n%s\ngot\n%s", test.expectedOut, buf.String())
}
return
}

View File

@@ -22,19 +22,19 @@ import (
"github.com/spf13/cobra"
"k8s.io/apiserver/pkg/util/flag"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/tools/clientcmd/api/latest"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
)
type ViewOptions struct {
PrintFlags *kubectlConfigPrintFlags
PrintFlags *genericclioptions.PrintFlags
PrintObject printers.ResourcePrinterFunc
ConfigAccess clientcmd.ConfigAccess
@@ -70,7 +70,7 @@ var (
func NewCmdConfigView(f cmdutil.Factory, streams genericclioptions.IOStreams, ConfigAccess clientcmd.ConfigAccess) *cobra.Command {
o := &ViewOptions{
PrintFlags: newKubeConfigPrintFlags(legacyscheme.Scheme).WithDefaultOutput("yaml"),
PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(legacyscheme.Scheme).WithDefaultOutput("yaml"),
ConfigAccess: ConfigAccess,
IOStreams: streams,

View File

@@ -21,10 +21,10 @@ import (
"os"
"testing"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
type viewClusterTest struct {

View File

@@ -19,20 +19,20 @@ package cmd
import (
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
scheme "k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/validation"
)
var (
@@ -59,46 +59,20 @@ var (
kubectl convert -f . | kubectl create -f -`))
)
// NewCmdConvert creates a command object for the generic "convert" action, which
// translates the config file into a given version.
func NewCmdConvert(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := NewConvertOptions(ioStreams)
cmd := &cobra.Command{
Use: "convert -f FILENAME",
DisableFlagsInUseLine: true,
Short: i18n.T("Convert config files between different API versions"),
Long: convert_long,
Example: convert_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd))
cmdutil.CheckErr(options.RunConvert())
},
}
options.PrintFlags.AddFlags(cmd)
usage := "to need to get converted."
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.MarkFlagRequired("filename")
cmdutil.AddValidateFlags(cmd)
cmd.Flags().BoolVar(&options.local, "local", options.local, "If true, convert will NOT try to contact api-server but run locally.")
cmd.Flags().String("output-version", "", i18n.T("Output the formatted object with the given group version (for ex: 'extensions/v1beta1').)"))
return cmd
}
// ConvertOptions have the data required to perform the convert operation
type ConvertOptions struct {
PrintFlags *genericclioptions.PrintFlags
PrintObj printers.ResourcePrinterFunc
Printer printers.ResourcePrinter
OutputVersion string
Namespace string
builder func() *resource.Builder
local bool
validator func() (validation.Schema, error)
resource.FilenameOptions
builder *resource.Builder
local bool
genericclioptions.IOStreams
specifiedOutputVersion schema.GroupVersion
}
func NewConvertOptions(ioStreams genericclioptions.IOStreams) *ConvertOptions {
@@ -109,57 +83,73 @@ func NewConvertOptions(ioStreams genericclioptions.IOStreams) *ConvertOptions {
}
}
// outputVersion returns the preferred output version for generic content (JSON, YAML, or templates)
// defaultVersion is never mutated. Nil simply allows clean passing in common usage from client.Config
func outputVersion(cmd *cobra.Command) (schema.GroupVersion, error) {
outputVersionString := cmdutil.GetFlagString(cmd, "output-version")
if len(outputVersionString) == 0 {
return schema.GroupVersion{}, nil
// NewCmdConvert creates a command object for the generic "convert" action, which
// translates the config file into a given version.
func NewCmdConvert(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewConvertOptions(ioStreams)
cmd := &cobra.Command{
Use: "convert -f FILENAME",
DisableFlagsInUseLine: true,
Short: i18n.T("Convert config files between different API versions"),
Long: convert_long,
Example: convert_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(o.RunConvert())
},
}
return schema.ParseGroupVersion(outputVersionString)
cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, convert will NOT try to contact api-server but run locally.")
cmd.Flags().StringVar(&o.OutputVersion, "output-version", o.OutputVersion, i18n.T("Output the formatted object with the given group version (for ex: 'extensions/v1beta1')."))
o.PrintFlags.AddFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "to need to get converted.")
cmd.MarkFlagRequired("filename")
return cmd
}
// Complete collects information required to run Convert command from command line.
func (o *ConvertOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) (err error) {
o.specifiedOutputVersion, err = outputVersion(cmd)
o.builder = f.NewBuilder
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
// build the builder
o.builder = f.NewBuilder().
WithScheme(scheme.Scheme).
LocalParam(o.local)
if !o.local {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
if err != nil {
return err
}
o.builder.Schema(schema)
o.validator = func() (validation.Schema, error) {
return f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
}
cmdNamespace, _, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
o.builder.NamespaceParam(cmdNamespace).
ContinueOnError().
FilenameParam(false, &o.FilenameOptions).
Flatten()
// build the printer
printer, err := o.PrintFlags.ToPrinter()
o.Printer, err = o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = printer.PrintObj
return nil
}
// RunConvert implements the generic Convert command
func (o *ConvertOptions) RunConvert() error {
r := o.builder.Do()
b := o.builder().
WithScheme(scheme.Scheme).
LocalParam(o.local)
if !o.local {
schema, err := o.validator()
if err != nil {
return err
}
b.Schema(schema)
}
r := b.NamespaceParam(o.Namespace).
ContinueOnError().
FilenameParam(false, &o.FilenameOptions).
Flatten().
Do()
err := r.Err()
if err != nil {
return err
@@ -175,36 +165,20 @@ func (o *ConvertOptions) RunConvert() error {
return fmt.Errorf("no objects passed to convert")
}
objects, err := asVersionedObject(infos, !singleItemImplied, o.specifiedOutputVersion, cmdutil.InternalVersionJSONEncoder())
var specifiedOutputVersion schema.GroupVersion
if len(o.OutputVersion) > 0 {
specifiedOutputVersion, err = schema.ParseGroupVersion(o.OutputVersion)
if err != nil {
return err
}
}
objects, err := asVersionedObject(infos, !singleItemImplied, specifiedOutputVersion, cmdutil.InternalVersionJSONEncoder())
if err != nil {
return err
}
if meta.IsListType(objects) {
obj, err := objectListToVersionedObject([]runtime.Object{objects}, o.specifiedOutputVersion)
if err != nil {
return err
}
return o.PrintObj(obj, o.Out)
}
return o.PrintObj(objects, o.Out)
}
// objectListToVersionedObject receives a list of api objects and a group version
// and squashes the list's items into a single versioned runtime.Object.
func objectListToVersionedObject(objects []runtime.Object, specifiedOutputVersion schema.GroupVersion) (runtime.Object, error) {
objectList := &api.List{Items: objects}
targetVersions := []schema.GroupVersion{}
if !specifiedOutputVersion.Empty() {
targetVersions = append(targetVersions, specifiedOutputVersion)
}
targetVersions = append(targetVersions, schema.GroupVersion{Group: "", Version: "v1"})
converted, err := tryConvert(scheme.Scheme, objectList, targetVersions...)
if err != nil {
return nil, err
}
return converted, nil
return o.Printer.PrintObj(objects, o.Out)
}
// asVersionedObject converts a list of infos into a single object - either a List containing

View File

@@ -23,9 +23,9 @@ import (
"strings"
"testing"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
type testcase struct {

View File

@@ -27,11 +27,11 @@ import (
"path/filepath"
"strings"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"bytes"
@@ -70,7 +70,7 @@ type CopyOptions struct {
Namespace string
ClientConfig *restclient.Config
Clientset internalclientset.Interface
Clientset kubernetes.Interface
genericclioptions.IOStreams
}
@@ -144,7 +144,7 @@ func (o *CopyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
return err
}
o.Clientset, err = f.ClientSet()
o.Clientset, err = f.KubernetesClientSet()
if err != nil {
return err
}
@@ -164,6 +164,9 @@ func (o *CopyOptions) Validate(cmd *cobra.Command, args []string) error {
}
func (o *CopyOptions) Run(args []string) error {
if len(args) < 2 {
return fmt.Errorf("source and destination are required")
}
srcSpec, err := extractFileSpec(args[0])
if err != nil {
return err

View File

@@ -33,10 +33,9 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -520,8 +519,8 @@ func TestClean(t *testing.T) {
func TestCopyToPod(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
ns := legacyscheme.Codecs
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},

View File

@@ -18,38 +18,35 @@ go_library(
"create_secret.go",
"create_service.go",
"create_serviceaccount.go",
"flags.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/create",
visibility = ["//build/visible_to:pkg_kubectl_cmd_create_CONSUMERS"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/cmd/util/editor:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/printers:go_default_library",
"//staging/src/k8s.io/api/batch/v1:go_default_library",
"//staging/src/k8s.io/api/batch/v1beta1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1: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/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library",
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/batch/v1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/batch/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
],
)
@@ -77,30 +74,26 @@ go_test(
],
embed = [":go_default_library"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testing:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//staging/src/k8s.io/api/batch/v1:go_default_library",
"//staging/src/k8s.io/api/batch/v1beta1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
],
)

View File

@@ -28,23 +28,24 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
kruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/client-go/dynamic"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
)
type CreateOptions struct {
PrintFlags *PrintFlags
PrintFlags *genericclioptions.PrintFlags
RecordFlags *genericclioptions.RecordFlags
DryRun bool
@@ -79,7 +80,7 @@ var (
func NewCreateOptions(ioStreams genericclioptions.IOStreams) *CreateOptions {
return &CreateOptions{
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{},
@@ -209,7 +210,7 @@ func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error {
}
if o.EditBeforeCreate {
return RunEditOnCreate(f, o.RecordFlags, o.IOStreams, cmd, &o.FilenameOptions)
return RunEditOnCreate(f, o.PrintFlags, o.RecordFlags, o.IOStreams, cmd, &o.FilenameOptions)
}
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
if err != nil {
@@ -298,13 +299,13 @@ func (o *CreateOptions) raw(f cmdutil.Factory) error {
return nil
}
func RunEditOnCreate(f cmdutil.Factory, recordFlags *genericclioptions.RecordFlags, ioStreams genericclioptions.IOStreams, cmd *cobra.Command, options *resource.FilenameOptions) error {
func RunEditOnCreate(f cmdutil.Factory, printFlags *genericclioptions.PrintFlags, recordFlags *genericclioptions.RecordFlags, ioStreams genericclioptions.IOStreams, cmd *cobra.Command, options *resource.FilenameOptions) error {
editOptions := editor.NewEditOptions(editor.EditBeforeCreateMode, ioStreams)
editOptions.FilenameOptions = *options
editOptions.ValidateOptions = cmdutil.ValidateOptions{
EnableValidation: cmdutil.GetFlagBool(cmd, "validate"),
}
editOptions.Output = cmdutil.GetFlagString(cmd, "output")
editOptions.PrintFlags = printFlags
editOptions.ApplyAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
editOptions.RecordFlags = recordFlags
@@ -317,7 +318,7 @@ func RunEditOnCreate(f cmdutil.Factory, recordFlags *genericclioptions.RecordFla
// createAndRefresh creates an object from input info and refreshes info with that object
func createAndRefresh(info *resource.Info) error {
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, nil)
if err != nil {
return err
}
@@ -327,8 +328,13 @@ func createAndRefresh(info *resource.Info) error {
// NameFromCommandArgs is a utility function for commands that assume the first argument is a resource name
func NameFromCommandArgs(cmd *cobra.Command, args []string) (string, error) {
if len(args) != 1 {
return "", cmdutil.UsageErrorf(cmd, "exactly one NAME is required, got %d", len(args))
argsLen := cmd.ArgsLenAtDash()
// ArgsLenAtDash returns -1 when -- was not specified
if argsLen == -1 {
argsLen = len(args)
}
if argsLen != 1 {
return "", cmdutil.UsageErrorf(cmd, "exactly one NAME is required, got %d", argsLen)
}
return args[0], nil
}
@@ -336,7 +342,7 @@ func NameFromCommandArgs(cmd *cobra.Command, args []string) (string, error) {
// CreateSubcommandOptions is an options struct to support create subcommands
type CreateSubcommandOptions struct {
// PrintFlags holds options necessary for obtaining a printer
PrintFlags *PrintFlags
PrintFlags *genericclioptions.PrintFlags
// Name of resource being created
Name string
// StructuredGenerator is the resource generator for the object being created
@@ -358,7 +364,7 @@ type CreateSubcommandOptions struct {
func NewCreateSubcommandOptions(ioStreams genericclioptions.IOStreams) *CreateSubcommandOptions {
return &CreateSubcommandOptions{
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
IOStreams: ioStreams,
}
}
@@ -412,7 +418,7 @@ func (o *CreateSubcommandOptions) Run() error {
}
if !o.DryRun {
// create subcommands have compiled knowledge of things they create, so type them directly
gvks, _, err := legacyscheme.Scheme.ObjectKinds(obj)
gvks, _, err := scheme.Scheme.ObjectKinds(obj)
if err != nil {
return err
}
@@ -428,13 +434,13 @@ func (o *CreateSubcommandOptions) Run() error {
asUnstructured := &unstructured.Unstructured{}
if err := legacyscheme.Scheme.Convert(obj, asUnstructured, nil); err != nil {
if err := scheme.Scheme.Convert(obj, asUnstructured, nil); err != nil {
return err
}
if mapping.Scope.Name() == meta.RESTScopeNameRoot {
o.Namespace = ""
}
actualObject, err := o.DynamicClient.Resource(mapping.Resource).Namespace(o.Namespace).Create(asUnstructured)
actualObject, err := o.DynamicClient.Resource(mapping.Resource).Namespace(o.Namespace).Create(asUnstructured, metav1.CreateOptions{})
if err != nil {
return err
}

View File

@@ -25,9 +25,9 @@ import (
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilflag "k8s.io/apiserver/pkg/util/flag"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -25,11 +25,10 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -166,7 +165,7 @@ func TestCreateClusterRole(t *testing.T) {
}
cmd.Run(cmd, []string{clusterRoleName})
actual := &rbac.ClusterRole{}
if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), buf.Bytes(), actual); err != nil {
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), buf.Bytes(), actual); err != nil {
t.Log(string(buf.Bytes()))
t.Fatal(err)
}

View File

@@ -19,10 +19,10 @@ package create
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -28,11 +28,11 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestCreateClusterRoleBinding(t *testing.T) {
@@ -71,7 +71,7 @@ func TestCreateClusterRoleBinding(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
ns := legacyscheme.Codecs
ns := scheme.Codecs
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
encoder := ns.EncoderForVersion(info.Serializer, groupVersion)

View File

@@ -19,10 +19,10 @@ package create
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -26,10 +26,9 @@ import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -39,8 +38,8 @@ func TestCreateConfigMap(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},

View File

@@ -19,10 +19,10 @@ package create
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@@ -117,7 +117,7 @@ func (o *DeploymentOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
return err
}
clientset, err := f.ClientSet()
clientset, err := f.KubernetesClientSet()
if err != nil {
return err
}

View File

@@ -24,13 +24,13 @@ import (
"github.com/stretchr/testify/assert"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func Test_generatorFromName(t *testing.T) {
@@ -92,7 +92,7 @@ func TestCreateDeployment(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
ns := legacyscheme.Codecs
ns := scheme.Codecs
fakeDiscovery := "{\"kind\":\"APIResourceList\",\"apiVersion\":\"v1\",\"groupVersion\":\"apps/v1\",\"resources\":[{\"name\":\"deployments\",\"singularName\":\"\",\"namespaced\":true,\"kind\":\"Deployment\",\"verbs\":[\"create\",\"delete\",\"deletecollection\",\"get\",\"list\",\"patch\",\"update\",\"watch\"],\"shortNames\":[\"deploy\"],\"categories\":[\"all\"]}]}"
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
@@ -122,7 +122,7 @@ func TestCreateDeploymentNoImage(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
ns := legacyscheme.Codecs
ns := scheme.Codecs
fakeDiscovery := "{\"kind\":\"APIResourceList\",\"apiVersion\":\"v1\",\"groupVersion\":\"apps/v1\",\"resources\":[{\"name\":\"deployments\",\"singularName\":\"\",\"namespaced\":true,\"kind\":\"Deployment\",\"verbs\":[\"create\",\"delete\",\"deletecollection\",\"get\",\"list\",\"patch\",\"update\",\"watch\"],\"shortNames\":[\"deploy\"],\"categories\":[\"all\"]}]}"
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
@@ -140,7 +140,7 @@ func TestCreateDeploymentNoImage(t *testing.T) {
cmd.Flags().Set("output", "name")
options := &DeploymentOpts{
CreateSubcommandOptions: &CreateSubcommandOptions{
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
DryRun: true,
IOStreams: ioStreams,
},

View File

@@ -23,14 +23,14 @@ import (
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
clientbatchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
batchv1client "k8s.io/client-go/kubernetes/typed/batch/v1"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@@ -40,31 +40,38 @@ var (
Create a job with the specified name.`))
jobExample = templates.Examples(i18n.T(`
# Create a job
kubectl create job my-job --image=busybox
# Create a job with command
kubectl create job my-job --image=busybox -- date
# Create a job from a CronJob named "a-cronjob"
kubectl create job test-job --from=cronjob/a-cronjob`))
)
type CreateJobOptions struct {
PrintFlags *PrintFlags
PrintFlags *genericclioptions.PrintFlags
PrintObj func(obj runtime.Object) error
Name string
From string
Name string
Image string
From string
Command []string
Namespace string
OutputFormat string
Client clientbatchv1.BatchV1Interface
DryRun bool
Builder *resource.Builder
Cmd *cobra.Command
Namespace string
Client batchv1client.BatchV1Interface
DryRun bool
Builder *resource.Builder
Cmd *cobra.Command
genericclioptions.IOStreams
}
func NewCreateJobOptions(ioStreams genericclioptions.IOStreams) *CreateJobOptions {
return &CreateJobOptions{
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
IOStreams: ioStreams,
}
}
@@ -72,15 +79,15 @@ func NewCreateJobOptions(ioStreams genericclioptions.IOStreams) *CreateJobOption
// NewCmdCreateJob is a command to ease creating Jobs from CronJobs.
func NewCmdCreateJob(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewCreateJobOptions(ioStreams)
cmd := &cobra.Command{
Use: "job NAME [--from=CRONJOB]",
Use: "job NAME [--image=image --from=cronjob/name] -- [COMMAND] [args...]",
Short: jobLong,
Long: jobLong,
Example: jobExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.RunCreateJob())
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
}
@@ -89,32 +96,39 @@ func NewCmdCreateJob(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddDryRunFlag(cmd)
cmd.Flags().StringVar(&o.Image, "image", o.Image, "Image name to run.")
cmd.Flags().StringVar(&o.From, "from", o.From, "The name of the resource to create a Job from (only cronjob is supported).")
return cmd
}
func (o *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) (err error) {
if len(args) == 0 {
return cmdutil.UsageErrorf(cmd, "NAME is required")
func (o *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
o.Name = name
if len(args) > 1 {
o.Command = args[1:]
}
clientConfig, err := f.ToRESTConfig()
if err != nil {
return err
}
o.Client, err = batchv1client.NewForConfig(clientConfig)
if err != nil {
return err
}
o.Name = args[0]
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
clientset, err := f.KubernetesClientSet()
if err != nil {
return err
}
o.Client = clientset.BatchV1()
o.Builder = f.NewBuilder()
o.DryRun = cmdutil.GetDryRunFlag(cmd)
o.Cmd = cmd
o.OutputFormat = cmdutil.GetFlagString(cmd, "output")
o.DryRun = cmdutil.GetDryRunFlag(cmd)
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
@@ -122,7 +136,6 @@ func (o *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
if err != nil {
return err
}
o.PrintObj = func(obj runtime.Object) error {
return printer.PrintObj(obj, o.Out)
}
@@ -130,50 +143,47 @@ func (o *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
return nil
}
func (o *CreateJobOptions) RunCreateJob() error {
infos, err := o.Builder.
Unstructured().
NamespaceParam(o.Namespace).DefaultNamespace().
ResourceTypeOrNameArgs(false, o.From).
Flatten().
Latest().
Do().
Infos()
if err != nil {
return err
func (o *CreateJobOptions) Validate() error {
if (len(o.Image) == 0 && len(o.From) == 0) || (len(o.Image) != 0 && len(o.From) != 0) {
return fmt.Errorf("either --image or --from must be specified")
}
if len(infos) != 1 {
return fmt.Errorf("from must be an existing cronjob")
if o.Command != nil && len(o.Command) != 0 && len(o.From) != 0 {
return fmt.Errorf("cannot specify --from and command")
}
uncastVersionedObj, err := scheme.Scheme.ConvertToVersion(infos[0].Object, batchv1beta1.SchemeGroupVersion)
if err != nil {
return fmt.Errorf("from must be an existing cronjob: %v", err)
}
cronJob, ok := uncastVersionedObj.(*batchv1beta1.CronJob)
if !ok {
return fmt.Errorf("from must be an existing cronjob")
}
return o.createJob(cronJob)
return nil
}
func (o *CreateJobOptions) createJob(cronJob *batchv1beta1.CronJob) error {
annotations := make(map[string]string)
annotations["cronjob.kubernetes.io/instantiate"] = "manual"
for k, v := range cronJob.Spec.JobTemplate.Annotations {
annotations[k] = v
}
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: o.Name,
Namespace: o.Namespace,
Annotations: annotations,
Labels: cronJob.Spec.JobTemplate.Labels,
},
Spec: cronJob.Spec.JobTemplate.Spec,
}
func (o *CreateJobOptions) Run() error {
var job *batchv1.Job
if len(o.Image) > 0 {
job = o.createJob()
} else {
infos, err := o.Builder.
Unstructured().
NamespaceParam(o.Namespace).DefaultNamespace().
ResourceTypeOrNameArgs(false, o.From).
Flatten().
Latest().
Do().
Infos()
if err != nil {
return err
}
if len(infos) != 1 {
return fmt.Errorf("from must be an existing cronjob")
}
uncastVersionedObj, err := scheme.Scheme.ConvertToVersion(infos[0].Object, batchv1beta1.SchemeGroupVersion)
if err != nil {
return fmt.Errorf("from must be an existing cronjob: %v", err)
}
cronJob, ok := uncastVersionedObj.(*batchv1beta1.CronJob)
if !ok {
return fmt.Errorf("from must be an existing cronjob")
}
job = o.createJobFromCronJob(cronJob)
}
if !o.DryRun {
var err error
job, err = o.Client.Jobs(o.Namespace).Create(job)
@@ -184,3 +194,45 @@ func (o *CreateJobOptions) createJob(cronJob *batchv1beta1.CronJob) error {
return o.PrintObj(job)
}
func (o *CreateJobOptions) createJob() *batchv1.Job {
return &batchv1.Job{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"},
ObjectMeta: metav1.ObjectMeta{
Name: o.Name,
},
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: o.Name,
Image: o.Image,
Command: o.Command,
},
},
RestartPolicy: corev1.RestartPolicyNever,
},
},
},
}
}
func (o *CreateJobOptions) createJobFromCronJob(cronJob *batchv1beta1.CronJob) *batchv1.Job {
annotations := make(map[string]string)
annotations["cronjob.kubernetes.io/instantiate"] = "manual"
for k, v := range cronJob.Spec.JobTemplate.Annotations {
annotations[k] = v
}
return &batchv1.Job{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"},
ObjectMeta: metav1.ObjectMeta{
Name: o.Name,
Annotations: annotations,
Labels: cronJob.Spec.JobTemplate.Labels,
},
Spec: cronJob.Spec.JobTemplate.Spec,
}
}

View File

@@ -17,121 +17,176 @@ limitations under the License.
package create
import (
"strings"
"testing"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
fake "k8s.io/client-go/kubernetes/fake"
clienttesting "k8s.io/client-go/testing"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestCreateJobFromCronJob(t *testing.T) {
var submittedJob *batchv1.Job
testNamespaceName := "test"
testCronJobName := "test-cronjob"
testJobName := "test-job"
testImageName := "fake"
expectedLabels := make(map[string]string)
expectedAnnotations := make(map[string]string)
expectedLabels["test-label"] = "test-value"
expectJob := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespaceName,
Labels: expectedLabels,
Annotations: expectedAnnotations,
func TestCreateJobValidation(t *testing.T) {
tests := map[string]struct {
image string
command []string
from string
expected string
}{
"empty flags": {
expected: "--image or --from must be specified",
},
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Image: testImageName},
"both image and from specified": {
image: "my-image",
from: "cronjob/xyz",
expected: "--image or --from must be specified",
},
"from and command specified": {
from: "cronjob/xyz",
command: []string{"test", "command"},
expected: "cannot specify --from and command",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
o := &CreateJobOptions{
Image: tc.image,
From: tc.from,
Command: tc.command,
}
err := o.Validate()
if err != nil && !strings.Contains(err.Error(), tc.expected) {
t.Errorf("unexpected error: %v", err)
}
})
}
}
func TestCreateJob(t *testing.T) {
jobName := "test-job"
tests := map[string]struct {
image string
command []string
expected *batchv1.Job
}{
"just image": {
image: "busybox",
expected: &batchv1.Job{
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"},
ObjectMeta: metav1.ObjectMeta{
Name: jobName,
},
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: jobName,
Image: "busybox",
},
},
RestartPolicy: corev1.RestartPolicyNever,
},
},
},
},
},
"image and command": {
image: "busybox",
command: []string{"date"},
expected: &batchv1.Job{
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"},
ObjectMeta: metav1.ObjectMeta{
Name: jobName,
},
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: jobName,
Image: "busybox",
Command: []string{"date"},
},
},
RestartPolicy: corev1.RestartPolicyNever,
},
},
},
},
},
}
cronJob := &batchv1beta1.CronJob{
ObjectMeta: metav1.ObjectMeta{
Name: testCronJobName,
Namespace: testNamespaceName,
},
Spec: batchv1beta1.CronJobSpec{
Schedule: "* * * * *",
JobTemplate: batchv1beta1.JobTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespaceName,
Labels: expectedLabels,
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
o := &CreateJobOptions{
Name: jobName,
Image: tc.image,
Command: tc.command,
}
job := o.createJob()
if !apiequality.Semantic.DeepEqual(job, tc.expected) {
t.Errorf("expected:\n%#v\ngot:\n%#v", tc.expected, job)
}
})
}
}
func TestCreateJobFromCronJob(t *testing.T) {
jobName := "test-job"
tests := map[string]struct {
from *batchv1beta1.CronJob
expected *batchv1.Job
}{
"from CronJob": {
from: &batchv1beta1.CronJob{
Spec: batchv1beta1.CronJobSpec{
JobTemplate: batchv1beta1.JobTemplateSpec{
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Image: "test-image"},
},
RestartPolicy: corev1.RestartPolicyNever,
},
},
},
},
},
},
expected: &batchv1.Job{
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"},
ObjectMeta: metav1.ObjectMeta{
Name: jobName,
Annotations: map[string]string{"cronjob.kubernetes.io/instantiate": "manual"},
},
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Image: "test-image"},
},
RestartPolicy: corev1.RestartPolicyNever,
},
},
},
Spec: expectJob.Spec,
},
},
}
clientset := fake.Clientset{}
clientset.PrependReactor("create", "jobs", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
ca := action.(clienttesting.CreateAction)
submittedJob = ca.GetObject().(*batchv1.Job)
return true, expectJob, nil
})
f := cmdtesting.NewTestFactory()
defer f.Cleanup()
printFlags := NewPrintFlags("created", legacyscheme.Scheme)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmdOptions := &CreateJobOptions{
PrintFlags: printFlags,
Name: testJobName,
Namespace: testNamespaceName,
Client: clientset.BatchV1(),
Cmd: NewCmdCreateJob(f, ioStreams),
PrintObj: func(obj runtime.Object) error {
p, err := printFlags.ToPrinter()
if err != nil {
return err
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
o := &CreateJobOptions{
Name: jobName,
}
return p.PrintObj(obj, buf)
},
IOStreams: ioStreams,
}
err := cmdOptions.createJob(cronJob)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if submittedJob.ObjectMeta.Name != testJobName {
t.Errorf("expected '%s', got '%s'", testJobName, submittedJob.ObjectMeta.Name)
}
if l := len(submittedJob.Annotations); l != 1 {
t.Errorf("expected length of annotations array to be 1, got %d", l)
}
if v, ok := submittedJob.Annotations["cronjob.kubernetes.io/instantiate"]; !ok || v != "manual" {
t.Errorf("expected annotation cronjob.kubernetes.io/instantiate=manual to exist, got '%s'", v)
}
if l := len(submittedJob.Labels); l != 1 {
t.Errorf("expected length of labels array to be 1, got %d", l)
}
if v, ok := submittedJob.Labels["test-label"]; !ok || v != "test-value" {
t.Errorf("expected label test-label=test-value to to exist, got '%s'", v)
}
if l := len(submittedJob.Spec.Template.Spec.Containers); l != 1 {
t.Errorf("expected length of container array to be 1, got %d", l)
}
if submittedJob.Spec.Template.Spec.Containers[0].Image != testImageName {
t.Errorf("expected '%s', got '%s'", testImageName, submittedJob.Spec.Template.Spec.Containers[0].Image)
job := o.createJobFromCronJob(tc.from)
if !apiequality.Semantic.DeepEqual(job, tc.expected) {
t.Errorf("expected:\n%#v\ngot:\n%#v", tc.expected, job)
}
})
}
}

View File

@@ -19,10 +19,10 @@ package create
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -22,10 +22,9 @@ import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -35,8 +34,8 @@ func TestCreateNamespace(t *testing.T) {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},

View File

@@ -19,10 +19,10 @@ package create
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -23,11 +23,11 @@ import (
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestCreatePdb(t *testing.T) {
@@ -35,7 +35,7 @@ func TestCreatePdb(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
ns := legacyscheme.Codecs
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "policy", Version: "v1beta1"},
@@ -58,7 +58,7 @@ func TestCreatePdb(t *testing.T) {
cmd.Flags().Set("dry-run", "true")
cmd.Flags().Set("output", outputFormat)
printFlags := NewPrintFlags("created", legacyscheme.Scheme)
printFlags := genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme)
printFlags.OutputFormat = &outputFormat
options := &PodDisruptionBudgetOpts{

View File

@@ -19,10 +19,10 @@ package create
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@@ -32,7 +32,7 @@ var (
pcExample = templates.Examples(i18n.T(`
# Create a priorityclass named high-priority
kubectl create priorityclass default-priority --value=1000 --description="high priority"
kubectl create priorityclass high-priority --value=1000 --description="high priority"
# Create a priorityclass named default-priority that considered as the global default priority
kubectl create priorityclass default-priority --value=1000 --global-default=true --description="default priority"`))

View File

@@ -23,11 +23,11 @@ import (
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestCreatePriorityClass(t *testing.T) {
@@ -35,7 +35,7 @@ func TestCreatePriorityClass(t *testing.T) {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
ns := legacyscheme.Codecs
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "scheduling.k8s.io", Version: "v1beta1"},
@@ -59,7 +59,7 @@ func TestCreatePriorityClass(t *testing.T) {
cmd.Flags().Set("dry-run", "true")
cmd.Flags().Set("output", outputFormat)
printFlags := NewPrintFlags("created", legacyscheme.Scheme)
printFlags := genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme)
printFlags.OutputFormat = &outputFormat
options := &PriorityClassOpts{

View File

@@ -19,10 +19,10 @@ package create
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -20,8 +20,8 @@ import (
"testing"
"k8s.io/api/core/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestCreateQuota(t *testing.T) {

View File

@@ -28,11 +28,11 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
clientgorbacv1 "k8s.io/client-go/kubernetes/typed/rbac/v1"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@@ -54,7 +54,7 @@ var (
kubectl create role foo --verb=get,list,watch --resource=pods,pods/status`))
// Valid resource verb list for validation.
validResourceVerbs = []string{"*", "get", "delete", "list", "create", "update", "patch", "watch", "proxy", "deletecollection", "use", "bind", "impersonate"}
validResourceVerbs = []string{"*", "get", "delete", "list", "create", "update", "patch", "watch", "proxy", "deletecollection", "use", "bind", "escalate", "impersonate"}
// Specialized verbs and GroupResources
specialVerbs = map[string][]schema.GroupResource{
@@ -74,6 +74,16 @@ var (
Resource: "clusterroles",
},
},
"escalate": {
{
Group: "rbac.authorization.k8s.io",
Resource: "roles",
},
{
Group: "rbac.authorization.k8s.io",
Resource: "clusterroles",
},
},
"impersonate": {
{
Group: "",
@@ -102,7 +112,7 @@ type ResourceOptions struct {
}
type CreateRoleOptions struct {
PrintFlags *PrintFlags
PrintFlags *genericclioptions.PrintFlags
Name string
Verbs []string
@@ -121,7 +131,7 @@ type CreateRoleOptions struct {
func NewCreateRoleOptions(ioStreams genericclioptions.IOStreams) *CreateRoleOptions {
return &CreateRoleOptions{
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
IOStreams: ioStreams,
}
@@ -194,6 +204,11 @@ func (o *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
}
resource.Resource = parts[0]
if resource.Resource == "*" && len(parts) == 1 && len(sections) == 1 {
o.Resources = []ResourceOptions{*resource}
break
}
o.Resources = append(o.Resources, *resource)
}
@@ -269,6 +284,9 @@ func (o *CreateRoleOptions) validateResource() error {
if len(r.Resource) == 0 {
return fmt.Errorf("resource must be specified if apiGroup/subresource specified")
}
if r.Resource == "*" {
return nil
}
resource := schema.GroupVersionResource{Resource: r.Resource, Group: r.Group}
groupVersionResource, err := o.Mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group})

View File

@@ -25,10 +25,10 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestCreateRole(t *testing.T) {
@@ -139,7 +139,7 @@ func TestCreateRole(t *testing.T) {
}
cmd.Run(cmd, []string{roleName})
actual := &rbac.Role{}
if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), buf.Bytes(), actual); err != nil {
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), buf.Bytes(), actual); err != nil {
t.Log(string(buf.Bytes()))
t.Fatal(err)
}
@@ -360,26 +360,28 @@ func TestComplete(t *testing.T) {
tf.Client = &fake.RESTClient{}
tf.ClientConfigVal = defaultClientConfig()
cmd := NewCmdCreateRole(tf, genericclioptions.NewTestIOStreamsDiscard())
cmd.Flags().Set("resource", "pods,deployments.extensions")
defaultTestResources := "pods,deployments.extensions"
tests := map[string]struct {
params []string
resources string
roleOptions *CreateRoleOptions
expected *CreateRoleOptions
expectErr bool
}{
"test-missing-name": {
params: []string{},
params: []string{},
resources: defaultTestResources,
roleOptions: &CreateRoleOptions{
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
},
expectErr: true,
},
"test-duplicate-verbs": {
params: []string{roleName},
params: []string{roleName},
resources: defaultTestResources,
roleOptions: &CreateRoleOptions{
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
Name: roleName,
Verbs: []string{
"get",
@@ -410,9 +412,10 @@ func TestComplete(t *testing.T) {
expectErr: false,
},
"test-verball": {
params: []string{roleName},
params: []string{roleName},
resources: defaultTestResources,
roleOptions: &CreateRoleOptions{
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
Name: roleName,
Verbs: []string{
"get",
@@ -438,10 +441,153 @@ func TestComplete(t *testing.T) {
},
expectErr: false,
},
"test-duplicate-resourcenames": {
params: []string{roleName},
"test-allresource": {
params: []string{roleName},
resources: "*,pods",
roleOptions: &CreateRoleOptions{
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
PrintFlags: genericclioptions.NewPrintFlags("created"),
Name: roleName,
Verbs: []string{"*"},
},
expected: &CreateRoleOptions{
Name: roleName,
Verbs: []string{"*"},
Resources: []ResourceOptions{
{
Resource: "*",
},
},
ResourceNames: []string{},
},
expectErr: false,
},
"test-allresource-subresource": {
params: []string{roleName},
resources: "*/scale,pods",
roleOptions: &CreateRoleOptions{
PrintFlags: genericclioptions.NewPrintFlags("created"),
Name: roleName,
Verbs: []string{"*"},
},
expected: &CreateRoleOptions{
Name: roleName,
Verbs: []string{"*"},
Resources: []ResourceOptions{
{
Resource: "*",
SubResource: "scale",
},
{
Resource: "pods",
},
},
ResourceNames: []string{},
},
expectErr: false,
},
"test-allresrouce-allgroup": {
params: []string{roleName},
resources: "*.*,pods",
roleOptions: &CreateRoleOptions{
PrintFlags: genericclioptions.NewPrintFlags("created"),
Name: roleName,
Verbs: []string{"*"},
},
expected: &CreateRoleOptions{
Name: roleName,
Verbs: []string{"*"},
Resources: []ResourceOptions{
{
Resource: "*",
Group: "*",
},
{
Resource: "pods",
},
},
ResourceNames: []string{},
},
expectErr: false,
},
"test-allresource-allgroup-subresource": {
params: []string{roleName},
resources: "*.*/scale,pods",
roleOptions: &CreateRoleOptions{
PrintFlags: genericclioptions.NewPrintFlags("created"),
Name: roleName,
Verbs: []string{"*"},
},
expected: &CreateRoleOptions{
Name: roleName,
Verbs: []string{"*"},
Resources: []ResourceOptions{
{
Resource: "*",
Group: "*",
SubResource: "scale",
},
{
Resource: "pods",
},
},
ResourceNames: []string{},
},
expectErr: false,
},
"test-allresource-specificgroup": {
params: []string{roleName},
resources: "*.extensions,pods",
roleOptions: &CreateRoleOptions{
PrintFlags: genericclioptions.NewPrintFlags("created"),
Name: roleName,
Verbs: []string{"*"},
},
expected: &CreateRoleOptions{
Name: roleName,
Verbs: []string{"*"},
Resources: []ResourceOptions{
{
Resource: "*",
Group: "extensions",
},
{
Resource: "pods",
},
},
ResourceNames: []string{},
},
expectErr: false,
},
"test-allresource-specificgroup-subresource": {
params: []string{roleName},
resources: "*.extensions/scale,pods",
roleOptions: &CreateRoleOptions{
PrintFlags: genericclioptions.NewPrintFlags("created"),
Name: roleName,
Verbs: []string{"*"},
},
expected: &CreateRoleOptions{
Name: roleName,
Verbs: []string{"*"},
Resources: []ResourceOptions{
{
Resource: "*",
Group: "extensions",
SubResource: "scale",
},
{
Resource: "pods",
},
},
ResourceNames: []string{},
},
expectErr: false,
},
"test-duplicate-resourcenames": {
params: []string{roleName},
resources: defaultTestResources,
roleOptions: &CreateRoleOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
Name: roleName,
Verbs: []string{"*"},
ResourceNames: []string{"foo", "foo"},
@@ -464,9 +610,10 @@ func TestComplete(t *testing.T) {
expectErr: false,
},
"test-valid-complete-case": {
params: []string{roleName},
params: []string{roleName},
resources: defaultTestResources,
roleOptions: &CreateRoleOptions{
PrintFlags: NewPrintFlags("created", legacyscheme.Scheme),
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
Name: roleName,
Verbs: []string{"*"},
ResourceNames: []string{"foo"},
@@ -491,6 +638,9 @@ func TestComplete(t *testing.T) {
}
for name, test := range tests {
cmd := NewCmdCreateRole(tf, genericclioptions.NewTestIOStreamsDiscard())
cmd.Flags().Set("resource", test.resources)
err := test.roleOptions.Complete(tf, cmd, test.params)
if !test.expectErr && err != nil {
t.Errorf("%s: unexpected error: %v", name, err)

View File

@@ -19,10 +19,10 @@ package create
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -28,11 +28,11 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
var groupVersion = schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1"}
@@ -73,7 +73,7 @@ func TestCreateRoleBinding(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
ns := legacyscheme.Codecs
ns := scheme.Codecs
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
encoder := ns.EncoderForVersion(info.Serializer, groupVersion)

View File

@@ -19,10 +19,10 @@ package create
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -22,10 +22,9 @@ import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -40,8 +39,8 @@ func TestCreateSecretGeneric(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
@@ -72,8 +71,8 @@ func TestCreateSecretDockerRegistry(t *testing.T) {
secretObject := &v1.Secret{}
secretObject.Name = "my-secret"
tf := cmdtesting.NewTestFactory().WithNamespace("test")
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},

View File

@@ -20,10 +20,10 @@ import (
"github.com/spf13/cobra"
"k8s.io/api/core/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -22,10 +22,9 @@ import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -35,8 +34,8 @@ func TestCreateService(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
negSer := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
negSer := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
@@ -68,8 +67,8 @@ func TestCreateServiceNodePort(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
negSer := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
negSer := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
@@ -101,8 +100,8 @@ func TestCreateServiceExternalName(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
negSer := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
negSer := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},

View File

@@ -19,10 +19,10 @@ package create
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -22,10 +22,9 @@ import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -35,8 +34,8 @@ func TestCreateServiceAccount(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},

View File

@@ -20,16 +20,15 @@ import (
"net/http"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
apitesting "k8s.io/kubernetes/pkg/api/testing"
api "k8s.io/kubernetes/pkg/apis/core"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -54,7 +53,7 @@ func TestCreateObject(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
@@ -89,7 +88,7 @@ func TestCreateMultipleObject(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
@@ -128,7 +127,7 @@ func TestCreateDirectory(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
@@ -163,45 +162,46 @@ func initTestErrorHandler(t *testing.T) {
})
}
func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList) {
pods := &api.PodList{
func testData() (*corev1.PodList, *corev1.ServiceList, *corev1.ReplicationControllerList) {
pods := &corev1.PodList{
ListMeta: metav1.ListMeta{
ResourceVersion: "15",
},
Items: []api.Pod{
Items: []corev1.Pod{
{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: apitesting.DeepEqualSafePodSpec(),
Spec: apitesting.V1DeepEqualSafePodSpec(),
},
{
ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"},
Spec: apitesting.DeepEqualSafePodSpec(),
Spec: apitesting.V1DeepEqualSafePodSpec(),
},
},
}
svc := &api.ServiceList{
svc := &corev1.ServiceList{
ListMeta: metav1.ListMeta{
ResourceVersion: "16",
},
Items: []api.Service{
Items: []corev1.Service{
{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Spec: corev1.ServiceSpec{
SessionAffinity: "None",
Type: api.ServiceTypeClusterIP,
Type: corev1.ServiceTypeClusterIP,
},
},
},
}
rc := &api.ReplicationControllerList{
one := int32(1)
rc := &corev1.ReplicationControllerList{
ListMeta: metav1.ListMeta{
ResourceVersion: "17",
},
Items: []api.ReplicationController{
Items: []corev1.ReplicationController{
{
ObjectMeta: metav1.ObjectMeta{Name: "rc1", Namespace: "test", ResourceVersion: "18"},
Spec: api.ReplicationControllerSpec{
Replicas: 1,
Spec: corev1.ReplicationControllerSpec{
Replicas: &one,
},
},
},

View File

@@ -1,93 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package create
import (
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/apimachinery/pkg/runtime"
genericprinters "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/printers"
)
// PrintFlags composes common printer flag structs
// used across all create commands, and provides a method
// of retrieving a known printer based on flag values provided.
type PrintFlags struct {
JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags
NamePrintFlags *genericclioptions.NamePrintFlags
TemplateFlags *printers.KubeTemplatePrintFlags
TypeSetter *genericprinters.TypeSetterPrinter
OutputFormat *string
}
func (f *PrintFlags) AllowedFormats() []string {
return append(append(f.JSONYamlPrintFlags.AllowedFormats(), f.NamePrintFlags.AllowedFormats()...),
f.TemplateFlags.AllowedFormats()...)
}
func (f *PrintFlags) Complete(successTemplate string) error {
return f.NamePrintFlags.Complete(successTemplate)
}
func (f *PrintFlags) ToPrinter() (printers.ResourcePrinter, error) {
outputFormat := ""
if f.OutputFormat != nil {
outputFormat = *f.OutputFormat
}
if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
return f.TypeSetter.WrapToPrinter(p, err)
}
if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
return f.TypeSetter.WrapToPrinter(p, err)
}
if p, err := f.TemplateFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
return f.TypeSetter.WrapToPrinter(p, err)
}
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: f.AllowedFormats()}
}
func (f *PrintFlags) AddFlags(cmd *cobra.Command) {
f.JSONYamlPrintFlags.AddFlags(cmd)
f.NamePrintFlags.AddFlags(cmd)
f.TemplateFlags.AddFlags(cmd)
if f.OutputFormat != nil {
cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, "Output format. One of: json|yaml|wide|name|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See custom columns [http://kubernetes.io/docs/user-guide/kubectl-overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].")
}
}
func NewPrintFlags(operation string, scheme runtime.ObjectTyper) *PrintFlags {
outputFormat := ""
return &PrintFlags{
OutputFormat: &outputFormat,
JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(),
NamePrintFlags: genericclioptions.NewNamePrintFlags(operation),
TemplateFlags: printers.NewKubeTemplatePrintFlags(),
TypeSetter: genericprinters.NewTypeSetter(scheme),
}
}

View File

@@ -24,22 +24,17 @@ import (
"github.com/golang/glog"
"github.com/spf13/cobra"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/client-go/dynamic"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
kubectlwait "k8s.io/kubernetes/pkg/kubectl/cmd/wait"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@@ -235,6 +230,7 @@ func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
r = r.IgnoreErrors(errors.IsNotFound)
}
deletedInfos := []*resource.Info{}
uidMap := kubectlwait.UIDMap{}
err := r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
@@ -246,13 +242,34 @@ func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
if o.GracePeriod >= 0 {
options = metav1.NewDeleteOptions(int64(o.GracePeriod))
}
policy := metav1.DeletePropagationForeground
policy := metav1.DeletePropagationBackground
if !o.Cascade {
policy = metav1.DeletePropagationOrphan
}
options.PropagationPolicy = &policy
return o.deleteResource(info, options)
response, err := o.deleteResource(info, options)
if err != nil {
return err
}
resourceLocation := kubectlwait.ResourceLocation{
GroupResource: info.Mapping.Resource.GroupResource(),
Namespace: info.Namespace,
Name: info.Name,
}
if status, ok := response.(*metav1.Status); ok && status.Details != nil {
uidMap[resourceLocation] = status.Details.UID
return nil
}
responseMetadata, err := meta.Accessor(response)
if err != nil {
// we don't have UID, but we didn't fail the delete, next best thing is just skipping the UID
glog.V(1).Info(err)
return nil
}
uidMap[resourceLocation] = responseMetadata.GetUID()
return nil
})
if err != nil {
return err
@@ -277,6 +294,7 @@ func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
}
waitOptions := kubectlwait.WaitOptions{
ResourceFinder: genericclioptions.ResourceFinderForResult(resource.InfoListVisitor(deletedInfos)),
UIDMap: uidMap,
DynamicClient: o.DynamicClient,
Timeout: effectiveTimeout,
@@ -285,80 +303,23 @@ func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
IOStreams: o.IOStreams,
}
err = waitOptions.RunWait()
if errors.IsForbidden(err) {
if errors.IsForbidden(err) || errors.IsMethodNotSupported(err) {
// if we're forbidden from waiting, we shouldn't fail.
// if the resource doesn't support a verb we need, we shouldn't fail.
glog.V(1).Info(err)
return nil
}
return err
}
func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) error {
// TODO: Remove this in or after 1.12 release.
// Server version >= 1.11 no longer needs this hack.
mapping := info.ResourceMapping()
if mapping.Resource.GroupResource() == (schema.GroupResource{Group: "extensions", Resource: "daemonsets"}) ||
mapping.Resource.GroupResource() == (schema.GroupResource{Group: "apps", Resource: "daemonsets"}) {
if err := updateDaemonSet(info.Namespace, info.Name, o.DynamicClient); err != nil {
return err
}
}
if err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions); err != nil {
return cmdutil.AddSourceToErr("deleting", info.Source, err)
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)
if err != nil {
return nil, cmdutil.AddSourceToErr("deleting", info.Source, err)
}
o.PrintObj(info)
return nil
}
// TODO: Remove this in or after 1.12 release.
// Server version >= 1.11 no longer needs this hack.
func updateDaemonSet(namespace, name string, dynamicClient dynamic.Interface) error {
dsClient := dynamicClient.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "daemonsets"}).Namespace(namespace)
obj, err := dsClient.Get(name, metav1.GetOptions{})
if err != nil {
return err
}
ds := &appsv1.DaemonSet{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, ds); err != nil {
return err
}
// We set the nodeSelector to a random label. This label is nearly guaranteed
// to not be set on any node so the DameonSetController will start deleting
// daemon pods. Once it's done deleting the daemon pods, it's safe to delete
// the DaemonSet.
ds.Spec.Template.Spec.NodeSelector = map[string]string{
string(uuid.NewUUID()): string(uuid.NewUUID()),
}
// force update to avoid version conflict
ds.ResourceVersion = ""
out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ds)
if err != nil {
return err
}
if _, err = dsClient.Update(&unstructured.Unstructured{Object: out}); err != nil {
return err
}
// Wait for the daemon set controller to kill all the daemon pods.
if err := wait.Poll(1*time.Second, 5*time.Minute, func() (bool, error) {
updatedObj, err := dsClient.Get(name, metav1.GetOptions{})
if err != nil {
return false, nil
}
updatedDS := &appsv1.DaemonSet{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(updatedObj.Object, ds); err != nil {
return false, nil
}
return updatedDS.Status.CurrentNumberScheduled+updatedDS.Status.NumberMisscheduled == 0, nil
}); err != nil {
return err
}
return nil
return deleteResponse, nil
}
// deletion printing is special because we do not have an object to print.

View File

@@ -21,8 +21,8 @@ import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/dynamic"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
// PrintFlags composes common printer flag structs

View File

@@ -26,14 +26,13 @@ import (
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -55,7 +54,7 @@ func TestDeleteObjectByTuple(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -120,7 +119,7 @@ func TestOrphanDependentsInDeleteObject(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
var policy *metav1.DeletionPropagation
tf.UnstructuredClient = &fake.RESTClient{
@@ -137,9 +136,9 @@ func TestOrphanDependentsInDeleteObject(t *testing.T) {
}),
}
// DeleteOptions.PropagationPolicy should be Foreground, when cascade is true (default).
foregroundPolicy := metav1.DeletePropagationForeground
policy = &foregroundPolicy
// DeleteOptions.PropagationPolicy should be Background, when cascade is true (default).
backgroundPolicy := metav1.DeletePropagationBackground
policy = &backgroundPolicy
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, streams)
cmd.Flags().Set("namespace", "test")
@@ -171,7 +170,7 @@ func TestDeleteNamedObject(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -223,7 +222,7 @@ func TestDeleteObject(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -259,7 +258,7 @@ func TestDeleteObjectGraceZero(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -275,7 +274,7 @@ func TestDeleteObjectGraceZero(t *testing.T) {
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &metav1.Status{})}, nil
}
case p == "/api/v1/namespaces/test" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &corev1.Namespace{})}, nil
case p == "/namespaces/test/pods/nginx" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
@@ -372,13 +371,13 @@ func TestDeleteAllNotFound(t *testing.T) {
initTestErrorHandler(t)
_, svc, _ := testData()
// Add an item to the list which will result in a 404 on delete
svc.Items = append(svc.Items, api.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus
svc.Items = append(svc.Items, corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
notFoundError := &errors.NewNotFound(corev1.Resource("services"), "foo").ErrStatus
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -424,11 +423,11 @@ func TestDeleteAllIgnoreNotFound(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
// Add an item to the list which will result in a 404 on delete
svc.Items = append(svc.Items, api.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus
svc.Items = append(svc.Items, corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
notFoundError := &errors.NewNotFound(corev1.Resource("services"), "foo").ErrStatus
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -466,7 +465,7 @@ func TestDeleteMultipleObject(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -503,7 +502,7 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -550,7 +549,7 @@ func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -590,7 +589,7 @@ func TestDeleteDirectory(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -624,7 +623,7 @@ func TestDeleteMultipleSelector(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,

View File

@@ -26,10 +26,10 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
)

View File

@@ -23,11 +23,10 @@ import (
"testing"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/printers"
)
@@ -108,7 +107,7 @@ func TestDescribeObject(t *testing.T) {
_, _, rc := testData()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -149,7 +148,7 @@ func TestDescribeListObjects(t *testing.T) {
pods, _, _ := testData()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -176,7 +175,7 @@ func TestDescribeObjectShowEvents(t *testing.T) {
pods, _, _ := testData()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -202,7 +201,7 @@ func TestDescribeObjectSkipEvents(t *testing.T) {
pods, _, _ := testData()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,

View File

@@ -30,14 +30,14 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/client-go/dynamic"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/apply/parse"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/utils/exec"
)
@@ -265,7 +265,7 @@ type Object interface {
// InfoObject is an implementation of the Object interface. It gets all
// the information from the Info object.
type InfoObject struct {
Remote runtime.Unstructured
Remote *unstructured.Unstructured
Info *resource.Info
Encoder runtime.Encoder
Parser *parse.Factory

View File

@@ -25,7 +25,7 @@ import (
"strings"
"testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/utils/exec"
)

View File

@@ -34,6 +34,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
@@ -42,12 +43,12 @@ import (
restclient "k8s.io/client-go/rest"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@@ -402,8 +403,7 @@ func (o *DrainOptions) unreplicatedFilter(pod corev1.Pod) (bool, *warning, *fata
func (o *DrainOptions) daemonsetFilter(pod corev1.Pod) (bool, *warning, *fatal) {
// Note that we return false in cases where the pod is DaemonSet managed,
// regardless of flags. We never delete them, the only question is whether
// their presence constitutes an error.
// regardless of flags.
//
// The exception is for pods that are orphaned (the referencing
// management resource - including DaemonSet - is not found).
@@ -412,12 +412,17 @@ func (o *DrainOptions) daemonsetFilter(pod corev1.Pod) (bool, *warning, *fatal)
if controllerRef == nil || controllerRef.Kind != "DaemonSet" {
return true, nil, nil
}
// Any finished pod can be removed.
if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
return true, nil, nil
}
if _, err := o.client.ExtensionsV1beta1().DaemonSets(pod.Namespace).Get(controllerRef.Name, metav1.GetOptions{}); err != nil {
// remove orphaned pods with a warning if --force is used
if apierrors.IsNotFound(err) && o.Force {
return true, &warning{err.Error()}, nil
}
return false, nil, &fatal{err.Error()}
}
@@ -449,9 +454,14 @@ func (o *DrainOptions) localStorageFilter(pod corev1.Pod) (bool, *warning, *fata
if !hasLocalStorage(pod) {
return true, nil, nil
}
// Any finished pod can be removed.
if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
return true, nil, nil
}
if !o.DeleteLocalData {
return false, nil, &fatal{kLocalStorageFatal}
}
return true, &warning{kLocalStorageWarning}, nil
}
@@ -572,37 +582,39 @@ func (o *DrainOptions) deleteOrEvictPods(pods []corev1.Pod) error {
}
func (o *DrainOptions) evictPods(pods []corev1.Pod, policyGroupVersion string, getPodFn func(namespace, name string) (*corev1.Pod, error)) error {
doneCh := make(chan bool, len(pods))
errCh := make(chan error, 1)
returnCh := make(chan error, 1)
for _, pod := range pods {
go func(pod corev1.Pod, doneCh chan bool, errCh chan error) {
go func(pod corev1.Pod, returnCh chan error) {
var err error
for {
err = o.evictPod(pod, policyGroupVersion)
if err == nil {
break
} else if apierrors.IsNotFound(err) {
doneCh <- true
returnCh <- nil
return
} else if apierrors.IsTooManyRequests(err) {
fmt.Fprintf(o.ErrOut, "error when evicting pod %q (will retry after 5s): %v\n", pod.Name, err)
time.Sleep(5 * time.Second)
} else {
errCh <- fmt.Errorf("error when evicting pod %q: %v", pod.Name, err)
returnCh <- fmt.Errorf("error when evicting pod %q: %v", pod.Name, err)
return
}
}
podArray := []corev1.Pod{pod}
_, err = o.waitForDelete(podArray, 1*time.Second, time.Duration(math.MaxInt64), true, getPodFn)
if err == nil {
doneCh <- true
returnCh <- nil
} else {
errCh <- fmt.Errorf("error when waiting for pod %q terminating: %v", pod.Name, err)
returnCh <- fmt.Errorf("error when waiting for pod %q terminating: %v", pod.Name, err)
}
}(pod, doneCh, errCh)
}(pod, returnCh)
}
doneCount := 0
var errors []error
// 0 timeout means infinite, we use MaxInt64 to represent it.
var globalTimeout time.Duration
if o.Timeout == 0 {
@@ -610,19 +622,20 @@ func (o *DrainOptions) evictPods(pods []corev1.Pod, policyGroupVersion string, g
} else {
globalTimeout = o.Timeout
}
for {
globalTimeoutCh := time.After(globalTimeout)
numPods := len(pods)
for doneCount < numPods {
select {
case err := <-errCh:
return err
case <-doneCh:
case err := <-returnCh:
doneCount++
if doneCount == len(pods) {
return nil
if err != nil {
errors = append(errors, err)
}
case <-time.After(globalTimeout):
case <-globalTimeoutCh:
return fmt.Errorf("Drain did not complete within %v", globalTimeout)
}
}
return utilerrors.NewAggregate(errors)
}
func (o *DrainOptions) deletePods(pods []corev1.Pod, getPodFn func(namespace, name string) (*corev1.Pod, error)) error {
@@ -756,7 +769,7 @@ func (o *DrainOptions) RunCordonOrUncordon(desired bool) error {
fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err)
continue
}
_, err = helper.Patch(o.Namespace, nodeInfo.Name, types.StrategicMergePatchType, patchBytes)
_, err = helper.Patch(o.Namespace, nodeInfo.Name, types.StrategicMergePatchType, patchBytes, nil)
if err != nil {
fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err)
continue

View File

@@ -31,7 +31,7 @@ import (
"time"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions"
corev1 "k8s.io/api/core/v1"
policyv1beta1 "k8s.io/api/policy/v1beta1"
@@ -42,15 +42,14 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/batch"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/extensions"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -152,8 +151,8 @@ func TestCordon(t *testing.T) {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
new_node := &corev1.Node{}
updated := false
@@ -309,6 +308,31 @@ func TestDrain(t *testing.T) {
},
}
ds_terminated_pod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
Labels: labels,
SelfLink: testapi.Default.SelfLink("pods", "bar"),
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "extensions/v1beta1",
Kind: "DaemonSet",
Name: "ds",
BlockOwnerDeletion: boolptr(true),
Controller: boolptr(true),
},
},
},
Spec: corev1.PodSpec{
NodeName: "node",
},
Status: corev1.PodStatus{
Phase: corev1.PodSucceeded,
},
}
ds_pod_with_emptyDir := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
@@ -379,6 +403,46 @@ func TestDrain(t *testing.T) {
},
},
},
Spec: corev1.PodSpec{
NodeName: "node",
Volumes: []corev1.Volume{
{
Name: "scratch",
VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{Medium: ""}},
},
},
},
}
terminated_job_pod_with_local_storage := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
Labels: labels,
SelfLink: testapi.Default.SelfLink("pods", "bar"),
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "v1",
Kind: "Job",
Name: "job",
BlockOwnerDeletion: boolptr(true),
Controller: boolptr(true),
},
},
},
Spec: corev1.PodSpec{
NodeName: "node",
Volumes: []corev1.Volume{
{
Name: "scratch",
VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{Medium: ""}},
},
},
},
Status: corev1.PodStatus{
Phase: corev1.PodSucceeded,
},
}
rs := extensions.ReplicaSet{
@@ -445,6 +509,26 @@ func TestDrain(t *testing.T) {
},
},
}
emptydir_terminated_pod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
Labels: labels,
},
Spec: corev1.PodSpec{
NodeName: "node",
Volumes: []corev1.Volume{
{
Name: "scratch",
VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{Medium: ""}},
},
},
},
Status: corev1.PodStatus{
Phase: corev1.PodFailed,
},
}
tests := []struct {
description string
@@ -478,6 +562,16 @@ func TestDrain(t *testing.T) {
expectFatal: true,
expectDelete: false,
},
{
description: "DS-managed terminated pod",
node: node,
expected: cordoned_node,
pods: []corev1.Pod{ds_terminated_pod},
rcs: []api.ReplicationController{rc},
args: []string{"node"},
expectFatal: false,
expectDelete: true,
},
{
description: "orphaned DS-managed pod",
node: node,
@@ -520,11 +614,21 @@ func TestDrain(t *testing.T) {
expectDelete: false,
},
{
description: "Job-managed pod",
description: "Job-managed pod with local storage",
node: node,
expected: cordoned_node,
pods: []corev1.Pod{job_pod},
rcs: []api.ReplicationController{rc},
args: []string{"node", "--force", "--delete-local-data=true"},
expectFatal: false,
expectDelete: true,
},
{
description: "Job-managed terminated pod",
node: node,
expected: cordoned_node,
pods: []corev1.Pod{terminated_job_pod_with_local_storage},
rcs: []api.ReplicationController{rc},
args: []string{"node"},
expectFatal: false,
expectDelete: true,
@@ -568,6 +672,16 @@ func TestDrain(t *testing.T) {
expectFatal: true,
expectDelete: false,
},
{
description: "terminated pod with emptyDir",
node: node,
expected: cordoned_node,
pods: []corev1.Pod{emptydir_terminated_pod},
rcs: []api.ReplicationController{rc},
args: []string{"node"},
expectFatal: false,
expectDelete: true,
},
{
description: "pod with EmptyDir and --delete-local-data",
node: node,
@@ -606,8 +720,8 @@ func TestDrain(t *testing.T) {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},

View File

@@ -21,10 +21,10 @@ import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -35,12 +35,12 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/kubectl/cmd/create"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
)
type EditTestCase struct {

View File

@@ -24,15 +24,16 @@ import (
dockerterm "github.com/docker/docker/pkg/term"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/kubectl/util/term"
"k8s.io/kubernetes/pkg/util/interrupt"
@@ -85,6 +86,7 @@ func NewCmdExec(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
},
}
cmd.Flags().StringVarP(&options.PodName, "pod", "p", options.PodName, "Pod name")
cmd.Flags().MarkDeprecated("pod", "This flag is deprecated and will be removed in future. Use exec POD_NAME instead.")
// TODO support UID
cmd.Flags().StringVarP(&options.ContainerName, "container", "c", options.ContainerName, "Container name. If omitted, the first container in the pod will be chosen")
cmd.Flags().BoolVarP(&options.Stdin, "stdin", "i", options.Stdin, "Pass stdin to the container")
@@ -153,7 +155,6 @@ func (p *ExecOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []s
return cmdutil.UsageErrorf(cmd, execUsageStr)
}
if len(p.PodName) != 0 {
printDeprecationWarning(p.ErrOut, "exec POD_NAME", "-p POD_NAME")
if len(argsIn) < 1 {
return cmdutil.UsageErrorf(cmd, execUsageStr)
}
@@ -186,11 +187,11 @@ func (p *ExecOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []s
}
p.Config = config
clientset, err := f.ClientSet()
clientset, err := f.KubernetesClientSet()
if err != nil {
return err
}
p.PodClient = clientset.Core()
p.PodClient = clientset.CoreV1()
return nil
}
@@ -271,7 +272,7 @@ func (p *ExecOptions) Run() error {
return err
}
if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed {
if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
return fmt.Errorf("cannot exec into a container in a completed pod; current phase is %s", pod.Status.Phase)
}

View File

@@ -29,14 +29,13 @@ import (
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/term"
)
@@ -135,7 +134,7 @@ func TestPodAndContainer(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
ns := legacyscheme.Codecs
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
@@ -173,7 +172,7 @@ func TestExec(t *testing.T) {
version := "v1"
tests := []struct {
name, podPath, execPath string
pod *api.Pod
pod *corev1.Pod
execErr bool
}{
{
@@ -195,8 +194,8 @@ func TestExec(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
@@ -252,20 +251,20 @@ func TestExec(t *testing.T) {
}
}
func execPod() *api.Pod {
return &api.Pod{
func execPod() *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyAlways,
DNSPolicy: corev1.DNSClusterFirst,
Containers: []corev1.Container{
{
Name: "bar",
},
},
},
Status: api.PodStatus{
Phase: api.PodRunning,
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
},
}
}

View File

@@ -23,11 +23,11 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/kubectl/explain"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

View File

@@ -24,18 +24,18 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/client-go/dynamic"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
@@ -92,7 +92,6 @@ type ExposeServiceOptions struct {
Generators func(string) map[string]kubectl.Generator
CanBeExposed polymorphichelpers.CanBeExposedFunc
ClientForMapping func(*meta.RESTMapping) (resource.RESTClient, error)
MapBasedSelectorForObject func(runtime.Object) (string, error)
PortsForObject polymorphichelpers.PortsForObjectFunc
ProtocolsForObject func(runtime.Object) (map[string]string, error)
@@ -127,7 +126,7 @@ func NewCmdExposeService(f cmdutil.Factory, streams genericclioptions.IOStreams)
}
cmd := &cobra.Command{
Use: "expose (-f FILENAME | TYPE NAME) [--port=port] [--protocol=TCP|UDP] [--target-port=number-or-name] [--name=name] [--external-ip=external-ip-of-service] [--type=type]",
Use: "expose (-f FILENAME | TYPE NAME) [--port=port] [--protocol=TCP|UDP|SCTP] [--target-port=number-or-name] [--name=name] [--external-ip=external-ip-of-service] [--type=type]",
DisableFlagsInUseLine: true,
Short: i18n.T("Take a replication controller, service, deployment or pod and expose it as a new Kubernetes Service"),
Long: exposeLong,
@@ -191,7 +190,6 @@ func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) e
o.Generators = cmdutil.GeneratorFn
o.Builder = f.NewBuilder()
o.CanBeExposed = polymorphichelpers.CanBeExposedFn
o.ClientForMapping = f.ClientForMapping
o.MapBasedSelectorForObject = polymorphichelpers.MapBasedSelectorForObjectFn
o.ProtocolsForObject = polymorphichelpers.ProtocolsForObjectFn
o.PortsForObject = polymorphichelpers.PortsForObjectFn
@@ -211,7 +209,7 @@ func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) e
func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) error {
r := o.Builder.
WithScheme(legacyscheme.Scheme).
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
ContinueOnError().
NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(o.EnforceNamespace, &o.FilenameOptions).
@@ -314,8 +312,7 @@ func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) erro
}
if inline := cmdutil.GetFlagString(cmd, "overrides"); len(inline) > 0 {
codec := runtime.NewCodec(cmdutil.InternalVersionJSONEncoder(), cmdutil.InternalVersionDecoder())
object, err = cmdutil.Merge(codec, object, inline)
object, err = cmdutil.Merge(scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...), object, inline)
if err != nil {
return err
}
@@ -333,7 +330,7 @@ func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) erro
}
asUnstructured := &unstructured.Unstructured{}
if err := legacyscheme.Scheme.Convert(object, asUnstructured, nil); err != nil {
if err := scheme.Scheme.Convert(object, asUnstructured, nil); err != nil {
return err
}
gvks, _, err := unstructuredscheme.NewUnstructuredObjectTyper().ObjectKinds(asUnstructured)
@@ -345,7 +342,7 @@ func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) erro
return err
}
// Serialize the object with the annotation applied.
actualObject, err := o.DynamicClient.Resource(objMapping.Resource).Namespace(o.Namespace).Create(asUnstructured)
actualObject, err := o.DynamicClient.Resource(objMapping.Resource).Namespace(o.Namespace).Create(asUnstructured, metav1.CreateOptions{})
if err != nil {
return err
}

View File

@@ -22,23 +22,17 @@ import (
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
// This init should be removed after switching this command and its tests to user external types.
func init() {
api.AddToScheme(scheme.Scheme)
}
func TestRunExposeService(t *testing.T) {
tests := []struct {
name string
@@ -59,19 +53,19 @@ func TestRunExposeService(t *testing.T) {
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Spec: corev1.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test"},
output: &api.Service{
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: api.ProtocolUDP,
Protocol: corev1.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
@@ -90,19 +84,19 @@ func TestRunExposeService(t *testing.T) {
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Spec: corev1.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test"},
output: &api.Service{
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: api.ProtocolUDP,
Protocol: corev1.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
@@ -121,20 +115,20 @@ func TestRunExposeService(t *testing.T) {
"GET": "/namespaces/default/services/mayor",
"POST": "/namespaces/default/services",
},
input: &api.Service{
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "mayor", Namespace: "default", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Spec: corev1.ServiceSpec{
Selector: map[string]string{"run": "this"},
},
},
// No --name flag specified below. Service will use the rc's name passed via the 'default-name' parameter
flags: map[string]string{"selector": "run=this", "port": "80", "labels": "runas=amayor"},
output: &api.Service{
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "mayor", Namespace: "", Labels: map[string]string{"runas": "amayor"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: api.ProtocolTCP,
Protocol: corev1.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(80),
},
@@ -153,25 +147,25 @@ func TestRunExposeService(t *testing.T) {
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Spec: corev1.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "type": "LoadBalancer", "dry-run": "true"},
output: &api.Service{
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: api.ProtocolUDP,
Protocol: corev1.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"func": "stream"},
Type: api.ServiceTypeLoadBalancer,
Type: corev1.ServiceTypeLoadBalancer,
},
},
status: 200,
@@ -184,26 +178,26 @@ func TestRunExposeService(t *testing.T) {
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Spec: corev1.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "type": "LoadBalancer", "session-affinity": "ClientIP", "dry-run": "true"},
output: &api.Service{
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: api.ProtocolUDP,
Protocol: corev1.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"func": "stream"},
Type: api.ServiceTypeLoadBalancer,
SessionAffinity: api.ServiceAffinityClientIP,
Type: corev1.ServiceTypeLoadBalancer,
SessionAffinity: corev1.ServiceAffinityClientIP,
},
},
status: 200,
@@ -216,19 +210,19 @@ func TestRunExposeService(t *testing.T) {
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Spec: corev1.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "10.10.10.10", "dry-run": "true"},
output: &api.Service{
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: api.ProtocolUDP,
Protocol: corev1.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
@@ -248,25 +242,25 @@ func TestRunExposeService(t *testing.T) {
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Spec: corev1.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "true"},
output: &api.Service{
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: api.ProtocolUDP,
Protocol: corev1.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"func": "stream"},
ClusterIP: api.ClusterIPNone,
ClusterIP: corev1.ClusterIPNone,
},
},
expected: "service/foo exposed",
@@ -280,19 +274,19 @@ func TestRunExposeService(t *testing.T) {
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Spec: corev1.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "true"},
output: &api.Service{
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{},
Selector: map[string]string{"func": "stream"},
ClusterIP: api.ClusterIPNone,
ClusterIP: corev1.ClusterIPNone,
},
},
expected: "service/foo exposed",
@@ -306,19 +300,19 @@ func TestRunExposeService(t *testing.T) {
"GET": "/namespaces/test/services/redis-master",
"POST": "/namespaces/test/services",
},
input: &api.Service{
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "redis-master", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Spec: corev1.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"filename": "../../../test/e2e/testing-manifests/guestbook/redis-master-service.yaml", "selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "dry-run": "true"},
output: &api.Service{
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: api.ProtocolUDP,
Protocol: corev1.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
@@ -336,16 +330,16 @@ func TestRunExposeService(t *testing.T) {
"GET": "/namespaces/test/pods/a-name-that-is-toooo-big-for-a-service-because-it-can-only-handle-63-characters",
"POST": "/namespaces/test/services",
},
input: &api.Pod{
input: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
},
flags: map[string]string{"selector": "svc=frompod", "port": "90", "labels": "svc=frompod", "generator": "service/v2"},
output: &api.Service{
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "a-name-that-is-toooo-big-for-a-service-because-it-can-only-handle-63-characters", Namespace: "", Labels: map[string]string{"svc": "frompod"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: api.ProtocolTCP,
Protocol: corev1.ProtocolTCP,
Port: 90,
TargetPort: intstr.FromInt(90),
},
@@ -364,17 +358,17 @@ func TestRunExposeService(t *testing.T) {
"GET": "/namespaces/test/services/foo",
"POST": "/namespaces/test/services",
},
input: &api.Service{
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: api.ProtocolTCP,
Protocol: corev1.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(80),
},
{
Protocol: api.ProtocolTCP,
Protocol: corev1.ProtocolTCP,
Port: 443,
TargetPort: intstr.FromInt(443),
},
@@ -382,19 +376,19 @@ func TestRunExposeService(t *testing.T) {
},
},
flags: map[string]string{"selector": "svc=fromfoo", "generator": "service/v2", "name": "fromfoo", "dry-run": "true"},
output: &api.Service{
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "fromfoo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: "port-1",
Protocol: api.ProtocolTCP,
Protocol: corev1.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(80),
},
{
Name: "port-2",
Protocol: api.ProtocolTCP,
Protocol: corev1.ProtocolTCP,
Port: 443,
TargetPort: intstr.FromInt(443),
},
@@ -412,57 +406,194 @@ func TestRunExposeService(t *testing.T) {
"GET": "/namespaces/test/services/foo",
"POST": "/namespaces/test/services",
},
input: &api.Service{
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: api.ProtocolTCP,
Protocol: corev1.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(80),
},
{
Protocol: api.ProtocolUDP,
Protocol: corev1.ProtocolUDP,
Port: 8080,
TargetPort: intstr.FromInt(8080),
},
{
Protocol: api.ProtocolUDP,
Protocol: corev1.ProtocolUDP,
Port: 8081,
TargetPort: intstr.FromInt(8081),
},
{
Protocol: corev1.ProtocolSCTP,
Port: 8082,
TargetPort: intstr.FromInt(8082),
},
},
},
},
flags: map[string]string{"selector": "svc=fromfoo", "generator": "service/v2", "name": "fromfoo", "dry-run": "true"},
output: &api.Service{
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "fromfoo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: "port-1",
Protocol: api.ProtocolTCP,
Protocol: corev1.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(80),
},
{
Name: "port-2",
Protocol: api.ProtocolUDP,
Protocol: corev1.ProtocolUDP,
Port: 8080,
TargetPort: intstr.FromInt(8080),
},
{
Name: "port-3",
Protocol: api.ProtocolUDP,
Protocol: corev1.ProtocolUDP,
Port: 8081,
TargetPort: intstr.FromInt(8081),
},
{
Name: "port-4",
Protocol: corev1.ProtocolSCTP,
Port: 8082,
TargetPort: intstr.FromInt(8082),
},
},
Selector: map[string]string{"svc": "fromfoo"},
},
},
status: 200,
},
{
name: "expose-service-from-service-no-selector-defined-sctp",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: corev1.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test"},
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: corev1.ProtocolSCTP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"app": "go"},
},
},
expected: "service/foo exposed",
status: 200,
},
{
name: "expose-service-from-service-sctp",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: corev1.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test"},
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: corev1.ProtocolSCTP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"func": "stream"},
},
},
expected: "service/foo exposed",
status: 200,
},
{
name: "expose-service-cluster-ip-sctp",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: corev1.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "10.10.10.10", "dry-run": "true"},
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: corev1.ProtocolSCTP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"func": "stream"},
ClusterIP: "10.10.10.10",
},
},
expected: "service /foo exposed",
status: 200,
},
{
name: "expose-headless-service-sctp",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: corev1.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "true"},
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Protocol: corev1.ProtocolSCTP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"func": "stream"},
ClusterIP: corev1.ClusterIPNone,
},
},
expected: "service/foo exposed",
status: 200,
},
}
for _, test := range tests {
@@ -470,8 +601,8 @@ func TestRunExposeService(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace(test.ns)
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},

View File

@@ -30,26 +30,27 @@ go_library(
"//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",
"//pkg/util/interrupt:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/watch:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
@@ -66,28 +67,32 @@ go_test(
],
embed = [":go_default_library"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testing:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/v1:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/cmd/util/openapi/testing:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/k8s.io/client-go/rest/watch:go_default_library",
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/autoscaling/v1:go_default_library",
"//staging/src/k8s.io/api/batch/v1:go_default_library",
"//staging/src/k8s.io/api/batch/v1beta1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/extensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
"//staging/src/k8s.io/client-go/rest/watch:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
],
)

View File

@@ -17,6 +17,7 @@ limitations under the License.
package get
import (
"context"
"encoding/json"
"fmt"
"io"
@@ -35,14 +36,15 @@ import (
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/client-go/rest"
watchtools "k8s.io/client-go/tools/watch"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
"k8s.io/kubernetes/pkg/util/interrupt"
@@ -51,7 +53,7 @@ import (
// GetOptions contains the input to the get command.
type GetOptions struct {
PrintFlags *PrintFlags
ToPrinter func(*meta.RESTMapping, bool) (printers.ResourcePrinterFunc, error)
ToPrinter func(*meta.RESTMapping, bool, bool) (printers.ResourcePrinterFunc, error)
IsHumanReadablePrinter bool
PrintWithOpenAPICols bool
@@ -205,10 +207,10 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
o.NoHeaders = cmdutil.GetFlagBool(cmd, "no-headers")
// TODO (soltysh): currently we don't support sorting and custom columns
// TODO (soltysh): currently we don't support custom columns
// with server side print. So in these cases force the old behavior.
outputOption := cmd.Flags().Lookup("output").Value.String()
if o.Sort && outputOption == "custom-columns" {
if outputOption == "custom-columns" {
o.ServerPrint = false
}
@@ -224,10 +226,7 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
o.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, false)
if resource.MultipleTypesRequested(args) {
o.PrintFlags.EnsureWithKind()
}
o.ToPrinter = func(mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinterFunc, error) {
o.ToPrinter = func(mapping *meta.RESTMapping, withNamespace bool, withKind bool) (printers.ResourcePrinterFunc, error) {
// make a new copy of current flags / opts before mutating
printFlags := o.PrintFlags.Copy()
@@ -242,6 +241,9 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
if withNamespace {
printFlags.EnsureWithNamespace()
}
if withKind {
printFlags.EnsureWithKind()
}
printer, err := printFlags.ToPrinter()
if err != nil {
@@ -294,6 +296,96 @@ func (o *GetOptions) Validate(cmd *cobra.Command) error {
return nil
}
type OriginalPositioner interface {
OriginalPosition(int) int
}
type NopPositioner struct{}
func (t *NopPositioner) OriginalPosition(ix int) int {
return ix
}
type RuntimeSorter struct {
field string
decoder runtime.Decoder
objects []runtime.Object
positioner OriginalPositioner
}
func (r *RuntimeSorter) Sort() error {
if len(r.objects) <= 1 {
// a list is only considered "sorted" if there are 0 or 1 items in it
// AND (if 1 item) the item is not a Table object
_, isTable := r.objects[0].(*metav1beta1.Table)
if len(r.objects) == 0 || !isTable {
return nil
}
}
includesTable := false
includesRuntimeObjs := false
for _, obj := range r.objects {
switch t := obj.(type) {
case *metav1beta1.Table:
includesTable = true
if err := kubectl.NewTableSorter(t, r.field).Sort(); err != nil {
continue
}
default:
includesRuntimeObjs = true
}
}
// we use a NopPositioner when dealing with Table objects
// because the objects themselves are not swapped, but rather
// the rows in each object are swapped / sorted.
r.positioner = &NopPositioner{}
if includesRuntimeObjs && includesTable {
return fmt.Errorf("sorting is not supported on mixed Table and non-Table object lists")
}
if includesTable {
return nil
}
// if not dealing with a Table response from the server, assume
// all objects are runtime.Object as usual, and sort using old method.
var err error
if r.positioner, err = kubectl.SortObjects(r.decoder, r.objects, r.field); err != nil {
return err
}
return nil
}
func (r *RuntimeSorter) OriginalPosition(ix int) int {
if r.positioner == nil {
return 0
}
return r.positioner.OriginalPosition(ix)
}
// allows custom decoder to be set for testing
func (r *RuntimeSorter) WithDecoder(decoder runtime.Decoder) *RuntimeSorter {
r.decoder = decoder
return r
}
func NewRuntimeSorter(objects []runtime.Object, sortBy string) *RuntimeSorter {
parsedField, err := printers.RelaxedJSONPathExpression(sortBy)
if err != nil {
parsedField = sortBy
}
return &RuntimeSorter{
field: parsedField,
decoder: cmdutil.InternalVersionDecoder(),
objects: objects,
}
}
// Run performs the get operation.
// TODO: remove the need to pass these arguments, like other commands.
func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
@@ -304,6 +396,18 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
return o.watch(f, cmd, args)
}
// openapi printing is mutually exclusive with server side printing
if o.PrintWithOpenAPICols && o.ServerPrint {
fmt.Fprintf(o.IOStreams.ErrOut, "warning: --%s requested, --%s will be ignored\n", useOpenAPIPrintColumnFlagLabel, useServerPrintColumns)
}
chunkSize := o.ChunkSize
if o.Sort {
// TODO(juanvallejo): in the future, we could have the client use chunking
// to gather all results, then sort them all at the end to reduce server load.
chunkSize = 0
}
r := f.NewBuilder().
Unstructured().
NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
@@ -311,19 +415,30 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
LabelSelectorParam(o.LabelSelector).
FieldSelectorParam(o.FieldSelector).
ExportParam(o.Export).
RequestChunksOf(o.ChunkSize).
RequestChunksOf(chunkSize).
IncludeUninitialized(o.IncludeUninitialized).
ResourceTypeOrNameArgs(true, args...).
ContinueOnError().
Latest().
Flatten().
TransformRequests(func(req *rest.Request) {
if o.ServerPrint && o.IsHumanReadablePrinter && !o.Sort {
group := metav1beta1.GroupName
version := metav1beta1.SchemeGroupVersion.Version
// We need full objects if printing with openapi columns
if o.PrintWithOpenAPICols {
return
}
if !o.ServerPrint || !o.IsHumanReadablePrinter {
return
}
tableParam := fmt.Sprintf("application/json;as=Table;v=%s;g=%s, application/json", version, group)
req.SetHeader("Accept", tableParam)
group := metav1beta1.GroupName
version := metav1beta1.SchemeGroupVersion.Version
tableParam := fmt.Sprintf("application/json;as=Table;v=%s;g=%s, application/json", version, group)
req.SetHeader("Accept", tableParam)
// if sorting, ensure we receive the full object in order to introspect its fields via jsonpath
if o.Sort {
req.Param("includeObject", "Object")
}
}).
Do()
@@ -345,6 +460,7 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
if err != nil {
allErrs = append(allErrs, err)
}
printWithKind := multipleGVKsRequested(infos)
objs := make([]runtime.Object, len(infos))
for ix := range infos {
@@ -366,12 +482,14 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
if err != nil {
return err
}
var sorter *kubectl.RuntimeSort
if o.Sort && len(objs) > 1 {
// TODO: questionable
if sorter, err = kubectl.SortObjects(cmdutil.InternalVersionDecoder(), objs, sorting); err != nil {
var positioner OriginalPositioner
if o.Sort {
sorter := NewRuntimeSorter(objs, sorting)
if err := sorter.Sort(); err != nil {
return err
}
positioner = sorter
}
var printer printers.ResourcePrinter
@@ -381,8 +499,8 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
for ix := range objs {
var mapping *meta.RESTMapping
var info *resource.Info
if sorter != nil {
info = infos[sorter.OriginalPosition(ix)]
if positioner != nil {
info = infos[positioner.OriginalPosition(ix)]
mapping = info.Mapping
} else {
info = infos[ix]
@@ -400,6 +518,7 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
nonEmptyObjCount++
printWithNamespace := o.AllNamespaces
if mapping != nil && mapping.Scope.Name() == meta.RESTScopeNameRoot {
printWithNamespace = false
}
@@ -414,7 +533,7 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
fmt.Fprintln(o.ErrOut)
}
printer, err = o.ToPrinter(mapping, printWithNamespace)
printer, err = o.ToPrinter(mapping, printWithNamespace, printWithKind)
if err != nil {
if !errs.Has(err.Error()) {
errs.Insert(err.Error())
@@ -443,7 +562,7 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
}
}
w.Flush()
if nonEmptyObjCount == 0 && !o.IgnoreNotFound {
if nonEmptyObjCount == 0 && !o.IgnoreNotFound && len(allErrs) == 0 {
fmt.Fprintln(o.ErrOut, "No resources found.")
}
return utilerrors.NewAggregate(allErrs)
@@ -493,30 +612,13 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
if err != nil {
return err
}
if len(infos) > 1 {
gvk := infos[0].Mapping.GroupVersionKind
uniqueGVKs := 1
// If requesting a resource count greater than a request's --chunk-size,
// we will end up making multiple requests to the server, with each
// request producing its own "Info" object. Although overall we are
// dealing with a single resource type, we will end up with multiple
// infos returned by the builder. To handle this case, only fail if we
// have at least one info with a different GVK than the others.
for _, info := range infos {
if info.Mapping.GroupVersionKind != gvk {
uniqueGVKs++
}
}
if uniqueGVKs > 1 {
return i18n.Errorf("watch is only supported on individual resources and resource collections - %d resources were found", uniqueGVKs)
}
if multipleGVKsRequested(infos) {
return i18n.Errorf("watch is only supported on individual resources and resource collections - more than 1 resources were found")
}
info := infos[0]
mapping := info.ResourceMapping()
printer, err := o.ToPrinter(mapping, o.AllNamespaces)
printer, err := o.ToPrinter(mapping, o.AllNamespaces, false)
if err != nil {
return err
}
@@ -570,9 +672,11 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
}
first := true
intr := interrupt.New(nil, w.Stop)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
intr := interrupt.New(nil, cancel)
intr.Run(func() error {
_, err := watch.Until(0, w, func(e watch.Event) (bool, error) {
_, err := watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) {
if !isList && first {
// drop the initial watch event in the single resource case
first = false
@@ -581,8 +685,12 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
// printing always takes the internal version, but the watch event uses externals
// TODO fix printing to use server-side or be version agnostic
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
if err := printer.PrintObj(attemptToConvertToInternal(e.Object, legacyscheme.Scheme, internalGV), o.Out); err != nil {
objToPrint := e.Object
if o.IsHumanReadablePrinter {
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
objToPrint = attemptToConvertToInternal(e.Object, legacyscheme.Scheme, internalGV)
}
if err := printer.PrintObj(objToPrint, o.Out); err != nil {
return false, err
}
return false, nil
@@ -596,7 +704,7 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
func attemptToConvertToInternal(obj runtime.Object, converter runtime.ObjectConvertor, targetVersion schema.GroupVersion) runtime.Object {
internalObject, err := converter.ConvertToVersion(obj, targetVersion)
if err != nil {
glog.V(1).Infof("Unable to convert %T to %v: err", obj, targetVersion, err)
glog.V(1).Infof("Unable to convert %T to %v: %v", obj, targetVersion, err)
return obj
}
return internalObject
@@ -650,7 +758,7 @@ func (o *GetOptions) printGeneric(r *resource.Result) error {
return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
}
printer, err := o.ToPrinter(nil, false)
printer, err := o.ToPrinter(nil, false, false)
if err != nil {
return err
}
@@ -727,6 +835,7 @@ func (o *GetOptions) printGeneric(r *resource.Result) error {
func addOpenAPIPrintColumnFlags(cmd *cobra.Command, opt *GetOptions) {
cmd.Flags().BoolVar(&opt.PrintWithOpenAPICols, useOpenAPIPrintColumnFlagLabel, opt.PrintWithOpenAPICols, "If true, use x-kubernetes-print-column metadata (if present) from the OpenAPI schema for displaying a resource.")
cmd.Flags().MarkDeprecated(useOpenAPIPrintColumnFlagLabel, "deprecated in favor of server-side printing")
}
func addServerPrintColumnFlags(cmd *cobra.Command, opt *GetOptions) {
@@ -750,3 +859,16 @@ func maybeWrapSortingPrinter(printer printers.ResourcePrinter, sortBy string) pr
}
return printer
}
func multipleGVKsRequested(infos []*resource.Info) bool {
if len(infos) < 2 {
return false
}
gvk := infos[0].Mapping.GroupVersionKind
for _, info := range infos {
if info.Mapping.GroupVersionKind != gvk {
return true
}
}
return false
}

View File

@@ -23,8 +23,8 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/printers"
)
@@ -35,7 +35,7 @@ type PrintFlags struct {
NamePrintFlags *genericclioptions.NamePrintFlags
CustomColumnsFlags *printers.CustomColumnsPrintFlags
HumanReadableFlags *HumanPrintFlags
TemplateFlags *printers.KubeTemplatePrintFlags
TemplateFlags *genericclioptions.KubeTemplatePrintFlags
NoHeaders *bool
OutputFormat *string
@@ -182,7 +182,7 @@ func NewGetPrintFlags() *PrintFlags {
JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(),
NamePrintFlags: genericclioptions.NewNamePrintFlags(""),
TemplateFlags: printers.NewKubeTemplatePrintFlags(),
TemplateFlags: genericclioptions.NewKubeTemplatePrintFlags(),
HumanReadableFlags: NewHumanPrintFlags(),
CustomColumnsFlags: printers.NewCustomColumnsPrintFlags(),

View File

@@ -19,6 +19,7 @@ package get
import (
"bytes"
encjson "encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
@@ -27,40 +28,36 @@ import (
"strings"
"testing"
apiapps "k8s.io/api/apps/v1"
apiautoscaling "k8s.io/api/autoscaling/v1"
apibatchv1 "k8s.io/api/batch/v1"
apibatchv1beta1 "k8s.io/api/batch/v1beta1"
api "k8s.io/api/core/v1"
apiextensionsv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
restclientwatch "k8s.io/client-go/rest/watch"
"k8s.io/kube-openapi/pkg/util/proto"
apitesting "k8s.io/kubernetes/pkg/api/testing"
"k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
openapitesting "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
var openapiSchemaPath = filepath.Join("..", "..", "..", "..", "api", "openapi-spec", "swagger.json")
// This init should be removed after switching this command and its tests to user external types.
func init() {
api.AddToScheme(scheme.Scheme)
scheme.Scheme.AddConversionFuncs(v1.Convert_core_PodSpec_To_v1_PodSpec)
scheme.Scheme.AddConversionFuncs(v1.Convert_v1_PodSecurityContext_To_core_PodSecurityContext)
}
var unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer
func defaultHeader() http.Header {
@@ -226,7 +223,7 @@ func TestGetUnknownSchemaObject(t *testing.T) {
func TestGetSchemaObject(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(schema.GroupVersion{Version: "v1"})
codec := scheme.Codecs.LegacyCodec(schema.GroupVersion{Version: "v1"})
t.Logf("%v", string(runtime.EncodeOrDie(codec, &api.ReplicationController{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})))
tf.UnstructuredClient = &fake.RESTClient{
@@ -249,7 +246,7 @@ func TestGetObjectsWithOpenAPIOutputFormatPresent(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
// overide the openAPISchema function to return custom output
// for Pod type.
@@ -265,11 +262,11 @@ func TestGetObjectsWithOpenAPIOutputFormatPresent(t *testing.T) {
cmd.Flags().Set(useOpenAPIPrintColumnFlagLabel, "true")
cmd.Run(cmd, []string{"pods", "foo"})
expected := `NAME RSRC
foo 10
expected := `NAME RSRC
foo 10
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -305,7 +302,7 @@ func TestGetObjects(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -317,11 +314,11 @@ func TestGetObjects(t *testing.T) {
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"pods", "foo"})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -330,7 +327,7 @@ func TestGetObjectsShowKind(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -343,11 +340,70 @@ func TestGetObjectsShowKind(t *testing.T) {
cmd.Flags().Set("show-kind", "true")
cmd.Run(cmd, []string{"pods", "foo"})
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestGetMultipleResourceTypesShowKinds(t *testing.T) {
pods, svcs, _ := testData()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])},
}
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/pods" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
case p == "/namespaces/test/replicationcontrollers" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.ReplicationControllerList{})}, nil
case p == "/namespaces/test/services" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svcs)}, nil
case p == "/namespaces/test/statefulsets" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &apiapps.StatefulSetList{})}, nil
case p == "/namespaces/test/horizontalpodautoscalers" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &apiautoscaling.HorizontalPodAutoscalerList{})}, nil
case p == "/namespaces/test/jobs" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &apibatchv1.JobList{})}, nil
case p == "/namespaces/test/cronjobs" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &apibatchv1beta1.CronJobList{})}, nil
case p == "/namespaces/test/daemonsets" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &apiapps.DaemonSetList{})}, nil
case p == "/namespaces/test/deployments" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &apiextensionsv1beta1.DeploymentList{})}, nil
case p == "/namespaces/test/replicasets" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &apiextensionsv1beta1.ReplicaSetList{})}, nil
default:
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
}
}),
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"all"})
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
pod/bar 0/0 0 <unknown>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/baz ClusterIP <none> <none> <none> <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -356,7 +412,7 @@ func TestGetObjectsShowLabels(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -369,11 +425,11 @@ func TestGetObjectsShowLabels(t *testing.T) {
cmd.Flags().Set("show-labels", "true")
cmd.Run(cmd, []string{"pods", "foo"})
expected := `NAME READY STATUS RESTARTS AGE LABELS
foo 0/0 0 <unknown> <none>
expected := `NAME READY STATUS RESTARTS AGE LABELS
foo 0/0 0 <unknown> <none>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -394,7 +450,7 @@ func TestGetObjectIgnoreNotFound(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -446,7 +502,7 @@ func TestGetSortedObjects(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -462,22 +518,166 @@ func TestGetSortedObjects(t *testing.T) {
cmd.Flags().Set("sort-by", ".metadata.name")
cmd.Run(cmd, []string{"pods"})
expected := `NAME READY STATUS RESTARTS AGE
a 0/0 0 <unknown>
b 0/0 0 <unknown>
c 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
a 0/0 0 <unknown>
b 0/0 0 <unknown>
c 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func sortTestData() []runtime.Object {
return []runtime.Object{
&api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "c", Namespace: "test", ResourceVersion: "10"},
Spec: apitesting.V1DeepEqualSafePodSpec(),
},
&api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "b", Namespace: "test", ResourceVersion: "11"},
Spec: apitesting.V1DeepEqualSafePodSpec(),
},
&api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "test", ResourceVersion: "9"},
Spec: apitesting.V1DeepEqualSafePodSpec(),
},
}
}
func sortTestTableData() []runtime.Object {
return []runtime.Object{
&metav1beta1.Table{
TypeMeta: metav1.TypeMeta{Kind: "Table"},
Rows: []metav1beta1.TableRow{
{
Object: runtime.RawExtension{
Object: &api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "c", Namespace: "test", ResourceVersion: "10"},
Spec: apitesting.V1DeepEqualSafePodSpec(),
},
},
},
{
Object: runtime.RawExtension{
Object: &api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "b", Namespace: "test", ResourceVersion: "11"},
Spec: apitesting.V1DeepEqualSafePodSpec(),
},
},
},
{
Object: runtime.RawExtension{
Object: &api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "test", ResourceVersion: "9"},
Spec: apitesting.V1DeepEqualSafePodSpec(),
},
},
},
},
},
}
}
func TestRuntimeSorter(t *testing.T) {
tests := []struct {
name string
field string
objs []runtime.Object
op func(sorter *RuntimeSorter, objs []runtime.Object, out io.Writer) error
expect string
expectError string
}{
{
name: "ensure sorter returns original position",
field: "metadata.name",
objs: sortTestData(),
op: func(sorter *RuntimeSorter, objs []runtime.Object, out io.Writer) error {
for idx := range objs {
p := sorter.OriginalPosition(idx)
fmt.Fprintf(out, "%v,", p)
}
return nil
},
expect: "2,1,0,",
},
{
name: "ensure sorter handles table object position",
field: "metadata.name",
objs: sortTestTableData(),
op: func(sorter *RuntimeSorter, objs []runtime.Object, out io.Writer) error {
for idx := range objs {
p := sorter.OriginalPosition(idx)
fmt.Fprintf(out, "%v,", p)
}
return nil
},
expect: "0,",
},
{
name: "ensure sorter sorts table objects",
field: "metadata.name",
objs: sortTestData(),
op: func(sorter *RuntimeSorter, objs []runtime.Object, out io.Writer) error {
for _, o := range objs {
fmt.Fprintf(out, "%s,", o.(*api.Pod).Name)
}
return nil
},
expect: "a,b,c,",
},
{
name: "ensure sorter rejects mixed Table + non-Table object lists",
field: "metadata.name",
objs: append(sortTestData(), sortTestTableData()...),
op: func(sorter *RuntimeSorter, objs []runtime.Object, out io.Writer) error { return nil },
expectError: "sorting is not supported on mixed Table",
},
{
name: "ensure sorter errors out on invalid jsonpath",
field: "metadata.unknown",
objs: sortTestData(),
op: func(sorter *RuntimeSorter, objs []runtime.Object, out io.Writer) error { return nil },
expectError: "couldn't find any field with path",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
sorter := NewRuntimeSorter(tc.objs, tc.field)
if err := sorter.Sort(); err != nil {
if len(tc.expectError) > 0 && strings.Contains(err.Error(), tc.expectError) {
return
}
if len(tc.expectError) > 0 {
t.Fatalf("unexpected error: expecting %s, but got %s", tc.expectError, err)
}
t.Fatalf("unexpected error: %v", err)
}
out := bytes.NewBuffer([]byte{})
err := tc.op(sorter, tc.objs, out)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if tc.expect != out.String() {
t.Fatalf("unexpected output: expecting %s, but got %s", tc.expect, out.String())
}
})
}
}
func TestGetObjectsIdentifiedByFile(t *testing.T) {
pods, _, _ := testData()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -490,11 +690,11 @@ func TestGetObjectsIdentifiedByFile(t *testing.T) {
cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml")
cmd.Run(cmd, []string{})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -503,7 +703,7 @@ func TestGetListObjects(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -515,12 +715,12 @@ func TestGetListObjects(t *testing.T) {
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"pods"})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
bar 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
bar 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -529,7 +729,7 @@ func TestGetListComponentStatus(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -547,7 +747,7 @@ serverbad Unhealthy bad status: 500
serverunknown Unhealthy fizzbuzz error
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -569,7 +769,7 @@ func TestGetMixedGenericObjects(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -609,7 +809,7 @@ func TestGetMixedGenericObjects(t *testing.T) {
}
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -618,7 +818,7 @@ func TestGetMultipleTypeObjects(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -640,14 +840,14 @@ func TestGetMultipleTypeObjects(t *testing.T) {
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"pods,services"})
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
pod/bar 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
pod/bar 0/0 0 <unknown>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/baz ClusterIP <none> <none> <none> <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -656,7 +856,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -755,7 +955,7 @@ func TestGetMultipleTypeObjectsWithLabelSelector(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -782,14 +982,14 @@ func TestGetMultipleTypeObjectsWithLabelSelector(t *testing.T) {
cmd.Flags().Set("selector", "a=b")
cmd.Run(cmd, []string{"pods,services"})
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
pod/bar 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
pod/bar 0/0 0 <unknown>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/baz ClusterIP <none> <none> <none> <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -798,7 +998,7 @@ func TestGetMultipleTypeObjectsWithFieldSelector(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -825,14 +1025,14 @@ func TestGetMultipleTypeObjectsWithFieldSelector(t *testing.T) {
cmd.Flags().Set("field-selector", "a=b")
cmd.Run(cmd, []string{"pods,services"})
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
pod/bar 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
pod/bar 0/0 0 <unknown>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/baz ClusterIP <none> <none> <none> <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -846,7 +1046,7 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -871,11 +1071,11 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) {
expected := `NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/baz ClusterIP <none> <none> <none> <unknown>
NAME STATUS ROLES AGE VERSION
node/foo Unknown <none> <unknown>
NAME STATUS ROLES AGE VERSION
node/foo Unknown <none> <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -954,7 +1154,7 @@ func TestWatchLabelSelector(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
podList := &api.PodList{
Items: pods,
@@ -989,14 +1189,14 @@ func TestWatchLabelSelector(t *testing.T) {
cmd.Flags().Set("selector", "a=b")
cmd.Run(cmd, []string{"pods"})
expected := `NAME READY STATUS RESTARTS AGE
bar 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
bar 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -1005,7 +1205,7 @@ func TestWatchFieldSelector(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
podList := &api.PodList{
Items: pods,
@@ -1040,14 +1240,14 @@ func TestWatchFieldSelector(t *testing.T) {
cmd.Flags().Set("field-selector", "a=b")
cmd.Run(cmd, []string{"pods"})
expected := `NAME READY STATUS RESTARTS AGE
bar 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
bar 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -1056,7 +1256,7 @@ func TestWatchResource(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -1084,13 +1284,13 @@ func TestWatchResource(t *testing.T) {
cmd.Flags().Set("watch", "true")
cmd.Run(cmd, []string{"pods", "foo"})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -1099,7 +1299,7 @@ func TestWatchResourceIdentifiedByFile(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -1128,13 +1328,13 @@ func TestWatchResourceIdentifiedByFile(t *testing.T) {
cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml")
cmd.Run(cmd, []string{})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -1143,7 +1343,7 @@ func TestWatchOnlyResource(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -1171,12 +1371,12 @@ func TestWatchOnlyResource(t *testing.T) {
cmd.Flags().Set("watch-only", "true")
cmd.Run(cmd, []string{"pods", "foo"})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
@@ -1185,7 +1385,7 @@ func TestWatchOnlyList(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
podList := &api.PodList{
Items: pods,
@@ -1216,12 +1416,12 @@ func TestWatchOnlyList(t *testing.T) {
cmd.Flags().Set("watch-only", "true")
cmd.Run(cmd, []string{"pods"})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected %v, got %v", e, a)
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}

View File

@@ -18,7 +18,7 @@ package get
import (
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/kubectl/scheme"

View File

@@ -24,8 +24,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) {
@@ -61,7 +61,7 @@ func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) {
{
name: "\"wide\" output format prints",
outputFormat: "wide",
expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +IP\\ +NODE\nfoo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\n",
expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +IP\\ +NODE\\ +NOMINATED NODE\nfoo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\\ +<none>\n",
},
{
name: "no-headers prints output with no headers",
@@ -72,7 +72,7 @@ func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) {
name: "no-headers and a \"wide\" output format prints output with no headers and additional columns",
outputFormat: "wide",
noHeaders: true,
expectedOutput: "foo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\n",
expectedOutput: "foo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\\ +<none>\n",
},
{
name: "show-kind displays the resource's kind, even when printing a single type of resource",

View File

@@ -34,11 +34,11 @@ import (
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@@ -304,7 +304,7 @@ func (o *LabelOptions) RunLabel() error {
helper := resource.NewHelper(client, mapping)
if createdPatch {
outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes)
outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes, nil)
} else {
outputObj, err = helper.Replace(namespace, name, false, obj)
}

View File

@@ -26,10 +26,9 @@ import (
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@@ -359,7 +358,7 @@ func TestLabelForResourceFromFile(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@@ -446,7 +445,7 @@ func TestLabelMultipleObjects(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,

View File

@@ -25,19 +25,23 @@ import (
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
const (
logsUsageStr = "logs [-f] [-p] (POD | TYPE/NAME) [-c CONTAINER]"
)
var (
logsExample = templates.Examples(i18n.T(`
# Return snapshot logs from pod nginx with only one container
@@ -67,11 +71,8 @@ var (
# Return snapshot logs from container nginx-1 of a deployment named nginx
kubectl logs deployment/nginx -c nginx-1`))
selectorTail int64 = 10
)
const (
logsUsageStr = "expected 'logs (POD | TYPE/NAME) [CONTAINER_NAME]'.\nPOD or TYPE/NAME is a required argument for the logs command"
selectorTail int64 = 10
logsUsageErrStr = fmt.Sprintf("expected '%s'.\nPOD or TYPE/NAME is a required argument for the logs command", logsUsageStr)
)
type LogsOptions struct {
@@ -79,6 +80,23 @@ type LogsOptions struct {
ResourceArg string
AllContainers bool
Options runtime.Object
Resources []string
ConsumeRequestFn func(*rest.Request, io.Writer) error
// PodLogOptions
SinceTime string
SinceSeconds time.Duration
Follow bool
Previous bool
Timestamps bool
LimitBytes int64
Tail int64
Container string
// whether or not a container name was given via --container
ContainerNameSpecified bool
Selector string
Object runtime.Object
GetPodTimeout time.Duration
@@ -92,6 +110,7 @@ func NewLogsOptions(streams genericclioptions.IOStreams, allContainers bool) *Lo
return &LogsOptions{
IOStreams: streams,
AllContainers: allContainers,
Tail: -1,
}
}
@@ -100,7 +119,7 @@ func NewCmdLogs(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
o := NewLogsOptions(streams, false)
cmd := &cobra.Command{
Use: "logs [-f] [-p] (POD | TYPE/NAME) [-c CONTAINER]",
Use: logsUsageStr,
DisableFlagsInUseLine: true,
Short: i18n.T("Print the logs for a container in a pod"),
Long: "Print the logs for a container in a pod or specified resource. If the pod has only one container, the container name is optional.",
@@ -118,42 +137,74 @@ func NewCmdLogs(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
Aliases: []string{"log"},
}
cmd.Flags().BoolVar(&o.AllContainers, "all-containers", o.AllContainers, "Get all containers's logs in the pod(s).")
cmd.Flags().BoolP("follow", "f", false, "Specify if the logs should be streamed.")
cmd.Flags().Bool("timestamps", false, "Include timestamps on each line in the log output")
cmd.Flags().Int64("limit-bytes", 0, "Maximum bytes of logs to return. Defaults to no limit.")
cmd.Flags().BoolP("previous", "p", false, "If true, print the logs for the previous instance of the container in a pod if it exists.")
cmd.Flags().Int64("tail", -1, "Lines of recent log file to display. Defaults to -1 with no selector, showing all log lines otherwise 10, if a selector is provided.")
cmd.Flags().String("since-time", "", i18n.T("Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used."))
cmd.Flags().Duration("since", 0, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
cmd.Flags().StringP("container", "c", "", "Print the logs of this container")
cmd.Flags().Bool("interactive", false, "If true, prompt the user for input when required.")
cmd.Flags().MarkDeprecated("interactive", "This flag is no longer respected and there is no replacement.")
cmd.Flags().BoolVarP(&o.Follow, "follow", "f", o.Follow, "Specify if the logs should be streamed.")
cmd.Flags().BoolVar(&o.Timestamps, "timestamps", o.Timestamps, "Include timestamps on each line in the log output")
cmd.Flags().Int64Var(&o.LimitBytes, "limit-bytes", o.LimitBytes, "Maximum bytes of logs to return. Defaults to no limit.")
cmd.Flags().BoolVarP(&o.Previous, "previous", "p", o.Previous, "If true, print the logs for the previous instance of the container in a pod if it exists.")
cmd.Flags().Int64Var(&o.Tail, "tail", o.Tail, "Lines of recent log file to display. Defaults to -1 with no selector, showing all log lines otherwise 10, if a selector is provided.")
cmd.Flags().StringVar(&o.SinceTime, "since-time", o.SinceTime, i18n.T("Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used."))
cmd.Flags().DurationVar(&o.SinceSeconds, "since", o.SinceSeconds, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
cmd.Flags().StringVarP(&o.Container, "container", "c", o.Container, "Print the logs of this container")
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodLogsTimeout)
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on.")
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on.")
return cmd
}
func (o *LogsOptions) ToLogOptions() (*corev1.PodLogOptions, error) {
logOptions := &corev1.PodLogOptions{
Container: o.Container,
Follow: o.Follow,
Previous: o.Previous,
Timestamps: o.Timestamps,
}
if len(o.SinceTime) > 0 {
t, err := util.ParseRFC3339(o.SinceTime, metav1.Now)
if err != nil {
return nil, err
}
logOptions.SinceTime = &t
}
if o.LimitBytes != 0 {
logOptions.LimitBytes = &o.LimitBytes
}
if o.SinceSeconds != 0 {
// round up to the nearest second
sec := int64(o.SinceSeconds.Round(time.Second).Seconds())
logOptions.SinceSeconds = &sec
}
if len(o.Selector) > 0 && o.Tail != -1 {
logOptions.TailLines = &selectorTail
} else if o.Tail != -1 {
logOptions.TailLines = &o.Tail
}
return logOptions, nil
}
func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
containerName := cmdutil.GetFlagString(cmd, "container")
selector := cmdutil.GetFlagString(cmd, "selector")
o.ContainerNameSpecified = cmd.Flag("container").Changed
o.Resources = args
switch len(args) {
case 0:
if len(selector) == 0 {
return cmdutil.UsageErrorf(cmd, "%s", logsUsageStr)
if len(o.Selector) == 0 {
return cmdutil.UsageErrorf(cmd, "%s", logsUsageErrStr)
}
case 1:
o.ResourceArg = args[0]
if len(selector) != 0 {
if len(o.Selector) != 0 {
return cmdutil.UsageErrorf(cmd, "only a selector (-l) or a POD name is allowed")
}
case 2:
if cmd.Flag("container").Changed {
return cmdutil.UsageErrorf(cmd, "only one of -c or an inline [CONTAINER] arg is allowed")
}
o.ResourceArg = args[0]
containerName = args[1]
o.Container = args[1]
default:
return cmdutil.UsageErrorf(cmd, "%s", logsUsageStr)
return cmdutil.UsageErrorf(cmd, "%s", logsUsageErrStr)
}
var err error
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
@@ -161,64 +212,37 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
return err
}
logOptions := &api.PodLogOptions{
Container: containerName,
Follow: cmdutil.GetFlagBool(cmd, "follow"),
Previous: cmdutil.GetFlagBool(cmd, "previous"),
Timestamps: cmdutil.GetFlagBool(cmd, "timestamps"),
}
if sinceTime := cmdutil.GetFlagString(cmd, "since-time"); len(sinceTime) > 0 {
t, err := util.ParseRFC3339(sinceTime, metav1.Now)
if err != nil {
return err
}
logOptions.SinceTime = &t
}
if limit := cmdutil.GetFlagInt64(cmd, "limit-bytes"); limit != 0 {
logOptions.LimitBytes = &limit
}
tail := cmdutil.GetFlagInt64(cmd, "tail")
if tail != -1 {
logOptions.TailLines = &tail
}
if sinceSeconds := cmdutil.GetFlagDuration(cmd, "since"); sinceSeconds != 0 {
// round up to the nearest second
sec := int64(sinceSeconds.Round(time.Second).Seconds())
logOptions.SinceSeconds = &sec
}
o.ConsumeRequestFn = DefaultConsumeRequest
o.GetPodTimeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
if err != nil {
return err
}
o.Options = logOptions
o.Options, err = o.ToLogOptions()
if err != nil {
return err
}
o.RESTClientGetter = f
o.LogsForObject = polymorphichelpers.LogsForObjectFn
if len(selector) != 0 {
if logOptions.Follow {
return cmdutil.UsageErrorf(cmd, "only one of follow (-f) or selector (-l) is allowed")
}
if logOptions.TailLines == nil && tail != -1 {
logOptions.TailLines = &selectorTail
}
}
if o.Object == nil {
builder := f.NewBuilder().
WithScheme(legacyscheme.Scheme).
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
NamespaceParam(o.Namespace).DefaultNamespace().
SingleResourceType()
if o.ResourceArg != "" {
builder.ResourceNames("pods", o.ResourceArg)
}
if selector != "" {
builder.ResourceTypes("pods").LabelSelectorParam(selector)
if o.Selector != "" {
builder.ResourceTypes("pods").LabelSelectorParam(o.Selector)
}
infos, err := builder.Do().Infos()
if err != nil {
return err
}
if selector == "" && len(infos) != 1 {
if o.Selector == "" && len(infos) != 1 {
return errors.New("expected a resource")
}
o.Object = infos[0].Object
@@ -228,15 +252,36 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
}
func (o LogsOptions) Validate() error {
logsOptions, ok := o.Options.(*api.PodLogOptions)
if o.Follow && len(o.Selector) > 0 {
return fmt.Errorf("only one of follow (-f) or selector (-l) is allowed")
}
if len(o.SinceTime) > 0 && o.SinceSeconds != 0 {
return fmt.Errorf("at most one of `sinceTime` or `sinceSeconds` may be specified")
}
logsOptions, ok := o.Options.(*corev1.PodLogOptions)
if !ok {
return errors.New("unexpected logs options object")
}
if o.AllContainers && len(logsOptions.Container) > 0 {
return fmt.Errorf("--all-containers=true should not be specified with container name %s", logsOptions.Container)
}
if errs := validation.ValidatePodLogOptions(logsOptions); len(errs) > 0 {
return errs.ToAggregate()
if o.ContainerNameSpecified && len(o.Resources) == 2 {
return fmt.Errorf("only one of -c or an inline [CONTAINER] arg is allowed")
}
if o.LimitBytes < 0 {
return fmt.Errorf("--limit-bytes must be greater than 0")
}
if logsOptions.SinceSeconds != nil && *logsOptions.SinceSeconds < int64(0) {
return fmt.Errorf("--since must be greater than 0")
}
if logsOptions.TailLines != nil && *logsOptions.TailLines < 0 {
return fmt.Errorf("TailLines must be greater than or equal to 0")
}
return nil
@@ -244,55 +289,27 @@ func (o LogsOptions) Validate() error {
// RunLogs retrieves a pod log
func (o LogsOptions) RunLogs() error {
switch t := o.Object.(type) {
case *api.PodList:
for _, p := range t.Items {
if err := o.getPodLogs(&p); err != nil {
return err
}
}
return nil
case *api.Pod:
return o.getPodLogs(t)
default:
return o.getLogs(o.Object)
}
}
// getPodLogs checks whether o.AllContainers is set to true.
// If so, it retrives all containers' log in the pod.
func (o LogsOptions) getPodLogs(pod *api.Pod) error {
if !o.AllContainers {
return o.getLogs(pod)
}
for _, c := range pod.Spec.InitContainers {
o.Options.(*api.PodLogOptions).Container = c.Name
if err := o.getLogs(pod); err != nil {
return err
}
}
for _, c := range pod.Spec.Containers {
o.Options.(*api.PodLogOptions).Container = c.Name
if err := o.getLogs(pod); err != nil {
return err
}
}
return nil
}
func (o LogsOptions) getLogs(obj runtime.Object) error {
req, err := o.LogsForObject(o.RESTClientGetter, obj, o.Options, o.GetPodTimeout)
requests, err := o.LogsForObject(o.RESTClientGetter, o.Object, o.Options, o.GetPodTimeout, o.AllContainers)
if err != nil {
return err
}
readCloser, err := req.Stream()
for _, request := range requests {
if err := o.ConsumeRequestFn(request, o.Out); err != nil {
return err
}
}
return nil
}
func DefaultConsumeRequest(request *rest.Request, out io.Writer) error {
readCloser, err := request.Stream()
if err != nil {
return err
}
defer readCloser.Close()
_, err = io.Copy(o.Out, readCloser)
_, err = io.Copy(out, readCloser)
return err
}

View File

@@ -17,42 +17,29 @@ limitations under the License.
package cmd
import (
"bytes"
"errors"
"io/ioutil"
"net/http"
"fmt"
"io"
"strings"
"testing"
"time"
"github.com/spf13/cobra"
"fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestLog(t *testing.T) {
tests := []struct {
name, version, podPath, logPath string
pod *api.Pod
pod *corev1.Pod
}{
{
name: "v1 - pod log",
version: "v1",
podPath: "/namespaces/test/pods/foo",
logPath: "/api/v1/namespaces/test/pods/foo/log",
pod: testPod(),
name: "v1 - pod log",
pod: testPod(),
},
}
for _, test := range tests {
@@ -61,41 +48,19 @@ func TestLog(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
case p == test.logPath && m == "GET":
body := ioutil.NopCloser(bytes.NewBufferString(logContent))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
t.Errorf("%s: unexpected request: %#v\n%#v", test.name, req.URL, req)
return nil, nil
}
}),
}
tf.ClientConfigVal = defaultClientConfig()
oldLogFn := polymorphichelpers.LogsForObjectFn
defer func() {
polymorphichelpers.LogsForObjectFn = oldLogFn
}()
clientset, err := tf.ClientSet()
if err != nil {
t.Fatal(err)
}
polymorphichelpers.LogsForObjectFn = logTestMock{client: clientset}.logsForObject
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdLogs(tf, streams)
cmd.Flags().Set("namespace", "test")
cmd.Run(cmd, []string{"foo"})
mock := &logTestMock{
logsContent: logContent,
}
opts := NewLogsOptions(streams, false)
opts.Namespace = "test"
opts.Object = test.pod
opts.Options = &corev1.PodLogOptions{}
opts.LogsForObject = mock.mockLogsForObject
opts.ConsumeRequestFn = mock.mockConsumeRequest
opts.RunLogs()
if buf.String() != logContent {
t.Errorf("%s: did not get expected log content. Got: %s", test.name, buf.String())
@@ -104,13 +69,13 @@ func TestLog(t *testing.T) {
}
}
func testPod() *api.Pod {
return &api.Pod{
func testPod() *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyAlways,
DNSPolicy: corev1.DNSClusterFirst,
Containers: []corev1.Container{
{
Name: "bar",
},
@@ -119,66 +84,152 @@ func testPod() *api.Pod {
}
}
func TestValidateLogFlags(t *testing.T) {
func TestValidateLogOptions(t *testing.T) {
f := cmdtesting.NewTestFactory()
defer f.Cleanup()
f.WithNamespace("")
tests := []struct {
name string
flags map[string]string
args []string
opts func(genericclioptions.IOStreams) *LogsOptions
expected string
}{
{
name: "since & since-time",
flags: map[string]string{"since": "1h", "since-time": "2006-01-02T15:04:05Z"},
name: "since & since-time",
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.SinceSeconds = time.Hour
o.SinceTime = "2006-01-02T15:04:05Z"
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
args: []string{"foo"},
expected: "at most one of `sinceTime` or `sinceSeconds` may be specified",
},
{
name: "negative since-time",
flags: map[string]string{"since": "-1s"},
name: "negative since-time",
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.SinceSeconds = -1 * time.Second
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
args: []string{"foo"},
expected: "must be greater than 0",
},
{
name: "negative limit-bytes",
flags: map[string]string{"limit-bytes": "-100"},
name: "negative limit-bytes",
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.LimitBytes = -100
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
args: []string{"foo"},
expected: "must be greater than 0",
},
{
name: "negative tail",
flags: map[string]string{"tail": "-100"},
name: "negative tail",
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.Tail = -100
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
args: []string{"foo"},
expected: "must be greater than or equal to 0",
},
{
name: "container name combined with --all-containers",
flags: map[string]string{"all-containers": "true"},
name: "container name combined with --all-containers",
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, true)
o.Container = "my-container"
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
args: []string{"my-pod", "my-container"},
expected: "--all-containers=true should not be specified with container",
},
{
name: "container name combined with second argument",
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.Container = "my-container"
o.ContainerNameSpecified = true
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
args: []string{"my-pod", "my-container"},
expected: "only one of -c or an inline",
},
{
name: "follow and selector conflict",
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.Selector = "foo"
o.Follow = true
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
expected: "only one of follow (-f) or selector (-l) is allowed",
},
}
for _, test := range tests {
streams := genericclioptions.NewTestIOStreamsDiscard()
cmd := NewCmdLogs(f, streams)
out := ""
for flag, value := range test.flags {
cmd.Flags().Set(flag, value)
}
// checkErr breaks tests in case of errors, plus we just
// need to check errors returned by the command validation
o := NewLogsOptions(streams, test.flags["all-containers"] == "true")
cmd.Run = func(cmd *cobra.Command, args []string) {
o.Complete(f, cmd, args)
out = o.Validate().Error()
}
cmd.Run(cmd, test.args)
if !strings.Contains(out, test.expected) {
t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expected, out)
o := test.opts(streams)
o.Resources = test.args
err := o.Validate()
if err == nil {
t.Fatalf("expected error %q, got none", test.expected)
}
if !strings.Contains(err.Error(), test.expected) {
t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expected, err.Error())
}
}
}
@@ -190,49 +241,32 @@ func TestLogComplete(t *testing.T) {
tests := []struct {
name string
args []string
flags map[string]string
opts func(genericclioptions.IOStreams) *LogsOptions
expected string
}{
{
name: "No args case",
flags: map[string]string{"selector": ""},
expected: "'logs (POD | TYPE/NAME) [CONTAINER_NAME]'.\nPOD or TYPE/NAME is a required argument for the logs command",
},
{
name: "One args case",
args: []string{"foo"},
flags: map[string]string{"selector": "foo"},
name: "One args case",
args: []string{"foo"},
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.Selector = "foo"
return o
},
expected: "only a selector (-l) or a POD name is allowed",
},
{
name: "Two args case",
args: []string{"foo", "foo1"},
flags: map[string]string{"container": "foo1"},
expected: "only one of -c or an inline [CONTAINER] arg is allowed",
},
{
name: "More than two args case",
args: []string{"foo", "foo1", "foo2"},
flags: map[string]string{"tail": "1"},
expected: "'logs (POD | TYPE/NAME) [CONTAINER_NAME]'.\nPOD or TYPE/NAME is a required argument for the logs command",
},
{
name: "follow and selecter conflict",
flags: map[string]string{"selector": "foo", "follow": "true"},
expected: "only one of follow (-f) or selector (-l) is allowed",
},
}
for _, test := range tests {
cmd := NewCmdLogs(f, genericclioptions.NewTestIOStreamsDiscard())
var err error
out := ""
for flag, value := range test.flags {
cmd.Flags().Set(flag, value)
}
// checkErr breaks tests in case of errors, plus we just
// need to check errors returned by the command validation
o := NewLogsOptions(genericclioptions.NewTestIOStreamsDiscard(), false)
err = o.Complete(f, cmd, test.args)
o := test.opts(genericclioptions.NewTestIOStreamsDiscard())
err := o.Complete(f, cmd, test.args)
if err == nil {
t.Fatalf("expected error %q, got none", test.expected)
}
out = err.Error()
if !strings.Contains(out, test.expected) {
t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expected, out)
@@ -241,17 +275,23 @@ func TestLogComplete(t *testing.T) {
}
type logTestMock struct {
client internalclientset.Interface
logsContent string
}
func (m logTestMock) logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration) (*restclient.Request, error) {
switch t := object.(type) {
case *api.Pod:
opts, ok := options.(*api.PodLogOptions)
func (l *logTestMock) mockConsumeRequest(req *restclient.Request, out io.Writer) error {
fmt.Fprintf(out, l.logsContent)
return nil
}
func (l *logTestMock) mockLogsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) ([]*restclient.Request, error) {
switch object.(type) {
case *corev1.Pod:
_, ok := options.(*corev1.PodLogOptions)
if !ok {
return nil, errors.New("provided options object is not a PodLogOptions")
}
return m.client.Core().Pods(t.Namespace).GetLogs(t.Name, opts), nil
return []*restclient.Request{{}}, nil
default:
return nil, fmt.Errorf("cannot get the logs from %T", object)
}

View File

@@ -33,11 +33,11 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@@ -193,6 +193,7 @@ func (o *PatchOptions) RunPatch() error {
r := o.builder.
Unstructured().
ContinueOnError().
LocalParam(o.Local).
NamespaceParam(o.namespace).DefaultNamespace().
FilenameParam(o.enforceNamespace, &o.FilenameOptions).
ResourceTypeOrNameArgs(false, o.args...).
@@ -208,16 +209,18 @@ func (o *PatchOptions) RunPatch() error {
if err != nil {
return err
}
count++
name, namespace := info.Name, info.Namespace
mapping := info.ResourceMapping()
client, err := o.unstructuredClientForMapping(mapping)
if err != nil {
return err
}
if !o.Local && !o.dryRun {
mapping := info.ResourceMapping()
client, err := o.unstructuredClientForMapping(mapping)
if err != nil {
return err
}
helper := resource.NewHelper(client, mapping)
patchedObj, err := helper.Patch(namespace, name, patchType, patchBytes)
patchedObj, err := helper.Patch(namespace, name, patchType, patchBytes, nil)
if err != nil {
return err
}
@@ -228,41 +231,26 @@ func (o *PatchOptions) RunPatch() error {
if mergePatch, err := o.Recorder.MakeRecordMergePatch(patchedObj); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
} else if len(mergePatch) > 0 {
if recordedObj, err := helper.Patch(info.Namespace, info.Name, types.MergePatchType, mergePatch); err != nil {
if recordedObj, err := helper.Patch(info.Namespace, info.Name, types.MergePatchType, mergePatch, nil); err != nil {
glog.V(4).Infof("error recording reason: %v", err)
} else {
patchedObj = recordedObj
}
}
count++
// After computing whether we changed data, refresh the resource info with the resulting object
if err := info.Refresh(patchedObj, true); err != nil {
return err
}
printer, err := o.ToPrinter(patchOperation(didPatch))
if err != nil {
return err
}
printer.PrintObj(info.Object, o.Out)
// if object was not successfully patched, exit with error code 1
if !didPatch {
return cmdutil.ErrExit
}
return nil
return printer.PrintObj(patchedObj, o.Out)
}
count++
originalObjJS, err := runtime.Encode(unstructured.UnstructuredJSONScheme, info.Object)
if err != nil {
return err
}
originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, mapping.GroupVersionKind, scheme.Scheme)
originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, info.Object.GetObjectKind().GroupVersionKind(), scheme.Scheme)
if err != nil {
return err
}
@@ -273,22 +261,11 @@ func (o *PatchOptions) RunPatch() error {
}
didPatch := !reflect.DeepEqual(info.Object, targetObj)
// 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,
// }
if didPatch {
if err := info.Refresh(targetObj, true); err != nil {
return err
}
}
printer, err := o.ToPrinter(patchOperation(didPatch))
if err != nil {
return err
}
return printer.PrintObj(info.Object, o.Out)
return printer.PrintObj(targetObj, o.Out)
})
if err != nil {
return err
@@ -306,7 +283,15 @@ func getPatchedJSON(patchType types.PatchType, originalJS, patchJS []byte, gvk s
if err != nil {
return nil, err
}
return patchObj.Apply(originalJS)
bytes, err := patchObj.Apply(originalJS)
// TODO: This is pretty hacky, we need a better structured error from the json-patch
if err != nil && strings.Contains(err.Error(), "doc is missing key") {
msg := err.Error()
ix := strings.Index(msg, "key:")
key := msg[ix+5:]
return bytes, fmt.Errorf("Object to be patched is missing field (%s)", key)
}
return bytes, err
case types.MergePatchType:
return jsonpatch.MergePatch(originalJS, patchJS)
@@ -329,5 +314,5 @@ func patchOperation(didPatch bool) string {
if didPatch {
return "patched"
}
return "not patched"
return "patched (no change)"
}

Some files were not shown because too many files have changed in this diff Show More