From da9387131e64bf9ffecd33c5044d4e96ab310311 Mon Sep 17 00:00:00 2001 From: "zuoxiu.jm" <291271447@qq.com> Date: Wed, 14 Nov 2018 13:07:07 +0800 Subject: [PATCH] prune internal clients from kubectl describer --- hack/.golint_failures | 1 + pkg/BUILD | 1 - pkg/api/events/OWNERS | 2 - pkg/kubectl/BUILD | 4 +- pkg/kubectl/cmd/convert/BUILD | 20 +- .../cmd/convert/import_known_versions.go | 37 + pkg/kubectl/cmd/describe/BUILD | 4 +- pkg/kubectl/cmd/describe/describe.go | 10 +- pkg/kubectl/cmd/describe/describe_test.go | 46 +- pkg/kubectl/describe/BUILD | 3 +- pkg/kubectl/describe/describe.go | 26 - pkg/kubectl/describe/interface.go | 71 + pkg/kubectl/describe/versioned/BUILD | 66 +- pkg/kubectl/describe/versioned/describe.go | 4408 ++++++++++++++++- .../describe/versioned}/describe_test.go | 1257 ++--- pkg/kubectl/history.go | 12 +- pkg/kubectl/util/BUILD | 7 + pkg/kubectl/util/certificate/BUILD | 23 + pkg/kubectl/util/certificate/certificate.go | 42 + pkg/{api/events => kubectl/util/event}/BUILD | 6 +- .../util/event}/sorted_event_list.go | 6 +- .../util/event}/sorted_event_list_test.go | 18 +- pkg/kubectl/util/fieldpath/BUILD | 27 + pkg/kubectl/util/fieldpath/fieldpath.go | 111 + pkg/kubectl/util/qos/BUILD | 28 + pkg/kubectl/util/qos/qos.go | 96 + pkg/kubectl/util/rbac/BUILD | 23 + pkg/kubectl/util/rbac/rbac.go | 128 + pkg/kubectl/util/resource/BUILD | 27 + pkg/kubectl/util/resource/resource.go | 138 + pkg/kubectl/util/slice/slice.go | 14 + pkg/kubectl/util/storage/BUILD | 26 + pkg/kubectl/util/storage/storage.go | 107 + pkg/printers/interface.go | 34 - pkg/printers/internalversion/BUILD | 49 +- pkg/printers/internalversion/describe.go | 4285 ---------------- .../internalversion/import_known_versions.go | 37 + pkg/printers/internalversion/printers.go | 30 + pkg/printers/internalversion/printers_test.go | 24 + 39 files changed, 6181 insertions(+), 5073 deletions(-) delete mode 100755 pkg/api/events/OWNERS create mode 100644 pkg/kubectl/cmd/convert/import_known_versions.go delete mode 100644 pkg/kubectl/describe/describe.go create mode 100644 pkg/kubectl/describe/interface.go rename pkg/{printers/internalversion => kubectl/describe/versioned}/describe_test.go (63%) create mode 100644 pkg/kubectl/util/certificate/BUILD create mode 100644 pkg/kubectl/util/certificate/certificate.go rename pkg/{api/events => kubectl/util/event}/BUILD (77%) rename pkg/{api/events => kubectl/util/event}/sorted_event_list.go (91%) rename pkg/{api/events => kubectl/util/event}/sorted_event_list_test.go (80%) create mode 100644 pkg/kubectl/util/fieldpath/BUILD create mode 100644 pkg/kubectl/util/fieldpath/fieldpath.go create mode 100644 pkg/kubectl/util/qos/BUILD create mode 100644 pkg/kubectl/util/qos/qos.go create mode 100644 pkg/kubectl/util/rbac/BUILD create mode 100644 pkg/kubectl/util/rbac/rbac.go create mode 100644 pkg/kubectl/util/resource/BUILD create mode 100644 pkg/kubectl/util/resource/resource.go create mode 100644 pkg/kubectl/util/storage/BUILD create mode 100644 pkg/kubectl/util/storage/storage.go delete mode 100644 pkg/printers/internalversion/describe.go create mode 100644 pkg/printers/internalversion/import_known_versions.go diff --git a/hack/.golint_failures b/hack/.golint_failures index 61342e2cf4..38c5a6ca34 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -181,6 +181,7 @@ pkg/kubectl/cmd/util/openapi pkg/kubectl/cmd/util/sanity pkg/kubectl/cmd/version pkg/kubectl/cmd/wait +pkg/kubectl/describe/versioned pkg/kubectl/generate pkg/kubectl/generate/versioned pkg/kubectl/metricsutil diff --git a/pkg/BUILD b/pkg/BUILD index 84b7252c89..6f74b6995c 100644 --- a/pkg/BUILD +++ b/pkg/BUILD @@ -12,7 +12,6 @@ filegroup( srcs = [ ":package-srcs", "//pkg/api/endpoints:all-srcs", - "//pkg/api/events:all-srcs", "//pkg/api/legacyscheme:all-srcs", "//pkg/api/persistentvolume:all-srcs", "//pkg/api/persistentvolumeclaim:all-srcs", diff --git a/pkg/api/events/OWNERS b/pkg/api/events/OWNERS deleted file mode 100755 index cbc4e8d9d7..0000000000 --- a/pkg/api/events/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -reviewers: -- gmarek diff --git a/pkg/kubectl/BUILD b/pkg/kubectl/BUILD index 04f32883ca..cfb06a7645 100644 --- a/pkg/kubectl/BUILD +++ b/pkg/kubectl/BUILD @@ -58,15 +58,13 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/kubectl", deps = [ - "//pkg/apis/core:go_default_library", - "//pkg/apis/core/v1:go_default_library", "//pkg/kubectl/apps:go_default_library", + "//pkg/kubectl/describe/versioned:go_default_library", "//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/util:go_default_library", "//pkg/kubectl/util/deployment:go_default_library", "//pkg/kubectl/util/podutils:go_default_library", "//pkg/kubectl/util/slice:go_default_library", - "//pkg/printers/internalversion: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/core/v1:go_default_library", diff --git a/pkg/kubectl/cmd/convert/BUILD b/pkg/kubectl/cmd/convert/BUILD index 05cb8c4edb..930c177c9c 100644 --- a/pkg/kubectl/cmd/convert/BUILD +++ b/pkg/kubectl/cmd/convert/BUILD @@ -2,12 +2,30 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", - srcs = ["convert.go"], + srcs = [ + "convert.go", + "import_known_versions.go", + ], importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/convert", visibility = ["//visibility:public"], deps = [ "//pkg/api/legacyscheme:go_default_library", + "//pkg/apis/apps/install:go_default_library", + "//pkg/apis/authentication/install:go_default_library", + "//pkg/apis/authorization/install:go_default_library", + "//pkg/apis/autoscaling/install:go_default_library", + "//pkg/apis/batch/install:go_default_library", + "//pkg/apis/certificates/install:go_default_library", + "//pkg/apis/coordination/install:go_default_library", "//pkg/apis/core:go_default_library", + "//pkg/apis/core/install:go_default_library", + "//pkg/apis/events/install:go_default_library", + "//pkg/apis/extensions/install:go_default_library", + "//pkg/apis/policy/install:go_default_library", + "//pkg/apis/rbac/install:go_default_library", + "//pkg/apis/scheduling/install:go_default_library", + "//pkg/apis/settings/install:go_default_library", + "//pkg/apis/storage/install:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", "//pkg/kubectl/util/templates:go_default_library", diff --git a/pkg/kubectl/cmd/convert/import_known_versions.go b/pkg/kubectl/cmd/convert/import_known_versions.go new file mode 100644 index 0000000000..2da5f1aadd --- /dev/null +++ b/pkg/kubectl/cmd/convert/import_known_versions.go @@ -0,0 +1,37 @@ +/* +Copyright 2016 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 convert + +// These imports are the API groups the client will support. +// TODO: Remove these manual install once we don't need legacy scheme in convert +import ( + _ "k8s.io/kubernetes/pkg/apis/apps/install" + _ "k8s.io/kubernetes/pkg/apis/authentication/install" + _ "k8s.io/kubernetes/pkg/apis/authorization/install" + _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" + _ "k8s.io/kubernetes/pkg/apis/batch/install" + _ "k8s.io/kubernetes/pkg/apis/certificates/install" + _ "k8s.io/kubernetes/pkg/apis/coordination/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" + _ "k8s.io/kubernetes/pkg/apis/events/install" + _ "k8s.io/kubernetes/pkg/apis/extensions/install" + _ "k8s.io/kubernetes/pkg/apis/policy/install" + _ "k8s.io/kubernetes/pkg/apis/rbac/install" + _ "k8s.io/kubernetes/pkg/apis/scheduling/install" + _ "k8s.io/kubernetes/pkg/apis/settings/install" + _ "k8s.io/kubernetes/pkg/apis/storage/install" +) diff --git a/pkg/kubectl/cmd/describe/BUILD b/pkg/kubectl/cmd/describe/BUILD index 4b1aaea02b..036e46df9c 100644 --- a/pkg/kubectl/cmd/describe/BUILD +++ b/pkg/kubectl/cmd/describe/BUILD @@ -7,10 +7,10 @@ go_library( visibility = ["//visibility:public"], deps = [ "//pkg/kubectl/cmd/util:go_default_library", + "//pkg/kubectl/describe:go_default_library", "//pkg/kubectl/describe/versioned:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", "//pkg/kubectl/util/templates:go_default_library", - "//pkg/printers: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/util/errors:go_default_library", @@ -30,9 +30,9 @@ go_test( embed = [":go_default_library"], deps = [ "//pkg/kubectl/cmd/testing:go_default_library", + "//pkg/kubectl/describe:go_default_library", "//pkg/kubectl/describe/versioned:go_default_library", "//pkg/kubectl/scheme:go_default_library", - "//pkg/printers:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/meta: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", diff --git a/pkg/kubectl/cmd/describe/describe.go b/pkg/kubectl/cmd/describe/describe.go index 472f1ffcff..c1c84cd171 100644 --- a/pkg/kubectl/cmd/describe/describe.go +++ b/pkg/kubectl/cmd/describe/describe.go @@ -29,10 +29,10 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions/resource" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/describe" describeversioned "k8s.io/kubernetes/pkg/kubectl/describe/versioned" "k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/templates" - "k8s.io/kubernetes/pkg/printers" ) var ( @@ -74,7 +74,7 @@ type DescribeOptions struct { Selector string Namespace string - Describer func(*meta.RESTMapping) (printers.Describer, error) + Describer func(*meta.RESTMapping) (describe.Describer, error) NewBuilder func() *resource.Builder BuilderArgs []string @@ -83,7 +83,7 @@ type DescribeOptions struct { AllNamespaces bool IncludeUninitialized bool - DescriberSettings *printers.DescriberSettings + DescriberSettings *describe.DescriberSettings FilenameOptions *resource.FilenameOptions genericclioptions.IOStreams @@ -92,7 +92,7 @@ type DescribeOptions struct { func NewCmdDescribe(parent string, f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command { o := &DescribeOptions{ FilenameOptions: &resource.FilenameOptions{}, - DescriberSettings: &printers.DescriberSettings{ + DescriberSettings: &describe.DescriberSettings{ ShowEvents: true, }, @@ -138,7 +138,7 @@ func (o *DescribeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [ o.BuilderArgs = args - o.Describer = func(mapping *meta.RESTMapping) (printers.Describer, error) { + o.Describer = func(mapping *meta.RESTMapping) (describe.Describer, error) { return describeversioned.DescriberFn(f, mapping) } diff --git a/pkg/kubectl/cmd/describe/describe_test.go b/pkg/kubectl/cmd/describe/describe_test.go index 7ed9fd93ae..4cd0f16622 100644 --- a/pkg/kubectl/cmd/describe/describe_test.go +++ b/pkg/kubectl/cmd/describe/describe_test.go @@ -27,19 +27,19 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions/resource" "k8s.io/client-go/rest/fake" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" - describe "k8s.io/kubernetes/pkg/kubectl/describe/versioned" + "k8s.io/kubernetes/pkg/kubectl/describe" + versioneddescribe "k8s.io/kubernetes/pkg/kubectl/describe/versioned" "k8s.io/kubernetes/pkg/kubectl/scheme" - "k8s.io/kubernetes/pkg/printers" ) // Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. func TestDescribeUnknownSchemaObject(t *testing.T) { d := &testDescriber{Output: "test output"} - oldFn := describe.DescriberFn + oldFn := versioneddescribe.DescriberFn defer func() { - describe.DescriberFn = oldFn + versioneddescribe.DescriberFn = oldFn }() - describe.DescriberFn = d.describerFor + versioneddescribe.DescriberFn = d.describerFor tf := cmdtesting.NewTestFactory().WithNamespace("non-default") defer tf.Cleanup() @@ -67,11 +67,11 @@ func TestDescribeUnknownSchemaObject(t *testing.T) { // Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. func TestDescribeUnknownNamespacedSchemaObject(t *testing.T) { d := &testDescriber{Output: "test output"} - oldFn := describe.DescriberFn + oldFn := versioneddescribe.DescriberFn defer func() { - describe.DescriberFn = oldFn + versioneddescribe.DescriberFn = oldFn }() - describe.DescriberFn = d.describerFor + versioneddescribe.DescriberFn = d.describerFor tf := cmdtesting.NewTestFactory() defer tf.Cleanup() @@ -99,11 +99,11 @@ func TestDescribeUnknownNamespacedSchemaObject(t *testing.T) { func TestDescribeObject(t *testing.T) { d := &testDescriber{Output: "test output"} - oldFn := describe.DescriberFn + oldFn := versioneddescribe.DescriberFn defer func() { - describe.DescriberFn = oldFn + versioneddescribe.DescriberFn = oldFn }() - describe.DescriberFn = d.describerFor + versioneddescribe.DescriberFn = d.describerFor _, _, rc := cmdtesting.TestData() tf := cmdtesting.NewTestFactory().WithNamespace("test") @@ -140,11 +140,11 @@ func TestDescribeObject(t *testing.T) { func TestDescribeListObjects(t *testing.T) { d := &testDescriber{Output: "test output"} - oldFn := describe.DescriberFn + oldFn := versioneddescribe.DescriberFn defer func() { - describe.DescriberFn = oldFn + versioneddescribe.DescriberFn = oldFn }() - describe.DescriberFn = d.describerFor + versioneddescribe.DescriberFn = d.describerFor pods, _, _ := cmdtesting.TestData() tf := cmdtesting.NewTestFactory().WithNamespace("test") @@ -167,11 +167,11 @@ func TestDescribeListObjects(t *testing.T) { func TestDescribeObjectShowEvents(t *testing.T) { d := &testDescriber{Output: "test output"} - oldFn := describe.DescriberFn + oldFn := versioneddescribe.DescriberFn defer func() { - describe.DescriberFn = oldFn + versioneddescribe.DescriberFn = oldFn }() - describe.DescriberFn = d.describerFor + versioneddescribe.DescriberFn = d.describerFor pods, _, _ := cmdtesting.TestData() tf := cmdtesting.NewTestFactory().WithNamespace("test") @@ -193,11 +193,11 @@ func TestDescribeObjectShowEvents(t *testing.T) { func TestDescribeObjectSkipEvents(t *testing.T) { d := &testDescriber{Output: "test output"} - oldFn := describe.DescriberFn + oldFn := versioneddescribe.DescriberFn defer func() { - describe.DescriberFn = oldFn + versioneddescribe.DescriberFn = oldFn }() - describe.DescriberFn = d.describerFor + versioneddescribe.DescriberFn = d.describerFor pods, _, _ := cmdtesting.TestData() tf := cmdtesting.NewTestFactory().WithNamespace("test") @@ -247,16 +247,16 @@ func TestDescribeHelpMessage(t *testing.T) { type testDescriber struct { Name, Namespace string - Settings printers.DescriberSettings + Settings describe.DescriberSettings Output string Err error } -func (t *testDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (output string, err error) { +func (t *testDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (output string, err error) { t.Namespace, t.Name = namespace, name t.Settings = describerSettings return t.Output, t.Err } -func (t *testDescriber) describerFor(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (printers.Describer, error) { +func (t *testDescriber) describerFor(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (describe.Describer, error) { return t, nil } diff --git a/pkg/kubectl/describe/BUILD b/pkg/kubectl/describe/BUILD index 533c5dfb2c..c120954a8d 100644 --- a/pkg/kubectl/describe/BUILD +++ b/pkg/kubectl/describe/BUILD @@ -2,11 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = ["describe.go"], + srcs = ["interface.go"], importpath = "k8s.io/kubernetes/pkg/kubectl/describe", visibility = ["//visibility:public"], deps = [ - "//pkg/printers:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library", ], diff --git a/pkg/kubectl/describe/describe.go b/pkg/kubectl/describe/describe.go deleted file mode 100644 index aa6f37a071..0000000000 --- a/pkg/kubectl/describe/describe.go +++ /dev/null @@ -1,26 +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 describe - -import ( - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/kubernetes/pkg/printers" -) - -// DescriberFunc gives a way to display the specified RESTMapping type -type DescriberFunc func(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (printers.Describer, error) diff --git a/pkg/kubectl/describe/interface.go b/pkg/kubectl/describe/interface.go new file mode 100644 index 0000000000..5842991878 --- /dev/null +++ b/pkg/kubectl/describe/interface.go @@ -0,0 +1,71 @@ +/* +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 describe + +import ( + "fmt" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +const ( + // LoadBalancerWidth is the width how we describe load balancer + LoadBalancerWidth = 16 + + // LabelNodeRolePrefix is a label prefix for node roles + // It's copied over to here until it's merged in core: https://github.com/kubernetes/kubernetes/pull/39112 + LabelNodeRolePrefix = "node-role.kubernetes.io/" + + // NodeLabelRole specifies the role of a node + NodeLabelRole = "kubernetes.io/role" +) + +// DescriberFunc gives a way to display the specified RESTMapping type +type DescriberFunc func(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (Describer, error) + +// Describer generates output for the named resource or an error +// if the output could not be generated. Implementers typically +// abstract the retrieval of the named object from a remote server. +type Describer interface { + Describe(namespace, name string, describerSettings DescriberSettings) (output string, err error) +} + +// DescriberSettings holds display configuration for each object +// describer to control what is printed. +type DescriberSettings struct { + ShowEvents bool +} + +// ObjectDescriber is an interface for displaying arbitrary objects with extra +// information. Use when an object is in hand (on disk, or already retrieved). +// Implementers may ignore the additional information passed on extra, or use it +// by default. ObjectDescribers may return ErrNoDescriber if no suitable describer +// is found. +type ObjectDescriber interface { + DescribeObject(object interface{}, extra ...interface{}) (output string, err error) +} + +// ErrNoDescriber is a structured error indicating the provided object or objects +// cannot be described. +type ErrNoDescriber struct { + Types []string +} + +// Error implements the error interface. +func (e ErrNoDescriber) Error() string { + return fmt.Sprintf("no describer has been defined for %v", e.Types) +} diff --git a/pkg/kubectl/describe/versioned/BUILD b/pkg/kubectl/describe/versioned/BUILD index b0c4d44ed4..21c1a0b78c 100644 --- a/pkg/kubectl/describe/versioned/BUILD +++ b/pkg/kubectl/describe/versioned/BUILD @@ -1,4 +1,4 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", @@ -7,10 +7,47 @@ go_library( visibility = ["//visibility:public"], deps = [ "//pkg/kubectl/describe:go_default_library", - "//pkg/printers:go_default_library", - "//pkg/printers/internalversion:go_default_library", + "//pkg/kubectl/scheme:go_default_library", + "//pkg/kubectl/util/certificate:go_default_library", + "//pkg/kubectl/util/deployment:go_default_library", + "//pkg/kubectl/util/event:go_default_library", + "//pkg/kubectl/util/fieldpath:go_default_library", + "//pkg/kubectl/util/qos:go_default_library", + "//pkg/kubectl/util/rbac:go_default_library", + "//pkg/kubectl/util/resource:go_default_library", + "//pkg/kubectl/util/slice:go_default_library", + "//pkg/kubectl/util/storage:go_default_library", + "//staging/src/k8s.io/api/apps/v1:go_default_library", + "//staging/src/k8s.io/api/autoscaling/v2beta2: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/certificates/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/api/networking/v1:go_default_library", + "//staging/src/k8s.io/api/policy/v1beta1:go_default_library", + "//staging/src/k8s.io/api/rbac/v1:go_default_library", + "//staging/src/k8s.io/api/scheduling/v1beta1:go_default_library", + "//staging/src/k8s.io/api/storage/v1: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/fields:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/labels: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/duration: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/cli-runtime/pkg/genericclioptions: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/typed/core/v1:go_default_library", + "//staging/src/k8s.io/client-go/rest:go_default_library", + "//staging/src/k8s.io/client-go/tools/reference:go_default_library", + "//vendor/github.com/fatih/camelcase:go_default_library", + "//vendor/k8s.io/klog:go_default_library", ], ) @@ -27,3 +64,26 @@ filegroup( tags = ["automanaged"], visibility = ["//visibility:public"], ) + +go_test( + name = "go_default_test", + srcs = ["describe_test.go"], + embed = [":go_default_library"], + deps = [ + "//pkg/kubectl/describe:go_default_library", + "//staging/src/k8s.io/api/apps/v1:go_default_library", + "//staging/src/k8s.io/api/autoscaling/v2beta2:go_default_library", + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/api/networking/v1:go_default_library", + "//staging/src/k8s.io/api/policy/v1beta1:go_default_library", + "//staging/src/k8s.io/api/storage/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/equality: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/util/intstr:go_default_library", + "//staging/src/k8s.io/client-go/kubernetes:go_default_library", + "//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library", + "//vendor/k8s.io/utils/pointer:go_default_library", + ], +) diff --git a/pkg/kubectl/describe/versioned/describe.go b/pkg/kubectl/describe/versioned/describe.go index b79b9ca20e..a6ff6a8483 100644 --- a/pkg/kubectl/describe/versioned/describe.go +++ b/pkg/kubectl/describe/versioned/describe.go @@ -1,5 +1,5 @@ /* -Copyright 2018 The Kubernetes Authors. +Copyright 2014 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. @@ -17,32 +17,4428 @@ limitations under the License. package versioned import ( + "bytes" + "crypto/x509" "fmt" + "io" + "net" + "net/url" + "reflect" + "sort" + "strconv" + "strings" + "text/tabwriter" + "time" + "github.com/fatih/camelcase" + + appsv1 "k8s.io/api/apps/v1" + autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" + batchv1 "k8s.io/api/batch/v1" + batchv1beta1 "k8s.io/api/batch/v1beta1" + certificatesv1beta1 "k8s.io/api/certificates/v1beta1" + corev1 "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + networkingv1 "k8s.io/api/networking/v1" + policyv1beta1 "k8s.io/api/policy/v1beta1" + rbacv1 "k8s.io/api/rbac/v1" + schedulingv1beta1 "k8s.io/api/scheduling/v1beta1" + storagev1 "k8s.io/api/storage/v1" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/duration" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/dynamic" + clientset "k8s.io/client-go/kubernetes" + corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/reference" + "k8s.io/klog" "k8s.io/kubernetes/pkg/kubectl/describe" - "k8s.io/kubernetes/pkg/printers" - printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" + "k8s.io/kubernetes/pkg/kubectl/scheme" + "k8s.io/kubernetes/pkg/kubectl/util/certificate" + deploymentutil "k8s.io/kubernetes/pkg/kubectl/util/deployment" + "k8s.io/kubernetes/pkg/kubectl/util/event" + "k8s.io/kubernetes/pkg/kubectl/util/fieldpath" + "k8s.io/kubernetes/pkg/kubectl/util/qos" + "k8s.io/kubernetes/pkg/kubectl/util/rbac" + resourcehelper "k8s.io/kubernetes/pkg/kubectl/util/resource" + "k8s.io/kubernetes/pkg/kubectl/util/slice" + storageutil "k8s.io/kubernetes/pkg/kubectl/util/storage" +) + +// Each level has 2 spaces for PrefixWriter +const ( + LEVEL_0 = iota + LEVEL_1 + LEVEL_2 + LEVEL_3 ) // DescriberFn gives a way to easily override the function for unit testing if needed var DescriberFn describe.DescriberFunc = Describer // Describer returns a Describer for displaying the specified RESTMapping type or an error. -func Describer(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (printers.Describer, error) { +func Describer(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (describe.Describer, error) { clientConfig, err := restClientGetter.ToRESTConfig() if err != nil { return nil, err } // try to get a describer - if describer, ok := printersinternal.DescriberFor(mapping.GroupVersionKind.GroupKind(), clientConfig); ok { + if describer, ok := DescriberFor(mapping.GroupVersionKind.GroupKind(), clientConfig); ok { return describer, nil } // if this is a kind we don't have a describer for yet, go generic if possible - if genericDescriber, ok := printersinternal.GenericDescriberFor(mapping, clientConfig); ok { + if genericDescriber, ok := GenericDescriberFor(mapping, clientConfig); ok { return genericDescriber, nil } // otherwise return an unregistered error return nil, fmt.Errorf("no description has been implemented for %s", mapping.GroupVersionKind.String()) } + +// PrefixWriter can write text at various indentation levels. +type PrefixWriter interface { + // Write writes text with the specified indentation level. + Write(level int, format string, a ...interface{}) + // WriteLine writes an entire line with no indentation level. + WriteLine(a ...interface{}) + // Flush forces indentation to be reset. + Flush() +} + +// prefixWriter implements PrefixWriter +type prefixWriter struct { + out io.Writer +} + +var _ PrefixWriter = &prefixWriter{} + +// NewPrefixWriter creates a new PrefixWriter. +func NewPrefixWriter(out io.Writer) PrefixWriter { + return &prefixWriter{out: out} +} + +func (pw *prefixWriter) Write(level int, format string, a ...interface{}) { + levelSpace := " " + prefix := "" + for i := 0; i < level; i++ { + prefix += levelSpace + } + fmt.Fprintf(pw.out, prefix+format, a...) +} + +func (pw *prefixWriter) WriteLine(a ...interface{}) { + fmt.Fprintln(pw.out, a...) +} + +func (pw *prefixWriter) Flush() { + if f, ok := pw.out.(flusher); ok { + f.Flush() + } +} + +func describerMap(clientConfig *rest.Config) (map[schema.GroupKind]describe.Describer, error) { + c, err := clientset.NewForConfig(clientConfig) + if err != nil { + return nil, err + } + externalclient, err := clientset.NewForConfig(clientConfig) + if err != nil { + return nil, err + } + + m := map[schema.GroupKind]describe.Describer{ + {Group: corev1.GroupName, Kind: "Pod"}: &PodDescriber{c}, + {Group: corev1.GroupName, Kind: "ReplicationController"}: &ReplicationControllerDescriber{c}, + {Group: corev1.GroupName, Kind: "Secret"}: &SecretDescriber{c}, + {Group: corev1.GroupName, Kind: "Service"}: &ServiceDescriber{c}, + {Group: corev1.GroupName, Kind: "ServiceAccount"}: &ServiceAccountDescriber{c}, + {Group: corev1.GroupName, Kind: "Node"}: &NodeDescriber{c}, + {Group: corev1.GroupName, Kind: "LimitRange"}: &LimitRangeDescriber{c}, + {Group: corev1.GroupName, Kind: "ResourceQuota"}: &ResourceQuotaDescriber{c}, + {Group: corev1.GroupName, Kind: "PersistentVolume"}: &PersistentVolumeDescriber{c}, + {Group: corev1.GroupName, Kind: "PersistentVolumeClaim"}: &PersistentVolumeClaimDescriber{c}, + {Group: corev1.GroupName, Kind: "Namespace"}: &NamespaceDescriber{c}, + {Group: corev1.GroupName, Kind: "Endpoints"}: &EndpointsDescriber{c}, + {Group: corev1.GroupName, Kind: "ConfigMap"}: &ConfigMapDescriber{c}, + {Group: corev1.GroupName, Kind: "PriorityClass"}: &PriorityClassDescriber{c}, + {Group: extensionsv1beta1.GroupName, Kind: "ReplicaSet"}: &ReplicaSetDescriber{c}, + {Group: extensionsv1beta1.GroupName, Kind: "NetworkPolicy"}: &NetworkPolicyDescriber{c}, + {Group: extensionsv1beta1.GroupName, Kind: "PodSecurityPolicy"}: &PodSecurityPolicyDescriber{c}, + {Group: autoscalingv2beta2.GroupName, Kind: "HorizontalPodAutoscaler"}: &HorizontalPodAutoscalerDescriber{c}, + {Group: extensionsv1beta1.GroupName, Kind: "DaemonSet"}: &DaemonSetDescriber{c}, + {Group: extensionsv1beta1.GroupName, Kind: "Deployment"}: &DeploymentDescriber{c, externalclient}, + {Group: extensionsv1beta1.GroupName, Kind: "Ingress"}: &IngressDescriber{c}, + {Group: batchv1.GroupName, Kind: "Job"}: &JobDescriber{c}, + {Group: batchv1.GroupName, Kind: "CronJob"}: &CronJobDescriber{c, externalclient}, + {Group: appsv1.GroupName, Kind: "StatefulSet"}: &StatefulSetDescriber{c}, + {Group: appsv1.GroupName, Kind: "Deployment"}: &DeploymentDescriber{c, externalclient}, + {Group: appsv1.GroupName, Kind: "DaemonSet"}: &DaemonSetDescriber{c}, + {Group: appsv1.GroupName, Kind: "ReplicaSet"}: &ReplicaSetDescriber{c}, + {Group: certificatesv1beta1.GroupName, Kind: "CertificateSigningRequest"}: &CertificateSigningRequestDescriber{c}, + {Group: storagev1.GroupName, Kind: "StorageClass"}: &StorageClassDescriber{c}, + {Group: policyv1beta1.GroupName, Kind: "PodDisruptionBudget"}: &PodDisruptionBudgetDescriber{c}, + {Group: rbacv1.GroupName, Kind: "Role"}: &RoleDescriber{externalclient}, + {Group: rbacv1.GroupName, Kind: "ClusterRole"}: &ClusterRoleDescriber{externalclient}, + {Group: rbacv1.GroupName, Kind: "RoleBinding"}: &RoleBindingDescriber{externalclient}, + {Group: rbacv1.GroupName, Kind: "ClusterRoleBinding"}: &ClusterRoleBindingDescriber{externalclient}, + {Group: networkingv1.GroupName, Kind: "NetworkPolicy"}: &NetworkPolicyDescriber{c}, + {Group: schedulingv1beta1.GroupName, Kind: "PriorityClass"}: &PriorityClassDescriber{c}, + } + + return m, nil +} + +// DescriberFor returns the default describe functions for each of the standard +// Kubernetes types. +func DescriberFor(kind schema.GroupKind, clientConfig *rest.Config) (describe.Describer, bool) { + describers, err := describerMap(clientConfig) + if err != nil { + klog.V(1).Info(err) + return nil, false + } + + f, ok := describers[kind] + return f, ok +} + +// GenericDescriberFor returns a generic describer for the specified mapping +// that uses only information available from runtime.Unstructured +func GenericDescriberFor(mapping *meta.RESTMapping, clientConfig *rest.Config) (describe.Describer, bool) { + // used to fetch the resource + dynamicClient, err := dynamic.NewForConfig(clientConfig) + if err != nil { + return nil, false + } + + // used to get events for the resource + clientSet, err := clientset.NewForConfig(clientConfig) + if err != nil { + return nil, false + } + eventsClient := clientSet.Core() + + return &genericDescriber{mapping, dynamicClient, eventsClient}, true +} + +type genericDescriber struct { + mapping *meta.RESTMapping + dynamic dynamic.Interface + events corev1client.EventsGetter +} + +func (g *genericDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (output string, err error) { + obj, err := g.dynamic.Resource(g.mapping.Resource).Namespace(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = g.events.Events(namespace).Search(scheme.Scheme, obj) + } + + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", obj.GetName()) + w.Write(LEVEL_0, "Namespace:\t%s\n", obj.GetNamespace()) + printLabelsMultiline(w, "Labels", obj.GetLabels()) + printAnnotationsMultiline(w, "Annotations", obj.GetAnnotations()) + printUnstructuredContent(w, LEVEL_0, obj.UnstructuredContent(), "", ".metadata.name", ".metadata.namespace", ".metadata.labels", ".metadata.annotations") + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + +func printUnstructuredContent(w PrefixWriter, level int, content map[string]interface{}, skipPrefix string, skip ...string) { + fields := []string{} + for field := range content { + fields = append(fields, field) + } + sort.Strings(fields) + + for _, field := range fields { + value := content[field] + switch typedValue := value.(type) { + case map[string]interface{}: + skipExpr := fmt.Sprintf("%s.%s", skipPrefix, field) + if slice.ContainsString(skip, skipExpr, nil) { + continue + } + w.Write(level, "%s:\n", smartLabelFor(field)) + printUnstructuredContent(w, level+1, typedValue, skipExpr, skip...) + + case []interface{}: + skipExpr := fmt.Sprintf("%s.%s", skipPrefix, field) + if slice.ContainsString(skip, skipExpr, nil) { + continue + } + w.Write(level, "%s:\n", smartLabelFor(field)) + for _, child := range typedValue { + switch typedChild := child.(type) { + case map[string]interface{}: + printUnstructuredContent(w, level+1, typedChild, skipExpr, skip...) + default: + w.Write(level+1, "%v\n", typedChild) + } + } + + default: + skipExpr := fmt.Sprintf("%s.%s", skipPrefix, field) + if slice.ContainsString(skip, skipExpr, nil) { + continue + } + w.Write(level, "%s:\t%v\n", smartLabelFor(field), typedValue) + } + } +} + +func smartLabelFor(field string) string { + commonAcronyms := []string{"API", "URL", "UID", "OSB", "GUID"} + + parts := camelcase.Split(field) + result := make([]string, 0, len(parts)) + for _, part := range parts { + if part == "_" { + continue + } + + if slice.ContainsString(commonAcronyms, strings.ToUpper(part), nil) { + part = strings.ToUpper(part) + } else { + part = strings.Title(part) + } + result = append(result, part) + } + + return strings.Join(result, " ") +} + +// DefaultObjectDescriber can describe the default Kubernetes objects. +var DefaultObjectDescriber describe.ObjectDescriber + +func init() { + d := &Describers{} + err := d.Add( + describeLimitRange, + describeQuota, + describePod, + describeService, + describeReplicationController, + describeDaemonSet, + describeNode, + describeNamespace, + ) + if err != nil { + klog.Fatalf("Cannot register describers: %v", err) + } + DefaultObjectDescriber = d +} + +// NamespaceDescriber generates information about a namespace +type NamespaceDescriber struct { + clientset.Interface +} + +func (d *NamespaceDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + ns, err := d.Core().Namespaces().Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + resourceQuotaList, err := d.Core().ResourceQuotas(name).List(metav1.ListOptions{}) + if err != nil { + if errors.IsNotFound(err) { + // Server does not support resource quotas. + // Not an error, will not show resource quotas information. + resourceQuotaList = nil + } else { + return "", err + } + } + limitRangeList, err := d.Core().LimitRanges(name).List(metav1.ListOptions{}) + if err != nil { + if errors.IsNotFound(err) { + // Server does not support limit ranges. + // Not an error, will not show limit ranges information. + limitRangeList = nil + } else { + return "", err + } + } + return describeNamespace(ns, resourceQuotaList, limitRangeList) +} + +func describeNamespace(namespace *corev1.Namespace, resourceQuotaList *corev1.ResourceQuotaList, limitRangeList *corev1.LimitRangeList) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", namespace.Name) + printLabelsMultiline(w, "Labels", namespace.Labels) + printAnnotationsMultiline(w, "Annotations", namespace.Annotations) + w.Write(LEVEL_0, "Status:\t%s\n", string(namespace.Status.Phase)) + if resourceQuotaList != nil { + w.Write(LEVEL_0, "\n") + DescribeResourceQuotas(resourceQuotaList, w) + } + if limitRangeList != nil { + w.Write(LEVEL_0, "\n") + DescribeLimitRanges(limitRangeList, w) + } + return nil + }) +} + +func describeLimitRangeSpec(spec corev1.LimitRangeSpec, prefix string, w PrefixWriter) { + for i := range spec.Limits { + item := spec.Limits[i] + maxResources := item.Max + minResources := item.Min + defaultLimitResources := item.Default + defaultRequestResources := item.DefaultRequest + ratio := item.MaxLimitRequestRatio + + set := map[corev1.ResourceName]bool{} + for k := range maxResources { + set[k] = true + } + for k := range minResources { + set[k] = true + } + for k := range defaultLimitResources { + set[k] = true + } + for k := range defaultRequestResources { + set[k] = true + } + for k := range ratio { + set[k] = true + } + + for k := range set { + // if no value is set, we output - + maxValue := "-" + minValue := "-" + defaultLimitValue := "-" + defaultRequestValue := "-" + ratioValue := "-" + + maxQuantity, maxQuantityFound := maxResources[k] + if maxQuantityFound { + maxValue = maxQuantity.String() + } + + minQuantity, minQuantityFound := minResources[k] + if minQuantityFound { + minValue = minQuantity.String() + } + + defaultLimitQuantity, defaultLimitQuantityFound := defaultLimitResources[k] + if defaultLimitQuantityFound { + defaultLimitValue = defaultLimitQuantity.String() + } + + defaultRequestQuantity, defaultRequestQuantityFound := defaultRequestResources[k] + if defaultRequestQuantityFound { + defaultRequestValue = defaultRequestQuantity.String() + } + + ratioQuantity, ratioQuantityFound := ratio[k] + if ratioQuantityFound { + ratioValue = ratioQuantity.String() + } + + msg := "%s%s\t%v\t%v\t%v\t%v\t%v\t%v\n" + w.Write(LEVEL_0, msg, prefix, item.Type, k, minValue, maxValue, defaultRequestValue, defaultLimitValue, ratioValue) + } + } +} + +// DescribeLimitRanges merges a set of limit range items into a single tabular description +func DescribeLimitRanges(limitRanges *corev1.LimitRangeList, w PrefixWriter) { + if len(limitRanges.Items) == 0 { + w.Write(LEVEL_0, "No resource limits.\n") + return + } + w.Write(LEVEL_0, "Resource Limits\n Type\tResource\tMin\tMax\tDefault Request\tDefault Limit\tMax Limit/Request Ratio\n") + w.Write(LEVEL_0, " ----\t--------\t---\t---\t---------------\t-------------\t-----------------------\n") + for _, limitRange := range limitRanges.Items { + describeLimitRangeSpec(limitRange.Spec, " ", w) + } +} + +// DescribeResourceQuotas merges a set of quota items into a single tabular description of all quotas +func DescribeResourceQuotas(quotas *corev1.ResourceQuotaList, w PrefixWriter) { + if len(quotas.Items) == 0 { + w.Write(LEVEL_0, "No resource quota.\n") + return + } + sort.Sort(SortableResourceQuotas(quotas.Items)) + + w.Write(LEVEL_0, "Resource Quotas") + for _, q := range quotas.Items { + w.Write(LEVEL_0, "\n Name:\t%s\n", q.Name) + if len(q.Spec.Scopes) > 0 { + scopes := make([]string, 0, len(q.Spec.Scopes)) + for _, scope := range q.Spec.Scopes { + scopes = append(scopes, string(scope)) + } + sort.Strings(scopes) + w.Write(LEVEL_0, " Scopes:\t%s\n", strings.Join(scopes, ", ")) + for _, scope := range scopes { + helpText := helpTextForResourceQuotaScope(corev1.ResourceQuotaScope(scope)) + if len(helpText) > 0 { + w.Write(LEVEL_0, " * %s\n", helpText) + } + } + } + + w.Write(LEVEL_0, " Resource\tUsed\tHard\n") + w.Write(LEVEL_0, " --------\t---\t---\n") + + resources := make([]corev1.ResourceName, 0, len(q.Status.Hard)) + for resource := range q.Status.Hard { + resources = append(resources, resource) + } + sort.Sort(SortableResourceNames(resources)) + + for _, resource := range resources { + hardQuantity := q.Status.Hard[resource] + usedQuantity := q.Status.Used[resource] + w.Write(LEVEL_0, " %s\t%s\t%s\n", string(resource), usedQuantity.String(), hardQuantity.String()) + } + } +} + +// LimitRangeDescriber generates information about a limit range +type LimitRangeDescriber struct { + clientset.Interface +} + +func (d *LimitRangeDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + lr := d.Core().LimitRanges(namespace) + + limitRange, err := lr.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + return describeLimitRange(limitRange) +} + +func describeLimitRange(limitRange *corev1.LimitRange) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", limitRange.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", limitRange.Namespace) + w.Write(LEVEL_0, "Type\tResource\tMin\tMax\tDefault Request\tDefault Limit\tMax Limit/Request Ratio\n") + w.Write(LEVEL_0, "----\t--------\t---\t---\t---------------\t-------------\t-----------------------\n") + describeLimitRangeSpec(limitRange.Spec, "", w) + return nil + }) +} + +// ResourceQuotaDescriber generates information about a resource quota +type ResourceQuotaDescriber struct { + clientset.Interface +} + +func (d *ResourceQuotaDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + rq := d.Core().ResourceQuotas(namespace) + + resourceQuota, err := rq.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + return describeQuota(resourceQuota) +} + +func helpTextForResourceQuotaScope(scope corev1.ResourceQuotaScope) string { + switch scope { + case corev1.ResourceQuotaScopeTerminating: + return "Matches all pods that have an active deadline. These pods have a limited lifespan on a node before being actively terminated by the system." + case corev1.ResourceQuotaScopeNotTerminating: + return "Matches all pods that do not have an active deadline. These pods usually include long running pods whose container command is not expected to terminate." + case corev1.ResourceQuotaScopeBestEffort: + return "Matches all pods that do not have resource requirements set. These pods have a best effort quality of service." + case corev1.ResourceQuotaScopeNotBestEffort: + return "Matches all pods that have at least one resource requirement set. These pods have a burstable or guaranteed quality of service." + default: + return "" + } +} +func describeQuota(resourceQuota *corev1.ResourceQuota) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", resourceQuota.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", resourceQuota.Namespace) + if len(resourceQuota.Spec.Scopes) > 0 { + scopes := make([]string, 0, len(resourceQuota.Spec.Scopes)) + for _, scope := range resourceQuota.Spec.Scopes { + scopes = append(scopes, string(scope)) + } + sort.Strings(scopes) + w.Write(LEVEL_0, "Scopes:\t%s\n", strings.Join(scopes, ", ")) + for _, scope := range scopes { + helpText := helpTextForResourceQuotaScope(corev1.ResourceQuotaScope(scope)) + if len(helpText) > 0 { + w.Write(LEVEL_0, " * %s\n", helpText) + } + } + } + w.Write(LEVEL_0, "Resource\tUsed\tHard\n") + w.Write(LEVEL_0, "--------\t----\t----\n") + + resources := make([]corev1.ResourceName, 0, len(resourceQuota.Status.Hard)) + for resource := range resourceQuota.Status.Hard { + resources = append(resources, resource) + } + sort.Sort(SortableResourceNames(resources)) + + msg := "%v\t%v\t%v\n" + for i := range resources { + resource := resources[i] + hardQuantity := resourceQuota.Status.Hard[resource] + usedQuantity := resourceQuota.Status.Used[resource] + w.Write(LEVEL_0, msg, resource, usedQuantity.String(), hardQuantity.String()) + } + return nil + }) +} + +// PodDescriber generates information about a pod and the replication controllers that +// create it. +type PodDescriber struct { + clientset.Interface +} + +func (d *PodDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + pod, err := d.Core().Pods(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + if describerSettings.ShowEvents { + eventsInterface := d.Core().Events(namespace) + selector := eventsInterface.GetFieldSelector(&name, &namespace, nil, nil) + options := metav1.ListOptions{FieldSelector: selector.String()} + events, err2 := eventsInterface.List(options) + if describerSettings.ShowEvents && err2 == nil && len(events.Items) > 0 { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Pod '%v': error '%v', but found events.\n", name, err) + DescribeEvents(events, w) + return nil + }) + } + } + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + if ref, err := reference.GetReference(scheme.Scheme, pod); err != nil { + klog.Errorf("Unable to construct reference to '%#v': %v", pod, err) + } else { + ref.Kind = "" + events, _ = d.Core().Events(namespace).Search(scheme.Scheme, ref) + } + } + + return describePod(pod, events) +} + +func describePod(pod *corev1.Pod, events *corev1.EventList) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", pod.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", pod.Namespace) + if pod.Spec.Priority != nil { + w.Write(LEVEL_0, "Priority:\t%d\n", *pod.Spec.Priority) + w.Write(LEVEL_0, "PriorityClassName:\t%s\n", stringOrNone(pod.Spec.PriorityClassName)) + } + if pod.Spec.NodeName == "" { + w.Write(LEVEL_0, "Node:\t\n") + } else { + w.Write(LEVEL_0, "Node:\t%s\n", pod.Spec.NodeName+"/"+pod.Status.HostIP) + } + if pod.Status.StartTime != nil { + w.Write(LEVEL_0, "Start Time:\t%s\n", pod.Status.StartTime.Time.Format(time.RFC1123Z)) + } + printLabelsMultiline(w, "Labels", pod.Labels) + printAnnotationsMultiline(w, "Annotations", pod.Annotations) + if pod.DeletionTimestamp != nil { + w.Write(LEVEL_0, "Status:\tTerminating (lasts %s)\n", translateTimestampUntil(*pod.DeletionTimestamp)) + w.Write(LEVEL_0, "Termination Grace Period:\t%ds\n", *pod.DeletionGracePeriodSeconds) + } else { + w.Write(LEVEL_0, "Status:\t%s\n", string(pod.Status.Phase)) + } + if len(pod.Status.Reason) > 0 { + w.Write(LEVEL_0, "Reason:\t%s\n", pod.Status.Reason) + } + if len(pod.Status.Message) > 0 { + w.Write(LEVEL_0, "Message:\t%s\n", pod.Status.Message) + } + w.Write(LEVEL_0, "IP:\t%s\n", pod.Status.PodIP) + if controlledBy := printController(pod); len(controlledBy) > 0 { + w.Write(LEVEL_0, "Controlled By:\t%s\n", controlledBy) + } + if len(pod.Status.NominatedNodeName) > 0 { + w.Write(LEVEL_0, "NominatedNodeName:\t%s\n", pod.Status.NominatedNodeName) + } + + if len(pod.Spec.InitContainers) > 0 { + describeContainers("Init Containers", pod.Spec.InitContainers, pod.Status.InitContainerStatuses, EnvValueRetriever(pod), w, "") + } + describeContainers("Containers", pod.Spec.Containers, pod.Status.ContainerStatuses, EnvValueRetriever(pod), w, "") + if len(pod.Spec.ReadinessGates) > 0 { + w.Write(LEVEL_0, "Readiness Gates:\n Type\tStatus\n") + for _, g := range pod.Spec.ReadinessGates { + status := "" + for _, c := range pod.Status.Conditions { + if c.Type == g.ConditionType { + status = fmt.Sprintf("%v", c.Status) + break + } + } + w.Write(LEVEL_1, "%v \t%v \n", + g.ConditionType, + status) + } + } + if len(pod.Status.Conditions) > 0 { + w.Write(LEVEL_0, "Conditions:\n Type\tStatus\n") + for _, c := range pod.Status.Conditions { + w.Write(LEVEL_1, "%v \t%v \n", + c.Type, + c.Status) + } + } + describeVolumes(pod.Spec.Volumes, w, "") + if pod.Status.QOSClass != "" { + w.Write(LEVEL_0, "QoS Class:\t%s\n", pod.Status.QOSClass) + } else { + w.Write(LEVEL_0, "QoS Class:\t%s\n", qos.GetPodQOS(pod)) + } + printLabelsMultiline(w, "Node-Selectors", pod.Spec.NodeSelector) + printPodTolerationsMultiline(w, "Tolerations", pod.Spec.Tolerations) + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + +func printController(controllee metav1.Object) string { + if controllerRef := metav1.GetControllerOf(controllee); controllerRef != nil { + return fmt.Sprintf("%s/%s", controllerRef.Kind, controllerRef.Name) + } + return "" +} + +func describeVolumes(volumes []corev1.Volume, w PrefixWriter, space string) { + if volumes == nil || len(volumes) == 0 { + w.Write(LEVEL_0, "%sVolumes:\t\n", space) + return + } + w.Write(LEVEL_0, "%sVolumes:\n", space) + for _, volume := range volumes { + nameIndent := "" + if len(space) > 0 { + nameIndent = " " + } + w.Write(LEVEL_1, "%s%v:\n", nameIndent, volume.Name) + switch { + case volume.VolumeSource.HostPath != nil: + printHostPathVolumeSource(volume.VolumeSource.HostPath, w) + case volume.VolumeSource.EmptyDir != nil: + printEmptyDirVolumeSource(volume.VolumeSource.EmptyDir, w) + case volume.VolumeSource.GCEPersistentDisk != nil: + printGCEPersistentDiskVolumeSource(volume.VolumeSource.GCEPersistentDisk, w) + case volume.VolumeSource.AWSElasticBlockStore != nil: + printAWSElasticBlockStoreVolumeSource(volume.VolumeSource.AWSElasticBlockStore, w) + case volume.VolumeSource.GitRepo != nil: + printGitRepoVolumeSource(volume.VolumeSource.GitRepo, w) + case volume.VolumeSource.Secret != nil: + printSecretVolumeSource(volume.VolumeSource.Secret, w) + case volume.VolumeSource.ConfigMap != nil: + printConfigMapVolumeSource(volume.VolumeSource.ConfigMap, w) + case volume.VolumeSource.NFS != nil: + printNFSVolumeSource(volume.VolumeSource.NFS, w) + case volume.VolumeSource.ISCSI != nil: + printISCSIVolumeSource(volume.VolumeSource.ISCSI, w) + case volume.VolumeSource.Glusterfs != nil: + printGlusterfsVolumeSource(volume.VolumeSource.Glusterfs, w) + case volume.VolumeSource.PersistentVolumeClaim != nil: + printPersistentVolumeClaimVolumeSource(volume.VolumeSource.PersistentVolumeClaim, w) + case volume.VolumeSource.RBD != nil: + printRBDVolumeSource(volume.VolumeSource.RBD, w) + case volume.VolumeSource.Quobyte != nil: + printQuobyteVolumeSource(volume.VolumeSource.Quobyte, w) + case volume.VolumeSource.DownwardAPI != nil: + printDownwardAPIVolumeSource(volume.VolumeSource.DownwardAPI, w) + case volume.VolumeSource.AzureDisk != nil: + printAzureDiskVolumeSource(volume.VolumeSource.AzureDisk, w) + case volume.VolumeSource.VsphereVolume != nil: + printVsphereVolumeSource(volume.VolumeSource.VsphereVolume, w) + case volume.VolumeSource.Cinder != nil: + printCinderVolumeSource(volume.VolumeSource.Cinder, w) + case volume.VolumeSource.PhotonPersistentDisk != nil: + printPhotonPersistentDiskVolumeSource(volume.VolumeSource.PhotonPersistentDisk, w) + case volume.VolumeSource.PortworxVolume != nil: + printPortworxVolumeSource(volume.VolumeSource.PortworxVolume, w) + case volume.VolumeSource.ScaleIO != nil: + printScaleIOVolumeSource(volume.VolumeSource.ScaleIO, w) + case volume.VolumeSource.CephFS != nil: + printCephFSVolumeSource(volume.VolumeSource.CephFS, w) + case volume.VolumeSource.StorageOS != nil: + printStorageOSVolumeSource(volume.VolumeSource.StorageOS, w) + case volume.VolumeSource.FC != nil: + printFCVolumeSource(volume.VolumeSource.FC, w) + case volume.VolumeSource.AzureFile != nil: + printAzureFileVolumeSource(volume.VolumeSource.AzureFile, w) + case volume.VolumeSource.FlexVolume != nil: + printFlexVolumeSource(volume.VolumeSource.FlexVolume, w) + case volume.VolumeSource.Flocker != nil: + printFlockerVolumeSource(volume.VolumeSource.Flocker, w) + case volume.VolumeSource.Projected != nil: + printProjectedVolumeSource(volume.VolumeSource.Projected, w) + default: + w.Write(LEVEL_1, "\n") + } + } +} + +func printHostPathVolumeSource(hostPath *corev1.HostPathVolumeSource, w PrefixWriter) { + hostPathType := "" + if hostPath.Type != nil { + hostPathType = string(*hostPath.Type) + } + w.Write(LEVEL_2, "Type:\tHostPath (bare host directory volume)\n"+ + " Path:\t%v\n"+ + " HostPathType:\t%v\n", + hostPath.Path, hostPathType) +} + +func printEmptyDirVolumeSource(emptyDir *corev1.EmptyDirVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tEmptyDir (a temporary directory that shares a pod's lifetime)\n"+ + " Medium:\t%v\n", emptyDir.Medium) +} + +func printGCEPersistentDiskVolumeSource(gce *corev1.GCEPersistentDiskVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tGCEPersistentDisk (a Persistent Disk resource in Google Compute Engine)\n"+ + " PDName:\t%v\n"+ + " FSType:\t%v\n"+ + " Partition:\t%v\n"+ + " ReadOnly:\t%v\n", + gce.PDName, gce.FSType, gce.Partition, gce.ReadOnly) +} + +func printAWSElasticBlockStoreVolumeSource(aws *corev1.AWSElasticBlockStoreVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tAWSElasticBlockStore (a Persistent Disk resource in AWS)\n"+ + " VolumeID:\t%v\n"+ + " FSType:\t%v\n"+ + " Partition:\t%v\n"+ + " ReadOnly:\t%v\n", + aws.VolumeID, aws.FSType, aws.Partition, aws.ReadOnly) +} + +func printGitRepoVolumeSource(git *corev1.GitRepoVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tGitRepo (a volume that is pulled from git when the pod is created)\n"+ + " Repository:\t%v\n"+ + " Revision:\t%v\n", + git.Repository, git.Revision) +} + +func printSecretVolumeSource(secret *corev1.SecretVolumeSource, w PrefixWriter) { + optional := secret.Optional != nil && *secret.Optional + w.Write(LEVEL_2, "Type:\tSecret (a volume populated by a Secret)\n"+ + " SecretName:\t%v\n"+ + " Optional:\t%v\n", + secret.SecretName, optional) +} + +func printConfigMapVolumeSource(configMap *corev1.ConfigMapVolumeSource, w PrefixWriter) { + optional := configMap.Optional != nil && *configMap.Optional + w.Write(LEVEL_2, "Type:\tConfigMap (a volume populated by a ConfigMap)\n"+ + " Name:\t%v\n"+ + " Optional:\t%v\n", + configMap.Name, optional) +} + +func printProjectedVolumeSource(projected *corev1.ProjectedVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tProjected (a volume that contains injected data from multiple sources)\n") + for _, source := range projected.Sources { + if source.Secret != nil { + w.Write(LEVEL_2, "SecretName:\t%v\n"+ + " SecretOptionalName:\t%v\n", + source.Secret.Name, source.Secret.Optional) + } else if source.DownwardAPI != nil { + w.Write(LEVEL_2, "DownwardAPI:\ttrue\n") + } else if source.ConfigMap != nil { + w.Write(LEVEL_2, "ConfigMapName:\t%v\n"+ + " ConfigMapOptional:\t%v\n", + source.ConfigMap.Name, source.ConfigMap.Optional) + } else if source.ServiceAccountToken != nil { + w.Write(LEVEL_2, "TokenExpirationSeconds:\t%v\n", + source.ServiceAccountToken.ExpirationSeconds) + } + } +} + +func printNFSVolumeSource(nfs *corev1.NFSVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tNFS (an NFS mount that lasts the lifetime of a pod)\n"+ + " Server:\t%v\n"+ + " Path:\t%v\n"+ + " ReadOnly:\t%v\n", + nfs.Server, nfs.Path, nfs.ReadOnly) +} + +func printQuobyteVolumeSource(quobyte *corev1.QuobyteVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tQuobyte (a Quobyte mount on the host that shares a pod's lifetime)\n"+ + " Registry:\t%v\n"+ + " Volume:\t%v\n"+ + " ReadOnly:\t%v\n", + quobyte.Registry, quobyte.Volume, quobyte.ReadOnly) +} + +func printPortworxVolumeSource(pwxVolume *corev1.PortworxVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tPortworxVolume (a Portworx Volume resource)\n"+ + " VolumeID:\t%v\n", + pwxVolume.VolumeID) +} + +func printISCSIVolumeSource(iscsi *corev1.ISCSIVolumeSource, w PrefixWriter) { + initiator := "" + if iscsi.InitiatorName != nil { + initiator = *iscsi.InitiatorName + } + w.Write(LEVEL_2, "Type:\tISCSI (an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod)\n"+ + " TargetPortal:\t%v\n"+ + " IQN:\t%v\n"+ + " Lun:\t%v\n"+ + " ISCSIInterface\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n"+ + " Portals:\t%v\n"+ + " DiscoveryCHAPAuth:\t%v\n"+ + " SessionCHAPAuth:\t%v\n"+ + " SecretRef:\t%v\n"+ + " InitiatorName:\t%v\n", + iscsi.TargetPortal, iscsi.IQN, iscsi.Lun, iscsi.ISCSIInterface, iscsi.FSType, iscsi.ReadOnly, iscsi.Portals, iscsi.DiscoveryCHAPAuth, iscsi.SessionCHAPAuth, iscsi.SecretRef, initiator) +} + +func printISCSIPersistentVolumeSource(iscsi *corev1.ISCSIPersistentVolumeSource, w PrefixWriter) { + initiatorName := "" + if iscsi.InitiatorName != nil { + initiatorName = *iscsi.InitiatorName + } + w.Write(LEVEL_2, "Type:\tISCSI (an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod)\n"+ + " TargetPortal:\t%v\n"+ + " IQN:\t%v\n"+ + " Lun:\t%v\n"+ + " ISCSIInterface\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n"+ + " Portals:\t%v\n"+ + " DiscoveryCHAPAuth:\t%v\n"+ + " SessionCHAPAuth:\t%v\n"+ + " SecretRef:\t%v\n"+ + " InitiatorName:\t%v\n", + iscsi.TargetPortal, iscsi.IQN, iscsi.Lun, iscsi.ISCSIInterface, iscsi.FSType, iscsi.ReadOnly, iscsi.Portals, iscsi.DiscoveryCHAPAuth, iscsi.SessionCHAPAuth, iscsi.SecretRef, initiatorName) +} + +func printGlusterfsVolumeSource(glusterfs *corev1.GlusterfsVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tGlusterfs (a Glusterfs mount on the host that shares a pod's lifetime)\n"+ + " EndpointsName:\t%v\n"+ + " Path:\t%v\n"+ + " ReadOnly:\t%v\n", + glusterfs.EndpointsName, glusterfs.Path, glusterfs.ReadOnly) +} + +func printGlusterfsPersistentVolumeSource(glusterfs *corev1.GlusterfsPersistentVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tGlusterfs (a Glusterfs mount on the host that shares a pod's lifetime)\n"+ + " EndpointsName:\t%v\n"+ + " EndpointsNamespace:\t%v\n"+ + " Path:\t%v\n"+ + " ReadOnly:\t%v\n", + glusterfs.EndpointsName, glusterfs.EndpointsNamespace, glusterfs.Path, glusterfs.ReadOnly) +} + +func printPersistentVolumeClaimVolumeSource(claim *corev1.PersistentVolumeClaimVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tPersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)\n"+ + " ClaimName:\t%v\n"+ + " ReadOnly:\t%v\n", + claim.ClaimName, claim.ReadOnly) +} + +func printRBDVolumeSource(rbd *corev1.RBDVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tRBD (a Rados Block Device mount on the host that shares a pod's lifetime)\n"+ + " CephMonitors:\t%v\n"+ + " RBDImage:\t%v\n"+ + " FSType:\t%v\n"+ + " RBDPool:\t%v\n"+ + " RadosUser:\t%v\n"+ + " Keyring:\t%v\n"+ + " SecretRef:\t%v\n"+ + " ReadOnly:\t%v\n", + rbd.CephMonitors, rbd.RBDImage, rbd.FSType, rbd.RBDPool, rbd.RadosUser, rbd.Keyring, rbd.SecretRef, rbd.ReadOnly) +} + +func printRBDPersistentVolumeSource(rbd *corev1.RBDPersistentVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tRBD (a Rados Block Device mount on the host that shares a pod's lifetime)\n"+ + " CephMonitors:\t%v\n"+ + " RBDImage:\t%v\n"+ + " FSType:\t%v\n"+ + " RBDPool:\t%v\n"+ + " RadosUser:\t%v\n"+ + " Keyring:\t%v\n"+ + " SecretRef:\t%v\n"+ + " ReadOnly:\t%v\n", + rbd.CephMonitors, rbd.RBDImage, rbd.FSType, rbd.RBDPool, rbd.RadosUser, rbd.Keyring, rbd.SecretRef, rbd.ReadOnly) +} + +func printDownwardAPIVolumeSource(d *corev1.DownwardAPIVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tDownwardAPI (a volume populated by information about the pod)\n Items:\n") + for _, mapping := range d.Items { + if mapping.FieldRef != nil { + w.Write(LEVEL_3, "%v -> %v\n", mapping.FieldRef.FieldPath, mapping.Path) + } + if mapping.ResourceFieldRef != nil { + w.Write(LEVEL_3, "%v -> %v\n", mapping.ResourceFieldRef.Resource, mapping.Path) + } + } +} + +func printAzureDiskVolumeSource(d *corev1.AzureDiskVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tAzureDisk (an Azure Data Disk mount on the host and bind mount to the pod)\n"+ + " DiskName:\t%v\n"+ + " DiskURI:\t%v\n"+ + " Kind: \t%v\n"+ + " FSType:\t%v\n"+ + " CachingMode:\t%v\n"+ + " ReadOnly:\t%v\n", + d.DiskName, d.DataDiskURI, *d.Kind, *d.FSType, *d.CachingMode, *d.ReadOnly) +} + +func printVsphereVolumeSource(vsphere *corev1.VsphereVirtualDiskVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tvSphereVolume (a Persistent Disk resource in vSphere)\n"+ + " VolumePath:\t%v\n"+ + " FSType:\t%v\n"+ + " StoragePolicyName:\t%v\n", + vsphere.VolumePath, vsphere.FSType, vsphere.StoragePolicyName) +} + +func printPhotonPersistentDiskVolumeSource(photon *corev1.PhotonPersistentDiskVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tPhotonPersistentDisk (a Persistent Disk resource in photon platform)\n"+ + " PdID:\t%v\n"+ + " FSType:\t%v\n", + photon.PdID, photon.FSType) +} + +func printCinderVolumeSource(cinder *corev1.CinderVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tCinder (a Persistent Disk resource in OpenStack)\n"+ + " VolumeID:\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n", + " SecretRef:\t%v\n"+ + cinder.VolumeID, cinder.FSType, cinder.ReadOnly, cinder.SecretRef) +} + +func printCinderPersistentVolumeSource(cinder *corev1.CinderPersistentVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tCinder (a Persistent Disk resource in OpenStack)\n"+ + " VolumeID:\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n", + " SecretRef:\t%v\n"+ + cinder.VolumeID, cinder.SecretRef, cinder.FSType, cinder.ReadOnly, cinder.SecretRef) +} + +func printScaleIOVolumeSource(sio *corev1.ScaleIOVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tScaleIO (a persistent volume backed by a block device in ScaleIO)\n"+ + " Gateway:\t%v\n"+ + " System:\t%v\n"+ + " Protection Domain:\t%v\n"+ + " Storage Pool:\t%v\n"+ + " Storage Mode:\t%v\n"+ + " VolumeName:\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n", + sio.Gateway, sio.System, sio.ProtectionDomain, sio.StoragePool, sio.StorageMode, sio.VolumeName, sio.FSType, sio.ReadOnly) +} + +func printScaleIOPersistentVolumeSource(sio *corev1.ScaleIOPersistentVolumeSource, w PrefixWriter) { + var secretNS, secretName string + if sio.SecretRef != nil { + secretName = sio.SecretRef.Name + secretNS = sio.SecretRef.Namespace + } + w.Write(LEVEL_2, "Type:\tScaleIO (a persistent volume backed by a block device in ScaleIO)\n"+ + " Gateway:\t%v\n"+ + " System:\t%v\n"+ + " Protection Domain:\t%v\n"+ + " Storage Pool:\t%v\n"+ + " Storage Mode:\t%v\n"+ + " VolumeName:\t%v\n"+ + " SecretName:\t%v\n"+ + " SecretNamespace:\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n", + sio.Gateway, sio.System, sio.ProtectionDomain, sio.StoragePool, sio.StorageMode, sio.VolumeName, secretName, secretNS, sio.FSType, sio.ReadOnly) +} + +func printLocalVolumeSource(ls *corev1.LocalVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tLocalVolume (a persistent volume backed by local storage on a node)\n"+ + " Path:\t%v\n", + ls.Path) +} + +func printCephFSVolumeSource(cephfs *corev1.CephFSVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tCephFS (a CephFS mount on the host that shares a pod's lifetime)\n"+ + " Monitors:\t%v\n"+ + " Path:\t%v\n"+ + " User:\t%v\n"+ + " SecretFile:\t%v\n"+ + " SecretRef:\t%v\n"+ + " ReadOnly:\t%v\n", + cephfs.Monitors, cephfs.Path, cephfs.User, cephfs.SecretFile, cephfs.SecretRef, cephfs.ReadOnly) +} + +func printCephFSPersistentVolumeSource(cephfs *corev1.CephFSPersistentVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tCephFS (a CephFS mount on the host that shares a pod's lifetime)\n"+ + " Monitors:\t%v\n"+ + " Path:\t%v\n"+ + " User:\t%v\n"+ + " SecretFile:\t%v\n"+ + " SecretRef:\t%v\n"+ + " ReadOnly:\t%v\n", + cephfs.Monitors, cephfs.Path, cephfs.User, cephfs.SecretFile, cephfs.SecretRef, cephfs.ReadOnly) +} + +func printStorageOSVolumeSource(storageos *corev1.StorageOSVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tStorageOS (a StorageOS Persistent Disk resource)\n"+ + " VolumeName:\t%v\n"+ + " VolumeNamespace:\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n", + storageos.VolumeName, storageos.VolumeNamespace, storageos.FSType, storageos.ReadOnly) +} + +func printStorageOSPersistentVolumeSource(storageos *corev1.StorageOSPersistentVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tStorageOS (a StorageOS Persistent Disk resource)\n"+ + " VolumeName:\t%v\n"+ + " VolumeNamespace:\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n", + storageos.VolumeName, storageos.VolumeNamespace, storageos.FSType, storageos.ReadOnly) +} + +func printFCVolumeSource(fc *corev1.FCVolumeSource, w PrefixWriter) { + lun := "" + if fc.Lun != nil { + lun = strconv.Itoa(int(*fc.Lun)) + } + w.Write(LEVEL_2, "Type:\tFC (a Fibre Channel disk)\n"+ + " TargetWWNs:\t%v\n"+ + " LUN:\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n", + strings.Join(fc.TargetWWNs, ", "), lun, fc.FSType, fc.ReadOnly) +} + +func printAzureFileVolumeSource(azureFile *corev1.AzureFileVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tAzureFile (an Azure File Service mount on the host and bind mount to the pod)\n"+ + " SecretName:\t%v\n"+ + " ShareName:\t%v\n"+ + " ReadOnly:\t%v\n", + azureFile.SecretName, azureFile.ShareName, azureFile.ReadOnly) +} + +func printAzureFilePersistentVolumeSource(azureFile *corev1.AzureFilePersistentVolumeSource, w PrefixWriter) { + ns := "" + if azureFile.SecretNamespace != nil { + ns = *azureFile.SecretNamespace + } + w.Write(LEVEL_2, "Type:\tAzureFile (an Azure File Service mount on the host and bind mount to the pod)\n"+ + " SecretName:\t%v\n"+ + " SecretNamespace:\t%v\n"+ + " ShareName:\t%v\n"+ + " ReadOnly:\t%v\n", + azureFile.SecretName, ns, azureFile.ShareName, azureFile.ReadOnly) +} + +func printFlexPersistentVolumeSource(flex *corev1.FlexPersistentVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tFlexVolume (a generic volume resource that is provisioned/attached using an exec based plugin)\n"+ + " Driver:\t%v\n"+ + " FSType:\t%v\n"+ + " SecretRef:\t%v\n"+ + " ReadOnly:\t%v\n"+ + " Options:\t%v\n", + flex.Driver, flex.FSType, flex.SecretRef, flex.ReadOnly, flex.Options) +} + +func printFlexVolumeSource(flex *corev1.FlexVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tFlexVolume (a generic volume resource that is provisioned/attached using an exec based plugin)\n"+ + " Driver:\t%v\n"+ + " FSType:\t%v\n"+ + " SecretRef:\t%v\n"+ + " ReadOnly:\t%v\n"+ + " Options:\t%v\n", + flex.Driver, flex.FSType, flex.SecretRef, flex.ReadOnly, flex.Options) +} + +func printFlockerVolumeSource(flocker *corev1.FlockerVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tFlocker (a Flocker volume mounted by the Flocker agent)\n"+ + " DatasetName:\t%v\n"+ + " DatasetUUID:\t%v\n", + flocker.DatasetName, flocker.DatasetUUID) +} + +func printCSIPersistentVolumeSource(csi *corev1.CSIPersistentVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tCSI (a Container Storage Interface (CSI) volume source)\n"+ + " Driver:\t%v\n"+ + " VolumeHandle:\t%v\n"+ + " ReadOnly:\t%v\n", + csi.Driver, csi.VolumeHandle, csi.ReadOnly) + printCSIPersistentVolumeAttributesMultiline(w, "VolumeAttributes", csi.VolumeAttributes) +} + +func printCSIPersistentVolumeAttributesMultiline(w PrefixWriter, title string, annotations map[string]string) { + printCSIPersistentVolumeAttributesMultilineIndent(w, "", title, "\t", annotations, sets.NewString()) +} + +func printCSIPersistentVolumeAttributesMultilineIndent(w PrefixWriter, initialIndent, title, innerIndent string, attributes map[string]string, skip sets.String) { + w.Write(LEVEL_2, "%s%s:%s", initialIndent, title, innerIndent) + + if len(attributes) == 0 { + w.WriteLine("") + return + } + + // to print labels in the sorted order + keys := make([]string, 0, len(attributes)) + for key := range attributes { + if skip.Has(key) { + continue + } + keys = append(keys, key) + } + if len(attributes) == 0 { + w.WriteLine("") + return + } + sort.Strings(keys) + + for i, key := range keys { + if i != 0 { + w.Write(LEVEL_2, initialIndent) + w.Write(LEVEL_2, innerIndent) + } + line := fmt.Sprintf("%s=%s", key, attributes[key]) + if len(line) > maxAnnotationLen { + w.Write(LEVEL_2, "%s...\n", line[:maxAnnotationLen]) + } else { + w.Write(LEVEL_2, "%s\n", line) + } + i++ + } +} + +type PersistentVolumeDescriber struct { + clientset.Interface +} + +func (d *PersistentVolumeDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + c := d.Core().PersistentVolumes() + + pv, err := c.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = d.Core().Events(namespace).Search(scheme.Scheme, pv) + } + + return describePersistentVolume(pv, events) +} + +func printVolumeNodeAffinity(w PrefixWriter, affinity *corev1.VolumeNodeAffinity) { + w.Write(LEVEL_0, "Node Affinity:\t") + if affinity == nil || affinity.Required == nil { + w.WriteLine("") + return + } + w.WriteLine("") + + if affinity.Required != nil { + w.Write(LEVEL_1, "Required Terms:\t") + if len(affinity.Required.NodeSelectorTerms) == 0 { + w.WriteLine("") + } else { + w.WriteLine("") + for i, term := range affinity.Required.NodeSelectorTerms { + printNodeSelectorTermsMultilineWithIndent(w, LEVEL_2, fmt.Sprintf("Term %v", i), "\t", term.MatchExpressions) + } + } + } +} + +// printLabelsMultiline prints multiple labels with a user-defined alignment. +func printNodeSelectorTermsMultilineWithIndent(w PrefixWriter, indentLevel int, title, innerIndent string, reqs []corev1.NodeSelectorRequirement) { + w.Write(indentLevel, "%s:%s", title, innerIndent) + + if len(reqs) == 0 { + w.WriteLine("") + return + } + + for i, req := range reqs { + if i != 0 { + w.Write(indentLevel, "%s", innerIndent) + } + exprStr := fmt.Sprintf("%s %s", req.Key, strings.ToLower(string(req.Operator))) + if len(req.Values) > 0 { + exprStr = fmt.Sprintf("%s [%s]", exprStr, strings.Join(req.Values, ", ")) + } + w.Write(LEVEL_0, "%s\n", exprStr) + } +} + +func describePersistentVolume(pv *corev1.PersistentVolume, events *corev1.EventList) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", pv.Name) + printLabelsMultiline(w, "Labels", pv.ObjectMeta.Labels) + printAnnotationsMultiline(w, "Annotations", pv.ObjectMeta.Annotations) + w.Write(LEVEL_0, "Finalizers:\t%v\n", pv.ObjectMeta.Finalizers) + w.Write(LEVEL_0, "StorageClass:\t%s\n", storageutil.GetPersistentVolumeClass(pv)) + if pv.ObjectMeta.DeletionTimestamp != nil { + w.Write(LEVEL_0, "Status:\tTerminating (lasts %s)\n", translateTimestampUntil(*pv.ObjectMeta.DeletionTimestamp)) + } else { + w.Write(LEVEL_0, "Status:\t%v\n", pv.Status.Phase) + } + if pv.Spec.ClaimRef != nil { + w.Write(LEVEL_0, "Claim:\t%s\n", pv.Spec.ClaimRef.Namespace+"/"+pv.Spec.ClaimRef.Name) + } else { + w.Write(LEVEL_0, "Claim:\t%s\n", "") + } + w.Write(LEVEL_0, "Reclaim Policy:\t%v\n", pv.Spec.PersistentVolumeReclaimPolicy) + w.Write(LEVEL_0, "Access Modes:\t%s\n", storageutil.GetAccessModesAsString(pv.Spec.AccessModes)) + if pv.Spec.VolumeMode != nil { + w.Write(LEVEL_0, "VolumeMode:\t%v\n", *pv.Spec.VolumeMode) + } + storage := pv.Spec.Capacity[corev1.ResourceStorage] + w.Write(LEVEL_0, "Capacity:\t%s\n", storage.String()) + printVolumeNodeAffinity(w, pv.Spec.NodeAffinity) + w.Write(LEVEL_0, "Message:\t%s\n", pv.Status.Message) + w.Write(LEVEL_0, "Source:\n") + + switch { + case pv.Spec.HostPath != nil: + printHostPathVolumeSource(pv.Spec.HostPath, w) + case pv.Spec.GCEPersistentDisk != nil: + printGCEPersistentDiskVolumeSource(pv.Spec.GCEPersistentDisk, w) + case pv.Spec.AWSElasticBlockStore != nil: + printAWSElasticBlockStoreVolumeSource(pv.Spec.AWSElasticBlockStore, w) + case pv.Spec.NFS != nil: + printNFSVolumeSource(pv.Spec.NFS, w) + case pv.Spec.ISCSI != nil: + printISCSIPersistentVolumeSource(pv.Spec.ISCSI, w) + case pv.Spec.Glusterfs != nil: + printGlusterfsPersistentVolumeSource(pv.Spec.Glusterfs, w) + case pv.Spec.RBD != nil: + printRBDPersistentVolumeSource(pv.Spec.RBD, w) + case pv.Spec.Quobyte != nil: + printQuobyteVolumeSource(pv.Spec.Quobyte, w) + case pv.Spec.VsphereVolume != nil: + printVsphereVolumeSource(pv.Spec.VsphereVolume, w) + case pv.Spec.Cinder != nil: + printCinderPersistentVolumeSource(pv.Spec.Cinder, w) + case pv.Spec.AzureDisk != nil: + printAzureDiskVolumeSource(pv.Spec.AzureDisk, w) + case pv.Spec.PhotonPersistentDisk != nil: + printPhotonPersistentDiskVolumeSource(pv.Spec.PhotonPersistentDisk, w) + case pv.Spec.PortworxVolume != nil: + printPortworxVolumeSource(pv.Spec.PortworxVolume, w) + case pv.Spec.ScaleIO != nil: + printScaleIOPersistentVolumeSource(pv.Spec.ScaleIO, w) + case pv.Spec.Local != nil: + printLocalVolumeSource(pv.Spec.Local, w) + case pv.Spec.CephFS != nil: + printCephFSPersistentVolumeSource(pv.Spec.CephFS, w) + case pv.Spec.StorageOS != nil: + printStorageOSPersistentVolumeSource(pv.Spec.StorageOS, w) + case pv.Spec.FC != nil: + printFCVolumeSource(pv.Spec.FC, w) + case pv.Spec.AzureFile != nil: + printAzureFilePersistentVolumeSource(pv.Spec.AzureFile, w) + case pv.Spec.FlexVolume != nil: + printFlexPersistentVolumeSource(pv.Spec.FlexVolume, w) + case pv.Spec.Flocker != nil: + printFlockerVolumeSource(pv.Spec.Flocker, w) + case pv.Spec.CSI != nil: + printCSIPersistentVolumeSource(pv.Spec.CSI, w) + default: + w.Write(LEVEL_1, "\n") + } + + if events != nil { + DescribeEvents(events, w) + } + + return nil + }) +} + +type PersistentVolumeClaimDescriber struct { + clientset.Interface +} + +func (d *PersistentVolumeClaimDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + c := d.Core().PersistentVolumeClaims(namespace) + + pvc, err := c.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + pc := d.Core().Pods(namespace) + + mountPods, err := getMountPods(pc, pvc.Name) + if err != nil { + return "", err + } + + events, _ := d.Core().Events(namespace).Search(scheme.Scheme, pvc) + + return describePersistentVolumeClaim(pvc, events, mountPods) +} + +func getMountPods(c corev1client.PodInterface, pvcName string) ([]corev1.Pod, error) { + nsPods, err := c.List(metav1.ListOptions{}) + if err != nil { + return []corev1.Pod{}, err + } + + var pods []corev1.Pod + + for _, pod := range nsPods.Items { + pvcs := getPvcs(pod.Spec.Volumes) + + for _, pvc := range pvcs { + if pvc.PersistentVolumeClaim.ClaimName == pvcName { + pods = append(pods, pod) + } + } + } + + return pods, nil +} + +func getPvcs(volumes []corev1.Volume) []corev1.Volume { + var pvcs []corev1.Volume + + for _, volume := range volumes { + if volume.VolumeSource.PersistentVolumeClaim != nil { + pvcs = append(pvcs, volume) + } + } + + return pvcs +} + +func describePersistentVolumeClaim(pvc *corev1.PersistentVolumeClaim, events *corev1.EventList, mountPods []corev1.Pod) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", pvc.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", pvc.Namespace) + w.Write(LEVEL_0, "StorageClass:\t%s\n", storageutil.GetPersistentVolumeClaimClass(pvc)) + if pvc.ObjectMeta.DeletionTimestamp != nil { + w.Write(LEVEL_0, "Status:\tTerminating (lasts %s)\n", translateTimestampUntil(*pvc.ObjectMeta.DeletionTimestamp)) + } else { + w.Write(LEVEL_0, "Status:\t%v\n", pvc.Status.Phase) + } + w.Write(LEVEL_0, "Volume:\t%s\n", pvc.Spec.VolumeName) + printLabelsMultiline(w, "Labels", pvc.Labels) + printAnnotationsMultiline(w, "Annotations", pvc.Annotations) + w.Write(LEVEL_0, "Finalizers:\t%v\n", pvc.ObjectMeta.Finalizers) + storage := pvc.Spec.Resources.Requests[corev1.ResourceStorage] + capacity := "" + accessModes := "" + if pvc.Spec.VolumeName != "" { + accessModes = storageutil.GetAccessModesAsString(pvc.Status.AccessModes) + storage = pvc.Status.Capacity[corev1.ResourceStorage] + capacity = storage.String() + } + w.Write(LEVEL_0, "Capacity:\t%s\n", capacity) + w.Write(LEVEL_0, "Access Modes:\t%s\n", accessModes) + if pvc.Spec.VolumeMode != nil { + w.Write(LEVEL_0, "VolumeMode:\t%v\n", *pvc.Spec.VolumeMode) + } + if len(pvc.Status.Conditions) > 0 { + w.Write(LEVEL_0, "Conditions:\n") + w.Write(LEVEL_1, "Type\tStatus\tLastProbeTime\tLastTransitionTime\tReason\tMessage\n") + w.Write(LEVEL_1, "----\t------\t-----------------\t------------------\t------\t-------\n") + for _, c := range pvc.Status.Conditions { + w.Write(LEVEL_1, "%v \t%v \t%s \t%s \t%v \t%v\n", + c.Type, + c.Status, + c.LastProbeTime.Time.Format(time.RFC1123Z), + c.LastTransitionTime.Time.Format(time.RFC1123Z), + c.Reason, + c.Message) + } + } + if events != nil { + DescribeEvents(events, w) + } + + printPodsMultiline(w, "Mounted By", mountPods) + + return nil + }) +} + +func describeContainers(label string, containers []corev1.Container, containerStatuses []corev1.ContainerStatus, + resolverFn EnvVarResolverFunc, w PrefixWriter, space string) { + statuses := map[string]corev1.ContainerStatus{} + for _, status := range containerStatuses { + statuses[status.Name] = status + } + + describeContainersLabel(containers, label, space, w) + + for _, container := range containers { + status, ok := statuses[container.Name] + describeContainerBasicInfo(container, status, ok, space, w) + describeContainerCommand(container, w) + if ok { + describeContainerState(status, w) + } + describeContainerResource(container, w) + describeContainerProbe(container, w) + if len(container.EnvFrom) > 0 { + describeContainerEnvFrom(container, resolverFn, w) + } + describeContainerEnvVars(container, resolverFn, w) + describeContainerVolumes(container, w) + } +} + +func describeContainersLabel(containers []corev1.Container, label, space string, w PrefixWriter) { + none := "" + if len(containers) == 0 { + none = " " + } + w.Write(LEVEL_0, "%s%s:%s\n", space, label, none) +} + +func describeContainerBasicInfo(container corev1.Container, status corev1.ContainerStatus, ok bool, space string, w PrefixWriter) { + nameIndent := "" + if len(space) > 0 { + nameIndent = " " + } + w.Write(LEVEL_1, "%s%v:\n", nameIndent, container.Name) + if ok { + w.Write(LEVEL_2, "Container ID:\t%s\n", status.ContainerID) + } + w.Write(LEVEL_2, "Image:\t%s\n", container.Image) + if ok { + w.Write(LEVEL_2, "Image ID:\t%s\n", status.ImageID) + } + portString := describeContainerPorts(container.Ports) + if strings.Contains(portString, ",") { + w.Write(LEVEL_2, "Ports:\t%s\n", portString) + } else { + w.Write(LEVEL_2, "Port:\t%s\n", stringOrNone(portString)) + } + hostPortString := describeContainerHostPorts(container.Ports) + if strings.Contains(hostPortString, ",") { + w.Write(LEVEL_2, "Host Ports:\t%s\n", hostPortString) + } else { + w.Write(LEVEL_2, "Host Port:\t%s\n", stringOrNone(hostPortString)) + } +} + +func describeContainerPorts(cPorts []corev1.ContainerPort) string { + ports := make([]string, 0, len(cPorts)) + for _, cPort := range cPorts { + ports = append(ports, fmt.Sprintf("%d/%s", cPort.ContainerPort, cPort.Protocol)) + } + return strings.Join(ports, ", ") +} + +func describeContainerHostPorts(cPorts []corev1.ContainerPort) string { + ports := make([]string, 0, len(cPorts)) + for _, cPort := range cPorts { + ports = append(ports, fmt.Sprintf("%d/%s", cPort.HostPort, cPort.Protocol)) + } + return strings.Join(ports, ", ") +} + +func describeContainerCommand(container corev1.Container, w PrefixWriter) { + if len(container.Command) > 0 { + w.Write(LEVEL_2, "Command:\n") + for _, c := range container.Command { + for _, s := range strings.Split(c, "\n") { + w.Write(LEVEL_3, "%s\n", s) + } + } + } + if len(container.Args) > 0 { + w.Write(LEVEL_2, "Args:\n") + for _, arg := range container.Args { + for _, s := range strings.Split(arg, "\n") { + w.Write(LEVEL_3, "%s\n", s) + } + } + } +} + +func describeContainerResource(container corev1.Container, w PrefixWriter) { + resources := container.Resources + if len(resources.Limits) > 0 { + w.Write(LEVEL_2, "Limits:\n") + } + for _, name := range SortedResourceNames(resources.Limits) { + quantity := resources.Limits[name] + w.Write(LEVEL_3, "%s:\t%s\n", name, quantity.String()) + } + + if len(resources.Requests) > 0 { + w.Write(LEVEL_2, "Requests:\n") + } + for _, name := range SortedResourceNames(resources.Requests) { + quantity := resources.Requests[name] + w.Write(LEVEL_3, "%s:\t%s\n", name, quantity.String()) + } +} + +func describeContainerState(status corev1.ContainerStatus, w PrefixWriter) { + describeStatus("State", status.State, w) + if status.LastTerminationState.Terminated != nil { + describeStatus("Last State", status.LastTerminationState, w) + } + w.Write(LEVEL_2, "Ready:\t%v\n", printBool(status.Ready)) + w.Write(LEVEL_2, "Restart Count:\t%d\n", status.RestartCount) +} + +func describeContainerProbe(container corev1.Container, w PrefixWriter) { + if container.LivenessProbe != nil { + probe := DescribeProbe(container.LivenessProbe) + w.Write(LEVEL_2, "Liveness:\t%s\n", probe) + } + if container.ReadinessProbe != nil { + probe := DescribeProbe(container.ReadinessProbe) + w.Write(LEVEL_2, "Readiness:\t%s\n", probe) + } +} + +func describeContainerVolumes(container corev1.Container, w PrefixWriter) { + // Show volumeMounts + none := "" + if len(container.VolumeMounts) == 0 { + none = "\t" + } + w.Write(LEVEL_2, "Mounts:%s\n", none) + sort.Sort(SortableVolumeMounts(container.VolumeMounts)) + for _, mount := range container.VolumeMounts { + flags := []string{} + switch { + case mount.ReadOnly: + flags = append(flags, "ro") + case !mount.ReadOnly: + flags = append(flags, "rw") + case len(mount.SubPath) > 0: + flags = append(flags, fmt.Sprintf("path=%q", mount.SubPath)) + } + w.Write(LEVEL_3, "%s from %s (%s)\n", mount.MountPath, mount.Name, strings.Join(flags, ",")) + } + // Show volumeDevices if exists + if len(container.VolumeDevices) > 0 { + w.Write(LEVEL_2, "Devices:%s\n", none) + sort.Sort(SortableVolumeDevices(container.VolumeDevices)) + for _, device := range container.VolumeDevices { + w.Write(LEVEL_3, "%s from %s\n", device.DevicePath, device.Name) + } + } +} + +func describeContainerEnvVars(container corev1.Container, resolverFn EnvVarResolverFunc, w PrefixWriter) { + none := "" + if len(container.Env) == 0 { + none = "\t" + } + w.Write(LEVEL_2, "Environment:%s\n", none) + + for _, e := range container.Env { + if e.ValueFrom == nil { + for i, s := range strings.Split(e.Value, "\n") { + if i == 0 { + w.Write(LEVEL_3, "%s:\t%s\n", e.Name, s) + } else { + w.Write(LEVEL_3, "\t%s\n", s) + } + } + continue + } + + switch { + case e.ValueFrom.FieldRef != nil: + var valueFrom string + if resolverFn != nil { + valueFrom = resolverFn(e) + } + w.Write(LEVEL_3, "%s:\t%s (%s:%s)\n", e.Name, valueFrom, e.ValueFrom.FieldRef.APIVersion, e.ValueFrom.FieldRef.FieldPath) + case e.ValueFrom.ResourceFieldRef != nil: + valueFrom, err := resourcehelper.ExtractContainerResourceValue(e.ValueFrom.ResourceFieldRef, &container) + if err != nil { + valueFrom = "" + } + resource := e.ValueFrom.ResourceFieldRef.Resource + if valueFrom == "0" && (resource == "limits.cpu" || resource == "limits.memory") { + valueFrom = "node allocatable" + } + w.Write(LEVEL_3, "%s:\t%s (%s)\n", e.Name, valueFrom, resource) + case e.ValueFrom.SecretKeyRef != nil: + optional := e.ValueFrom.SecretKeyRef.Optional != nil && *e.ValueFrom.SecretKeyRef.Optional + w.Write(LEVEL_3, "%s:\t\tOptional: %t\n", e.Name, e.ValueFrom.SecretKeyRef.Key, e.ValueFrom.SecretKeyRef.Name, optional) + case e.ValueFrom.ConfigMapKeyRef != nil: + optional := e.ValueFrom.ConfigMapKeyRef.Optional != nil && *e.ValueFrom.ConfigMapKeyRef.Optional + w.Write(LEVEL_3, "%s:\t\tOptional: %t\n", e.Name, e.ValueFrom.ConfigMapKeyRef.Key, e.ValueFrom.ConfigMapKeyRef.Name, optional) + } + } +} + +func describeContainerEnvFrom(container corev1.Container, resolverFn EnvVarResolverFunc, w PrefixWriter) { + none := "" + if len(container.EnvFrom) == 0 { + none = "\t" + } + w.Write(LEVEL_2, "Environment Variables from:%s\n", none) + + for _, e := range container.EnvFrom { + from := "" + name := "" + optional := false + if e.ConfigMapRef != nil { + from = "ConfigMap" + name = e.ConfigMapRef.Name + optional = e.ConfigMapRef.Optional != nil && *e.ConfigMapRef.Optional + } else if e.SecretRef != nil { + from = "Secret" + name = e.SecretRef.Name + optional = e.SecretRef.Optional != nil && *e.SecretRef.Optional + } + if len(e.Prefix) == 0 { + w.Write(LEVEL_3, "%s\t%s\tOptional: %t\n", name, from, optional) + } else { + w.Write(LEVEL_3, "%s\t%s with prefix '%s'\tOptional: %t\n", name, from, e.Prefix, optional) + } + } +} + +// DescribeProbe is exported for consumers in other API groups that have probes +func DescribeProbe(probe *corev1.Probe) string { + attrs := fmt.Sprintf("delay=%ds timeout=%ds period=%ds #success=%d #failure=%d", probe.InitialDelaySeconds, probe.TimeoutSeconds, probe.PeriodSeconds, probe.SuccessThreshold, probe.FailureThreshold) + switch { + case probe.Exec != nil: + return fmt.Sprintf("exec %v %s", probe.Exec.Command, attrs) + case probe.HTTPGet != nil: + url := &url.URL{} + url.Scheme = strings.ToLower(string(probe.HTTPGet.Scheme)) + if len(probe.HTTPGet.Port.String()) > 0 { + url.Host = net.JoinHostPort(probe.HTTPGet.Host, probe.HTTPGet.Port.String()) + } else { + url.Host = probe.HTTPGet.Host + } + url.Path = probe.HTTPGet.Path + return fmt.Sprintf("http-get %s %s", url.String(), attrs) + case probe.TCPSocket != nil: + return fmt.Sprintf("tcp-socket %s:%s %s", probe.TCPSocket.Host, probe.TCPSocket.Port.String(), attrs) + } + return fmt.Sprintf("unknown %s", attrs) +} + +type EnvVarResolverFunc func(e corev1.EnvVar) string + +// EnvValueFrom is exported for use by describers in other packages +func EnvValueRetriever(pod *corev1.Pod) EnvVarResolverFunc { + return func(e corev1.EnvVar) string { + gv, err := schema.ParseGroupVersion(e.ValueFrom.FieldRef.APIVersion) + if err != nil { + return "" + } + gvk := gv.WithKind("Pod") + internalFieldPath, _, err := scheme.Scheme.ConvertFieldLabel(gvk, e.ValueFrom.FieldRef.FieldPath, "") + if err != nil { + return "" // pod validation should catch this on create + } + + valueFrom, err := fieldpath.ExtractFieldPathAsString(pod, internalFieldPath) + if err != nil { + return "" // pod validation should catch this on create + } + + return valueFrom + } +} + +func describeStatus(stateName string, state corev1.ContainerState, w PrefixWriter) { + switch { + case state.Running != nil: + w.Write(LEVEL_2, "%s:\tRunning\n", stateName) + w.Write(LEVEL_3, "Started:\t%v\n", state.Running.StartedAt.Time.Format(time.RFC1123Z)) + case state.Waiting != nil: + w.Write(LEVEL_2, "%s:\tWaiting\n", stateName) + if state.Waiting.Reason != "" { + w.Write(LEVEL_3, "Reason:\t%s\n", state.Waiting.Reason) + } + case state.Terminated != nil: + w.Write(LEVEL_2, "%s:\tTerminated\n", stateName) + if state.Terminated.Reason != "" { + w.Write(LEVEL_3, "Reason:\t%s\n", state.Terminated.Reason) + } + if state.Terminated.Message != "" { + w.Write(LEVEL_3, "Message:\t%s\n", state.Terminated.Message) + } + w.Write(LEVEL_3, "Exit Code:\t%d\n", state.Terminated.ExitCode) + if state.Terminated.Signal > 0 { + w.Write(LEVEL_3, "Signal:\t%d\n", state.Terminated.Signal) + } + w.Write(LEVEL_3, "Started:\t%s\n", state.Terminated.StartedAt.Time.Format(time.RFC1123Z)) + w.Write(LEVEL_3, "Finished:\t%s\n", state.Terminated.FinishedAt.Time.Format(time.RFC1123Z)) + default: + w.Write(LEVEL_2, "%s:\tWaiting\n", stateName) + } +} + +func describeVolumeClaimTemplates(templates []corev1.PersistentVolumeClaim, w PrefixWriter) { + if len(templates) == 0 { + w.Write(LEVEL_0, "Volume Claims:\t\n") + return + } + w.Write(LEVEL_0, "Volume Claims:\n") + for _, pvc := range templates { + w.Write(LEVEL_1, "Name:\t%s\n", pvc.Name) + w.Write(LEVEL_1, "StorageClass:\t%s\n", storageutil.GetPersistentVolumeClaimClass(&pvc)) + printLabelsMultilineWithIndent(w, " ", "Labels", "\t", pvc.Labels, sets.NewString()) + printLabelsMultilineWithIndent(w, " ", "Annotations", "\t", pvc.Annotations, sets.NewString()) + if capacity, ok := pvc.Spec.Resources.Requests[corev1.ResourceStorage]; ok { + w.Write(LEVEL_1, "Capacity:\t%s\n", capacity.String()) + } else { + w.Write(LEVEL_1, "Capacity:\t%s\n", "") + } + w.Write(LEVEL_1, "Access Modes:\t%s\n", pvc.Spec.AccessModes) + } +} + +func printBoolPtr(value *bool) string { + if value != nil { + return printBool(*value) + } + + return "" +} + +func printBool(value bool) string { + if value { + return "True" + } + + return "False" +} + +// ReplicationControllerDescriber generates information about a replication controller +// and the pods it has created. +type ReplicationControllerDescriber struct { + clientset.Interface +} + +func (d *ReplicationControllerDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + rc := d.Core().ReplicationControllers(namespace) + pc := d.Core().Pods(namespace) + + controller, err := rc.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + running, waiting, succeeded, failed, err := getPodStatusForController(pc, labels.SelectorFromSet(controller.Spec.Selector), controller.UID) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = d.Core().Events(namespace).Search(scheme.Scheme, controller) + } + + return describeReplicationController(controller, events, running, waiting, succeeded, failed) +} + +func describeReplicationController(controller *corev1.ReplicationController, events *corev1.EventList, running, waiting, succeeded, failed int) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", controller.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", controller.Namespace) + w.Write(LEVEL_0, "Selector:\t%s\n", labels.FormatLabels(controller.Spec.Selector)) + printLabelsMultiline(w, "Labels", controller.Labels) + printAnnotationsMultiline(w, "Annotations", controller.Annotations) + w.Write(LEVEL_0, "Replicas:\t%d current / %d desired\n", controller.Status.Replicas, *controller.Spec.Replicas) + w.Write(LEVEL_0, "Pods Status:\t%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed) + DescribePodTemplate(controller.Spec.Template, w) + if len(controller.Status.Conditions) > 0 { + w.Write(LEVEL_0, "Conditions:\n Type\tStatus\tReason\n") + w.Write(LEVEL_1, "----\t------\t------\n") + for _, c := range controller.Status.Conditions { + w.Write(LEVEL_1, "%v \t%v\t%v\n", c.Type, c.Status, c.Reason) + } + } + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + +func DescribePodTemplate(template *corev1.PodTemplateSpec, w PrefixWriter) { + w.Write(LEVEL_0, "Pod Template:\n") + if template == nil { + w.Write(LEVEL_1, "") + return + } + printLabelsMultiline(w, " Labels", template.Labels) + if len(template.Annotations) > 0 { + printAnnotationsMultiline(w, " Annotations", template.Annotations) + } + if len(template.Spec.ServiceAccountName) > 0 { + w.Write(LEVEL_1, "Service Account:\t%s\n", template.Spec.ServiceAccountName) + } + if len(template.Spec.InitContainers) > 0 { + describeContainers("Init Containers", template.Spec.InitContainers, nil, nil, w, " ") + } + describeContainers("Containers", template.Spec.Containers, nil, nil, w, " ") + describeVolumes(template.Spec.Volumes, w, " ") +} + +// ReplicaSetDescriber generates information about a ReplicaSet and the pods it has created. +type ReplicaSetDescriber struct { + clientset.Interface +} + +func (d *ReplicaSetDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + rsc := d.Apps().ReplicaSets(namespace) + pc := d.Core().Pods(namespace) + + rs, err := rsc.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector) + if err != nil { + return "", err + } + + running, waiting, succeeded, failed, getPodErr := getPodStatusForController(pc, selector, rs.UID) + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = d.Core().Events(namespace).Search(scheme.Scheme, rs) + } + + return describeReplicaSet(rs, events, running, waiting, succeeded, failed, getPodErr) +} + +func describeReplicaSet(rs *appsv1.ReplicaSet, events *corev1.EventList, running, waiting, succeeded, failed int, getPodErr error) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", rs.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", rs.Namespace) + w.Write(LEVEL_0, "Selector:\t%s\n", metav1.FormatLabelSelector(rs.Spec.Selector)) + printLabelsMultiline(w, "Labels", rs.Labels) + printAnnotationsMultiline(w, "Annotations", rs.Annotations) + if controlledBy := printController(rs); len(controlledBy) > 0 { + w.Write(LEVEL_0, "Controlled By:\t%s\n", controlledBy) + } + w.Write(LEVEL_0, "Replicas:\t%d current / %d desired\n", rs.Status.Replicas, *rs.Spec.Replicas) + w.Write(LEVEL_0, "Pods Status:\t") + if getPodErr != nil { + w.Write(LEVEL_0, "error in fetching pods: %s\n", getPodErr) + } else { + w.Write(LEVEL_0, "%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed) + } + DescribePodTemplate(&rs.Spec.Template, w) + if len(rs.Status.Conditions) > 0 { + w.Write(LEVEL_0, "Conditions:\n Type\tStatus\tReason\n") + w.Write(LEVEL_1, "----\t------\t------\n") + for _, c := range rs.Status.Conditions { + w.Write(LEVEL_1, "%v \t%v\t%v\n", c.Type, c.Status, c.Reason) + } + } + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + +// JobDescriber generates information about a job and the pods it has created. +type JobDescriber struct { + clientset.Interface +} + +func (d *JobDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + job, err := d.Batch().Jobs(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = d.Core().Events(namespace).Search(scheme.Scheme, job) + } + + return describeJob(job, events) +} + +func describeJob(job *batchv1.Job, events *corev1.EventList) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", job.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", job.Namespace) + selector, _ := metav1.LabelSelectorAsSelector(job.Spec.Selector) + w.Write(LEVEL_0, "Selector:\t%s\n", selector) + printLabelsMultiline(w, "Labels", job.Labels) + printAnnotationsMultiline(w, "Annotations", job.Annotations) + if controlledBy := printController(job); len(controlledBy) > 0 { + w.Write(LEVEL_0, "Controlled By:\t%s\n", controlledBy) + } + w.Write(LEVEL_0, "Parallelism:\t%d\n", *job.Spec.Parallelism) + if job.Spec.Completions != nil { + w.Write(LEVEL_0, "Completions:\t%d\n", *job.Spec.Completions) + } else { + w.Write(LEVEL_0, "Completions:\t\n") + } + if job.Status.StartTime != nil { + w.Write(LEVEL_0, "Start Time:\t%s\n", job.Status.StartTime.Time.Format(time.RFC1123Z)) + } + if job.Status.CompletionTime != nil { + w.Write(LEVEL_0, "Completed At:\t%s\n", job.Status.CompletionTime.Time.Format(time.RFC1123Z)) + } + if job.Status.StartTime != nil && job.Status.CompletionTime != nil { + w.Write(LEVEL_0, "Duration:\t%s\n", duration.HumanDuration(job.Status.CompletionTime.Sub(job.Status.StartTime.Time))) + } + if job.Spec.ActiveDeadlineSeconds != nil { + w.Write(LEVEL_0, "Active Deadline Seconds:\t%ds\n", *job.Spec.ActiveDeadlineSeconds) + } + w.Write(LEVEL_0, "Pods Statuses:\t%d Running / %d Succeeded / %d Failed\n", job.Status.Active, job.Status.Succeeded, job.Status.Failed) + DescribePodTemplate(&job.Spec.Template, w) + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + +// CronJobDescriber generates information about a cron job and the jobs it has created. +type CronJobDescriber struct { + clientset.Interface + external clientset.Interface +} + +func (d *CronJobDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + cronJob, err := d.external.BatchV1beta1().CronJobs(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = d.Core().Events(namespace).Search(scheme.Scheme, cronJob) + } + return describeCronJob(cronJob, events) +} + +func describeCronJob(cronJob *batchv1beta1.CronJob, events *corev1.EventList) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", cronJob.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", cronJob.Namespace) + printLabelsMultiline(w, "Labels", cronJob.Labels) + printAnnotationsMultiline(w, "Annotations", cronJob.Annotations) + w.Write(LEVEL_0, "Schedule:\t%s\n", cronJob.Spec.Schedule) + w.Write(LEVEL_0, "Concurrency Policy:\t%s\n", cronJob.Spec.ConcurrencyPolicy) + w.Write(LEVEL_0, "Suspend:\t%s\n", printBoolPtr(cronJob.Spec.Suspend)) + if cronJob.Spec.StartingDeadlineSeconds != nil { + w.Write(LEVEL_0, "Starting Deadline Seconds:\t%ds\n", *cronJob.Spec.StartingDeadlineSeconds) + } else { + w.Write(LEVEL_0, "Starting Deadline Seconds:\t\n") + } + describeJobTemplate(cronJob.Spec.JobTemplate, w) + if cronJob.Status.LastScheduleTime != nil { + w.Write(LEVEL_0, "Last Schedule Time:\t%s\n", cronJob.Status.LastScheduleTime.Time.Format(time.RFC1123Z)) + } else { + w.Write(LEVEL_0, "Last Schedule Time:\t\n") + } + printActiveJobs(w, "Active Jobs", cronJob.Status.Active) + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + +func describeJobTemplate(jobTemplate batchv1beta1.JobTemplateSpec, w PrefixWriter) { + if jobTemplate.Spec.Selector != nil { + selector, _ := metav1.LabelSelectorAsSelector(jobTemplate.Spec.Selector) + w.Write(LEVEL_0, "Selector:\t%s\n", selector) + } else { + w.Write(LEVEL_0, "Selector:\t\n") + } + if jobTemplate.Spec.Parallelism != nil { + w.Write(LEVEL_0, "Parallelism:\t%d\n", *jobTemplate.Spec.Parallelism) + } else { + w.Write(LEVEL_0, "Parallelism:\t\n") + } + if jobTemplate.Spec.Completions != nil { + w.Write(LEVEL_0, "Completions:\t%d\n", *jobTemplate.Spec.Completions) + } else { + w.Write(LEVEL_0, "Completions:\t\n") + } + if jobTemplate.Spec.ActiveDeadlineSeconds != nil { + w.Write(LEVEL_0, "Active Deadline Seconds:\t%ds\n", *jobTemplate.Spec.ActiveDeadlineSeconds) + } + DescribePodTemplate(&jobTemplate.Spec.Template, w) +} + +func printActiveJobs(w PrefixWriter, title string, jobs []corev1.ObjectReference) { + w.Write(LEVEL_0, "%s:\t", title) + if len(jobs) == 0 { + w.WriteLine("") + return + } + + for i, job := range jobs { + if i != 0 { + w.Write(LEVEL_0, ", ") + } + w.Write(LEVEL_0, "%s", job.Name) + } + w.WriteLine("") +} + +// DaemonSetDescriber generates information about a daemon set and the pods it has created. +type DaemonSetDescriber struct { + clientset.Interface +} + +func (d *DaemonSetDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + dc := d.Apps().DaemonSets(namespace) + pc := d.Core().Pods(namespace) + + daemon, err := dc.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + selector, err := metav1.LabelSelectorAsSelector(daemon.Spec.Selector) + if err != nil { + return "", err + } + running, waiting, succeeded, failed, err := getPodStatusForController(pc, selector, daemon.UID) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = d.Core().Events(namespace).Search(scheme.Scheme, daemon) + } + + return describeDaemonSet(daemon, events, running, waiting, succeeded, failed) +} + +func describeDaemonSet(daemon *appsv1.DaemonSet, events *corev1.EventList, running, waiting, succeeded, failed int) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", daemon.Name) + selector, err := metav1.LabelSelectorAsSelector(daemon.Spec.Selector) + if err != nil { + // this shouldn't happen if LabelSelector passed validation + return err + } + w.Write(LEVEL_0, "Selector:\t%s\n", selector) + w.Write(LEVEL_0, "Node-Selector:\t%s\n", labels.FormatLabels(daemon.Spec.Template.Spec.NodeSelector)) + printLabelsMultiline(w, "Labels", daemon.Labels) + printAnnotationsMultiline(w, "Annotations", daemon.Annotations) + w.Write(LEVEL_0, "Desired Number of Nodes Scheduled: %d\n", daemon.Status.DesiredNumberScheduled) + w.Write(LEVEL_0, "Current Number of Nodes Scheduled: %d\n", daemon.Status.CurrentNumberScheduled) + w.Write(LEVEL_0, "Number of Nodes Scheduled with Up-to-date Pods: %d\n", daemon.Status.UpdatedNumberScheduled) + w.Write(LEVEL_0, "Number of Nodes Scheduled with Available Pods: %d\n", daemon.Status.NumberAvailable) + w.Write(LEVEL_0, "Number of Nodes Misscheduled: %d\n", daemon.Status.NumberMisscheduled) + w.Write(LEVEL_0, "Pods Status:\t%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed) + DescribePodTemplate(&daemon.Spec.Template, w) + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + +// SecretDescriber generates information about a secret +type SecretDescriber struct { + clientset.Interface +} + +func (d *SecretDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + c := d.Core().Secrets(namespace) + + secret, err := c.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + return describeSecret(secret) +} + +func describeSecret(secret *corev1.Secret) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", secret.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", secret.Namespace) + printLabelsMultiline(w, "Labels", secret.Labels) + skipAnnotations := sets.NewString(corev1.LastAppliedConfigAnnotation) + printAnnotationsMultilineWithFilter(w, "Annotations", secret.Annotations, skipAnnotations) + + w.Write(LEVEL_0, "\nType:\t%s\n", secret.Type) + + w.Write(LEVEL_0, "\nData\n====\n") + for k, v := range secret.Data { + switch { + case k == corev1.ServiceAccountTokenKey && secret.Type == corev1.SecretTypeServiceAccountToken: + w.Write(LEVEL_0, "%s:\t%s\n", k, string(v)) + default: + w.Write(LEVEL_0, "%s:\t%d bytes\n", k, len(v)) + } + } + + return nil + }) +} + +type IngressDescriber struct { + clientset.Interface +} + +func (i *IngressDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + c := i.Extensions().Ingresses(namespace) + ing, err := c.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + return i.describeIngress(ing, describerSettings) +} + +func (i *IngressDescriber) describeBackend(ns string, backend *extensionsv1beta1.IngressBackend) string { + endpoints, _ := i.Core().Endpoints(ns).Get(backend.ServiceName, metav1.GetOptions{}) + service, _ := i.Core().Services(ns).Get(backend.ServiceName, metav1.GetOptions{}) + spName := "" + for i := range service.Spec.Ports { + sp := &service.Spec.Ports[i] + switch backend.ServicePort.Type { + case intstr.String: + if backend.ServicePort.StrVal == sp.Name { + spName = sp.Name + } + case intstr.Int: + if int32(backend.ServicePort.IntVal) == sp.Port { + spName = sp.Name + } + } + } + return formatEndpoints(endpoints, sets.NewString(spName)) +} + +func (i *IngressDescriber) describeIngress(ing *extensionsv1beta1.Ingress, describerSettings describe.DescriberSettings) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%v\n", ing.Name) + w.Write(LEVEL_0, "Namespace:\t%v\n", ing.Namespace) + w.Write(LEVEL_0, "Address:\t%v\n", loadBalancerStatusStringer(ing.Status.LoadBalancer, true)) + def := ing.Spec.Backend + ns := ing.Namespace + if def == nil { + // Ingresses that don't specify a default backend inherit the + // default backend in the kube-system namespace. + def = &extensionsv1beta1.IngressBackend{ + ServiceName: "default-http-backend", + ServicePort: intstr.IntOrString{Type: intstr.Int, IntVal: 80}, + } + ns = metav1.NamespaceSystem + } + w.Write(LEVEL_0, "Default backend:\t%s (%s)\n", backendStringer(def), i.describeBackend(ns, def)) + if len(ing.Spec.TLS) != 0 { + describeIngressTLS(w, ing.Spec.TLS) + } + w.Write(LEVEL_0, "Rules:\n Host\tPath\tBackends\n") + w.Write(LEVEL_1, "----\t----\t--------\n") + count := 0 + for _, rules := range ing.Spec.Rules { + if rules.HTTP == nil { + continue + } + count++ + host := rules.Host + if len(host) == 0 { + host = "*" + } + w.Write(LEVEL_1, "%s\t\n", host) + for _, path := range rules.HTTP.Paths { + w.Write(LEVEL_2, "\t%s \t%s (%s)\n", path.Path, backendStringer(&path.Backend), i.describeBackend(ns, &path.Backend)) + } + } + if count == 0 { + w.Write(LEVEL_1, "%s\t%s \t%s (%s)\n", "*", "*", backendStringer(def), i.describeBackend(ns, def)) + } + describeIngressAnnotations(w, ing.Annotations) + + if describerSettings.ShowEvents { + events, _ := i.Core().Events(ing.Namespace).Search(scheme.Scheme, ing) + if events != nil { + DescribeEvents(events, w) + } + } + return nil + }) +} + +func describeIngressTLS(w PrefixWriter, ingTLS []extensionsv1beta1.IngressTLS) { + w.Write(LEVEL_0, "TLS:\n") + for _, t := range ingTLS { + if t.SecretName == "" { + w.Write(LEVEL_1, "SNI routes %v\n", strings.Join(t.Hosts, ",")) + } else { + w.Write(LEVEL_1, "%v terminates %v\n", t.SecretName, strings.Join(t.Hosts, ",")) + } + } + return +} + +// TODO: Move from annotations into Ingress status. +func describeIngressAnnotations(w PrefixWriter, annotations map[string]string) { + w.Write(LEVEL_0, "Annotations:\n") + for k, v := range annotations { + w.Write(LEVEL_1, "%v:\t%s\n", k, v) + } + return +} + +// ServiceDescriber generates information about a service. +type ServiceDescriber struct { + clientset.Interface +} + +func (d *ServiceDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + c := d.Core().Services(namespace) + + service, err := c.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + endpoints, _ := d.Core().Endpoints(namespace).Get(name, metav1.GetOptions{}) + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = d.Core().Events(namespace).Search(scheme.Scheme, service) + } + return describeService(service, endpoints, events) +} + +func buildIngressString(ingress []corev1.LoadBalancerIngress) string { + var buffer bytes.Buffer + + for i := range ingress { + if i != 0 { + buffer.WriteString(", ") + } + if ingress[i].IP != "" { + buffer.WriteString(ingress[i].IP) + } else { + buffer.WriteString(ingress[i].Hostname) + } + } + return buffer.String() +} + +func describeService(service *corev1.Service, endpoints *corev1.Endpoints, events *corev1.EventList) (string, error) { + if endpoints == nil { + endpoints = &corev1.Endpoints{} + } + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", service.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", service.Namespace) + printLabelsMultiline(w, "Labels", service.Labels) + printAnnotationsMultiline(w, "Annotations", service.Annotations) + w.Write(LEVEL_0, "Selector:\t%s\n", labels.FormatLabels(service.Spec.Selector)) + w.Write(LEVEL_0, "Type:\t%s\n", service.Spec.Type) + w.Write(LEVEL_0, "IP:\t%s\n", service.Spec.ClusterIP) + if len(service.Spec.ExternalIPs) > 0 { + w.Write(LEVEL_0, "External IPs:\t%v\n", strings.Join(service.Spec.ExternalIPs, ",")) + } + if service.Spec.LoadBalancerIP != "" { + w.Write(LEVEL_0, "IP:\t%s\n", service.Spec.LoadBalancerIP) + } + if service.Spec.ExternalName != "" { + w.Write(LEVEL_0, "External Name:\t%s\n", service.Spec.ExternalName) + } + if len(service.Status.LoadBalancer.Ingress) > 0 { + list := buildIngressString(service.Status.LoadBalancer.Ingress) + w.Write(LEVEL_0, "LoadBalancer Ingress:\t%s\n", list) + } + for i := range service.Spec.Ports { + sp := &service.Spec.Ports[i] + + name := sp.Name + if name == "" { + name = "" + } + w.Write(LEVEL_0, "Port:\t%s\t%d/%s\n", name, sp.Port, sp.Protocol) + if sp.TargetPort.Type == intstr.Type(intstr.Int) { + w.Write(LEVEL_0, "TargetPort:\t%d/%s\n", sp.TargetPort.IntVal, sp.Protocol) + } else { + w.Write(LEVEL_0, "TargetPort:\t%s/%s\n", sp.TargetPort.StrVal, sp.Protocol) + } + if sp.NodePort != 0 { + w.Write(LEVEL_0, "NodePort:\t%s\t%d/%s\n", name, sp.NodePort, sp.Protocol) + } + w.Write(LEVEL_0, "Endpoints:\t%s\n", formatEndpoints(endpoints, sets.NewString(sp.Name))) + } + w.Write(LEVEL_0, "Session Affinity:\t%s\n", service.Spec.SessionAffinity) + if service.Spec.ExternalTrafficPolicy != "" { + w.Write(LEVEL_0, "External Traffic Policy:\t%s\n", service.Spec.ExternalTrafficPolicy) + } + if service.Spec.HealthCheckNodePort != 0 { + w.Write(LEVEL_0, "HealthCheck NodePort:\t%d\n", service.Spec.HealthCheckNodePort) + } + if len(service.Spec.LoadBalancerSourceRanges) > 0 { + w.Write(LEVEL_0, "LoadBalancer Source Ranges:\t%v\n", strings.Join(service.Spec.LoadBalancerSourceRanges, ",")) + } + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + +// EndpointsDescriber generates information about an Endpoint. +type EndpointsDescriber struct { + clientset.Interface +} + +func (d *EndpointsDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + c := d.Core().Endpoints(namespace) + + ep, err := c.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = d.Core().Events(namespace).Search(scheme.Scheme, ep) + } + + return describeEndpoints(ep, events) +} + +func describeEndpoints(ep *corev1.Endpoints, events *corev1.EventList) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", ep.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", ep.Namespace) + printLabelsMultiline(w, "Labels", ep.Labels) + printAnnotationsMultiline(w, "Annotations", ep.Annotations) + + w.Write(LEVEL_0, "Subsets:\n") + for i := range ep.Subsets { + subset := &ep.Subsets[i] + + addresses := make([]string, 0, len(subset.Addresses)) + for _, addr := range subset.Addresses { + addresses = append(addresses, addr.IP) + } + addressesString := strings.Join(addresses, ",") + if len(addressesString) == 0 { + addressesString = "" + } + w.Write(LEVEL_1, "Addresses:\t%s\n", addressesString) + + notReadyAddresses := make([]string, 0, len(subset.NotReadyAddresses)) + for _, addr := range subset.NotReadyAddresses { + notReadyAddresses = append(notReadyAddresses, addr.IP) + } + notReadyAddressesString := strings.Join(notReadyAddresses, ",") + if len(notReadyAddressesString) == 0 { + notReadyAddressesString = "" + } + w.Write(LEVEL_1, "NotReadyAddresses:\t%s\n", notReadyAddressesString) + + if len(subset.Ports) > 0 { + w.Write(LEVEL_1, "Ports:\n") + w.Write(LEVEL_2, "Name\tPort\tProtocol\n") + w.Write(LEVEL_2, "----\t----\t--------\n") + for _, port := range subset.Ports { + name := port.Name + if len(name) == 0 { + name = "" + } + w.Write(LEVEL_2, "%s\t%d\t%s\n", name, port.Port, port.Protocol) + } + } + w.Write(LEVEL_0, "\n") + } + + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + +// ServiceAccountDescriber generates information about a service. +type ServiceAccountDescriber struct { + clientset.Interface +} + +func (d *ServiceAccountDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + c := d.Core().ServiceAccounts(namespace) + + serviceAccount, err := c.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + tokens := []corev1.Secret{} + + // missingSecrets is the set of all secrets present in the + // serviceAccount but not present in the set of existing secrets. + missingSecrets := sets.NewString() + secrets, err := d.Core().Secrets(namespace).List(metav1.ListOptions{}) + + // errors are tolerated here in order to describe the serviceAccount with all + // of the secrets that it references, even if those secrets cannot be fetched. + if err == nil { + // existingSecrets is the set of all secrets remaining on a + // service account that are not present in the "tokens" slice. + existingSecrets := sets.NewString() + + for _, s := range secrets.Items { + if s.Type == corev1.SecretTypeServiceAccountToken { + name, _ := s.Annotations[corev1.ServiceAccountNameKey] + uid, _ := s.Annotations[corev1.ServiceAccountUIDKey] + if name == serviceAccount.Name && uid == string(serviceAccount.UID) { + tokens = append(tokens, s) + } + } + existingSecrets.Insert(s.Name) + } + + for _, s := range serviceAccount.Secrets { + if !existingSecrets.Has(s.Name) { + missingSecrets.Insert(s.Name) + } + } + for _, s := range serviceAccount.ImagePullSecrets { + if !existingSecrets.Has(s.Name) { + missingSecrets.Insert(s.Name) + } + } + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = d.Core().Events(namespace).Search(scheme.Scheme, serviceAccount) + } + + return describeServiceAccount(serviceAccount, tokens, missingSecrets, events) +} + +func describeServiceAccount(serviceAccount *corev1.ServiceAccount, tokens []corev1.Secret, missingSecrets sets.String, events *corev1.EventList) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", serviceAccount.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", serviceAccount.Namespace) + printLabelsMultiline(w, "Labels", serviceAccount.Labels) + printAnnotationsMultiline(w, "Annotations", serviceAccount.Annotations) + + var ( + emptyHeader = " " + pullHeader = "Image pull secrets:" + mountHeader = "Mountable secrets: " + tokenHeader = "Tokens: " + + pullSecretNames = []string{} + mountSecretNames = []string{} + tokenSecretNames = []string{} + ) + + for _, s := range serviceAccount.ImagePullSecrets { + pullSecretNames = append(pullSecretNames, s.Name) + } + for _, s := range serviceAccount.Secrets { + mountSecretNames = append(mountSecretNames, s.Name) + } + for _, s := range tokens { + tokenSecretNames = append(tokenSecretNames, s.Name) + } + + types := map[string][]string{ + pullHeader: pullSecretNames, + mountHeader: mountSecretNames, + tokenHeader: tokenSecretNames, + } + for _, header := range sets.StringKeySet(types).List() { + names := types[header] + if len(names) == 0 { + w.Write(LEVEL_0, "%s\t\n", header) + } else { + prefix := header + for _, name := range names { + if missingSecrets.Has(name) { + w.Write(LEVEL_0, "%s\t%s (not found)\n", prefix, name) + } else { + w.Write(LEVEL_0, "%s\t%s\n", prefix, name) + } + prefix = emptyHeader + } + } + } + + if events != nil { + DescribeEvents(events, w) + } + + return nil + }) +} + +// RoleDescriber generates information about a node. +type RoleDescriber struct { + clientset.Interface +} + +func (d *RoleDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + role, err := d.Rbac().Roles(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + breakdownRules := []rbacv1.PolicyRule{} + for _, rule := range role.Rules { + breakdownRules = append(breakdownRules, rbac.BreakdownRule(rule)...) + } + + compactRules, err := rbac.CompactRules(breakdownRules) + if err != nil { + return "", err + } + sort.Stable(rbac.SortableRuleSlice(compactRules)) + + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", role.Name) + printLabelsMultiline(w, "Labels", role.Labels) + printAnnotationsMultiline(w, "Annotations", role.Annotations) + + w.Write(LEVEL_0, "PolicyRule:\n") + w.Write(LEVEL_1, "Resources\tNon-Resource URLs\tResource Names\tVerbs\n") + w.Write(LEVEL_1, "---------\t-----------------\t--------------\t-----\n") + for _, r := range compactRules { + w.Write(LEVEL_1, "%s\t%v\t%v\t%v\n", combineResourceGroup(r.Resources, r.APIGroups), r.NonResourceURLs, r.ResourceNames, r.Verbs) + } + + return nil + }) +} + +// ClusterRoleDescriber generates information about a node. +type ClusterRoleDescriber struct { + clientset.Interface +} + +func (d *ClusterRoleDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + role, err := d.Rbac().ClusterRoles().Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + breakdownRules := []rbacv1.PolicyRule{} + for _, rule := range role.Rules { + breakdownRules = append(breakdownRules, rbac.BreakdownRule(rule)...) + } + + compactRules, err := rbac.CompactRules(breakdownRules) + if err != nil { + return "", err + } + sort.Stable(rbac.SortableRuleSlice(compactRules)) + + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", role.Name) + printLabelsMultiline(w, "Labels", role.Labels) + printAnnotationsMultiline(w, "Annotations", role.Annotations) + + w.Write(LEVEL_0, "PolicyRule:\n") + w.Write(LEVEL_1, "Resources\tNon-Resource URLs\tResource Names\tVerbs\n") + w.Write(LEVEL_1, "---------\t-----------------\t--------------\t-----\n") + for _, r := range compactRules { + w.Write(LEVEL_1, "%s\t%v\t%v\t%v\n", combineResourceGroup(r.Resources, r.APIGroups), r.NonResourceURLs, r.ResourceNames, r.Verbs) + } + + return nil + }) +} + +func combineResourceGroup(resource, group []string) string { + if len(resource) == 0 { + return "" + } + parts := strings.SplitN(resource[0], "/", 2) + combine := parts[0] + + if len(group) > 0 && group[0] != "" { + combine = combine + "." + group[0] + } + + if len(parts) == 2 { + combine = combine + "/" + parts[1] + } + return combine +} + +// RoleBindingDescriber generates information about a node. +type RoleBindingDescriber struct { + clientset.Interface +} + +func (d *RoleBindingDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + binding, err := d.Rbac().RoleBindings(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", binding.Name) + printLabelsMultiline(w, "Labels", binding.Labels) + printAnnotationsMultiline(w, "Annotations", binding.Annotations) + + w.Write(LEVEL_0, "Role:\n") + w.Write(LEVEL_1, "Kind:\t%s\n", binding.RoleRef.Kind) + w.Write(LEVEL_1, "Name:\t%s\n", binding.RoleRef.Name) + + w.Write(LEVEL_0, "Subjects:\n") + w.Write(LEVEL_1, "Kind\tName\tNamespace\n") + w.Write(LEVEL_1, "----\t----\t---------\n") + for _, s := range binding.Subjects { + w.Write(LEVEL_1, "%s\t%s\t%s\n", s.Kind, s.Name, s.Namespace) + } + + return nil + }) +} + +// ClusterRoleBindingDescriber generates information about a node. +type ClusterRoleBindingDescriber struct { + clientset.Interface +} + +func (d *ClusterRoleBindingDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + binding, err := d.Rbac().ClusterRoleBindings().Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", binding.Name) + printLabelsMultiline(w, "Labels", binding.Labels) + printAnnotationsMultiline(w, "Annotations", binding.Annotations) + + w.Write(LEVEL_0, "Role:\n") + w.Write(LEVEL_1, "Kind:\t%s\n", binding.RoleRef.Kind) + w.Write(LEVEL_1, "Name:\t%s\n", binding.RoleRef.Name) + + w.Write(LEVEL_0, "Subjects:\n") + w.Write(LEVEL_1, "Kind\tName\tNamespace\n") + w.Write(LEVEL_1, "----\t----\t---------\n") + for _, s := range binding.Subjects { + w.Write(LEVEL_1, "%s\t%s\t%s\n", s.Kind, s.Name, s.Namespace) + } + + return nil + }) +} + +// NodeDescriber generates information about a node. +type NodeDescriber struct { + clientset.Interface +} + +func (d *NodeDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + mc := d.Core().Nodes() + node, err := mc.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + fieldSelector, err := fields.ParseSelector("spec.nodeName=" + name + ",status.phase!=" + string(corev1.PodSucceeded) + ",status.phase!=" + string(corev1.PodFailed)) + if err != nil { + return "", err + } + // in a policy aware setting, users may have access to a node, but not all pods + // in that case, we note that the user does not have access to the pods + canViewPods := true + nodeNonTerminatedPodsList, err := d.Core().Pods(namespace).List(metav1.ListOptions{FieldSelector: fieldSelector.String()}) + if err != nil { + if !errors.IsForbidden(err) { + return "", err + } + canViewPods = false + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + if ref, err := reference.GetReference(scheme.Scheme, node); err != nil { + klog.Errorf("Unable to construct reference to '%#v': %v", node, err) + } else { + // TODO: We haven't decided the namespace for Node object yet. + ref.UID = types.UID(ref.Name) + events, _ = d.Core().Events("").Search(scheme.Scheme, ref) + } + } + + return describeNode(node, nodeNonTerminatedPodsList, events, canViewPods) +} + +func describeNode(node *corev1.Node, nodeNonTerminatedPodsList *corev1.PodList, events *corev1.EventList, canViewPods bool) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", node.Name) + if roles := findNodeRoles(node); len(roles) > 0 { + w.Write(LEVEL_0, "Roles:\t%s\n", strings.Join(roles, ",")) + } else { + w.Write(LEVEL_0, "Roles:\t%s\n", "") + } + printLabelsMultiline(w, "Labels", node.Labels) + printAnnotationsMultiline(w, "Annotations", node.Annotations) + w.Write(LEVEL_0, "CreationTimestamp:\t%s\n", node.CreationTimestamp.Time.Format(time.RFC1123Z)) + printNodeTaintsMultiline(w, "Taints", node.Spec.Taints) + w.Write(LEVEL_0, "Unschedulable:\t%v\n", node.Spec.Unschedulable) + if len(node.Status.Conditions) > 0 { + w.Write(LEVEL_0, "Conditions:\n Type\tStatus\tLastHeartbeatTime\tLastTransitionTime\tReason\tMessage\n") + w.Write(LEVEL_1, "----\t------\t-----------------\t------------------\t------\t-------\n") + for _, c := range node.Status.Conditions { + w.Write(LEVEL_1, "%v \t%v \t%s \t%s \t%v \t%v\n", + c.Type, + c.Status, + c.LastHeartbeatTime.Time.Format(time.RFC1123Z), + c.LastTransitionTime.Time.Format(time.RFC1123Z), + c.Reason, + c.Message) + } + } + + w.Write(LEVEL_0, "Addresses:\n") + for _, address := range node.Status.Addresses { + w.Write(LEVEL_1, "%s:\t%s\n", address.Type, address.Address) + } + + printResourceList := func(resourceList corev1.ResourceList) { + resources := make([]corev1.ResourceName, 0, len(resourceList)) + for resource := range resourceList { + resources = append(resources, resource) + } + sort.Sort(SortableResourceNames(resources)) + for _, resource := range resources { + value := resourceList[resource] + w.Write(LEVEL_0, " %s:\t%s\n", resource, value.String()) + } + } + + if len(node.Status.Capacity) > 0 { + w.Write(LEVEL_0, "Capacity:\n") + printResourceList(node.Status.Capacity) + } + if len(node.Status.Allocatable) > 0 { + w.Write(LEVEL_0, "Allocatable:\n") + printResourceList(node.Status.Allocatable) + } + + w.Write(LEVEL_0, "System Info:\n") + w.Write(LEVEL_0, " Machine ID:\t%s\n", node.Status.NodeInfo.MachineID) + w.Write(LEVEL_0, " System UUID:\t%s\n", node.Status.NodeInfo.SystemUUID) + w.Write(LEVEL_0, " Boot ID:\t%s\n", node.Status.NodeInfo.BootID) + w.Write(LEVEL_0, " Kernel Version:\t%s\n", node.Status.NodeInfo.KernelVersion) + w.Write(LEVEL_0, " OS Image:\t%s\n", node.Status.NodeInfo.OSImage) + w.Write(LEVEL_0, " Operating System:\t%s\n", node.Status.NodeInfo.OperatingSystem) + w.Write(LEVEL_0, " Architecture:\t%s\n", node.Status.NodeInfo.Architecture) + w.Write(LEVEL_0, " Container Runtime Version:\t%s\n", node.Status.NodeInfo.ContainerRuntimeVersion) + w.Write(LEVEL_0, " Kubelet Version:\t%s\n", node.Status.NodeInfo.KubeletVersion) + w.Write(LEVEL_0, " Kube-Proxy Version:\t%s\n", node.Status.NodeInfo.KubeProxyVersion) + + if len(node.Spec.PodCIDR) > 0 { + w.Write(LEVEL_0, "PodCIDR:\t%s\n", node.Spec.PodCIDR) + } + if len(node.Spec.ProviderID) > 0 { + w.Write(LEVEL_0, "ProviderID:\t%s\n", node.Spec.ProviderID) + } + if canViewPods && nodeNonTerminatedPodsList != nil { + describeNodeResource(nodeNonTerminatedPodsList, node, w) + } else { + w.Write(LEVEL_0, "Pods:\tnot authorized\n") + } + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + +type StatefulSetDescriber struct { + client clientset.Interface +} + +func (p *StatefulSetDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + ps, err := p.client.Apps().StatefulSets(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + pc := p.client.Core().Pods(namespace) + + selector, err := metav1.LabelSelectorAsSelector(ps.Spec.Selector) + if err != nil { + return "", err + } + + running, waiting, succeeded, failed, err := getPodStatusForController(pc, selector, ps.UID) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = p.client.Core().Events(namespace).Search(scheme.Scheme, ps) + } + + return describeStatefulSet(ps, selector, events, running, waiting, succeeded, failed) +} + +func describeStatefulSet(ps *appsv1.StatefulSet, selector labels.Selector, events *corev1.EventList, running, waiting, succeeded, failed int) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", ps.ObjectMeta.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", ps.ObjectMeta.Namespace) + w.Write(LEVEL_0, "CreationTimestamp:\t%s\n", ps.CreationTimestamp.Time.Format(time.RFC1123Z)) + w.Write(LEVEL_0, "Selector:\t%s\n", selector) + printLabelsMultiline(w, "Labels", ps.Labels) + printAnnotationsMultiline(w, "Annotations", ps.Annotations) + w.Write(LEVEL_0, "Replicas:\t%d desired | %d total\n", ps.Spec.Replicas, ps.Status.Replicas) + w.Write(LEVEL_0, "Update Strategy:\t%s\n", ps.Spec.UpdateStrategy.Type) + if ps.Spec.UpdateStrategy.RollingUpdate != nil { + ru := ps.Spec.UpdateStrategy.RollingUpdate + if ru.Partition != nil { + w.Write(LEVEL_1, "Partition:\t%d\n", ru.Partition) + } + } + + w.Write(LEVEL_0, "Pods Status:\t%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed) + DescribePodTemplate(&ps.Spec.Template, w) + describeVolumeClaimTemplates(ps.Spec.VolumeClaimTemplates, w) + if events != nil { + DescribeEvents(events, w) + } + + return nil + }) +} + +type CertificateSigningRequestDescriber struct { + client clientset.Interface +} + +func (p *CertificateSigningRequestDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + csr, err := p.client.Certificates().CertificateSigningRequests().Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + cr, err := certificate.ParseCSR(csr) + if err != nil { + return "", fmt.Errorf("Error parsing CSR: %v", err) + } + status, err := extractCSRStatus(csr) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = p.client.Core().Events(namespace).Search(scheme.Scheme, csr) + } + + return describeCertificateSigningRequest(csr, cr, status, events) +} + +func describeCertificateSigningRequest(csr *certificatesv1beta1.CertificateSigningRequest, cr *x509.CertificateRequest, status string, events *corev1.EventList) (string, error) { + printListHelper := func(w PrefixWriter, prefix, name string, values []string) { + if len(values) == 0 { + return + } + w.Write(LEVEL_0, prefix+name+":\t") + w.Write(LEVEL_0, strings.Join(values, "\n"+prefix+"\t")) + w.Write(LEVEL_0, "\n") + } + + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", csr.Name) + w.Write(LEVEL_0, "Labels:\t%s\n", labels.FormatLabels(csr.Labels)) + w.Write(LEVEL_0, "Annotations:\t%s\n", labels.FormatLabels(csr.Annotations)) + w.Write(LEVEL_0, "CreationTimestamp:\t%s\n", csr.CreationTimestamp.Time.Format(time.RFC1123Z)) + w.Write(LEVEL_0, "Requesting User:\t%s\n", csr.Spec.Username) + w.Write(LEVEL_0, "Status:\t%s\n", status) + + w.Write(LEVEL_0, "Subject:\n") + w.Write(LEVEL_0, "\tCommon Name:\t%s\n", cr.Subject.CommonName) + w.Write(LEVEL_0, "\tSerial Number:\t%s\n", cr.Subject.SerialNumber) + printListHelper(w, "\t", "Organization", cr.Subject.Organization) + printListHelper(w, "\t", "Organizational Unit", cr.Subject.OrganizationalUnit) + printListHelper(w, "\t", "Country", cr.Subject.Country) + printListHelper(w, "\t", "Locality", cr.Subject.Locality) + printListHelper(w, "\t", "Province", cr.Subject.Province) + printListHelper(w, "\t", "StreetAddress", cr.Subject.StreetAddress) + printListHelper(w, "\t", "PostalCode", cr.Subject.PostalCode) + + if len(cr.DNSNames)+len(cr.EmailAddresses)+len(cr.IPAddresses) > 0 { + w.Write(LEVEL_0, "Subject Alternative Names:\n") + printListHelper(w, "\t", "DNS Names", cr.DNSNames) + printListHelper(w, "\t", "Email Addresses", cr.EmailAddresses) + var ipaddrs []string + for _, ipaddr := range cr.IPAddresses { + ipaddrs = append(ipaddrs, ipaddr.String()) + } + printListHelper(w, "\t", "IP Addresses", ipaddrs) + } + + if events != nil { + DescribeEvents(events, w) + } + + return nil + }) +} + +// HorizontalPodAutoscalerDescriber generates information about a horizontal pod autoscaler. +type HorizontalPodAutoscalerDescriber struct { + client clientset.Interface +} + +func (d *HorizontalPodAutoscalerDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + hpa, err := d.client.AutoscalingV2beta2().HorizontalPodAutoscalers(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = d.client.Core().Events(namespace).Search(scheme.Scheme, hpa) + } + + return describeHorizontalPodAutoscaler(hpa, events, d) +} + +func describeHorizontalPodAutoscaler(hpa *autoscalingv2beta2.HorizontalPodAutoscaler, events *corev1.EventList, d *HorizontalPodAutoscalerDescriber) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", hpa.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", hpa.Namespace) + printLabelsMultiline(w, "Labels", hpa.Labels) + printAnnotationsMultiline(w, "Annotations", hpa.Annotations) + w.Write(LEVEL_0, "CreationTimestamp:\t%s\n", hpa.CreationTimestamp.Time.Format(time.RFC1123Z)) + w.Write(LEVEL_0, "Reference:\t%s/%s\n", + hpa.Spec.ScaleTargetRef.Kind, + hpa.Spec.ScaleTargetRef.Name) + w.Write(LEVEL_0, "Metrics:\t( current / target )\n") + for i, metric := range hpa.Spec.Metrics { + switch metric.Type { + case autoscalingv2beta2.ExternalMetricSourceType: + if metric.External.Target.AverageValue != nil { + current := "" + if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].External != nil && + &hpa.Status.CurrentMetrics[i].External.Current.AverageValue != nil { + current = hpa.Status.CurrentMetrics[i].External.Current.AverageValue.String() + } + w.Write(LEVEL_1, "%q (target average value):\t%s / %s\n", metric.External.Metric.Name, current, metric.External.Target.AverageValue.String()) + } else { + current := "" + if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].External != nil { + current = hpa.Status.CurrentMetrics[i].External.Current.Value.String() + } + w.Write(LEVEL_1, "%q (target value):\t%s / %s\n", metric.External.Metric.Name, current, metric.External.Target.Value.String()) + + } + case autoscalingv2beta2.PodsMetricSourceType: + current := "" + if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Pods != nil { + current = hpa.Status.CurrentMetrics[i].Pods.Current.AverageValue.String() + } + w.Write(LEVEL_1, "%q on pods:\t%s / %s\n", metric.Pods.Metric.Name, current, metric.Pods.Target.AverageValue.String()) + case autoscalingv2beta2.ObjectMetricSourceType: + current := "" + if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Object != nil { + current = hpa.Status.CurrentMetrics[i].Object.Current.Value.String() + } + w.Write(LEVEL_1, "%q on %s/%s:\t%s / %s\n", metric.Object.Metric.Name, metric.Object.DescribedObject.Kind, metric.Object.DescribedObject.Name, current, metric.Object.Target.Value.String()) + case autoscalingv2beta2.ResourceMetricSourceType: + w.Write(LEVEL_1, "resource %s on pods", string(metric.Resource.Name)) + if metric.Resource.Target.AverageValue != nil { + current := "" + if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Resource != nil { + current = hpa.Status.CurrentMetrics[i].Resource.Current.AverageValue.String() + } + w.Write(LEVEL_0, ":\t%s / %s\n", current, metric.Resource.Target.AverageValue.String()) + } else { + current := "" + if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Resource != nil && hpa.Status.CurrentMetrics[i].Resource.Current.AverageUtilization != nil { + current = fmt.Sprintf("%d%% (%s)", *hpa.Status.CurrentMetrics[i].Resource.Current.AverageUtilization, hpa.Status.CurrentMetrics[i].Resource.Current.AverageValue.String()) + } + + target := "" + if metric.Resource.Target.AverageUtilization != nil { + target = fmt.Sprintf("%d%%", *metric.Resource.Target.AverageUtilization) + } + w.Write(LEVEL_1, "(as a percentage of request):\t%s / %s\n", current, target) + } + default: + w.Write(LEVEL_1, "", string(metric.Type)) + } + } + minReplicas := "" + if hpa.Spec.MinReplicas != nil { + minReplicas = fmt.Sprintf("%d", *hpa.Spec.MinReplicas) + } + w.Write(LEVEL_0, "Min replicas:\t%s\n", minReplicas) + w.Write(LEVEL_0, "Max replicas:\t%d\n", hpa.Spec.MaxReplicas) + w.Write(LEVEL_0, "%s pods:\t", hpa.Spec.ScaleTargetRef.Kind) + w.Write(LEVEL_0, "%d current / %d desired\n", hpa.Status.CurrentReplicas, hpa.Status.DesiredReplicas) + + if len(hpa.Status.Conditions) > 0 { + w.Write(LEVEL_0, "Conditions:\n") + w.Write(LEVEL_1, "Type\tStatus\tReason\tMessage\n") + w.Write(LEVEL_1, "----\t------\t------\t-------\n") + for _, c := range hpa.Status.Conditions { + w.Write(LEVEL_1, "%v\t%v\t%v\t%v\n", c.Type, c.Status, c.Reason, c.Message) + } + } + + if events != nil { + DescribeEvents(events, w) + } + + return nil + }) +} + +func describeNodeResource(nodeNonTerminatedPodsList *corev1.PodList, node *corev1.Node, w PrefixWriter) { + w.Write(LEVEL_0, "Non-terminated Pods:\t(%d in total)\n", len(nodeNonTerminatedPodsList.Items)) + w.Write(LEVEL_1, "Namespace\tName\t\tCPU Requests\tCPU Limits\tMemory Requests\tMemory Limits\tAGE\n") + w.Write(LEVEL_1, "---------\t----\t\t------------\t----------\t---------------\t-------------\t---\n") + allocatable := node.Status.Capacity + if len(node.Status.Allocatable) > 0 { + allocatable = node.Status.Allocatable + } + + for _, pod := range nodeNonTerminatedPodsList.Items { + req, limit := resourcehelper.PodRequestsAndLimits(&pod) + cpuReq, cpuLimit, memoryReq, memoryLimit := req[corev1.ResourceCPU], limit[corev1.ResourceCPU], req[corev1.ResourceMemory], limit[corev1.ResourceMemory] + fractionCpuReq := float64(cpuReq.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 + fractionCpuLimit := float64(cpuLimit.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 + fractionMemoryReq := float64(memoryReq.Value()) / float64(allocatable.Memory().Value()) * 100 + fractionMemoryLimit := float64(memoryLimit.Value()) / float64(allocatable.Memory().Value()) * 100 + w.Write(LEVEL_1, "%s\t%s\t\t%s (%d%%)\t%s (%d%%)\t%s (%d%%)\t%s (%d%%)\t%s\n", pod.Namespace, pod.Name, + cpuReq.String(), int64(fractionCpuReq), cpuLimit.String(), int64(fractionCpuLimit), + memoryReq.String(), int64(fractionMemoryReq), memoryLimit.String(), int64(fractionMemoryLimit), translateTimestampSince(pod.CreationTimestamp)) + } + + w.Write(LEVEL_0, "Allocated resources:\n (Total limits may be over 100 percent, i.e., overcommitted.)\n") + w.Write(LEVEL_1, "Resource\tRequests\tLimits\n") + w.Write(LEVEL_1, "--------\t--------\t------\n") + reqs, limits := getPodsTotalRequestsAndLimits(nodeNonTerminatedPodsList) + cpuReqs, cpuLimits, memoryReqs, memoryLimits, ephemeralstorageReqs, ephemeralstorageLimits := + reqs[corev1.ResourceCPU], limits[corev1.ResourceCPU], reqs[corev1.ResourceMemory], limits[corev1.ResourceMemory], reqs[corev1.ResourceEphemeralStorage], limits[corev1.ResourceEphemeralStorage] + fractionCpuReqs := float64(0) + fractionCpuLimits := float64(0) + if allocatable.Cpu().MilliValue() != 0 { + fractionCpuReqs = float64(cpuReqs.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 + fractionCpuLimits = float64(cpuLimits.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 + } + fractionMemoryReqs := float64(0) + fractionMemoryLimits := float64(0) + if allocatable.Memory().Value() != 0 { + fractionMemoryReqs = float64(memoryReqs.Value()) / float64(allocatable.Memory().Value()) * 100 + fractionMemoryLimits = float64(memoryLimits.Value()) / float64(allocatable.Memory().Value()) * 100 + } + fractionEphemeralStorageReqs := float64(0) + fractionEphemeralStorageLimits := float64(0) + if allocatable.StorageEphemeral().Value() != 0 { + fractionEphemeralStorageReqs = float64(ephemeralstorageReqs.Value()) / float64(allocatable.StorageEphemeral().Value()) * 100 + fractionEphemeralStorageLimits = float64(ephemeralstorageLimits.Value()) / float64(allocatable.StorageEphemeral().Value()) * 100 + } + w.Write(LEVEL_1, "%s\t%s (%d%%)\t%s (%d%%)\n", + corev1.ResourceCPU, cpuReqs.String(), int64(fractionCpuReqs), cpuLimits.String(), int64(fractionCpuLimits)) + w.Write(LEVEL_1, "%s\t%s (%d%%)\t%s (%d%%)\n", + corev1.ResourceMemory, memoryReqs.String(), int64(fractionMemoryReqs), memoryLimits.String(), int64(fractionMemoryLimits)) + w.Write(LEVEL_1, "%s\t%s (%d%%)\t%s (%d%%)\n", + corev1.ResourceEphemeralStorage, ephemeralstorageReqs.String(), int64(fractionEphemeralStorageReqs), ephemeralstorageLimits.String(), int64(fractionEphemeralStorageLimits)) + extResources := make([]string, 0, len(allocatable)) + for resource := range allocatable { + if !resourcehelper.IsStandardContainerResourceName(string(resource)) && resource != corev1.ResourcePods { + extResources = append(extResources, string(resource)) + } + } + sort.Strings(extResources) + for _, ext := range extResources { + extRequests, extLimits := reqs[corev1.ResourceName(ext)], limits[corev1.ResourceName(ext)] + w.Write(LEVEL_1, "%s\t%s\t%s\n", ext, extRequests.String(), extLimits.String()) + } +} + +func getPodsTotalRequestsAndLimits(podList *corev1.PodList) (reqs map[corev1.ResourceName]resource.Quantity, limits map[corev1.ResourceName]resource.Quantity) { + reqs, limits = map[corev1.ResourceName]resource.Quantity{}, map[corev1.ResourceName]resource.Quantity{} + for _, pod := range podList.Items { + podReqs, podLimits := resourcehelper.PodRequestsAndLimits(&pod) + for podReqName, podReqValue := range podReqs { + if value, ok := reqs[podReqName]; !ok { + reqs[podReqName] = *podReqValue.Copy() + } else { + value.Add(podReqValue) + reqs[podReqName] = value + } + } + for podLimitName, podLimitValue := range podLimits { + if value, ok := limits[podLimitName]; !ok { + limits[podLimitName] = *podLimitValue.Copy() + } else { + value.Add(podLimitValue) + limits[podLimitName] = value + } + } + } + return +} + +func DescribeEvents(el *corev1.EventList, w PrefixWriter) { + if len(el.Items) == 0 { + w.Write(LEVEL_0, "Events:\t\n") + return + } + w.Flush() + sort.Sort(event.SortableEvents(el.Items)) + w.Write(LEVEL_0, "Events:\n Type\tReason\tAge\tFrom\tMessage\n") + w.Write(LEVEL_1, "----\t------\t----\t----\t-------\n") + for _, e := range el.Items { + var interval string + if e.Count > 1 { + interval = fmt.Sprintf("%s (x%d over %s)", translateTimestampSince(e.LastTimestamp), e.Count, translateTimestampSince(e.FirstTimestamp)) + } else { + interval = translateTimestampSince(e.FirstTimestamp) + } + w.Write(LEVEL_1, "%v\t%v\t%s\t%v\t%v\n", + e.Type, + e.Reason, + interval, + formatEventSource(e.Source), + strings.TrimSpace(e.Message), + ) + } +} + +// DeploymentDescriber generates information about a deployment. +type DeploymentDescriber struct { + clientset.Interface + external clientset.Interface +} + +func (dd *DeploymentDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + d, err := dd.external.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + selector, err := metav1.LabelSelectorAsSelector(d.Spec.Selector) + if err != nil { + return "", err + } + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = dd.Core().Events(namespace).Search(scheme.Scheme, d) + } + + return describeDeployment(d, selector, d, events, dd) +} + +func describeDeployment(d *appsv1.Deployment, selector labels.Selector, internalDeployment *appsv1.Deployment, events *corev1.EventList, dd *DeploymentDescriber) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", d.ObjectMeta.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", d.ObjectMeta.Namespace) + w.Write(LEVEL_0, "CreationTimestamp:\t%s\n", d.CreationTimestamp.Time.Format(time.RFC1123Z)) + printLabelsMultiline(w, "Labels", d.Labels) + printAnnotationsMultiline(w, "Annotations", d.Annotations) + w.Write(LEVEL_0, "Selector:\t%s\n", selector) + w.Write(LEVEL_0, "Replicas:\t%d desired | %d updated | %d total | %d available | %d unavailable\n", *(d.Spec.Replicas), d.Status.UpdatedReplicas, d.Status.Replicas, d.Status.AvailableReplicas, d.Status.UnavailableReplicas) + w.Write(LEVEL_0, "StrategyType:\t%s\n", d.Spec.Strategy.Type) + w.Write(LEVEL_0, "MinReadySeconds:\t%d\n", d.Spec.MinReadySeconds) + if d.Spec.Strategy.RollingUpdate != nil { + ru := d.Spec.Strategy.RollingUpdate + w.Write(LEVEL_0, "RollingUpdateStrategy:\t%s max unavailable, %s max surge\n", ru.MaxUnavailable.String(), ru.MaxSurge.String()) + } + DescribePodTemplate(&internalDeployment.Spec.Template, w) + if len(d.Status.Conditions) > 0 { + w.Write(LEVEL_0, "Conditions:\n Type\tStatus\tReason\n") + w.Write(LEVEL_1, "----\t------\t------\n") + for _, c := range d.Status.Conditions { + w.Write(LEVEL_1, "%v \t%v\t%v\n", c.Type, c.Status, c.Reason) + } + } + oldRSs, _, newRS, err := deploymentutil.GetAllReplicaSets(d, dd.external.AppsV1()) + if err == nil { + w.Write(LEVEL_0, "OldReplicaSets:\t%s\n", printReplicaSetsByLabels(oldRSs)) + var newRSs []*appsv1.ReplicaSet + if newRS != nil { + newRSs = append(newRSs, newRS) + } + w.Write(LEVEL_0, "NewReplicaSet:\t%s\n", printReplicaSetsByLabels(newRSs)) + } + if events != nil { + DescribeEvents(events, w) + } + + return nil + }) +} + +func printReplicaSetsByLabels(matchingRSs []*appsv1.ReplicaSet) string { + // Format the matching ReplicaSets into strings. + rsStrings := make([]string, 0, len(matchingRSs)) + for _, rs := range matchingRSs { + rsStrings = append(rsStrings, fmt.Sprintf("%s (%d/%d replicas created)", rs.Name, rs.Status.Replicas, *rs.Spec.Replicas)) + } + + list := strings.Join(rsStrings, ", ") + if list == "" { + return "" + } + return list +} + +func getPodStatusForController(c corev1client.PodInterface, selector labels.Selector, uid types.UID) (running, waiting, succeeded, failed int, err error) { + options := metav1.ListOptions{LabelSelector: selector.String()} + rcPods, err := c.List(options) + if err != nil { + return + } + for _, pod := range rcPods.Items { + controllerRef := metav1.GetControllerOf(&pod) + // Skip pods that are orphans or owned by other controllers. + if controllerRef == nil || controllerRef.UID != uid { + continue + } + switch pod.Status.Phase { + case corev1.PodRunning: + running++ + case corev1.PodPending: + waiting++ + case corev1.PodSucceeded: + succeeded++ + case corev1.PodFailed: + failed++ + } + } + return +} + +// ConfigMapDescriber generates information about a ConfigMap +type ConfigMapDescriber struct { + clientset.Interface +} + +func (d *ConfigMapDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + c := d.Core().ConfigMaps(namespace) + + configMap, err := c.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", configMap.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", configMap.Namespace) + printLabelsMultiline(w, "Labels", configMap.Labels) + printAnnotationsMultiline(w, "Annotations", configMap.Annotations) + + w.Write(LEVEL_0, "\nData\n====\n") + for k, v := range configMap.Data { + w.Write(LEVEL_0, "%s:\n----\n", k) + w.Write(LEVEL_0, "%s\n", string(v)) + } + if describerSettings.ShowEvents { + events, err := d.Core().Events(namespace).Search(scheme.Scheme, configMap) + if err != nil { + return err + } + if events != nil { + DescribeEvents(events, w) + } + } + return nil + }) +} + +// NetworkPolicyDescriber generates information about a networkingv1.NetworkPolicy +type NetworkPolicyDescriber struct { + clientset.Interface +} + +func (d *NetworkPolicyDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + c := d.Networking().NetworkPolicies(namespace) + + networkPolicy, err := c.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + return describeNetworkPolicy(networkPolicy) +} + +func describeNetworkPolicy(networkPolicy *networkingv1.NetworkPolicy) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", networkPolicy.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", networkPolicy.Namespace) + w.Write(LEVEL_0, "Created on:\t%s\n", networkPolicy.CreationTimestamp) + printLabelsMultiline(w, "Labels", networkPolicy.Labels) + printAnnotationsMultiline(w, "Annotations", networkPolicy.Annotations) + describeNetworkPolicySpec(networkPolicy.Spec, w) + return nil + }) +} + +func describeNetworkPolicySpec(nps networkingv1.NetworkPolicySpec, w PrefixWriter) { + w.Write(LEVEL_0, "Spec:\n") + w.Write(LEVEL_1, "PodSelector: ") + if len(nps.PodSelector.MatchLabels) == 0 && len(nps.PodSelector.MatchExpressions) == 0 { + w.Write(LEVEL_2, " (Allowing the specific traffic to all pods in this namespace)\n") + } else { + w.Write(LEVEL_2, "%s\n", metav1.FormatLabelSelector(&nps.PodSelector)) + } + w.Write(LEVEL_1, "Allowing ingress traffic:\n") + printNetworkPolicySpecIngressFrom(nps.Ingress, " ", w) + w.Write(LEVEL_1, "Allowing egress traffic:\n") + printNetworkPolicySpecEgressTo(nps.Egress, " ", w) + w.Write(LEVEL_1, "Policy Types: %v\n", policyTypesToString(nps.PolicyTypes)) +} + +func printNetworkPolicySpecIngressFrom(npirs []networkingv1.NetworkPolicyIngressRule, initialIndent string, w PrefixWriter) { + if len(npirs) == 0 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, " (Selected pods are isolated for ingress connectivity)") + return + } + for i, npir := range npirs { + if len(npir.Ports) == 0 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "To Port: (traffic allowed to all ports)") + } else { + for _, port := range npir.Ports { + var proto corev1.Protocol + if port.Protocol != nil { + proto = *port.Protocol + } else { + proto = corev1.ProtocolTCP + } + w.Write(LEVEL_0, "%s%s: %s/%s\n", initialIndent, "To Port", port.Port, proto) + } + } + if len(npir.From) == 0 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "From: (traffic not restricted by source)") + } else { + for _, from := range npir.From { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "From:") + if from.PodSelector != nil && from.NamespaceSelector != nil { + w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "NamespaceSelector", metav1.FormatLabelSelector(from.NamespaceSelector)) + w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "PodSelector", metav1.FormatLabelSelector(from.PodSelector)) + } else if from.PodSelector != nil { + w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "PodSelector", metav1.FormatLabelSelector(from.PodSelector)) + } else if from.NamespaceSelector != nil { + w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "NamespaceSelector", metav1.FormatLabelSelector(from.NamespaceSelector)) + } else if from.IPBlock != nil { + w.Write(LEVEL_1, "%sIPBlock:\n", initialIndent) + w.Write(LEVEL_2, "%sCIDR: %s\n", initialIndent, from.IPBlock.CIDR) + w.Write(LEVEL_2, "%sExcept: %v\n", initialIndent, strings.Join(from.IPBlock.Except, ", ")) + } + } + } + if i != len(npirs)-1 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "----------") + } + } +} + +func printNetworkPolicySpecEgressTo(npers []networkingv1.NetworkPolicyEgressRule, initialIndent string, w PrefixWriter) { + if len(npers) == 0 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, " (Selected pods are isolated for egress connectivity)") + return + } + for i, nper := range npers { + if len(nper.Ports) == 0 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "To Port: (traffic allowed to all ports)") + } else { + for _, port := range nper.Ports { + var proto corev1.Protocol + if port.Protocol != nil { + proto = *port.Protocol + } else { + proto = corev1.ProtocolTCP + } + w.Write(LEVEL_0, "%s%s: %s/%s\n", initialIndent, "To Port", port.Port, proto) + } + } + if len(nper.To) == 0 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "To: (traffic not restricted by source)") + } else { + for _, to := range nper.To { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "To:") + if to.PodSelector != nil && to.NamespaceSelector != nil { + w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "NamespaceSelector", metav1.FormatLabelSelector(to.NamespaceSelector)) + w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "PodSelector", metav1.FormatLabelSelector(to.PodSelector)) + } else if to.PodSelector != nil { + w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "PodSelector", metav1.FormatLabelSelector(to.PodSelector)) + } else if to.NamespaceSelector != nil { + w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "NamespaceSelector", metav1.FormatLabelSelector(to.NamespaceSelector)) + } else if to.IPBlock != nil { + w.Write(LEVEL_1, "%sIPBlock:\n", initialIndent) + w.Write(LEVEL_2, "%sCIDR: %s\n", initialIndent, to.IPBlock.CIDR) + w.Write(LEVEL_2, "%sExcept: %v\n", initialIndent, strings.Join(to.IPBlock.Except, ", ")) + } + } + } + if i != len(npers)-1 { + w.Write(LEVEL_0, "%s%s\n", initialIndent, "----------") + } + } +} + +type StorageClassDescriber struct { + clientset.Interface +} + +func (s *StorageClassDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + sc, err := s.Storage().StorageClasses().Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = s.Core().Events(namespace).Search(scheme.Scheme, sc) + } + + return describeStorageClass(sc, events) +} + +func describeStorageClass(sc *storagev1.StorageClass, events *corev1.EventList) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", sc.Name) + w.Write(LEVEL_0, "IsDefaultClass:\t%s\n", storageutil.IsDefaultAnnotationText(sc.ObjectMeta)) + w.Write(LEVEL_0, "Annotations:\t%s\n", labels.FormatLabels(sc.Annotations)) + w.Write(LEVEL_0, "Provisioner:\t%s\n", sc.Provisioner) + w.Write(LEVEL_0, "Parameters:\t%s\n", labels.FormatLabels(sc.Parameters)) + w.Write(LEVEL_0, "AllowVolumeExpansion:\t%s\n", printBoolPtr(sc.AllowVolumeExpansion)) + if len(sc.MountOptions) == 0 { + w.Write(LEVEL_0, "MountOptions:\t\n") + } else { + w.Write(LEVEL_0, "MountOptions:\n") + for _, option := range sc.MountOptions { + w.Write(LEVEL_1, "%s\n", option) + } + } + if sc.ReclaimPolicy != nil { + w.Write(LEVEL_0, "ReclaimPolicy:\t%s\n", *sc.ReclaimPolicy) + } + if sc.VolumeBindingMode != nil { + w.Write(LEVEL_0, "VolumeBindingMode:\t%s\n", *sc.VolumeBindingMode) + } + if sc.AllowedTopologies != nil { + printAllowedTopologies(w, sc.AllowedTopologies) + } + if events != nil { + DescribeEvents(events, w) + } + + return nil + }) +} + +func printAllowedTopologies(w PrefixWriter, topologies []corev1.TopologySelectorTerm) { + w.Write(LEVEL_0, "AllowedTopologies:\t") + if len(topologies) == 0 { + w.WriteLine("") + return + } + w.WriteLine("") + for i, term := range topologies { + printTopologySelectorTermsMultilineWithIndent(w, LEVEL_1, fmt.Sprintf("Term %d", i), "\t", term.MatchLabelExpressions) + } +} + +func printTopologySelectorTermsMultilineWithIndent(w PrefixWriter, indentLevel int, title, innerIndent string, reqs []corev1.TopologySelectorLabelRequirement) { + w.Write(indentLevel, "%s:%s", title, innerIndent) + + if len(reqs) == 0 { + w.WriteLine("") + return + } + + for i, req := range reqs { + if i != 0 { + w.Write(indentLevel, "%s", innerIndent) + } + exprStr := fmt.Sprintf("%s %s", req.Key, "in") + if len(req.Values) > 0 { + exprStr = fmt.Sprintf("%s [%s]", exprStr, strings.Join(req.Values, ", ")) + } + w.Write(LEVEL_0, "%s\n", exprStr) + } +} + +type PodDisruptionBudgetDescriber struct { + clientset.Interface +} + +func (p *PodDisruptionBudgetDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + pdb, err := p.Policy().PodDisruptionBudgets(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = p.Core().Events(namespace).Search(scheme.Scheme, pdb) + } + + return describePodDisruptionBudget(pdb, events) +} + +func describePodDisruptionBudget(pdb *policyv1beta1.PodDisruptionBudget, events *corev1.EventList) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", pdb.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", pdb.Namespace) + + if pdb.Spec.MinAvailable != nil { + w.Write(LEVEL_0, "Min available:\t%s\n", pdb.Spec.MinAvailable.String()) + } else if pdb.Spec.MaxUnavailable != nil { + w.Write(LEVEL_0, "Max unavailable:\t%s\n", pdb.Spec.MaxUnavailable.String()) + } + + if pdb.Spec.Selector != nil { + w.Write(LEVEL_0, "Selector:\t%s\n", metav1.FormatLabelSelector(pdb.Spec.Selector)) + } else { + w.Write(LEVEL_0, "Selector:\t\n") + } + w.Write(LEVEL_0, "Status:\n") + w.Write(LEVEL_2, "Allowed disruptions:\t%d\n", pdb.Status.PodDisruptionsAllowed) + w.Write(LEVEL_2, "Current:\t%d\n", pdb.Status.CurrentHealthy) + w.Write(LEVEL_2, "Desired:\t%d\n", pdb.Status.DesiredHealthy) + w.Write(LEVEL_2, "Total:\t%d\n", pdb.Status.ExpectedPods) + if events != nil { + DescribeEvents(events, w) + } + + return nil + }) +} + +// PriorityClassDescriber generates information about a PriorityClass. +type PriorityClassDescriber struct { + clientset.Interface +} + +func (s *PriorityClassDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + pc, err := s.Scheduling().PriorityClasses().Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = s.Core().Events(namespace).Search(scheme.Scheme, pc) + } + + return describePriorityClass(pc, events) +} + +func describePriorityClass(pc *schedulingv1beta1.PriorityClass, events *corev1.EventList) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", pc.Name) + w.Write(LEVEL_0, "Value:\t%v\n", pc.Value) + w.Write(LEVEL_0, "GlobalDefault:\t%v\n", pc.GlobalDefault) + w.Write(LEVEL_0, "Description:\t%s\n", pc.Description) + + w.Write(LEVEL_0, "Annotations:\t%s\n", labels.FormatLabels(pc.Annotations)) + if events != nil { + DescribeEvents(events, w) + } + + return nil + }) +} + +// PodSecurityPolicyDescriber generates information about a PodSecuritypolicyv1beta1. +type PodSecurityPolicyDescriber struct { + clientset.Interface +} + +func (d *PodSecurityPolicyDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + psp, err := d.Policy().PodSecurityPolicies().Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + return describePodSecurityPolicy(psp) +} + +func describePodSecurityPolicy(psp *policyv1beta1.PodSecurityPolicy) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", psp.Name) + + w.Write(LEVEL_0, "\nSettings:\n") + + w.Write(LEVEL_1, "Allow Privileged:\t%t\n", psp.Spec.Privileged) + w.Write(LEVEL_1, "Allow Privilege Escalation:\t%v\n", psp.Spec.AllowPrivilegeEscalation) + w.Write(LEVEL_1, "Default Add Capabilities:\t%v\n", capsToString(psp.Spec.DefaultAddCapabilities)) + w.Write(LEVEL_1, "Required Drop Capabilities:\t%s\n", capsToString(psp.Spec.RequiredDropCapabilities)) + w.Write(LEVEL_1, "Allowed Capabilities:\t%s\n", capsToString(psp.Spec.AllowedCapabilities)) + w.Write(LEVEL_1, "Allowed Volume Types:\t%s\n", fsTypeToString(psp.Spec.Volumes)) + + if len(psp.Spec.AllowedFlexVolumes) > 0 { + w.Write(LEVEL_1, "Allowed FlexVolume Types:\t%s\n", flexVolumesToString(psp.Spec.AllowedFlexVolumes)) + } + if len(psp.Spec.AllowedUnsafeSysctls) > 0 { + w.Write(LEVEL_1, "Allowed Unsafe Sysctls:\t%s\n", sysctlsToString(psp.Spec.AllowedUnsafeSysctls)) + } + if len(psp.Spec.ForbiddenSysctls) > 0 { + w.Write(LEVEL_1, "Forbidden Sysctls:\t%s\n", sysctlsToString(psp.Spec.ForbiddenSysctls)) + } + w.Write(LEVEL_1, "Allow Host Network:\t%t\n", psp.Spec.HostNetwork) + w.Write(LEVEL_1, "Allow Host Ports:\t%s\n", hostPortRangeToString(psp.Spec.HostPorts)) + w.Write(LEVEL_1, "Allow Host PID:\t%t\n", psp.Spec.HostPID) + w.Write(LEVEL_1, "Allow Host IPC:\t%t\n", psp.Spec.HostIPC) + w.Write(LEVEL_1, "Read Only Root Filesystem:\t%v\n", psp.Spec.ReadOnlyRootFilesystem) + + w.Write(LEVEL_1, "SELinux Context Strategy: %s\t\n", string(psp.Spec.SELinux.Rule)) + var user, role, seLinuxType, level string + if psp.Spec.SELinux.SELinuxOptions != nil { + user = psp.Spec.SELinux.SELinuxOptions.User + role = psp.Spec.SELinux.SELinuxOptions.Role + seLinuxType = psp.Spec.SELinux.SELinuxOptions.Type + level = psp.Spec.SELinux.SELinuxOptions.Level + } + w.Write(LEVEL_2, "User:\t%s\n", stringOrNone(user)) + w.Write(LEVEL_2, "Role:\t%s\n", stringOrNone(role)) + w.Write(LEVEL_2, "Type:\t%s\n", stringOrNone(seLinuxType)) + w.Write(LEVEL_2, "Level:\t%s\n", stringOrNone(level)) + + w.Write(LEVEL_1, "Run As User Strategy: %s\t\n", string(psp.Spec.RunAsUser.Rule)) + w.Write(LEVEL_2, "Ranges:\t%s\n", idRangeToString(psp.Spec.RunAsUser.Ranges)) + + w.Write(LEVEL_1, "FSGroup Strategy: %s\t\n", string(psp.Spec.FSGroup.Rule)) + w.Write(LEVEL_2, "Ranges:\t%s\n", idRangeToString(psp.Spec.FSGroup.Ranges)) + + w.Write(LEVEL_1, "Supplemental Groups Strategy: %s\t\n", string(psp.Spec.SupplementalGroups.Rule)) + w.Write(LEVEL_2, "Ranges:\t%s\n", idRangeToString(psp.Spec.SupplementalGroups.Ranges)) + + return nil + }) +} + +func stringOrNone(s string) string { + return stringOrDefaultValue(s, "") +} + +func stringOrDefaultValue(s, defaultValue string) string { + if len(s) > 0 { + return s + } + return defaultValue +} + +func fsTypeToString(volumes []policyv1beta1.FSType) string { + strVolumes := []string{} + for _, v := range volumes { + strVolumes = append(strVolumes, string(v)) + } + return stringOrNone(strings.Join(strVolumes, ",")) +} + +func flexVolumesToString(flexVolumes []policyv1beta1.AllowedFlexVolume) string { + volumes := []string{} + for _, flexVolume := range flexVolumes { + volumes = append(volumes, "driver="+flexVolume.Driver) + } + return stringOrDefaultValue(strings.Join(volumes, ","), "") +} + +func sysctlsToString(sysctls []string) string { + return stringOrNone(strings.Join(sysctls, ",")) +} + +func hostPortRangeToString(ranges []policyv1beta1.HostPortRange) string { + formattedString := "" + if ranges != nil { + strRanges := []string{} + for _, r := range ranges { + strRanges = append(strRanges, fmt.Sprintf("%d-%d", r.Min, r.Max)) + } + formattedString = strings.Join(strRanges, ",") + } + return stringOrNone(formattedString) +} + +func idRangeToString(ranges []policyv1beta1.IDRange) string { + formattedString := "" + if ranges != nil { + strRanges := []string{} + for _, r := range ranges { + strRanges = append(strRanges, fmt.Sprintf("%d-%d", r.Min, r.Max)) + } + formattedString = strings.Join(strRanges, ",") + } + return stringOrNone(formattedString) +} + +func capsToString(caps []corev1.Capability) string { + formattedString := "" + if caps != nil { + strCaps := []string{} + for _, c := range caps { + strCaps = append(strCaps, string(c)) + } + formattedString = strings.Join(strCaps, ",") + } + return stringOrNone(formattedString) +} + +func policyTypesToString(pts []networkingv1.PolicyType) string { + formattedString := "" + if pts != nil { + strPts := []string{} + for _, p := range pts { + strPts = append(strPts, string(p)) + } + formattedString = strings.Join(strPts, ", ") + } + return stringOrNone(formattedString) +} + +// newErrNoDescriber creates a new ErrNoDescriber with the names of the provided types. +func newErrNoDescriber(types ...reflect.Type) error { + names := make([]string, 0, len(types)) + for _, t := range types { + names = append(names, t.String()) + } + return describe.ErrNoDescriber{Types: names} +} + +// Describers implements ObjectDescriber against functions registered via Add. Those functions can +// be strongly typed. Types are exactly matched (no conversion or assignable checks). +type Describers struct { + searchFns map[reflect.Type][]typeFunc +} + +// DescribeObject implements ObjectDescriber and will attempt to print the provided object to a string, +// if at least one describer function has been registered with the exact types passed, or if any +// describer can print the exact object in its first argument (the remainder will be provided empty +// values). If no function registered with Add can satisfy the passed objects, an ErrNoDescriber will +// be returned +// TODO: reorder and partial match extra. +func (d *Describers) DescribeObject(exact interface{}, extra ...interface{}) (string, error) { + exactType := reflect.TypeOf(exact) + fns, ok := d.searchFns[exactType] + if !ok { + return "", newErrNoDescriber(exactType) + } + if len(extra) == 0 { + for _, typeFn := range fns { + if len(typeFn.Extra) == 0 { + return typeFn.Describe(exact, extra...) + } + } + typeFn := fns[0] + for _, t := range typeFn.Extra { + v := reflect.New(t).Elem() + extra = append(extra, v.Interface()) + } + return fns[0].Describe(exact, extra...) + } + + types := make([]reflect.Type, 0, len(extra)) + for _, obj := range extra { + types = append(types, reflect.TypeOf(obj)) + } + for _, typeFn := range fns { + if typeFn.Matches(types) { + return typeFn.Describe(exact, extra...) + } + } + return "", newErrNoDescriber(append([]reflect.Type{exactType}, types...)...) +} + +// Add adds one or more describer functions to the describe.Describer. The passed function must +// match the signature: +// +// func(...) (string, error) +// +// Any number of arguments may be provided. +func (d *Describers) Add(fns ...interface{}) error { + for _, fn := range fns { + fv := reflect.ValueOf(fn) + ft := fv.Type() + if ft.Kind() != reflect.Func { + return fmt.Errorf("expected func, got: %v", ft) + } + numIn := ft.NumIn() + if numIn == 0 { + return fmt.Errorf("expected at least one 'in' params, got: %v", ft) + } + if ft.NumOut() != 2 { + return fmt.Errorf("expected two 'out' params - (string, error), got: %v", ft) + } + types := make([]reflect.Type, 0, numIn) + for i := 0; i < numIn; i++ { + types = append(types, ft.In(i)) + } + if ft.Out(0) != reflect.TypeOf(string("")) { + return fmt.Errorf("expected string return, got: %v", ft) + } + var forErrorType error + // This convolution is necessary, otherwise TypeOf picks up on the fact + // that forErrorType is nil. + errorType := reflect.TypeOf(&forErrorType).Elem() + if ft.Out(1) != errorType { + return fmt.Errorf("expected error return, got: %v", ft) + } + + exact := types[0] + extra := types[1:] + if d.searchFns == nil { + d.searchFns = make(map[reflect.Type][]typeFunc) + } + fns := d.searchFns[exact] + fn := typeFunc{Extra: extra, Fn: fv} + fns = append(fns, fn) + d.searchFns[exact] = fns + } + return nil +} + +// typeFunc holds information about a describer function and the types it accepts +type typeFunc struct { + Extra []reflect.Type + Fn reflect.Value +} + +// Matches returns true when the passed types exactly match the Extra list. +func (fn typeFunc) Matches(types []reflect.Type) bool { + if len(fn.Extra) != len(types) { + return false + } + // reorder the items in array types and fn.Extra + // convert the type into string and sort them, check if they are matched + varMap := make(map[reflect.Type]bool) + for i := range fn.Extra { + varMap[fn.Extra[i]] = true + } + for i := range types { + if _, found := varMap[types[i]]; !found { + return false + } + } + return true +} + +// Describe invokes the nested function with the exact number of arguments. +func (fn typeFunc) Describe(exact interface{}, extra ...interface{}) (string, error) { + values := []reflect.Value{reflect.ValueOf(exact)} + for _, obj := range extra { + values = append(values, reflect.ValueOf(obj)) + } + out := fn.Fn.Call(values) + s := out[0].Interface().(string) + var err error + if !out[1].IsNil() { + err = out[1].Interface().(error) + } + return s, err +} + +// printLabelsMultiline prints multiple labels with a proper alignment. +func printLabelsMultiline(w PrefixWriter, title string, labels map[string]string) { + printLabelsMultilineWithIndent(w, "", title, "\t", labels, sets.NewString()) +} + +// printLabelsMultiline prints multiple labels with a user-defined alignment. +func printLabelsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerIndent string, labels map[string]string, skip sets.String) { + w.Write(LEVEL_0, "%s%s:%s", initialIndent, title, innerIndent) + + if labels == nil || len(labels) == 0 { + w.WriteLine("") + return + } + + // to print labels in the sorted order + keys := make([]string, 0, len(labels)) + for key := range labels { + if skip.Has(key) { + continue + } + keys = append(keys, key) + } + if len(keys) == 0 { + w.WriteLine("") + return + } + sort.Strings(keys) + + for i, key := range keys { + if i != 0 { + w.Write(LEVEL_0, "%s", initialIndent) + w.Write(LEVEL_0, "%s", innerIndent) + } + w.Write(LEVEL_0, "%s=%s\n", key, labels[key]) + i++ + } +} + +// printTaintsMultiline prints multiple taints with a proper alignment. +func printNodeTaintsMultiline(w PrefixWriter, title string, taints []corev1.Taint) { + printTaintsMultilineWithIndent(w, "", title, "\t", taints) +} + +// printTaintsMultilineWithIndent prints multiple taints with a user-defined alignment. +func printTaintsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerIndent string, taints []corev1.Taint) { + w.Write(LEVEL_0, "%s%s:%s", initialIndent, title, innerIndent) + + if taints == nil || len(taints) == 0 { + w.WriteLine("") + return + } + + // to print taints in the sorted order + sort.Slice(taints, func(i, j int) bool { + cmpKey := func(taint corev1.Taint) string { + return string(taint.Effect) + "," + taint.Key + } + return cmpKey(taints[i]) < cmpKey(taints[j]) + }) + + for i, taint := range taints { + if i != 0 { + w.Write(LEVEL_0, "%s", initialIndent) + w.Write(LEVEL_0, "%s", innerIndent) + } + w.Write(LEVEL_0, "%s\n", taint.ToString()) + } +} + +// printPodsMultiline prints multiple pods with a proper alignment. +func printPodsMultiline(w PrefixWriter, title string, pods []corev1.Pod) { + printPodsMultilineWithIndent(w, "", title, "\t", pods) +} + +// printPodsMultilineWithIndent prints multiple pods with a user-defined alignment. +func printPodsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerIndent string, pods []corev1.Pod) { + w.Write(LEVEL_0, "%s%s:%s", initialIndent, title, innerIndent) + + if pods == nil || len(pods) == 0 { + w.WriteLine("") + return + } + + // to print pods in the sorted order + sort.Slice(pods, func(i, j int) bool { + cmpKey := func(pod corev1.Pod) string { + return pod.Name + } + return cmpKey(pods[i]) < cmpKey(pods[j]) + }) + + for i, pod := range pods { + if i != 0 { + w.Write(LEVEL_0, "%s", initialIndent) + w.Write(LEVEL_0, "%s", innerIndent) + } + w.Write(LEVEL_0, "%s\n", pod.Name) + } +} + +// printPodTolerationsMultiline prints multiple tolerations with a proper alignment. +func printPodTolerationsMultiline(w PrefixWriter, title string, tolerations []corev1.Toleration) { + printTolerationsMultilineWithIndent(w, "", title, "\t", tolerations) +} + +// printTolerationsMultilineWithIndent prints multiple tolerations with a user-defined alignment. +func printTolerationsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerIndent string, tolerations []corev1.Toleration) { + w.Write(LEVEL_0, "%s%s:%s", initialIndent, title, innerIndent) + + if tolerations == nil || len(tolerations) == 0 { + w.WriteLine("") + return + } + + // to print tolerations in the sorted order + sort.Slice(tolerations, func(i, j int) bool { + return tolerations[i].Key < tolerations[j].Key + }) + + for i, toleration := range tolerations { + if i != 0 { + w.Write(LEVEL_0, "%s", initialIndent) + w.Write(LEVEL_0, "%s", innerIndent) + } + w.Write(LEVEL_0, "%s", toleration.Key) + if len(toleration.Value) != 0 { + w.Write(LEVEL_0, "=%s", toleration.Value) + } + if len(toleration.Effect) != 0 { + w.Write(LEVEL_0, ":%s", toleration.Effect) + } + if toleration.TolerationSeconds != nil { + w.Write(LEVEL_0, " for %ds", *toleration.TolerationSeconds) + } + w.Write(LEVEL_0, "\n") + } +} + +type flusher interface { + Flush() +} + +func tabbedString(f func(io.Writer) error) (string, error) { + out := new(tabwriter.Writer) + buf := &bytes.Buffer{} + out.Init(buf, 0, 8, 2, ' ', 0) + + err := f(out) + if err != nil { + return "", err + } + + out.Flush() + str := string(buf.String()) + return str, nil +} + +type SortableResourceNames []corev1.ResourceName + +func (list SortableResourceNames) Len() int { + return len(list) +} + +func (list SortableResourceNames) Swap(i, j int) { + list[i], list[j] = list[j], list[i] +} + +func (list SortableResourceNames) Less(i, j int) bool { + return list[i] < list[j] +} + +// SortedResourceNames returns the sorted resource names of a resource list. +func SortedResourceNames(list corev1.ResourceList) []corev1.ResourceName { + resources := make([]corev1.ResourceName, 0, len(list)) + for res := range list { + resources = append(resources, res) + } + sort.Sort(SortableResourceNames(resources)) + return resources +} + +type SortableResourceQuotas []corev1.ResourceQuota + +func (list SortableResourceQuotas) Len() int { + return len(list) +} + +func (list SortableResourceQuotas) Swap(i, j int) { + list[i], list[j] = list[j], list[i] +} + +func (list SortableResourceQuotas) Less(i, j int) bool { + return list[i].Name < list[j].Name +} + +type SortableVolumeMounts []corev1.VolumeMount + +func (list SortableVolumeMounts) Len() int { + return len(list) +} + +func (list SortableVolumeMounts) Swap(i, j int) { + list[i], list[j] = list[j], list[i] +} + +func (list SortableVolumeMounts) Less(i, j int) bool { + return list[i].MountPath < list[j].MountPath +} + +type SortableVolumeDevices []corev1.VolumeDevice + +func (list SortableVolumeDevices) Len() int { + return len(list) +} + +func (list SortableVolumeDevices) Swap(i, j int) { + list[i], list[j] = list[j], list[i] +} + +func (list SortableVolumeDevices) Less(i, j int) bool { + return list[i].DevicePath < list[j].DevicePath +} + +var maxAnnotationLen = 140 + +// printAnnotationsMultilineWithFilter prints filtered multiple annotations with a proper alignment. +func printAnnotationsMultilineWithFilter(w PrefixWriter, title string, annotations map[string]string, skip sets.String) { + printAnnotationsMultilineWithIndent(w, "", title, "\t", annotations, skip) +} + +// printAnnotationsMultiline prints multiple annotations with a proper alignment. +func printAnnotationsMultiline(w PrefixWriter, title string, annotations map[string]string) { + printAnnotationsMultilineWithIndent(w, "", title, "\t", annotations, sets.NewString()) +} + +// printAnnotationsMultilineWithIndent prints multiple annotations with a user-defined alignment. +// If annotation string is too long, we omit chars more than 200 length. +func printAnnotationsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerIndent string, annotations map[string]string, skip sets.String) { + + w.Write(LEVEL_0, "%s%s:%s", initialIndent, title, innerIndent) + + if len(annotations) == 0 { + w.WriteLine("") + return + } + + // to print labels in the sorted order + keys := make([]string, 0, len(annotations)) + for key := range annotations { + if skip.Has(key) { + continue + } + keys = append(keys, key) + } + if len(annotations) == 0 { + w.WriteLine("") + return + } + sort.Strings(keys) + indent := initialIndent + innerIndent + for i, key := range keys { + if i != 0 { + w.Write(LEVEL_0, indent) + } + value := strings.TrimSuffix(annotations[key], "\n") + if (len(value)+len(key)+2) > maxAnnotationLen || strings.Contains(value, "\n") { + w.Write(LEVEL_0, "%s:\n", key) + for _, s := range strings.Split(value, "\n") { + w.Write(LEVEL_0, "%s %s\n", indent, shorten(s, maxAnnotationLen-2)) + } + } else { + w.Write(LEVEL_0, "%s: %s\n", key, value) + } + i++ + } +} + +func shorten(s string, maxLength int) string { + if len(s) > maxLength { + return s[:maxLength] + "..." + } + return s +} + +// translateTimestampUntil returns the elapsed time until timestamp in +// human-readable approximation. +func translateTimestampUntil(timestamp metav1.Time) string { + if timestamp.IsZero() { + return "" + } + + return duration.HumanDuration(time.Until(timestamp.Time)) +} + +// translateTimestampSince returns the elapsed time since timestamp in +// human-readable approximation. +func translateTimestampSince(timestamp metav1.Time) string { + if timestamp.IsZero() { + return "" + } + + return duration.HumanDuration(time.Since(timestamp.Time)) +} + +// formatEventSource formats EventSource as a comma separated string excluding Host when empty +func formatEventSource(es corev1.EventSource) string { + EventSourceString := []string{es.Component} + if len(es.Host) > 0 { + EventSourceString = append(EventSourceString, es.Host) + } + return strings.Join(EventSourceString, ", ") +} + +// Pass ports=nil for all ports. +func formatEndpoints(endpoints *corev1.Endpoints, ports sets.String) string { + if len(endpoints.Subsets) == 0 { + return "" + } + list := []string{} + max := 3 + more := false + count := 0 + for i := range endpoints.Subsets { + ss := &endpoints.Subsets[i] + if len(ss.Ports) == 0 { + // It's possible to have headless services with no ports. + for i := range ss.Addresses { + if len(list) == max { + more = true + } + if !more { + list = append(list, ss.Addresses[i].IP) + } + count++ + } + } else { + // "Normal" services with ports defined. + for i := range ss.Ports { + port := &ss.Ports[i] + if ports == nil || ports.Has(port.Name) { + for i := range ss.Addresses { + if len(list) == max { + more = true + } + addr := &ss.Addresses[i] + if !more { + hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port.Port))) + list = append(list, hostPort) + } + count++ + } + } + } + } + } + ret := strings.Join(list, ",") + if more { + return fmt.Sprintf("%s + %d more...", ret, count-max) + } + return ret +} + +func extractCSRStatus(csr *certificatesv1beta1.CertificateSigningRequest) (string, error) { + var approved, denied bool + for _, c := range csr.Status.Conditions { + switch c.Type { + case certificatesv1beta1.CertificateApproved: + approved = true + case certificatesv1beta1.CertificateDenied: + denied = true + default: + return "", fmt.Errorf("unknown csr condition %q", c) + } + } + var status string + // must be in order of presidence + if denied { + status += "Denied" + } else if approved { + status += "Approved" + } else { + status += "Pending" + } + if len(csr.Status.Certificate) > 0 { + status += ",Issued" + } + return status, nil +} + +// backendStringer behaves just like a string interface and converts the given backend to a string. +func backendStringer(backend *extensionsv1beta1.IngressBackend) string { + if backend == nil { + return "" + } + return fmt.Sprintf("%v:%v", backend.ServiceName, backend.ServicePort.String()) +} + +// findNodeRoles returns the roles of a given node. +// The roles are determined by looking for: +// * a node-role.kubernetes.io/="" label +// * a kubernetes.io/role="" label +func findNodeRoles(node *corev1.Node) []string { + roles := sets.NewString() + for k, v := range node.Labels { + switch { + case strings.HasPrefix(k, describe.LabelNodeRolePrefix): + if role := strings.TrimPrefix(k, describe.LabelNodeRolePrefix); len(role) > 0 { + roles.Insert(role) + } + + case k == describe.NodeLabelRole && v != "": + roles.Insert(v) + } + } + return roles.List() +} + +// loadBalancerStatusStringer behaves mostly like a string interface and converts the given status to a string. +// `wide` indicates whether the returned value is meant for --o=wide output. If not, it's clipped to 16 bytes. +func loadBalancerStatusStringer(s corev1.LoadBalancerStatus, wide bool) string { + ingress := s.Ingress + result := sets.NewString() + for i := range ingress { + if ingress[i].IP != "" { + result.Insert(ingress[i].IP) + } else if ingress[i].Hostname != "" { + result.Insert(ingress[i].Hostname) + } + } + + r := strings.Join(result.List(), ",") + if !wide && len(r) > describe.LoadBalancerWidth { + r = r[0:(describe.LoadBalancerWidth-3)] + "..." + } + return r +} diff --git a/pkg/printers/internalversion/describe_test.go b/pkg/kubectl/describe/versioned/describe_test.go similarity index 63% rename from pkg/printers/internalversion/describe_test.go rename to pkg/kubectl/describe/versioned/describe_test.go index 25ea2a92a8..4c1d4c4812 100644 --- a/pkg/printers/internalversion/describe_test.go +++ b/pkg/kubectl/describe/versioned/describe_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package internalversion +package versioned import ( "bytes" @@ -26,22 +26,21 @@ import ( "time" appsv1 "k8s.io/api/apps/v1" + autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + policyv1beta1 "k8s.io/api/policy/v1beta1" + storagev1 "k8s.io/api/storage/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" versionedfake "k8s.io/client-go/kubernetes/fake" - "k8s.io/kubernetes/pkg/apis/apps" - "k8s.io/kubernetes/pkg/apis/autoscaling" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/apis/networking" - "k8s.io/kubernetes/pkg/apis/policy" - "k8s.io/kubernetes/pkg/apis/storage" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" - "k8s.io/kubernetes/pkg/printers" + "k8s.io/kubernetes/pkg/kubectl/describe" utilpointer "k8s.io/utils/pointer" ) @@ -49,23 +48,23 @@ type describeClient struct { T *testing.T Namespace string Err error - internalclientset.Interface + kubernetes.Interface } func TestDescribePod(t *testing.T) { deletionTimestamp := metav1.Time{Time: time.Now().UTC().AddDate(10, 0, 0)} gracePeriod := int64(1234) - condition1 := api.PodConditionType("condition1") - condition2 := api.PodConditionType("condition2") - fake := fake.NewSimpleClientset(&api.Pod{ + condition1 := corev1.PodConditionType("condition1") + condition2 := corev1.PodConditionType("condition2") + fake := fake.NewSimpleClientset(&corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", DeletionTimestamp: &deletionTimestamp, DeletionGracePeriodSeconds: &gracePeriod, }, - Spec: api.PodSpec{ - ReadinessGates: []api.PodReadinessGate{ + Spec: corev1.PodSpec{ + ReadinessGates: []corev1.PodReadinessGate{ { ConditionType: condition1, }, @@ -74,18 +73,18 @@ func TestDescribePod(t *testing.T) { }, }, }, - Status: api.PodStatus{ - Conditions: []api.PodCondition{ + Status: corev1.PodStatus{ + Conditions: []corev1.PodCondition{ { Type: condition1, - Status: api.ConditionTrue, + Status: corev1.ConditionTrue, }, }, }, }) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := PodDescriber{c} - out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -98,22 +97,22 @@ func TestDescribePod(t *testing.T) { } func TestDescribePodNode(t *testing.T) { - fake := fake.NewSimpleClientset(&api.Pod{ + fake := fake.NewSimpleClientset(&corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", }, - Spec: api.PodSpec{ + Spec: corev1.PodSpec{ NodeName: "all-in-one", }, - Status: api.PodStatus{ + Status: corev1.PodStatus{ HostIP: "127.0.0.1", NominatedNodeName: "nodeA", }, }) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := PodDescriber{c} - out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -126,24 +125,24 @@ func TestDescribePodNode(t *testing.T) { } func TestDescribePodTolerations(t *testing.T) { - fake := fake.NewSimpleClientset(&api.Pod{ + fake := fake.NewSimpleClientset(&corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", }, - Spec: api.PodSpec{ - Tolerations: []api.Toleration{ - {Key: "key0", Operator: api.TolerationOpExists}, + Spec: corev1.PodSpec{ + Tolerations: []corev1.Toleration{ + {Key: "key0", Operator: corev1.TolerationOpExists}, {Key: "key1", Value: "value1"}, - {Key: "key2", Operator: api.TolerationOpEqual, Value: "value2", Effect: api.TaintEffectNoSchedule}, - {Key: "key3", Value: "value3", Effect: api.TaintEffectNoExecute, TolerationSeconds: &[]int64{300}[0]}, - {Key: "key4", Effect: api.TaintEffectNoExecute, TolerationSeconds: &[]int64{60}[0]}, + {Key: "key2", Operator: corev1.TolerationOpEqual, Value: "value2", Effect: corev1.TaintEffectNoSchedule}, + {Key: "key3", Value: "value3", Effect: corev1.TaintEffectNoExecute, TolerationSeconds: &[]int64{300}[0]}, + {Key: "key4", Effect: corev1.TaintEffectNoExecute, TolerationSeconds: &[]int64{60}[0]}, }, }, }) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := PodDescriber{c} - out, err := d.Describe("foo", "bar", printers.DescriberSettings{}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -158,7 +157,7 @@ func TestDescribePodTolerations(t *testing.T) { } func TestDescribeSecret(t *testing.T) { - fake := fake.NewSimpleClientset(&api.Secret{ + fake := fake.NewSimpleClientset(&corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", @@ -170,7 +169,7 @@ func TestDescribeSecret(t *testing.T) { }) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := SecretDescriber{c} - out, err := d.Describe("foo", "bar", printers.DescriberSettings{}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -183,14 +182,14 @@ func TestDescribeSecret(t *testing.T) { } func TestDescribeNamespace(t *testing.T) { - fake := fake.NewSimpleClientset(&api.Namespace{ + fake := fake.NewSimpleClientset(&corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: "myns", }, }) c := &describeClient{T: t, Namespace: "", Interface: fake} d := NamespaceDescriber{c} - out, err := d.Describe("", "myns", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("", "myns", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -201,18 +200,18 @@ func TestDescribeNamespace(t *testing.T) { func TestDescribePodPriority(t *testing.T) { priority := int32(1000) - fake := fake.NewSimpleClientset(&api.Pod{ + fake := fake.NewSimpleClientset(&corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", }, - Spec: api.PodSpec{ + Spec: corev1.PodSpec{ PriorityClassName: "high-priority", Priority: &priority, }, }) c := &describeClient{T: t, Namespace: "", Interface: fake} d := PodDescriber{c} - out, err := d.Describe("", "bar", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -222,7 +221,7 @@ func TestDescribePodPriority(t *testing.T) { } func TestDescribeConfigMap(t *testing.T) { - fake := fake.NewSimpleClientset(&api.ConfigMap{ + fake := fake.NewSimpleClientset(&corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "mycm", Namespace: "foo", @@ -234,7 +233,7 @@ func TestDescribeConfigMap(t *testing.T) { }) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := ConfigMapDescriber{c} - out, err := d.Describe("foo", "mycm", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("foo", "mycm", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -244,21 +243,21 @@ func TestDescribeConfigMap(t *testing.T) { } func TestDescribeLimitRange(t *testing.T) { - fake := fake.NewSimpleClientset(&api.LimitRange{ + fake := fake.NewSimpleClientset(&corev1.LimitRange{ ObjectMeta: metav1.ObjectMeta{ Name: "mylr", Namespace: "foo", }, - Spec: api.LimitRangeSpec{ - Limits: []api.LimitRangeItem{ + Spec: corev1.LimitRangeSpec{ + Limits: []corev1.LimitRangeItem{ { - Type: api.LimitTypePod, + Type: corev1.LimitTypePod, Max: getResourceList("100m", "10000Mi"), Min: getResourceList("5m", "100Mi"), MaxLimitRequestRatio: getResourceList("10", ""), }, { - Type: api.LimitTypeContainer, + Type: corev1.LimitTypeContainer, Max: getResourceList("100m", "10000Mi"), Min: getResourceList("5m", "100Mi"), Default: getResourceList("50m", "500Mi"), @@ -266,7 +265,7 @@ func TestDescribeLimitRange(t *testing.T) { MaxLimitRequestRatio: getResourceList("10", ""), }, { - Type: api.LimitTypePersistentVolumeClaim, + Type: corev1.LimitTypePersistentVolumeClaim, Max: getStorageResourceList("10Gi"), Min: getStorageResourceList("5Gi"), }, @@ -275,7 +274,7 @@ func TestDescribeLimitRange(t *testing.T) { }) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := LimitRangeDescriber{c} - out, err := d.Describe("foo", "mylr", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("foo", "mylr", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -288,21 +287,21 @@ func TestDescribeLimitRange(t *testing.T) { } } -func getStorageResourceList(storage string) api.ResourceList { - res := api.ResourceList{} +func getStorageResourceList(storage string) corev1.ResourceList { + res := corev1.ResourceList{} if storage != "" { - res[api.ResourceStorage] = resource.MustParse(storage) + res[corev1.ResourceStorage] = resource.MustParse(storage) } return res } -func getResourceList(cpu, memory string) api.ResourceList { - res := api.ResourceList{} +func getResourceList(cpu, memory string) corev1.ResourceList { + res := corev1.ResourceList{} if cpu != "" { - res[api.ResourceCPU] = resource.MustParse(cpu) + res[corev1.ResourceCPU] = resource.MustParse(cpu) } if memory != "" { - res[api.ResourceMemory] = resource.MustParse(memory) + res[corev1.ResourceMemory] = resource.MustParse(memory) } return res } @@ -310,22 +309,22 @@ func getResourceList(cpu, memory string) api.ResourceList { func TestDescribeService(t *testing.T) { testCases := []struct { name string - service *api.Service + service *corev1.Service expect []string }{ { name: "test1", - service: &api.Service{ + service: &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", }, - Spec: api.ServiceSpec{ - Type: api.ServiceTypeLoadBalancer, - Ports: []api.ServicePort{{ + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Ports: []corev1.ServicePort{{ Name: "port-tcp", Port: 8080, - Protocol: api.ProtocolTCP, + Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromInt(9527), NodePort: 31111, }}, @@ -353,17 +352,17 @@ func TestDescribeService(t *testing.T) { }, { name: "test2", - service: &api.Service{ + service: &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", }, - Spec: api.ServiceSpec{ - Type: api.ServiceTypeLoadBalancer, - Ports: []api.ServicePort{{ + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Ports: []corev1.ServicePort{{ Name: "port-tcp", Port: 8080, - Protocol: api.ProtocolTCP, + Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromString("targetPort"), NodePort: 31111, }}, @@ -395,7 +394,7 @@ func TestDescribeService(t *testing.T) { fake := fake.NewSimpleClientset(testCase.service) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := ServiceDescriber{c} - out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -411,44 +410,44 @@ func TestDescribeService(t *testing.T) { func TestPodDescribeResultsSorted(t *testing.T) { // Arrange fake := fake.NewSimpleClientset( - &api.EventList{ - Items: []api.Event{ + &corev1.EventList{ + Items: []corev1.Event{ { ObjectMeta: metav1.ObjectMeta{Name: "one"}, - Source: api.EventSource{Component: "kubelet"}, + Source: corev1.EventSource{Component: "kubelet"}, Message: "Item 1", FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), LastTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), Count: 1, - Type: api.EventTypeNormal, + Type: corev1.EventTypeNormal, }, { ObjectMeta: metav1.ObjectMeta{Name: "two"}, - Source: api.EventSource{Component: "scheduler"}, + Source: corev1.EventSource{Component: "scheduler"}, Message: "Item 2", FirstTimestamp: metav1.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)), LastTimestamp: metav1.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)), Count: 1, - Type: api.EventTypeNormal, + Type: corev1.EventTypeNormal, }, { ObjectMeta: metav1.ObjectMeta{Name: "three"}, - Source: api.EventSource{Component: "kubelet"}, + Source: corev1.EventSource{Component: "kubelet"}, Message: "Item 3", FirstTimestamp: metav1.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)), LastTimestamp: metav1.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)), Count: 1, - Type: api.EventTypeNormal, + Type: corev1.EventTypeNormal, }, }, }, - &api.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}}, + &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}}, ) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := PodDescriber{c} // Act - out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) // Assert if err != nil { @@ -484,17 +483,17 @@ func VerifyDatesInOrder( func TestDescribeContainers(t *testing.T) { trueVal := true testCases := []struct { - container api.Container - status api.ContainerStatus + container corev1.Container + status corev1.ContainerStatus expectedElements []string }{ // Running state. { - container: api.Container{Name: "test", Image: "image"}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image"}, + status: corev1.ContainerStatus{ Name: "test", - State: api.ContainerState{ - Running: &api.ContainerStateRunning{ + State: corev1.ContainerState{ + Running: &corev1.ContainerStateRunning{ StartedAt: metav1.NewTime(time.Now()), }, }, @@ -505,11 +504,11 @@ func TestDescribeContainers(t *testing.T) { }, // Waiting state. { - container: api.Container{Name: "test", Image: "image"}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image"}, + status: corev1.ContainerStatus{ Name: "test", - State: api.ContainerState{ - Waiting: &api.ContainerStateWaiting{ + State: corev1.ContainerState{ + Waiting: &corev1.ContainerStateWaiting{ Reason: "potato", }, }, @@ -520,11 +519,11 @@ func TestDescribeContainers(t *testing.T) { }, // Terminated state. { - container: api.Container{Name: "test", Image: "image"}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image"}, + status: corev1.ContainerStatus{ Name: "test", - State: api.ContainerState{ - Terminated: &api.ContainerStateTerminated{ + State: corev1.ContainerState{ + Terminated: &corev1.ContainerStateTerminated{ StartedAt: metav1.NewTime(time.Now()), FinishedAt: metav1.NewTime(time.Now()), Reason: "potato", @@ -538,16 +537,16 @@ func TestDescribeContainers(t *testing.T) { }, // Last Terminated { - container: api.Container{Name: "test", Image: "image"}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image"}, + status: corev1.ContainerStatus{ Name: "test", - State: api.ContainerState{ - Running: &api.ContainerStateRunning{ + State: corev1.ContainerState{ + Running: &corev1.ContainerStateRunning{ StartedAt: metav1.NewTime(time.Now()), }, }, - LastTerminationState: api.ContainerState{ - Terminated: &api.ContainerStateTerminated{ + LastTerminationState: corev1.ContainerState{ + Terminated: &corev1.ContainerStateTerminated{ StartedAt: metav1.NewTime(time.Now().Add(time.Second * 3)), FinishedAt: metav1.NewTime(time.Now()), Reason: "crashing", @@ -561,8 +560,8 @@ func TestDescribeContainers(t *testing.T) { }, // No state defaults to waiting. { - container: api.Container{Name: "test", Image: "image"}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image"}, + status: corev1.ContainerStatus{ Name: "test", Ready: true, RestartCount: 7, @@ -571,8 +570,8 @@ func TestDescribeContainers(t *testing.T) { }, // Env { - container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{ConfigMapRef: &api.ConfigMapEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image", Env: []corev1.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []corev1.EnvFromSource{{ConfigMapRef: &corev1.ConfigMapEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "a123"}}}}}, + status: corev1.ContainerStatus{ Name: "test", Ready: true, RestartCount: 7, @@ -580,8 +579,8 @@ func TestDescribeContainers(t *testing.T) { expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap\tOptional: false"}, }, { - container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{Prefix: "p_", ConfigMapRef: &api.ConfigMapEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image", Env: []corev1.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []corev1.EnvFromSource{{Prefix: "p_", ConfigMapRef: &corev1.ConfigMapEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "a123"}}}}}, + status: corev1.ContainerStatus{ Name: "test", Ready: true, RestartCount: 7, @@ -589,8 +588,8 @@ func TestDescribeContainers(t *testing.T) { expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap with prefix 'p_'\tOptional: false"}, }, { - container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{ConfigMapRef: &api.ConfigMapEnvSource{Optional: &trueVal, LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image", Env: []corev1.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []corev1.EnvFromSource{{ConfigMapRef: &corev1.ConfigMapEnvSource{Optional: &trueVal, LocalObjectReference: corev1.LocalObjectReference{Name: "a123"}}}}}, + status: corev1.ContainerStatus{ Name: "test", Ready: true, RestartCount: 7, @@ -598,8 +597,8 @@ func TestDescribeContainers(t *testing.T) { expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap\tOptional: true"}, }, { - container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{SecretRef: &api.SecretEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}, Optional: &trueVal}}}}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image", Env: []corev1.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []corev1.EnvFromSource{{SecretRef: &corev1.SecretEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "a123"}, Optional: &trueVal}}}}, + status: corev1.ContainerStatus{ Name: "test", Ready: true, RestartCount: 7, @@ -607,8 +606,8 @@ func TestDescribeContainers(t *testing.T) { expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tSecret\tOptional: true"}, }, { - container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{Prefix: "p_", SecretRef: &api.SecretEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image", Env: []corev1.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []corev1.EnvFromSource{{Prefix: "p_", SecretRef: &corev1.SecretEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "a123"}}}}}, + status: corev1.ContainerStatus{ Name: "test", Ready: true, RestartCount: 7, @@ -617,8 +616,8 @@ func TestDescribeContainers(t *testing.T) { }, // Command { - container: api.Container{Name: "test", Image: "image", Command: []string{"sleep", "1000"}}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image", Command: []string{"sleep", "1000"}}, + status: corev1.ContainerStatus{ Name: "test", Ready: true, RestartCount: 7, @@ -627,8 +626,8 @@ func TestDescribeContainers(t *testing.T) { }, // Command with newline { - container: api.Container{Name: "test", Image: "image", Command: []string{"sleep", "1000\n2000"}}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image", Command: []string{"sleep", "1000\n2000"}}, + status: corev1.ContainerStatus{ Name: "test", Ready: true, RestartCount: 7, @@ -637,8 +636,8 @@ func TestDescribeContainers(t *testing.T) { }, // Args { - container: api.Container{Name: "test", Image: "image", Args: []string{"time", "1000"}}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image", Args: []string{"time", "1000"}}, + status: corev1.ContainerStatus{ Name: "test", Ready: true, RestartCount: 7, @@ -647,8 +646,8 @@ func TestDescribeContainers(t *testing.T) { }, // Args with newline { - container: api.Container{Name: "test", Image: "image", Args: []string{"time", "1000\n2000"}}, - status: api.ContainerStatus{ + container: corev1.Container{Name: "test", Image: "image", Args: []string{"time", "1000\n2000"}}, + status: corev1.ContainerStatus{ Name: "test", Ready: true, RestartCount: 7, @@ -657,18 +656,18 @@ func TestDescribeContainers(t *testing.T) { }, // Using limits. { - container: api.Container{ + container: corev1.Container{ Name: "test", Image: "image", - Resources: api.ResourceRequirements{ - Limits: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("1000"), - api.ResourceName(api.ResourceMemory): resource.MustParse("4G"), - api.ResourceName(api.ResourceStorage): resource.MustParse("20G"), + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("1000"), + corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("4G"), + corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("20G"), }, }, }, - status: api.ContainerStatus{ + status: corev1.ContainerStatus{ Name: "test", Ready: true, RestartCount: 7, @@ -677,14 +676,14 @@ func TestDescribeContainers(t *testing.T) { }, // Using requests. { - container: api.Container{ + container: corev1.Container{ Name: "test", Image: "image", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("1000"), - api.ResourceName(api.ResourceMemory): resource.MustParse("4G"), - api.ResourceName(api.ResourceStorage): resource.MustParse("20G"), + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("1000"), + corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("4G"), + corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("20G"), }, }, }, @@ -692,10 +691,10 @@ func TestDescribeContainers(t *testing.T) { }, // volumeMounts read/write { - container: api.Container{ + container: corev1.Container{ Name: "test", Image: "image", - VolumeMounts: []api.VolumeMount{ + VolumeMounts: []corev1.VolumeMount{ { Name: "mounted-volume", MountPath: "/opt/", @@ -706,10 +705,10 @@ func TestDescribeContainers(t *testing.T) { }, // volumeMounts readonly { - container: api.Container{ + container: corev1.Container{ Name: "test", Image: "image", - VolumeMounts: []api.VolumeMount{ + VolumeMounts: []corev1.VolumeMount{ { Name: "mounted-volume", MountPath: "/opt/", @@ -722,10 +721,10 @@ func TestDescribeContainers(t *testing.T) { // volumeDevices { - container: api.Container{ + container: corev1.Container{ Name: "test", Image: "image", - VolumeDevices: []api.VolumeDevice{ + VolumeDevices: []corev1.VolumeDevice{ { Name: "volume-device", DevicePath: "/dev/xvda", @@ -739,12 +738,12 @@ func TestDescribeContainers(t *testing.T) { for i, testCase := range testCases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { out := new(bytes.Buffer) - pod := api.Pod{ - Spec: api.PodSpec{ - Containers: []api.Container{testCase.container}, + pod := corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{testCase.container}, }, - Status: api.PodStatus{ - ContainerStatuses: []api.ContainerStatus{testCase.status}, + Status: corev1.PodStatus{ + ContainerStatuses: []corev1.ContainerStatus{testCase.status}, }, } writer := NewPrefixWriter(out) @@ -760,13 +759,13 @@ func TestDescribeContainers(t *testing.T) { } func TestDescribers(t *testing.T) { - first := &api.Event{} - second := &api.Pod{} - var third *api.Pod + first := &corev1.Event{} + second := &corev1.Pod{} + var third *corev1.Pod testErr := fmt.Errorf("test") d := Describers{} d.Add( - func(e *api.Event, p *api.Pod) (string, error) { + func(e *corev1.Event, p *corev1.Pod) (string, error) { if e != first { t.Errorf("first argument not equal: %#v", e) } @@ -783,8 +782,8 @@ func TestDescribers(t *testing.T) { if out, err := d.DescribeObject(first, second, third); out != "" || err == nil { t.Errorf("unexpected result: %s %v", out, err) } else { - if noDescriber, ok := err.(printers.ErrNoDescriber); ok { - if !reflect.DeepEqual(noDescriber.Types, []string{"*core.Event", "*core.Pod", "*core.Pod"}) { + if noDescriber, ok := err.(describe.ErrNoDescriber); ok { + if !reflect.DeepEqual(noDescriber.Types, []string{"*v1.Event", "*v1.Pod", "*v1.Pod"}) { t.Errorf("unexpected describer: %v", err) } } else { @@ -793,7 +792,7 @@ func TestDescribers(t *testing.T) { } d.Add( - func(e *api.Event) (string, error) { + func(e *corev1.Event) (string, error) { if e != first { t.Errorf("first argument not equal: %#v", e) } @@ -806,7 +805,7 @@ func TestDescribers(t *testing.T) { } func TestDefaultDescribers(t *testing.T) { - out, err := DefaultObjectDescriber.DescribeObject(&api.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) + out, err := DefaultObjectDescriber.DescribeObject(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -814,7 +813,7 @@ func TestDefaultDescribers(t *testing.T) { t.Errorf("unexpected output: %s", out) } - out, err = DefaultObjectDescriber.DescribeObject(&api.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) + out, err = DefaultObjectDescriber.DescribeObject(&corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -822,7 +821,10 @@ func TestDefaultDescribers(t *testing.T) { t.Errorf("unexpected output: %s", out) } - out, err = DefaultObjectDescriber.DescribeObject(&api.ReplicationController{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) + out, err = DefaultObjectDescriber.DescribeObject(&corev1.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: corev1.ReplicationControllerSpec{Replicas: utilpointer.Int32Ptr(1)}, + }) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -830,7 +832,7 @@ func TestDefaultDescribers(t *testing.T) { t.Errorf("unexpected output: %s", out) } - out, err = DefaultObjectDescriber.DescribeObject(&api.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) + out, err = DefaultObjectDescriber.DescribeObject(&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -842,31 +844,31 @@ func TestDefaultDescribers(t *testing.T) { func TestGetPodsTotalRequests(t *testing.T) { testCases := []struct { name string - pods *api.PodList - expectedReqs map[api.ResourceName]resource.Quantity + pods *corev1.PodList + expectedReqs map[corev1.ResourceName]resource.Quantity }{ { name: "test1", - pods: &api.PodList{ - Items: []api.Pod{ + pods: &corev1.PodList{ + Items: []corev1.Pod{ { - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ { - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("1"), - api.ResourceName(api.ResourceMemory): resource.MustParse("300Mi"), - api.ResourceName(api.ResourceStorage): resource.MustParse("1G"), + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("1"), + corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("300Mi"), + corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("1G"), }, }, }, { - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("90m"), - api.ResourceName(api.ResourceMemory): resource.MustParse("120Mi"), - api.ResourceName(api.ResourceStorage): resource.MustParse("200M"), + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("90m"), + corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("120Mi"), + corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("200M"), }, }, }, @@ -874,23 +876,23 @@ func TestGetPodsTotalRequests(t *testing.T) { }, }, { - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ { - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("60m"), - api.ResourceName(api.ResourceMemory): resource.MustParse("43Mi"), - api.ResourceName(api.ResourceStorage): resource.MustParse("500M"), + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("60m"), + corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("43Mi"), + corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("500M"), }, }, }, { - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("34m"), - api.ResourceName(api.ResourceMemory): resource.MustParse("83Mi"), - api.ResourceName(api.ResourceStorage): resource.MustParse("700M"), + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("34m"), + corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("83Mi"), + corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("700M"), }, }, }, @@ -899,10 +901,10 @@ func TestGetPodsTotalRequests(t *testing.T) { }, }, }, - expectedReqs: map[api.ResourceName]resource.Quantity{ - api.ResourceName(api.ResourceCPU): resource.MustParse("1.184"), - api.ResourceName(api.ResourceMemory): resource.MustParse("546Mi"), - api.ResourceName(api.ResourceStorage): resource.MustParse("2.4G"), + expectedReqs: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("1.184"), + corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("546Mi"), + corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("2.4G"), }, }, } @@ -918,24 +920,24 @@ func TestGetPodsTotalRequests(t *testing.T) { } func TestPersistentVolumeDescriber(t *testing.T) { - block := api.PersistentVolumeBlock - file := api.PersistentVolumeFilesystem + block := corev1.PersistentVolumeBlock + file := corev1.PersistentVolumeFilesystem deletionTimestamp := metav1.Time{Time: time.Now().UTC().AddDate(10, 0, 0)} testCases := []struct { name string plugin string - pv *api.PersistentVolume + pv *corev1.PersistentVolume expectedElements []string unexpectedElements []string }{ { name: "test0", plugin: "hostpath", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - HostPath: &api.HostPathVolumeSource{Type: new(api.HostPathType)}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + HostPath: &corev1.HostPathVolumeSource{Type: new(corev1.HostPathType)}, }, }, }, @@ -944,11 +946,11 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test1", plugin: "gce", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + GCEPersistentDisk: &corev1.GCEPersistentDiskVolumeSource{}, }, VolumeMode: &file, }, @@ -958,11 +960,11 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test2", plugin: "ebs", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + AWSElasticBlockStore: &corev1.AWSElasticBlockStoreVolumeSource{}, }, }, }, @@ -971,11 +973,11 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test3", plugin: "nfs", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - NFS: &api.NFSVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + NFS: &corev1.NFSVolumeSource{}, }, }, }, @@ -984,11 +986,11 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test4", plugin: "iscsi", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - ISCSI: &api.ISCSIPersistentVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + ISCSI: &corev1.ISCSIPersistentVolumeSource{}, }, VolumeMode: &block, }, @@ -998,11 +1000,11 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test5", plugin: "gluster", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Glusterfs: &api.GlusterfsPersistentVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + Glusterfs: &corev1.GlusterfsPersistentVolumeSource{}, }, }, }, @@ -1011,11 +1013,11 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test6", plugin: "rbd", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - RBD: &api.RBDPersistentVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + RBD: &corev1.RBDPersistentVolumeSource{}, }, }, }, @@ -1024,11 +1026,11 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test7", plugin: "quobyte", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Quobyte: &api.QuobyteVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + Quobyte: &corev1.QuobyteVolumeSource{}, }, }, }, @@ -1037,11 +1039,11 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test8", plugin: "cinder", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Cinder: &api.CinderPersistentVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + Cinder: &corev1.CinderPersistentVolumeSource{}, }, }, }, @@ -1050,11 +1052,11 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test9", plugin: "fc", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - FC: &api.FCVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + FC: &corev1.FCVolumeSource{}, }, VolumeMode: &block, }, @@ -1064,11 +1066,11 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test10", plugin: "local", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + Local: &corev1.LocalVolumeSource{}, }, }, }, @@ -1078,13 +1080,13 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test11", plugin: "local", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + Local: &corev1.LocalVolumeSource{}, }, - NodeAffinity: &api.VolumeNodeAffinity{}, + NodeAffinity: &corev1.VolumeNodeAffinity{}, }, }, expectedElements: []string{"Node Affinity: "}, @@ -1093,14 +1095,14 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test12", plugin: "local", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + Local: &corev1.LocalVolumeSource{}, }, - NodeAffinity: &api.VolumeNodeAffinity{ - Required: &api.NodeSelector{}, + NodeAffinity: &corev1.VolumeNodeAffinity{ + Required: &corev1.NodeSelector{}, }, }, }, @@ -1110,20 +1112,20 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test13", plugin: "local", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + Local: &corev1.LocalVolumeSource{}, }, - NodeAffinity: &api.VolumeNodeAffinity{ - Required: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ + NodeAffinity: &corev1.VolumeNodeAffinity{ + Required: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ { - MatchExpressions: []api.NodeSelectorRequirement{}, + MatchExpressions: []corev1.NodeSelectorRequirement{}, }, { - MatchExpressions: []api.NodeSelectorRequirement{}, + MatchExpressions: []corev1.NodeSelectorRequirement{}, }, }, }, @@ -1135,17 +1137,17 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test14", plugin: "local", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "bar"}, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + Local: &corev1.LocalVolumeSource{}, }, - NodeAffinity: &api.VolumeNodeAffinity{ - Required: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ + NodeAffinity: &corev1.VolumeNodeAffinity{ + Required: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ { - MatchExpressions: []api.NodeSelectorRequirement{ + MatchExpressions: []corev1.NodeSelectorRequirement{ { Key: "foo", Operator: "In", @@ -1169,14 +1171,14 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test15", plugin: "local", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", DeletionTimestamp: &deletionTimestamp, }, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + Local: &corev1.LocalVolumeSource{}, }, }, }, @@ -1185,7 +1187,7 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test16", plugin: "local", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", GenerateName: "test-GenerateName", @@ -1196,15 +1198,15 @@ func TestPersistentVolumeDescriber(t *testing.T) { Labels: map[string]string{"label1": "label1", "label2": "label2", "label3": "label3"}, Annotations: map[string]string{"annotation1": "annotation1", "annotation2": "annotation2", "annotation3": "annotation3"}, }, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - Local: &api.LocalVolumeSource{}, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + Local: &corev1.LocalVolumeSource{}, }, - NodeAffinity: &api.VolumeNodeAffinity{ - Required: &api.NodeSelector{ - NodeSelectorTerms: []api.NodeSelectorTerm{ + NodeAffinity: &corev1.VolumeNodeAffinity{ + Required: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ { - MatchExpressions: []api.NodeSelectorRequirement{ + MatchExpressions: []corev1.NodeSelectorRequirement{ { Key: "foo", Operator: "In", @@ -1228,7 +1230,7 @@ func TestPersistentVolumeDescriber(t *testing.T) { { name: "test17", plugin: "local", - pv: &api.PersistentVolume{ + pv: &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", GenerateName: "test-GenerateName", @@ -1239,9 +1241,9 @@ func TestPersistentVolumeDescriber(t *testing.T) { Labels: map[string]string{"label1": "label1", "label2": "label2", "label3": "label3"}, Annotations: map[string]string{"annotation1": "annotation1", "annotation2": "annotation2", "annotation3": "annotation3"}, }, - Spec: api.PersistentVolumeSpec{ - PersistentVolumeSource: api.PersistentVolumeSource{ - CSI: &api.CSIPersistentVolumeSource{ + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeSource: corev1.PersistentVolumeSource{ + CSI: &corev1.CSIPersistentVolumeSource{ Driver: "drive", VolumeHandle: "handler", ReadOnly: true, @@ -1262,7 +1264,7 @@ func TestPersistentVolumeDescriber(t *testing.T) { t.Run(test.name, func(t *testing.T) { fake := fake.NewSimpleClientset(test.pv) c := PersistentVolumeDescriber{fake} - str, err := c.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + str, err := c.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("Unexpected error for test %s: %v", test.plugin, err) } @@ -1284,57 +1286,57 @@ func TestPersistentVolumeDescriber(t *testing.T) { } func TestPersistentVolumeClaimDescriber(t *testing.T) { - block := api.PersistentVolumeBlock - file := api.PersistentVolumeFilesystem + block := corev1.PersistentVolumeBlock + file := corev1.PersistentVolumeFilesystem goldClassName := "gold" now := time.Now() deletionTimestamp := metav1.Time{Time: time.Now().UTC().AddDate(10, 0, 0)} testCases := []struct { name string - pvc *api.PersistentVolumeClaim + pvc *corev1.PersistentVolumeClaim expectedElements []string unexpectedElements []string }{ { name: "default", - pvc: &api.PersistentVolumeClaim{ + pvc: &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}, - Spec: api.PersistentVolumeClaimSpec{ + Spec: corev1.PersistentVolumeClaimSpec{ VolumeName: "volume1", StorageClassName: &goldClassName, }, - Status: api.PersistentVolumeClaimStatus{ - Phase: api.ClaimBound, + Status: corev1.PersistentVolumeClaimStatus{ + Phase: corev1.ClaimBound, }, }, unexpectedElements: []string{"VolumeMode", "Filesystem"}, }, { name: "filesystem", - pvc: &api.PersistentVolumeClaim{ + pvc: &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}, - Spec: api.PersistentVolumeClaimSpec{ + Spec: corev1.PersistentVolumeClaimSpec{ VolumeName: "volume2", StorageClassName: &goldClassName, VolumeMode: &file, }, - Status: api.PersistentVolumeClaimStatus{ - Phase: api.ClaimBound, + Status: corev1.PersistentVolumeClaimStatus{ + Phase: corev1.ClaimBound, }, }, expectedElements: []string{"VolumeMode", "Filesystem"}, }, { name: "block", - pvc: &api.PersistentVolumeClaim{ + pvc: &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}, - Spec: api.PersistentVolumeClaimSpec{ + Spec: corev1.PersistentVolumeClaimSpec{ VolumeName: "volume3", StorageClassName: &goldClassName, VolumeMode: &block, }, - Status: api.PersistentVolumeClaimStatus{ - Phase: api.ClaimBound, + Status: corev1.PersistentVolumeClaimStatus{ + Phase: corev1.ClaimBound, }, }, expectedElements: []string{"VolumeMode", "Block"}, @@ -1342,15 +1344,15 @@ func TestPersistentVolumeClaimDescriber(t *testing.T) { // Tests for Status.Condition. { name: "condition-type", - pvc: &api.PersistentVolumeClaim{ + pvc: &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}, - Spec: api.PersistentVolumeClaimSpec{ + Spec: corev1.PersistentVolumeClaimSpec{ VolumeName: "volume4", StorageClassName: &goldClassName, }, - Status: api.PersistentVolumeClaimStatus{ - Conditions: []api.PersistentVolumeClaimCondition{ - {Type: api.PersistentVolumeClaimResizing}, + Status: corev1.PersistentVolumeClaimStatus{ + Conditions: []corev1.PersistentVolumeClaimCondition{ + {Type: corev1.PersistentVolumeClaimResizing}, }, }, }, @@ -1358,15 +1360,15 @@ func TestPersistentVolumeClaimDescriber(t *testing.T) { }, { name: "condition-status", - pvc: &api.PersistentVolumeClaim{ + pvc: &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}, - Spec: api.PersistentVolumeClaimSpec{ + Spec: corev1.PersistentVolumeClaimSpec{ VolumeName: "volume5", StorageClassName: &goldClassName, }, - Status: api.PersistentVolumeClaimStatus{ - Conditions: []api.PersistentVolumeClaimCondition{ - {Status: api.ConditionTrue}, + Status: corev1.PersistentVolumeClaimStatus{ + Conditions: []corev1.PersistentVolumeClaimCondition{ + {Status: corev1.ConditionTrue}, }, }, }, @@ -1374,14 +1376,14 @@ func TestPersistentVolumeClaimDescriber(t *testing.T) { }, { name: "condition-last-probe-time", - pvc: &api.PersistentVolumeClaim{ + pvc: &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}, - Spec: api.PersistentVolumeClaimSpec{ + Spec: corev1.PersistentVolumeClaimSpec{ VolumeName: "volume6", StorageClassName: &goldClassName, }, - Status: api.PersistentVolumeClaimStatus{ - Conditions: []api.PersistentVolumeClaimCondition{ + Status: corev1.PersistentVolumeClaimStatus{ + Conditions: []corev1.PersistentVolumeClaimCondition{ {LastProbeTime: metav1.Time{Time: now}}, }, }, @@ -1390,14 +1392,14 @@ func TestPersistentVolumeClaimDescriber(t *testing.T) { }, { name: "condition-last-transition-time", - pvc: &api.PersistentVolumeClaim{ + pvc: &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}, - Spec: api.PersistentVolumeClaimSpec{ + Spec: corev1.PersistentVolumeClaimSpec{ VolumeName: "volume7", StorageClassName: &goldClassName, }, - Status: api.PersistentVolumeClaimStatus{ - Conditions: []api.PersistentVolumeClaimCondition{ + Status: corev1.PersistentVolumeClaimStatus{ + Conditions: []corev1.PersistentVolumeClaimCondition{ {LastTransitionTime: metav1.Time{Time: now}}, }, }, @@ -1406,14 +1408,14 @@ func TestPersistentVolumeClaimDescriber(t *testing.T) { }, { name: "condition-reason", - pvc: &api.PersistentVolumeClaim{ + pvc: &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}, - Spec: api.PersistentVolumeClaimSpec{ + Spec: corev1.PersistentVolumeClaimSpec{ VolumeName: "volume8", StorageClassName: &goldClassName, }, - Status: api.PersistentVolumeClaimStatus{ - Conditions: []api.PersistentVolumeClaimCondition{ + Status: corev1.PersistentVolumeClaimStatus{ + Conditions: []corev1.PersistentVolumeClaimCondition{ {Reason: "OfflineResize"}, }, }, @@ -1422,14 +1424,14 @@ func TestPersistentVolumeClaimDescriber(t *testing.T) { }, { name: "condition-message", - pvc: &api.PersistentVolumeClaim{ + pvc: &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}, - Spec: api.PersistentVolumeClaimSpec{ + Spec: corev1.PersistentVolumeClaimSpec{ VolumeName: "volume9", StorageClassName: &goldClassName, }, - Status: api.PersistentVolumeClaimStatus{ - Conditions: []api.PersistentVolumeClaimCondition{ + Status: corev1.PersistentVolumeClaimStatus{ + Conditions: []corev1.PersistentVolumeClaimCondition{ {Message: "User request resize"}, }, }, @@ -1438,17 +1440,17 @@ func TestPersistentVolumeClaimDescriber(t *testing.T) { }, { name: "deletion-timestamp", - pvc: &api.PersistentVolumeClaim{ + pvc: &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Namespace: "foo", Name: "bar", DeletionTimestamp: &deletionTimestamp, }, - Spec: api.PersistentVolumeClaimSpec{ + Spec: corev1.PersistentVolumeClaimSpec{ VolumeName: "volume10", StorageClassName: &goldClassName, }, - Status: api.PersistentVolumeClaimStatus{}, + Status: corev1.PersistentVolumeClaimStatus{}, }, expectedElements: []string{"Terminating (lasts 10y)"}, }, @@ -1458,7 +1460,7 @@ func TestPersistentVolumeClaimDescriber(t *testing.T) { t.Run(test.name, func(t *testing.T) { fake := fake.NewSimpleClientset(test.pvc) c := PersistentVolumeClaimDescriber{fake} - str, err := c.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + str, err := c.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("Unexpected error for test %s: %v", test.name, err) } @@ -1499,7 +1501,7 @@ func TestDescribeDeployment(t *testing.T) { }, }) d := DeploymentDescriber{fake, versionedFake} - out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -1509,9 +1511,9 @@ func TestDescribeDeployment(t *testing.T) { } func TestDescribeStorageClass(t *testing.T) { - reclaimPolicy := api.PersistentVolumeReclaimRetain - bindingMode := storage.VolumeBindingMode("bindingmode") - f := fake.NewSimpleClientset(&storage.StorageClass{ + reclaimPolicy := corev1.PersistentVolumeReclaimRetain + bindingMode := storagev1.VolumeBindingMode("bindingmode") + f := fake.NewSimpleClientset(&storagev1.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", ResourceVersion: "4", @@ -1526,9 +1528,9 @@ func TestDescribeStorageClass(t *testing.T) { }, ReclaimPolicy: &reclaimPolicy, VolumeBindingMode: &bindingMode, - AllowedTopologies: []api.TopologySelectorTerm{ + AllowedTopologies: []corev1.TopologySelectorTerm{ { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ + MatchLabelExpressions: []corev1.TopologySelectorLabelRequirement{ { Key: "failure-domain.beta.kubernetes.io/zone", Values: []string{"zone1"}, @@ -1540,7 +1542,7 @@ func TestDescribeStorageClass(t *testing.T) { }, }, { - MatchLabelExpressions: []api.TopologySelectorLabelRequirement{ + MatchLabelExpressions: []corev1.TopologySelectorLabelRequirement{ { Key: "failure-domain.beta.kubernetes.io/zone", Values: []string{"zone2"}, @@ -1554,7 +1556,7 @@ func TestDescribeStorageClass(t *testing.T) { }, }) s := StorageClassDescriber{f} - out, err := s.Describe("", "foo", printers.DescriberSettings{ShowEvents: true}) + out, err := s.Describe("", "foo", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -1578,21 +1580,21 @@ func TestDescribeStorageClass(t *testing.T) { func TestDescribePodDisruptionBudget(t *testing.T) { minAvailable := intstr.FromInt(22) - f := fake.NewSimpleClientset(&policy.PodDisruptionBudget{ + f := fake.NewSimpleClientset(&policyv1beta1.PodDisruptionBudget{ ObjectMeta: metav1.ObjectMeta{ Namespace: "ns1", Name: "pdb1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, - Spec: policy.PodDisruptionBudgetSpec{ + Spec: policyv1beta1.PodDisruptionBudgetSpec{ MinAvailable: &minAvailable, }, - Status: policy.PodDisruptionBudgetStatus{ + Status: policyv1beta1.PodDisruptionBudgetStatus{ PodDisruptionsAllowed: 5, }, }) s := PodDisruptionBudgetDescriber{f} - out, err := s.Describe("ns1", "pdb1", printers.DescriberSettings{ShowEvents: true}) + out, err := s.Describe("ns1", "pdb1", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -1614,19 +1616,19 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { } tests := []struct { name string - hpa autoscaling.HorizontalPodAutoscaler + hpa autoscalingv2beta2.HorizontalPodAutoscaler }{ { "minReplicas unset", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MaxReplicas: 10, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, @@ -1634,31 +1636,31 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "external source type, target average value (no current)", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ + Type: autoscalingv2beta2.ExternalMetricSourceType, + External: &autoscalingv2beta2.ExternalMetricSource{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-external-metric", Selector: metricLabelSelector, }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, @@ -1666,42 +1668,42 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "external source type, target average value (with current)", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ + Type: autoscalingv2beta2.ExternalMetricSourceType, + External: &autoscalingv2beta2.ExternalMetricSource{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-external-metric", Selector: metricLabelSelector, }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, - CurrentMetrics: []autoscaling.MetricStatus{ + CurrentMetrics: []autoscalingv2beta2.MetricStatus{ { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricStatus{ - Metric: autoscaling.MetricIdentifier{ + Type: autoscalingv2beta2.ExternalMetricSourceType, + External: &autoscalingv2beta2.ExternalMetricStatus{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-external-metric", Selector: metricLabelSelector, }, - Current: autoscaling.MetricValueStatus{ + Current: autoscalingv2beta2.MetricValueStatus{ AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, @@ -1712,31 +1714,31 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "external source type, target value (no current)", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ + Type: autoscalingv2beta2.ExternalMetricSourceType, + External: &autoscalingv2beta2.ExternalMetricSource{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-external-metric", Selector: metricLabelSelector, }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.ValueMetricType, Value: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, @@ -1744,42 +1746,42 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "external source type, target value (with current)", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricSource{ - Metric: autoscaling.MetricIdentifier{ + Type: autoscalingv2beta2.ExternalMetricSourceType, + External: &autoscalingv2beta2.ExternalMetricSource{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-external-metric", Selector: metricLabelSelector, }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.ValueMetricType, Value: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, - CurrentMetrics: []autoscaling.MetricStatus{ + CurrentMetrics: []autoscalingv2beta2.MetricStatus{ { - Type: autoscaling.ExternalMetricSourceType, - External: &autoscaling.ExternalMetricStatus{ - Metric: autoscaling.MetricIdentifier{ + Type: autoscalingv2beta2.ExternalMetricSourceType, + External: &autoscalingv2beta2.ExternalMetricStatus{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-external-metric", Selector: metricLabelSelector, }, - Current: autoscaling.MetricValueStatus{ + Current: autoscalingv2beta2.MetricValueStatus{ Value: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, @@ -1790,30 +1792,30 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "pods source type (no current)", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.PodsMetricSourceType, - Pods: &autoscaling.PodsMetricSource{ - Metric: autoscaling.MetricIdentifier{ + Type: autoscalingv2beta2.PodsMetricSourceType, + Pods: &autoscalingv2beta2.PodsMetricSource{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-pods-metric", }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, @@ -1821,40 +1823,40 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "pods source type (with current)", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.PodsMetricSourceType, - Pods: &autoscaling.PodsMetricSource{ - Metric: autoscaling.MetricIdentifier{ + Type: autoscalingv2beta2.PodsMetricSourceType, + Pods: &autoscalingv2beta2.PodsMetricSource{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-pods-metric", }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, - CurrentMetrics: []autoscaling.MetricStatus{ + CurrentMetrics: []autoscalingv2beta2.MetricStatus{ { - Type: autoscaling.PodsMetricSourceType, - Pods: &autoscaling.PodsMetricStatus{ - Metric: autoscaling.MetricIdentifier{ + Type: autoscalingv2beta2.PodsMetricSourceType, + Pods: &autoscalingv2beta2.PodsMetricStatus{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-pods-metric", }, - Current: autoscaling.MetricValueStatus{ + Current: autoscalingv2beta2.MetricValueStatus{ AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, @@ -1865,34 +1867,34 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "object source type (no current)", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.ObjectMetricSourceType, - Object: &autoscaling.ObjectMetricSource{ - DescribedObject: autoscaling.CrossVersionObjectReference{ + Type: autoscalingv2beta2.ObjectMetricSourceType, + Object: &autoscalingv2beta2.ObjectMetricSource{ + DescribedObject: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-service", Kind: "Service", }, - Metric: autoscaling.MetricIdentifier{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-service-metric", }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.ValueMetricType, Value: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, @@ -1900,48 +1902,48 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "object source type (with current)", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.ObjectMetricSourceType, - Object: &autoscaling.ObjectMetricSource{ - DescribedObject: autoscaling.CrossVersionObjectReference{ + Type: autoscalingv2beta2.ObjectMetricSourceType, + Object: &autoscalingv2beta2.ObjectMetricSource{ + DescribedObject: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-service", Kind: "Service", }, - Metric: autoscaling.MetricIdentifier{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-service-metric", }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.ValueMetricType, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.ValueMetricType, Value: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, - CurrentMetrics: []autoscaling.MetricStatus{ + CurrentMetrics: []autoscalingv2beta2.MetricStatus{ { - Type: autoscaling.ObjectMetricSourceType, - Object: &autoscaling.ObjectMetricStatus{ - DescribedObject: autoscaling.CrossVersionObjectReference{ + Type: autoscalingv2beta2.ObjectMetricSourceType, + Object: &autoscalingv2beta2.ObjectMetricStatus{ + DescribedObject: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-service", Kind: "Service", }, - Metric: autoscaling.MetricIdentifier{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-service-metric", }, - Current: autoscaling.MetricValueStatus{ + Current: autoscalingv2beta2.MetricValueStatus{ Value: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, @@ -1952,28 +1954,28 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "resource source type, target average value (no current)", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, + Type: autoscalingv2beta2.ResourceMetricSourceType, + Resource: &autoscalingv2beta2.ResourceMetricSource{ + Name: corev1.ResourceCPU, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, @@ -1981,36 +1983,36 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "resource source type, target average value (with current)", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, + Type: autoscalingv2beta2.ResourceMetricSourceType, + Resource: &autoscalingv2beta2.ResourceMetricSource{ + Name: corev1.ResourceCPU, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, - CurrentMetrics: []autoscaling.MetricStatus{ + CurrentMetrics: []autoscalingv2beta2.MetricStatus{ { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricStatus{ - Name: api.ResourceCPU, - Current: autoscaling.MetricValueStatus{ + Type: autoscalingv2beta2.ResourceMetricSourceType, + Resource: &autoscalingv2beta2.ResourceMetricStatus{ + Name: corev1.ResourceCPU, + Current: autoscalingv2beta2.MetricValueStatus{ AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, @@ -2021,28 +2023,28 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "resource source type, target utilization (no current)", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, + Type: autoscalingv2beta2.ResourceMetricSourceType, + Resource: &autoscalingv2beta2.ResourceMetricSource{ + Name: corev1.ResourceCPU, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.UtilizationMetricType, AverageUtilization: &targetUtilizationVal, }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, @@ -2050,36 +2052,36 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "resource source type, target utilization (with current)", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, + Type: autoscalingv2beta2.ResourceMetricSourceType, + Resource: &autoscalingv2beta2.ResourceMetricSource{ + Name: corev1.ResourceCPU, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.UtilizationMetricType, AverageUtilization: &targetUtilizationVal, }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, - CurrentMetrics: []autoscaling.MetricStatus{ + CurrentMetrics: []autoscalingv2beta2.MetricStatus{ { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricStatus{ - Name: api.ResourceCPU, - Current: autoscaling.MetricValueStatus{ + Type: autoscalingv2beta2.ResourceMetricSourceType, + Resource: &autoscalingv2beta2.ResourceMetricStatus{ + Name: corev1.ResourceCPU, + Current: autoscalingv2beta2.MetricValueStatus{ AverageUtilization: ¤tUtilizationVal, AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI), }, @@ -2091,71 +2093,71 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { }, { "multiple metrics", - autoscaling.HorizontalPodAutoscaler{ - Spec: autoscaling.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscaling.CrossVersionObjectReference{ + autoscalingv2beta2.HorizontalPodAutoscaler{ + Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, - Metrics: []autoscaling.MetricSpec{ + Metrics: []autoscalingv2beta2.MetricSpec{ { - Type: autoscaling.PodsMetricSourceType, - Pods: &autoscaling.PodsMetricSource{ - Metric: autoscaling.MetricIdentifier{ + Type: autoscalingv2beta2.PodsMetricSourceType, + Pods: &autoscalingv2beta2.PodsMetricSource{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-pods-metric", }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricSource{ - Name: api.ResourceCPU, - Target: autoscaling.MetricTarget{ - Type: autoscaling.UtilizationMetricType, + Type: autoscalingv2beta2.ResourceMetricSourceType, + Resource: &autoscalingv2beta2.ResourceMetricSource{ + Name: corev1.ResourceCPU, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.UtilizationMetricType, AverageUtilization: &targetUtilizationVal, }, }, }, { - Type: autoscaling.PodsMetricSourceType, - Pods: &autoscaling.PodsMetricSource{ - Metric: autoscaling.MetricIdentifier{ + Type: autoscalingv2beta2.PodsMetricSourceType, + Pods: &autoscalingv2beta2.PodsMetricSource{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "other-pods-metric", }, - Target: autoscaling.MetricTarget{ - Type: autoscaling.AverageValueMetricType, + Target: autoscalingv2beta2.MetricTarget{ + Type: autoscalingv2beta2.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(400, resource.DecimalSI), }, }, }, }, }, - Status: autoscaling.HorizontalPodAutoscalerStatus{ + Status: autoscalingv2beta2.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, - CurrentMetrics: []autoscaling.MetricStatus{ + CurrentMetrics: []autoscalingv2beta2.MetricStatus{ { - Type: autoscaling.PodsMetricSourceType, - Pods: &autoscaling.PodsMetricStatus{ - Metric: autoscaling.MetricIdentifier{ + Type: autoscalingv2beta2.PodsMetricSourceType, + Pods: &autoscalingv2beta2.PodsMetricStatus{ + Metric: autoscalingv2beta2.MetricIdentifier{ Name: "some-pods-metric", }, - Current: autoscaling.MetricValueStatus{ + Current: autoscalingv2beta2.MetricValueStatus{ AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, }, { - Type: autoscaling.ResourceMetricSourceType, - Resource: &autoscaling.ResourceMetricStatus{ - Name: api.ResourceCPU, - Current: autoscaling.MetricValueStatus{ + Type: autoscalingv2beta2.ResourceMetricSourceType, + Resource: &autoscalingv2beta2.ResourceMetricStatus{ + Name: corev1.ResourceCPU, + Current: autoscalingv2beta2.MetricValueStatus{ AverageUtilization: ¤tUtilizationVal, AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI), }, @@ -2175,7 +2177,7 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { } fake := fake.NewSimpleClientset(&test.hpa) desc := HorizontalPodAutoscalerDescriber{fake} - str, err := desc.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + str, err := desc.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("Unexpected error for test %s: %v", test.name, err) } @@ -2189,25 +2191,25 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) { func TestDescribeEvents(t *testing.T) { - events := &api.EventList{ - Items: []api.Event{ + events := &corev1.EventList{ + Items: []corev1.Event{ { ObjectMeta: metav1.ObjectMeta{ Namespace: "foo", }, - Source: api.EventSource{Component: "kubelet"}, + Source: corev1.EventSource{Component: "kubelet"}, Message: "Item 1", FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), LastTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), Count: 1, - Type: api.EventTypeNormal, + Type: corev1.EventTypeNormal, }, }, } - m := map[string]printers.Describer{ + m := map[string]describe.Describer{ "DaemonSetDescriber": &DaemonSetDescriber{ - fake.NewSimpleClientset(&apps.DaemonSet{ + fake.NewSimpleClientset(&appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", @@ -2228,7 +2230,7 @@ func TestDescribeEvents(t *testing.T) { }), }, "EndpointsDescriber": &EndpointsDescriber{ - fake.NewSimpleClientset(&api.Endpoints{ + fake.NewSimpleClientset(&corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", @@ -2239,48 +2241,54 @@ func TestDescribeEvents(t *testing.T) { // - IngressDescriber // - JobDescriber "NodeDescriber": &NodeDescriber{ - fake.NewSimpleClientset(&api.Node{ + fake.NewSimpleClientset(&corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", - SelfLink: "url/url/url", + SelfLink: "url/url/url/url", }, }, events), }, "PersistentVolumeDescriber": &PersistentVolumeDescriber{ - fake.NewSimpleClientset(&api.PersistentVolume{ + fake.NewSimpleClientset(&corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", - SelfLink: "url/url/url", + SelfLink: "url/url/url/url", }, }, events), }, "PodDescriber": &PodDescriber{ - fake.NewSimpleClientset(&api.Pod{ + fake.NewSimpleClientset(&corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", - SelfLink: "url/url/url", + SelfLink: "url/url/url/url", }, }, events), }, "ReplicaSetDescriber": &ReplicaSetDescriber{ - fake.NewSimpleClientset(&apps.ReplicaSet{ + fake.NewSimpleClientset(&appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", }, + Spec: appsv1.ReplicaSetSpec{ + Replicas: utilpointer.Int32Ptr(1), + }, }, events), }, "ReplicationControllerDescriber": &ReplicationControllerDescriber{ - fake.NewSimpleClientset(&api.ReplicationController{ + fake.NewSimpleClientset(&corev1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", }, + Spec: corev1.ReplicationControllerSpec{ + Replicas: utilpointer.Int32Ptr(1), + }, }, events), }, "Service": &ServiceDescriber{ - fake.NewSimpleClientset(&api.Service{ + fake.NewSimpleClientset(&corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", @@ -2288,14 +2296,14 @@ func TestDescribeEvents(t *testing.T) { }, events), }, "StorageClass": &StorageClassDescriber{ - fake.NewSimpleClientset(&storage.StorageClass{ + fake.NewSimpleClientset(&storagev1.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", }, }, events), }, "HorizontalPodAutoscaler": &HorizontalPodAutoscalerDescriber{ - fake.NewSimpleClientset(&autoscaling.HorizontalPodAutoscaler{ + fake.NewSimpleClientset(&autoscalingv2beta2.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", @@ -2303,7 +2311,7 @@ func TestDescribeEvents(t *testing.T) { }, events), }, "ConfigMap": &ConfigMapDescriber{ - fake.NewSimpleClientset(&api.ConfigMap{ + fake.NewSimpleClientset(&corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", @@ -2314,7 +2322,7 @@ func TestDescribeEvents(t *testing.T) { for name, d := range m { t.Run(name, func(t *testing.T) { - out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error for %q: %v", name, err) } @@ -2325,7 +2333,7 @@ func TestDescribeEvents(t *testing.T) { t.Errorf("events not found for %q when ShowEvents=true: %s", name, out) } - out, err = d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: false}) + out, err = d.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: false}) if err != nil { t.Errorf("unexpected error for %q: %s", name, err) } @@ -2493,31 +2501,31 @@ func TestDescribePodSecurityPolicy(t *testing.T) { "Supplemental Groups Strategy: RunAsAny", } - fake := fake.NewSimpleClientset(&policy.PodSecurityPolicy{ + fake := fake.NewSimpleClientset(&policyv1beta1.PodSecurityPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "mypsp", }, - Spec: policy.PodSecurityPolicySpec{ + Spec: policyv1beta1.PodSecurityPolicySpec{ AllowedUnsafeSysctls: []string{"kernel.*", "net.ipv4.ip_local_port_range"}, ForbiddenSysctls: []string{"net.ipv4.ip_default_ttl"}, - SELinux: policy.SELinuxStrategyOptions{ - Rule: policy.SELinuxStrategyRunAsAny, + SELinux: policyv1beta1.SELinuxStrategyOptions{ + Rule: policyv1beta1.SELinuxStrategyRunAsAny, }, - RunAsUser: policy.RunAsUserStrategyOptions{ - Rule: policy.RunAsUserStrategyRunAsAny, + RunAsUser: policyv1beta1.RunAsUserStrategyOptions{ + Rule: policyv1beta1.RunAsUserStrategyRunAsAny, }, - FSGroup: policy.FSGroupStrategyOptions{ - Rule: policy.FSGroupStrategyRunAsAny, + FSGroup: policyv1beta1.FSGroupStrategyOptions{ + Rule: policyv1beta1.FSGroupStrategyRunAsAny, }, - SupplementalGroups: policy.SupplementalGroupsStrategyOptions{ - Rule: policy.SupplementalGroupsStrategyRunAsAny, + SupplementalGroups: policyv1beta1.SupplementalGroupsStrategyOptions{ + Rule: policyv1beta1.SupplementalGroupsStrategyRunAsAny, }, }, }) c := &describeClient{T: t, Namespace: "", Interface: fake} d := PodSecurityPolicyDescriber{c} - out, err := d.Describe("", "mypsp", printers.DescriberSettings{}) + out, err := d.Describe("", "mypsp", describe.DescriberSettings{}) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -2530,33 +2538,33 @@ func TestDescribePodSecurityPolicy(t *testing.T) { } func TestDescribeResourceQuota(t *testing.T) { - fake := fake.NewSimpleClientset(&api.ResourceQuota{ + fake := fake.NewSimpleClientset(&corev1.ResourceQuota{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", }, - Status: api.ResourceQuotaStatus{ - Hard: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("1"), - api.ResourceName(api.ResourceLimitsCPU): resource.MustParse("2"), - api.ResourceName(api.ResourceLimitsMemory): resource.MustParse("2G"), - api.ResourceName(api.ResourceMemory): resource.MustParse("1G"), - api.ResourceName(api.ResourceRequestsCPU): resource.MustParse("1"), - api.ResourceName(api.ResourceRequestsMemory): resource.MustParse("1G"), + Status: corev1.ResourceQuotaStatus{ + Hard: corev1.ResourceList{ + corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("1"), + corev1.ResourceName(corev1.ResourceLimitsCPU): resource.MustParse("2"), + corev1.ResourceName(corev1.ResourceLimitsMemory): resource.MustParse("2G"), + corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("1G"), + corev1.ResourceName(corev1.ResourceRequestsCPU): resource.MustParse("1"), + corev1.ResourceName(corev1.ResourceRequestsMemory): resource.MustParse("1G"), }, - Used: api.ResourceList{ - api.ResourceName(api.ResourceCPU): resource.MustParse("0"), - api.ResourceName(api.ResourceLimitsCPU): resource.MustParse("0"), - api.ResourceName(api.ResourceLimitsMemory): resource.MustParse("0G"), - api.ResourceName(api.ResourceMemory): resource.MustParse("0G"), - api.ResourceName(api.ResourceRequestsCPU): resource.MustParse("0"), - api.ResourceName(api.ResourceRequestsMemory): resource.MustParse("0G"), + Used: corev1.ResourceList{ + corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("0"), + corev1.ResourceName(corev1.ResourceLimitsCPU): resource.MustParse("0"), + corev1.ResourceName(corev1.ResourceLimitsMemory): resource.MustParse("0G"), + corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("0G"), + corev1.ResourceName(corev1.ResourceRequestsCPU): resource.MustParse("0"), + corev1.ResourceName(corev1.ResourceRequestsMemory): resource.MustParse("0G"), }, }, }) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := ResourceQuotaDescriber{c} - out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2623,15 +2631,15 @@ Spec: port80 := intstr.FromInt(80) port82 := intstr.FromInt(82) - protoTCP := api.ProtocolTCP + protoTCP := corev1.ProtocolTCP - versionedFake := fake.NewSimpleClientset(&networking.NetworkPolicy{ + versionedFake := fake.NewSimpleClientset(&networkingv1.NetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "network-policy-1", Namespace: "default", CreationTimestamp: metav1.NewTime(expectedTime), }, - Spec: networking.NetworkPolicySpec{ + Spec: networkingv1.NetworkPolicySpec{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "id1": "app1", @@ -2642,13 +2650,13 @@ Spec: {Key: "foo2", Operator: "NotIn", Values: []string{"bar1", "bar2"}}, }, }, - Ingress: []networking.NetworkPolicyIngressRule{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ { - Ports: []networking.NetworkPolicyPort{ + Ports: []networkingv1.NetworkPolicyPort{ {Port: &port80}, {Port: &port82, Protocol: &protoTCP}, }, - From: []networking.NetworkPolicyPeer{ + From: []networkingv1.NetworkPolicyPeer{ { PodSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -2691,7 +2699,7 @@ Spec: }, }, { - IPBlock: &networking.IPBlock{ + IPBlock: &networkingv1.IPBlock{ CIDR: "192.168.0.0/16", Except: []string{"192.168.3.0/24", "192.168.4.0/24"}, }, @@ -2700,13 +2708,13 @@ Spec: }, {}, }, - Egress: []networking.NetworkPolicyEgressRule{ + Egress: []networkingv1.NetworkPolicyEgressRule{ { - Ports: []networking.NetworkPolicyPort{ + Ports: []networkingv1.NetworkPolicyPort{ {Port: &port80}, {Port: &port82, Protocol: &protoTCP}, }, - To: []networking.NetworkPolicyPeer{ + To: []networkingv1.NetworkPolicyPeer{ { PodSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -2749,7 +2757,7 @@ Spec: }, }, { - IPBlock: &networking.IPBlock{ + IPBlock: &networkingv1.IPBlock{ CIDR: "192.168.0.0/16", Except: []string{"192.168.3.0/24", "192.168.4.0/24"}, }, @@ -2758,11 +2766,11 @@ Spec: }, {}, }, - PolicyTypes: []networking.PolicyType{networking.PolicyTypeIngress, networking.PolicyTypeEgress}, + PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress, networkingv1.PolicyTypeEgress}, }, }) d := NetworkPolicyDescriber{versionedFake} - out, err := d.Describe("", "network-policy-1", printers.DescriberSettings{}) + out, err := d.Describe("", "network-policy-1", describe.DescriberSettings{}) if err != nil { t.Errorf("unexpected error: %s", err) } @@ -2772,17 +2780,17 @@ Spec: } func TestDescribeServiceAccount(t *testing.T) { - fake := fake.NewSimpleClientset(&api.ServiceAccount{ + fake := fake.NewSimpleClientset(&corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", }, - Secrets: []api.ObjectReference{ + Secrets: []corev1.ObjectReference{ { Name: "test-objectref", }, }, - ImagePullSecrets: []api.LocalObjectReference{ + ImagePullSecrets: []corev1.LocalObjectReference{ { Name: "test-local-ref", }, @@ -2790,7 +2798,7 @@ func TestDescribeServiceAccount(t *testing.T) { }) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := ServiceAccountDescriber{c} - out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2809,18 +2817,18 @@ Events: ` + "\n" } func TestDescribeNode(t *testing.T) { - fake := fake.NewSimpleClientset(&api.Node{ + fake := fake.NewSimpleClientset(&corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", }, - Spec: api.NodeSpec{ + Spec: corev1.NodeSpec{ Unschedulable: true, }, }) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := NodeDescriber{c} - out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2835,31 +2843,33 @@ func TestDescribeNode(t *testing.T) { } func TestDescribeStatefulSet(t *testing.T) { - fake := fake.NewSimpleClientset(&apps.StatefulSet{ + var partition int32 = 2 + var replicas int32 = 1 + fake := fake.NewSimpleClientset(&appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", }, - Spec: apps.StatefulSetSpec{ - Replicas: 1, + Spec: appsv1.StatefulSetSpec{ + Replicas: &replicas, Selector: &metav1.LabelSelector{}, - Template: api.PodTemplateSpec{ - Spec: api.PodSpec{ - Containers: []api.Container{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ {Image: "mytest-image:latest"}, }, }, }, - UpdateStrategy: apps.StatefulSetUpdateStrategy{ - Type: apps.RollingUpdateStatefulSetStrategyType, - RollingUpdate: &apps.RollingUpdateStatefulSetStrategy{ - Partition: 2, + UpdateStrategy: appsv1.StatefulSetUpdateStrategy{ + Type: appsv1.RollingUpdateStatefulSetStrategyType, + RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{ + Partition: &partition, }, }, }, }) d := StatefulSetDescriber{fake} - out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: true}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: true}) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2881,8 +2891,9 @@ func boolPtr(b bool) *bool { } func TestControllerRef(t *testing.T) { + var replicas int32 = 1 f := fake.NewSimpleClientset( - &api.ReplicationController{ + &corev1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "foo", @@ -2891,19 +2902,19 @@ func TestControllerRef(t *testing.T) { TypeMeta: metav1.TypeMeta{ Kind: "ReplicationController", }, - Spec: api.ReplicationControllerSpec{ - Replicas: 1, + Spec: corev1.ReplicationControllerSpec{ + Replicas: &replicas, Selector: map[string]string{"abc": "xyz"}, - Template: &api.PodTemplateSpec{ - Spec: api.PodSpec{ - Containers: []api.Container{ + Template: &corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ {Image: "mytest-image:latest"}, }, }, }, }, }, - &api.Pod{ + &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "barpod", Namespace: "foo", @@ -2913,16 +2924,16 @@ func TestControllerRef(t *testing.T) { TypeMeta: metav1.TypeMeta{ Kind: "Pod", }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ {Image: "mytest-image:latest"}, }, }, - Status: api.PodStatus{ - Phase: api.PodRunning, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, }, }, - &api.Pod{ + &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "orphan", Namespace: "foo", @@ -2931,16 +2942,16 @@ func TestControllerRef(t *testing.T) { TypeMeta: metav1.TypeMeta{ Kind: "Pod", }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ {Image: "mytest-image:latest"}, }, }, - Status: api.PodStatus{ - Phase: api.PodRunning, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, }, }, - &api.Pod{ + &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "buzpod", Namespace: "foo", @@ -2950,17 +2961,17 @@ func TestControllerRef(t *testing.T) { TypeMeta: metav1.TypeMeta{ Kind: "Pod", }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ {Image: "mytest-image:latest"}, }, }, - Status: api.PodStatus{ - Phase: api.PodRunning, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, }, }) d := ReplicationControllerDescriber{f} - out, err := d.Describe("foo", "bar", printers.DescriberSettings{ShowEvents: false}) + out, err := d.Describe("foo", "bar", describe.DescriberSettings{ShowEvents: false}) if err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/pkg/kubectl/history.go b/pkg/kubectl/history.go index be49c2cf94..a1a4829aeb 100644 --- a/pkg/kubectl/history.go +++ b/pkg/kubectl/history.go @@ -34,12 +34,10 @@ import ( "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/client-go/kubernetes" clientappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1" - api "k8s.io/kubernetes/pkg/apis/core" - apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" kapps "k8s.io/kubernetes/pkg/kubectl/apps" + describe "k8s.io/kubernetes/pkg/kubectl/describe/versioned" deploymentutil "k8s.io/kubernetes/pkg/kubectl/util/deployment" sliceutil "k8s.io/kubernetes/pkg/kubectl/util/slice" - printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" ) const ( @@ -168,12 +166,8 @@ func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision i func printTemplate(template *corev1.PodTemplateSpec) (string, error) { buf := bytes.NewBuffer([]byte{}) - internalTemplate := &api.PodTemplateSpec{} - if err := apiv1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(template, internalTemplate, nil); err != nil { - return "", fmt.Errorf("failed to convert podtemplate, %v", err) - } - w := printersinternal.NewPrefixWriter(buf) - printersinternal.DescribePodTemplate(internalTemplate, w) + w := describe.NewPrefixWriter(buf) + describe.DescribePodTemplate(template, w) return buf.String(), nil } diff --git a/pkg/kubectl/util/BUILD b/pkg/kubectl/util/BUILD index f1d00a04a8..fc010ff599 100644 --- a/pkg/kubectl/util/BUILD +++ b/pkg/kubectl/util/BUILD @@ -66,13 +66,20 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", + "//pkg/kubectl/util/certificate:all-srcs", "//pkg/kubectl/util/deployment:all-srcs", + "//pkg/kubectl/util/event:all-srcs", + "//pkg/kubectl/util/fieldpath:all-srcs", "//pkg/kubectl/util/hash:all-srcs", "//pkg/kubectl/util/i18n:all-srcs", "//pkg/kubectl/util/logs:all-srcs", "//pkg/kubectl/util/podutils:all-srcs", "//pkg/kubectl/util/printers:all-srcs", + "//pkg/kubectl/util/qos:all-srcs", + "//pkg/kubectl/util/rbac:all-srcs", + "//pkg/kubectl/util/resource:all-srcs", "//pkg/kubectl/util/slice:all-srcs", + "//pkg/kubectl/util/storage:all-srcs", "//pkg/kubectl/util/templates:all-srcs", "//pkg/kubectl/util/term:all-srcs", ], diff --git a/pkg/kubectl/util/certificate/BUILD b/pkg/kubectl/util/certificate/BUILD new file mode 100644 index 0000000000..cac97d4170 --- /dev/null +++ b/pkg/kubectl/util/certificate/BUILD @@ -0,0 +1,23 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["certificate.go"], + importpath = "k8s.io/kubernetes/pkg/kubectl/util/certificate", + visibility = ["//visibility:public"], + deps = ["//staging/src/k8s.io/api/certificates/v1beta1:go_default_library"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubectl/util/certificate/certificate.go b/pkg/kubectl/util/certificate/certificate.go new file mode 100644 index 0000000000..201958c6fc --- /dev/null +++ b/pkg/kubectl/util/certificate/certificate.go @@ -0,0 +1,42 @@ +/* +Copyright 2016 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 certificate + +import ( + "crypto/x509" + "encoding/pem" + "errors" + + certificatesv1beta1 "k8s.io/api/certificates/v1beta1" +) + +// TODO(yue9944882): Remove this helper package once it's copied to k/api + +// ParseCSR extracts the CSR from the API object and decodes it. +func ParseCSR(obj *certificatesv1beta1.CertificateSigningRequest) (*x509.CertificateRequest, error) { + // extract PEM from request object + pemBytes := obj.Spec.Request + block, _ := pem.Decode(pemBytes) + if block == nil || block.Type != "CERTIFICATE REQUEST" { + return nil, errors.New("PEM block type must be CERTIFICATE REQUEST") + } + csr, err := x509.ParseCertificateRequest(block.Bytes) + if err != nil { + return nil, err + } + return csr, nil +} diff --git a/pkg/api/events/BUILD b/pkg/kubectl/util/event/BUILD similarity index 77% rename from pkg/api/events/BUILD rename to pkg/kubectl/util/event/BUILD index 7bda51ac2f..1c0fe6300d 100644 --- a/pkg/api/events/BUILD +++ b/pkg/kubectl/util/event/BUILD @@ -9,8 +9,8 @@ load( go_library( name = "go_default_library", srcs = ["sorted_event_list.go"], - importpath = "k8s.io/kubernetes/pkg/api/events", - deps = ["//pkg/apis/core:go_default_library"], + importpath = "k8s.io/kubernetes/pkg/kubectl/util/event", + deps = ["//staging/src/k8s.io/api/core/v1:go_default_library"], ) go_test( @@ -18,7 +18,7 @@ go_test( srcs = ["sorted_event_list_test.go"], embed = [":go_default_library"], deps = [ - "//pkg/apis/core:go_default_library", + "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ], ) diff --git a/pkg/api/events/sorted_event_list.go b/pkg/kubectl/util/event/sorted_event_list.go similarity index 91% rename from pkg/api/events/sorted_event_list.go rename to pkg/kubectl/util/event/sorted_event_list.go index 9976c10ce7..9967f953e6 100644 --- a/pkg/api/events/sorted_event_list.go +++ b/pkg/kubectl/util/event/sorted_event_list.go @@ -14,14 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package events +package event import ( - api "k8s.io/kubernetes/pkg/apis/core" + corev1 "k8s.io/api/core/v1" ) // SortableEvents implements sort.Interface for []api.Event based on the Timestamp field -type SortableEvents []api.Event +type SortableEvents []corev1.Event func (list SortableEvents) Len() int { return len(list) diff --git a/pkg/api/events/sorted_event_list_test.go b/pkg/kubectl/util/event/sorted_event_list_test.go similarity index 80% rename from pkg/api/events/sorted_event_list_test.go rename to pkg/kubectl/util/event/sorted_event_list_test.go index 687f6fa19f..5c1d79257d 100644 --- a/pkg/api/events/sorted_event_list_test.go +++ b/pkg/kubectl/util/event/sorted_event_list_test.go @@ -14,43 +14,43 @@ See the License for the specific language governing permissions and limitations under the License. */ -package events +package event import ( "sort" "testing" "time" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - api "k8s.io/kubernetes/pkg/apis/core" ) func TestSortableEvents(t *testing.T) { // Arrange - list := SortableEvents([]api.Event{ + list := SortableEvents([]corev1.Event{ { - Source: api.EventSource{Component: "kubelet"}, + Source: corev1.EventSource{Component: "kubelet"}, Message: "Item 1", FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), LastTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), Count: 1, - Type: api.EventTypeNormal, + Type: corev1.EventTypeNormal, }, { - Source: api.EventSource{Component: "scheduler"}, + Source: corev1.EventSource{Component: "scheduler"}, Message: "Item 2", FirstTimestamp: metav1.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)), LastTimestamp: metav1.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)), Count: 1, - Type: api.EventTypeNormal, + Type: corev1.EventTypeNormal, }, { - Source: api.EventSource{Component: "kubelet"}, + Source: corev1.EventSource{Component: "kubelet"}, Message: "Item 3", FirstTimestamp: metav1.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)), LastTimestamp: metav1.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)), Count: 1, - Type: api.EventTypeNormal, + Type: corev1.EventTypeNormal, }, }) diff --git a/pkg/kubectl/util/fieldpath/BUILD b/pkg/kubectl/util/fieldpath/BUILD new file mode 100644 index 0000000000..188a07d00d --- /dev/null +++ b/pkg/kubectl/util/fieldpath/BUILD @@ -0,0 +1,27 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fieldpath.go"], + importpath = "k8s.io/kubernetes/pkg/kubectl/util/fieldpath", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubectl/util/fieldpath/fieldpath.go b/pkg/kubectl/util/fieldpath/fieldpath.go new file mode 100644 index 0000000000..efd08cde0f --- /dev/null +++ b/pkg/kubectl/util/fieldpath/fieldpath.go @@ -0,0 +1,111 @@ +/* +Copyright 2015 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 fieldpath + +import ( + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" +) + +// TODO(yue9944882): Remove this helper package once it's copied to k/apimachinery + +// FormatMap formats map[string]string to a string. +func FormatMap(m map[string]string) (fmtStr string) { + // output with keys in sorted order to provide stable output + keys := sets.NewString() + for key := range m { + keys.Insert(key) + } + for _, key := range keys.List() { + fmtStr += fmt.Sprintf("%v=%q\n", key, m[key]) + } + fmtStr = strings.TrimSuffix(fmtStr, "\n") + + return +} + +// ExtractFieldPathAsString extracts the field from the given object +// and returns it as a string. The object must be a pointer to an +// API type. +func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error) { + accessor, err := meta.Accessor(obj) + if err != nil { + return "", nil + } + + if path, subscript, ok := SplitMaybeSubscriptedPath(fieldPath); ok { + switch path { + case "metadata.annotations": + if errs := validation.IsQualifiedName(strings.ToLower(subscript)); len(errs) != 0 { + return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";")) + } + return accessor.GetAnnotations()[subscript], nil + case "metadata.labels": + if errs := validation.IsQualifiedName(subscript); len(errs) != 0 { + return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";")) + } + return accessor.GetLabels()[subscript], nil + default: + return "", fmt.Errorf("fieldPath %q does not support subscript", fieldPath) + } + } + + switch fieldPath { + case "metadata.annotations": + return FormatMap(accessor.GetAnnotations()), nil + case "metadata.labels": + return FormatMap(accessor.GetLabels()), nil + case "metadata.name": + return accessor.GetName(), nil + case "metadata.namespace": + return accessor.GetNamespace(), nil + case "metadata.uid": + return string(accessor.GetUID()), nil + } + + return "", fmt.Errorf("unsupported fieldPath: %v", fieldPath) +} + +// SplitMaybeSubscriptedPath checks whether the specified fieldPath is +// subscripted, and +// - if yes, this function splits the fieldPath into path and subscript, and +// returns (path, subscript, true). +// - if no, this function returns (fieldPath, "", false). +// +// Example inputs and outputs: +// - "metadata.annotations['myKey']" --> ("metadata.annotations", "myKey", true) +// - "metadata.annotations['a[b]c']" --> ("metadata.annotations", "a[b]c", true) +// - "metadata.labels['']" --> ("metadata.labels", "", true) +// - "metadata.labels" --> ("metadata.labels", "", false) +func SplitMaybeSubscriptedPath(fieldPath string) (string, string, bool) { + if !strings.HasSuffix(fieldPath, "']") { + return fieldPath, "", false + } + s := strings.TrimSuffix(fieldPath, "']") + parts := strings.SplitN(s, "['", 2) + if len(parts) < 2 { + return fieldPath, "", false + } + if len(parts[0]) == 0 { + return fieldPath, "", false + } + return parts[0], parts[1], true +} diff --git a/pkg/kubectl/util/qos/BUILD b/pkg/kubectl/util/qos/BUILD new file mode 100644 index 0000000000..baa609dae9 --- /dev/null +++ b/pkg/kubectl/util/qos/BUILD @@ -0,0 +1,28 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["qos.go"], + importpath = "k8s.io/kubernetes/pkg/kubectl/util/qos", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/core:go_default_library", + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubectl/util/qos/qos.go b/pkg/kubectl/util/qos/qos.go new file mode 100644 index 0000000000..4e0d06ca01 --- /dev/null +++ b/pkg/kubectl/util/qos/qos.go @@ -0,0 +1,96 @@ +/* +Copyright 2015 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 qos + +import ( + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/apis/core" +) + +var supportedQoSComputeResources = sets.NewString(string(core.ResourceCPU), string(core.ResourceMemory)) + +func isSupportedQoSComputeResource(name v1.ResourceName) bool { + return supportedQoSComputeResources.Has(string(name)) +} + +// GetPodQOS returns the QoS class of a pod. +// A pod is besteffort if none of its containers have specified any requests or limits. +// A pod is guaranteed only when requests and limits are specified for all the containers and they are equal. +// A pod is burstable if limits and requests do not match across all containers. +func GetPodQOS(pod *v1.Pod) v1.PodQOSClass { + requests := v1.ResourceList{} + limits := v1.ResourceList{} + zeroQuantity := resource.MustParse("0") + isGuaranteed := true + for _, container := range pod.Spec.Containers { + // process requests + for name, quantity := range container.Resources.Requests { + if !isSupportedQoSComputeResource(name) { + continue + } + if quantity.Cmp(zeroQuantity) == 1 { + delta := quantity.Copy() + if _, exists := requests[name]; !exists { + requests[name] = *delta + } else { + delta.Add(requests[name]) + requests[name] = *delta + } + } + } + // process limits + qosLimitsFound := sets.NewString() + for name, quantity := range container.Resources.Limits { + if !isSupportedQoSComputeResource(name) { + continue + } + if quantity.Cmp(zeroQuantity) == 1 { + qosLimitsFound.Insert(string(name)) + delta := quantity.Copy() + if _, exists := limits[name]; !exists { + limits[name] = *delta + } else { + delta.Add(limits[name]) + limits[name] = *delta + } + } + } + + if !qosLimitsFound.HasAll(string(v1.ResourceMemory), string(v1.ResourceCPU)) { + isGuaranteed = false + } + } + if len(requests) == 0 && len(limits) == 0 { + return v1.PodQOSBestEffort + } + // Check is requests match limits for all resources. + if isGuaranteed { + for name, req := range requests { + if lim, exists := limits[name]; !exists || lim.Cmp(req) != 0 { + isGuaranteed = false + break + } + } + } + if isGuaranteed && + len(requests) == len(limits) { + return v1.PodQOSGuaranteed + } + return v1.PodQOSBurstable +} diff --git a/pkg/kubectl/util/rbac/BUILD b/pkg/kubectl/util/rbac/BUILD new file mode 100644 index 0000000000..6a398bc854 --- /dev/null +++ b/pkg/kubectl/util/rbac/BUILD @@ -0,0 +1,23 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["rbac.go"], + importpath = "k8s.io/kubernetes/pkg/kubectl/util/rbac", + visibility = ["//visibility:public"], + deps = ["//staging/src/k8s.io/api/rbac/v1:go_default_library"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubectl/util/rbac/rbac.go b/pkg/kubectl/util/rbac/rbac.go new file mode 100644 index 0000000000..a149a51afb --- /dev/null +++ b/pkg/kubectl/util/rbac/rbac.go @@ -0,0 +1,128 @@ +/* +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 rbac + +import ( + rbacv1 "k8s.io/api/rbac/v1" + "reflect" + "strings" +) + +type simpleResource struct { + Group string + Resource string + ResourceNameExist bool + ResourceName string +} + +// CompactRules combines rules that contain a single APIGroup/Resource, differ only by verb, and contain no other attributes. +// this is a fast check, and works well with the decomposed "missing rules" list from a Covers check. +func CompactRules(rules []rbacv1.PolicyRule) ([]rbacv1.PolicyRule, error) { + compacted := make([]rbacv1.PolicyRule, 0, len(rules)) + + simpleRules := map[simpleResource]*rbacv1.PolicyRule{} + for _, rule := range rules { + if resource, isSimple := isSimpleResourceRule(&rule); isSimple { + if existingRule, ok := simpleRules[resource]; ok { + // Add the new verbs to the existing simple resource rule + if existingRule.Verbs == nil { + existingRule.Verbs = []string{} + } + existingRule.Verbs = append(existingRule.Verbs, rule.Verbs...) + } else { + // Copy the rule to accumulate matching simple resource rules into + simpleRules[resource] = rule.DeepCopy() + } + } else { + compacted = append(compacted, rule) + } + } + + // Once we've consolidated the simple resource rules, add them to the compacted list + for _, simpleRule := range simpleRules { + compacted = append(compacted, *simpleRule) + } + + return compacted, nil +} + +// isSimpleResourceRule returns true if the given rule contains verbs, a single resource, a single API group, at most one Resource Name, and no other values +func isSimpleResourceRule(rule *rbacv1.PolicyRule) (simpleResource, bool) { + resource := simpleResource{} + + // If we have "complex" rule attributes, return early without allocations or expensive comparisons + if len(rule.ResourceNames) > 1 || len(rule.NonResourceURLs) > 0 { + return resource, false + } + // If we have multiple api groups or resources, return early + if len(rule.APIGroups) != 1 || len(rule.Resources) != 1 { + return resource, false + } + + // Test if this rule only contains APIGroups/Resources/Verbs/ResourceNames + simpleRule := &rbacv1.PolicyRule{APIGroups: rule.APIGroups, Resources: rule.Resources, Verbs: rule.Verbs, ResourceNames: rule.ResourceNames} + if !reflect.DeepEqual(simpleRule, rule) { + return resource, false + } + + if len(rule.ResourceNames) == 0 { + resource = simpleResource{Group: rule.APIGroups[0], Resource: rule.Resources[0], ResourceNameExist: false} + } else { + resource = simpleResource{Group: rule.APIGroups[0], Resource: rule.Resources[0], ResourceNameExist: true, ResourceName: rule.ResourceNames[0]} + } + + return resource, true +} + +// BreakdownRule takes a rule and builds an equivalent list of rules that each have at most one verb, one +// resource, and one resource name +func BreakdownRule(rule rbacv1.PolicyRule) []rbacv1.PolicyRule { + subrules := []rbacv1.PolicyRule{} + for _, group := range rule.APIGroups { + for _, resource := range rule.Resources { + for _, verb := range rule.Verbs { + if len(rule.ResourceNames) > 0 { + for _, resourceName := range rule.ResourceNames { + subrules = append(subrules, rbacv1.PolicyRule{APIGroups: []string{group}, Resources: []string{resource}, Verbs: []string{verb}, ResourceNames: []string{resourceName}}) + } + + } else { + subrules = append(subrules, rbacv1.PolicyRule{APIGroups: []string{group}, Resources: []string{resource}, Verbs: []string{verb}}) + } + + } + } + } + + // Non-resource URLs are unique because they only combine with verbs. + for _, nonResourceURL := range rule.NonResourceURLs { + for _, verb := range rule.Verbs { + subrules = append(subrules, rbacv1.PolicyRule{NonResourceURLs: []string{nonResourceURL}, Verbs: []string{verb}}) + } + } + + return subrules +} + +// SortableRuleSlice is used to sort rule slice +type SortableRuleSlice []rbacv1.PolicyRule + +func (s SortableRuleSlice) Len() int { return len(s) } +func (s SortableRuleSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s SortableRuleSlice) Less(i, j int) bool { + return strings.Compare(s[i].String(), s[j].String()) < 0 +} diff --git a/pkg/kubectl/util/resource/BUILD b/pkg/kubectl/util/resource/BUILD new file mode 100644 index 0000000000..bbbe532b71 --- /dev/null +++ b/pkg/kubectl/util/resource/BUILD @@ -0,0 +1,27 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["resource.go"], + importpath = "k8s.io/kubernetes/pkg/kubectl/util/resource", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubectl/util/resource/resource.go b/pkg/kubectl/util/resource/resource.go new file mode 100644 index 0000000000..e1b7fe4e4c --- /dev/null +++ b/pkg/kubectl/util/resource/resource.go @@ -0,0 +1,138 @@ +/* +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 resource + +import ( + "fmt" + "math" + "strconv" + "strings" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/sets" +) + +// PodRequestsAndLimits returns a dictionary of all defined resources summed up for all +// containers of the pod. +func PodRequestsAndLimits(pod *v1.Pod) (reqs, limits v1.ResourceList) { + reqs, limits = v1.ResourceList{}, v1.ResourceList{} + for _, container := range pod.Spec.Containers { + addResourceList(reqs, container.Resources.Requests) + addResourceList(limits, container.Resources.Limits) + } + // init containers define the minimum of any resource + for _, container := range pod.Spec.InitContainers { + maxResourceList(reqs, container.Resources.Requests) + maxResourceList(limits, container.Resources.Limits) + } + return +} + +// addResourceList adds the resources in newList to list +func addResourceList(list, new v1.ResourceList) { + for name, quantity := range new { + if value, ok := list[name]; !ok { + list[name] = *quantity.Copy() + } else { + value.Add(quantity) + list[name] = value + } + } +} + +// maxResourceList sets list to the greater of list/newList for every resource +// either list +func maxResourceList(list, new v1.ResourceList) { + for name, quantity := range new { + if value, ok := list[name]; !ok { + list[name] = *quantity.Copy() + continue + } else { + if quantity.Cmp(value) > 0 { + list[name] = *quantity.Copy() + } + } + } +} + +// ExtractContainerResourceValue extracts the value of a resource +// in an already known container +func ExtractContainerResourceValue(fs *v1.ResourceFieldSelector, container *v1.Container) (string, error) { + divisor := resource.Quantity{} + if divisor.Cmp(fs.Divisor) == 0 { + divisor = resource.MustParse("1") + } else { + divisor = fs.Divisor + } + + switch fs.Resource { + case "limits.cpu": + return convertResourceCPUToString(container.Resources.Limits.Cpu(), divisor) + case "limits.memory": + return convertResourceMemoryToString(container.Resources.Limits.Memory(), divisor) + case "limits.ephemeral-storage": + return convertResourceEphemeralStorageToString(container.Resources.Limits.StorageEphemeral(), divisor) + case "requests.cpu": + return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor) + case "requests.memory": + return convertResourceMemoryToString(container.Resources.Requests.Memory(), divisor) + case "requests.ephemeral-storage": + return convertResourceEphemeralStorageToString(container.Resources.Requests.StorageEphemeral(), divisor) + } + + return "", fmt.Errorf("Unsupported container resource : %v", fs.Resource) +} + +// convertResourceCPUToString converts cpu value to the format of divisor and returns +// ceiling of the value. +func convertResourceCPUToString(cpu *resource.Quantity, divisor resource.Quantity) (string, error) { + c := int64(math.Ceil(float64(cpu.MilliValue()) / float64(divisor.MilliValue()))) + return strconv.FormatInt(c, 10), nil +} + +// convertResourceMemoryToString converts memory value to the format of divisor and returns +// ceiling of the value. +func convertResourceMemoryToString(memory *resource.Quantity, divisor resource.Quantity) (string, error) { + m := int64(math.Ceil(float64(memory.Value()) / float64(divisor.Value()))) + return strconv.FormatInt(m, 10), nil +} + +// convertResourceEphemeralStorageToString converts ephemeral storage value to the format of divisor and returns +// ceiling of the value. +func convertResourceEphemeralStorageToString(ephemeralStorage *resource.Quantity, divisor resource.Quantity) (string, error) { + m := int64(math.Ceil(float64(ephemeralStorage.Value()) / float64(divisor.Value()))) + return strconv.FormatInt(m, 10), nil +} + +var standardContainerResources = sets.NewString( + string(v1.ResourceCPU), + string(v1.ResourceMemory), + string(v1.ResourceEphemeralStorage), +) + +// IsStandardContainerResourceName returns true if the container can make a resource request +// for the specified resource +func IsStandardContainerResourceName(str string) bool { + return standardContainerResources.Has(str) || IsHugePageResourceName(v1.ResourceName(str)) +} + +// IsHugePageResourceName returns true if the resource name has the huge page +// resource prefix. +func IsHugePageResourceName(name v1.ResourceName) bool { + return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix) +} diff --git a/pkg/kubectl/util/slice/slice.go b/pkg/kubectl/util/slice/slice.go index 0eef7db1a3..f997d5cb40 100644 --- a/pkg/kubectl/util/slice/slice.go +++ b/pkg/kubectl/util/slice/slice.go @@ -22,3 +22,17 @@ import ( // SortInts64 sorts []int64 in increasing order func SortInts64(a []int64) { sort.Slice(a, func(i, j int) bool { return a[i] < a[j] }) } + +// ContainsString checks if a given slice of strings contains the provided string. +// If a modifier func is provided, it is called with the slice item before the comparation. +func ContainsString(slice []string, s string, modifier func(s string) string) bool { + for _, item := range slice { + if item == s { + return true + } + if modifier != nil && modifier(item) == s { + return true + } + } + return false +} diff --git a/pkg/kubectl/util/storage/BUILD b/pkg/kubectl/util/storage/BUILD new file mode 100644 index 0000000000..cd1ed24f70 --- /dev/null +++ b/pkg/kubectl/util/storage/BUILD @@ -0,0 +1,26 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["storage.go"], + importpath = "k8s.io/kubernetes/pkg/kubectl/util/storage", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubectl/util/storage/storage.go b/pkg/kubectl/util/storage/storage.go new file mode 100644 index 0000000000..c62cc4ea12 --- /dev/null +++ b/pkg/kubectl/util/storage/storage.go @@ -0,0 +1,107 @@ +/* +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 storage + +import ( + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "strings" +) + +// TODO(yue9944882): Remove this helper package once it's copied to k/api + +// IsDefaultStorageClassAnnotation represents a StorageClass annotation that +// marks a class as the default StorageClass +const IsDefaultStorageClassAnnotation = "storageclass.kubernetes.io/is-default-class" + +// BetaIsDefaultStorageClassAnnotation is the beta version of BetaIsDefaultStorageClassAnnotation. +const BetaIsDefaultStorageClassAnnotation = "storageclass.beta.kubernetes.io/is-default-class" + +// IsDefaultAnnotationText returns a pretty Yes/No String if +// the annotation is set +func IsDefaultAnnotationText(obj metav1.ObjectMeta) string { + if obj.Annotations[IsDefaultStorageClassAnnotation] == "true" { + return "Yes" + } + if obj.Annotations[BetaIsDefaultStorageClassAnnotation] == "true" { + return "Yes" + } + + return "No" +} + +// GetAccessModesAsString returns a string representation of an array of access modes. +// modes, when present, are always in the same order: RWO,ROX,RWX. +func GetAccessModesAsString(modes []v1.PersistentVolumeAccessMode) string { + modes = removeDuplicateAccessModes(modes) + modesStr := []string{} + if containsAccessMode(modes, v1.ReadWriteOnce) { + modesStr = append(modesStr, "RWO") + } + if containsAccessMode(modes, v1.ReadOnlyMany) { + modesStr = append(modesStr, "ROX") + } + if containsAccessMode(modes, v1.ReadWriteMany) { + modesStr = append(modesStr, "RWX") + } + return strings.Join(modesStr, ",") +} + +// removeDuplicateAccessModes returns an array of access modes without any duplicates +func removeDuplicateAccessModes(modes []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode { + accessModes := []v1.PersistentVolumeAccessMode{} + for _, m := range modes { + if !containsAccessMode(accessModes, m) { + accessModes = append(accessModes, m) + } + } + return accessModes +} + +func containsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { + for _, m := range modes { + if m == mode { + return true + } + } + return false +} + +// GetPersistentVolumeClass returns StorageClassName. +func GetPersistentVolumeClass(volume *v1.PersistentVolume) string { + // Use beta annotation first + if class, found := volume.Annotations[v1.BetaStorageClassAnnotation]; found { + return class + } + + return volume.Spec.StorageClassName +} + +// GetPersistentVolumeClaimClass returns StorageClassName. If no storage class was +// requested, it returns "". +func GetPersistentVolumeClaimClass(claim *v1.PersistentVolumeClaim) string { + // Use beta annotation first + if class, found := claim.Annotations[v1.BetaStorageClassAnnotation]; found { + return class + } + + if claim.Spec.StorageClassName != nil { + return *claim.Spec.StorageClassName + } + + return "" +} diff --git a/pkg/printers/interface.go b/pkg/printers/interface.go index f528de5caa..6e14f61c5b 100644 --- a/pkg/printers/interface.go +++ b/pkg/printers/interface.go @@ -17,7 +17,6 @@ limitations under the License. package printers import ( - "fmt" "io" "k8s.io/apimachinery/pkg/runtime" @@ -58,36 +57,3 @@ type PrintOptions struct { // indicates if it is OK to ignore missing keys for rendering an output template. AllowMissingKeys bool } - -// Describer generates output for the named resource or an error -// if the output could not be generated. Implementers typically -// abstract the retrieval of the named object from a remote server. -type Describer interface { - Describe(namespace, name string, describerSettings DescriberSettings) (output string, err error) -} - -// DescriberSettings holds display configuration for each object -// describer to control what is printed. -type DescriberSettings struct { - ShowEvents bool -} - -// ObjectDescriber is an interface for displaying arbitrary objects with extra -// information. Use when an object is in hand (on disk, or already retrieved). -// Implementers may ignore the additional information passed on extra, or use it -// by default. ObjectDescribers may return ErrNoDescriber if no suitable describer -// is found. -type ObjectDescriber interface { - DescribeObject(object interface{}, extra ...interface{}) (output string, err error) -} - -// ErrNoDescriber is a structured error indicating the provided object or objects -// cannot be described. -type ErrNoDescriber struct { - Types []string -} - -// Error implements the error interface. -func (e ErrNoDescriber) Error() string { - return fmt.Sprintf("no describer has been defined for %v", e.Types) -} diff --git a/pkg/printers/internalversion/BUILD b/pkg/printers/internalversion/BUILD index e8dc560b98..147e1e197b 100644 --- a/pkg/printers/internalversion/BUILD +++ b/pkg/printers/internalversion/BUILD @@ -9,7 +9,6 @@ load( go_test( name = "go_default_test", srcs = [ - "describe_test.go", "printers_test.go", "sorted_resource_name_list_test.go", ], @@ -23,16 +22,11 @@ go_test( "//pkg/apis/coordination:go_default_library", "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", - "//pkg/apis/networking:go_default_library", "//pkg/apis/policy:go_default_library", "//pkg/apis/scheduling:go_default_library", "//pkg/apis/storage:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", - "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/printers:go_default_library", - "//staging/src/k8s.io/api/apps/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/api/equality: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", @@ -45,8 +39,6 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/util/sets: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/client-go/kubernetes/fake:go_default_library", - "//vendor/k8s.io/utils/pointer:go_default_library", "//vendor/sigs.k8s.io/yaml:go_default_library", ], ) @@ -54,40 +46,42 @@ go_test( go_library( name = "go_default_library", srcs = [ - "describe.go", + "import_known_versions.go", "printers.go", ], importpath = "k8s.io/kubernetes/pkg/printers/internalversion", deps = [ - "//pkg/api/events:go_default_library", - "//pkg/api/legacyscheme:go_default_library", - "//pkg/api/ref:go_default_library", - "//pkg/api/resource:go_default_library", "//pkg/apis/apps:go_default_library", + "//pkg/apis/apps/install:go_default_library", + "//pkg/apis/authentication/install:go_default_library", + "//pkg/apis/authorization/install:go_default_library", "//pkg/apis/autoscaling:go_default_library", + "//pkg/apis/autoscaling/install:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/batch/install:go_default_library", "//pkg/apis/certificates:go_default_library", + "//pkg/apis/certificates/install:go_default_library", "//pkg/apis/coordination:go_default_library", + "//pkg/apis/coordination/install:go_default_library", "//pkg/apis/core:go_default_library", "//pkg/apis/core/helper:go_default_library", - "//pkg/apis/core/helper/qos:go_default_library", + "//pkg/apis/core/install:go_default_library", + "//pkg/apis/events/install:go_default_library", "//pkg/apis/extensions:go_default_library", + "//pkg/apis/extensions/install:go_default_library", "//pkg/apis/networking:go_default_library", "//pkg/apis/policy:go_default_library", + "//pkg/apis/policy/install:go_default_library", "//pkg/apis/rbac:go_default_library", - "//pkg/apis/rbac/v1:go_default_library", + "//pkg/apis/rbac/install:go_default_library", "//pkg/apis/scheduling:go_default_library", + "//pkg/apis/scheduling/install:go_default_library", + "//pkg/apis/settings/install:go_default_library", "//pkg/apis/storage:go_default_library", + "//pkg/apis/storage/install:go_default_library", "//pkg/apis/storage/util:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", - "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", - "//pkg/controller/deployment/util:go_default_library", - "//pkg/fieldpath:go_default_library", "//pkg/printers:go_default_library", - "//pkg/registry/rbac/validation:go_default_library", "//pkg/util/node:go_default_library", - "//pkg/util/slice:go_default_library", - "//staging/src/k8s.io/api/apps/v1:go_default_library", "//staging/src/k8s.io/api/apps/v1beta1:go_default_library", "//staging/src/k8s.io/api/autoscaling/v2beta1:go_default_library", "//staging/src/k8s.io/api/batch/v1:go_default_library", @@ -97,28 +91,17 @@ go_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/api/policy/v1beta1: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/api/scheduling/v1beta1:go_default_library", "//staging/src/k8s.io/api/storage/v1: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/v1beta1: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/duration: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/client-go/dynamic:go_default_library", - "//staging/src/k8s.io/client-go/kubernetes:go_default_library", - "//staging/src/k8s.io/client-go/rest:go_default_library", - "//vendor/github.com/fatih/camelcase:go_default_library", - "//vendor/k8s.io/klog:go_default_library", ], ) diff --git a/pkg/printers/internalversion/describe.go b/pkg/printers/internalversion/describe.go deleted file mode 100644 index 0ff288be76..0000000000 --- a/pkg/printers/internalversion/describe.go +++ /dev/null @@ -1,4285 +0,0 @@ -/* -Copyright 2014 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 internalversion - -import ( - "bytes" - "crypto/x509" - "fmt" - "io" - "net" - "net/url" - "reflect" - "sort" - "strconv" - "strings" - "text/tabwriter" - "time" - - "k8s.io/klog" - - "github.com/fatih/camelcase" - - appsv1 "k8s.io/api/apps/v1" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/duration" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/client-go/dynamic" - externalclient "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/api/events" - "k8s.io/kubernetes/pkg/api/legacyscheme" - "k8s.io/kubernetes/pkg/api/ref" - resourcehelper "k8s.io/kubernetes/pkg/api/resource" - "k8s.io/kubernetes/pkg/apis/apps" - "k8s.io/kubernetes/pkg/apis/autoscaling" - "k8s.io/kubernetes/pkg/apis/batch" - "k8s.io/kubernetes/pkg/apis/certificates" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/apis/core/helper" - "k8s.io/kubernetes/pkg/apis/core/helper/qos" - "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/apis/networking" - "k8s.io/kubernetes/pkg/apis/policy" - "k8s.io/kubernetes/pkg/apis/rbac" - rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1" - "k8s.io/kubernetes/pkg/apis/scheduling" - "k8s.io/kubernetes/pkg/apis/storage" - storageutil "k8s.io/kubernetes/pkg/apis/storage/util" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" - deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" - "k8s.io/kubernetes/pkg/fieldpath" - "k8s.io/kubernetes/pkg/printers" - "k8s.io/kubernetes/pkg/registry/rbac/validation" - "k8s.io/kubernetes/pkg/util/slice" -) - -// Each level has 2 spaces for PrefixWriter -const ( - LEVEL_0 = iota - LEVEL_1 - LEVEL_2 - LEVEL_3 -) - -// PrefixWriter can write text at various indentation levels. -type PrefixWriter interface { - // Write writes text with the specified indentation level. - Write(level int, format string, a ...interface{}) - // WriteLine writes an entire line with no indentation level. - WriteLine(a ...interface{}) - // Flush forces indentation to be reset. - Flush() -} - -// prefixWriter implements PrefixWriter -type prefixWriter struct { - out io.Writer -} - -var _ PrefixWriter = &prefixWriter{} - -// NewPrefixWriter creates a new PrefixWriter. -func NewPrefixWriter(out io.Writer) PrefixWriter { - return &prefixWriter{out: out} -} - -func (pw *prefixWriter) Write(level int, format string, a ...interface{}) { - levelSpace := " " - prefix := "" - for i := 0; i < level; i++ { - prefix += levelSpace - } - fmt.Fprintf(pw.out, prefix+format, a...) -} - -func (pw *prefixWriter) WriteLine(a ...interface{}) { - fmt.Fprintln(pw.out, a...) -} - -func (pw *prefixWriter) Flush() { - if f, ok := pw.out.(flusher); ok { - f.Flush() - } -} - -func describerMap(clientConfig *rest.Config) (map[schema.GroupKind]printers.Describer, error) { - c, err := clientset.NewForConfig(clientConfig) - if err != nil { - return nil, err - } - externalclient, err := externalclient.NewForConfig(clientConfig) - if err != nil { - return nil, err - } - - m := map[schema.GroupKind]printers.Describer{ - api.Kind("Pod"): &PodDescriber{c}, - api.Kind("ReplicationController"): &ReplicationControllerDescriber{c}, - api.Kind("Secret"): &SecretDescriber{c}, - api.Kind("Service"): &ServiceDescriber{c}, - api.Kind("ServiceAccount"): &ServiceAccountDescriber{c}, - api.Kind("Node"): &NodeDescriber{c}, - api.Kind("LimitRange"): &LimitRangeDescriber{c}, - api.Kind("ResourceQuota"): &ResourceQuotaDescriber{c}, - api.Kind("PersistentVolume"): &PersistentVolumeDescriber{c}, - api.Kind("PersistentVolumeClaim"): &PersistentVolumeClaimDescriber{c}, - api.Kind("Namespace"): &NamespaceDescriber{c}, - api.Kind("Endpoints"): &EndpointsDescriber{c}, - api.Kind("ConfigMap"): &ConfigMapDescriber{c}, - api.Kind("PriorityClass"): &PriorityClassDescriber{c}, - - extensions.Kind("ReplicaSet"): &ReplicaSetDescriber{c}, - extensions.Kind("NetworkPolicy"): &NetworkPolicyDescriber{c}, - extensions.Kind("PodSecurityPolicy"): &PodSecurityPolicyDescriber{c}, - autoscaling.Kind("HorizontalPodAutoscaler"): &HorizontalPodAutoscalerDescriber{c}, - extensions.Kind("DaemonSet"): &DaemonSetDescriber{c}, - extensions.Kind("Deployment"): &DeploymentDescriber{c, externalclient}, - extensions.Kind("Ingress"): &IngressDescriber{c}, - batch.Kind("Job"): &JobDescriber{c}, - batch.Kind("CronJob"): &CronJobDescriber{c, externalclient}, - apps.Kind("StatefulSet"): &StatefulSetDescriber{c}, - apps.Kind("Deployment"): &DeploymentDescriber{c, externalclient}, - apps.Kind("DaemonSet"): &DaemonSetDescriber{c}, - apps.Kind("ReplicaSet"): &ReplicaSetDescriber{c}, - certificates.Kind("CertificateSigningRequest"): &CertificateSigningRequestDescriber{c}, - storage.Kind("StorageClass"): &StorageClassDescriber{c}, - policy.Kind("PodDisruptionBudget"): &PodDisruptionBudgetDescriber{c}, - rbac.Kind("Role"): &RoleDescriber{externalclient}, - rbac.Kind("ClusterRole"): &ClusterRoleDescriber{externalclient}, - rbac.Kind("RoleBinding"): &RoleBindingDescriber{externalclient}, - rbac.Kind("ClusterRoleBinding"): &ClusterRoleBindingDescriber{externalclient}, - networking.Kind("NetworkPolicy"): &NetworkPolicyDescriber{c}, - scheduling.Kind("PriorityClass"): &PriorityClassDescriber{c}, - } - - return m, nil -} - -// DescriberFor returns the default describe functions for each of the standard -// Kubernetes types. -func DescriberFor(kind schema.GroupKind, clientConfig *rest.Config) (printers.Describer, bool) { - describers, err := describerMap(clientConfig) - if err != nil { - klog.V(1).Info(err) - return nil, false - } - - f, ok := describers[kind] - return f, ok -} - -// GenericDescriberFor returns a generic describer for the specified mapping -// that uses only information available from runtime.Unstructured -func GenericDescriberFor(mapping *meta.RESTMapping, clientConfig *rest.Config) (printers.Describer, bool) { - // used to fetch the resource - dynamicClient, err := dynamic.NewForConfig(clientConfig) - if err != nil { - return nil, false - } - - // used to get events for the resource - clientSet, err := clientset.NewForConfig(clientConfig) - if err != nil { - return nil, false - } - eventsClient := clientSet.Core() - - return &genericDescriber{mapping, dynamicClient, eventsClient}, true -} - -type genericDescriber struct { - mapping *meta.RESTMapping - dynamic dynamic.Interface - events coreclient.EventsGetter -} - -func (g *genericDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (output string, err error) { - obj, err := g.dynamic.Resource(g.mapping.Resource).Namespace(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = g.events.Events(namespace).Search(legacyscheme.Scheme, obj) - } - - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", obj.GetName()) - w.Write(LEVEL_0, "Namespace:\t%s\n", obj.GetNamespace()) - printLabelsMultiline(w, "Labels", obj.GetLabels()) - printAnnotationsMultiline(w, "Annotations", obj.GetAnnotations()) - printUnstructuredContent(w, LEVEL_0, obj.UnstructuredContent(), "", ".metadata.name", ".metadata.namespace", ".metadata.labels", ".metadata.annotations") - if events != nil { - DescribeEvents(events, w) - } - return nil - }) -} - -func printUnstructuredContent(w PrefixWriter, level int, content map[string]interface{}, skipPrefix string, skip ...string) { - fields := []string{} - for field := range content { - fields = append(fields, field) - } - sort.Strings(fields) - - for _, field := range fields { - value := content[field] - switch typedValue := value.(type) { - case map[string]interface{}: - skipExpr := fmt.Sprintf("%s.%s", skipPrefix, field) - if slice.ContainsString(skip, skipExpr, nil) { - continue - } - w.Write(level, "%s:\n", smartLabelFor(field)) - printUnstructuredContent(w, level+1, typedValue, skipExpr, skip...) - - case []interface{}: - skipExpr := fmt.Sprintf("%s.%s", skipPrefix, field) - if slice.ContainsString(skip, skipExpr, nil) { - continue - } - w.Write(level, "%s:\n", smartLabelFor(field)) - for _, child := range typedValue { - switch typedChild := child.(type) { - case map[string]interface{}: - printUnstructuredContent(w, level+1, typedChild, skipExpr, skip...) - default: - w.Write(level+1, "%v\n", typedChild) - } - } - - default: - skipExpr := fmt.Sprintf("%s.%s", skipPrefix, field) - if slice.ContainsString(skip, skipExpr, nil) { - continue - } - w.Write(level, "%s:\t%v\n", smartLabelFor(field), typedValue) - } - } -} - -func smartLabelFor(field string) string { - commonAcronyms := []string{"API", "URL", "UID", "OSB", "GUID"} - - parts := camelcase.Split(field) - result := make([]string, 0, len(parts)) - for _, part := range parts { - if part == "_" { - continue - } - - if slice.ContainsString(commonAcronyms, strings.ToUpper(part), nil) { - part = strings.ToUpper(part) - } else { - part = strings.Title(part) - } - result = append(result, part) - } - - return strings.Join(result, " ") -} - -// DefaultObjectDescriber can describe the default Kubernetes objects. -var DefaultObjectDescriber printers.ObjectDescriber - -func init() { - d := &Describers{} - err := d.Add( - describeLimitRange, - describeQuota, - describePod, - describeService, - describeReplicationController, - describeDaemonSet, - describeNode, - describeNamespace, - ) - if err != nil { - klog.Fatalf("Cannot register describers: %v", err) - } - DefaultObjectDescriber = d -} - -// NamespaceDescriber generates information about a namespace -type NamespaceDescriber struct { - clientset.Interface -} - -func (d *NamespaceDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - ns, err := d.Core().Namespaces().Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - resourceQuotaList, err := d.Core().ResourceQuotas(name).List(metav1.ListOptions{}) - if err != nil { - if errors.IsNotFound(err) { - // Server does not support resource quotas. - // Not an error, will not show resource quotas information. - resourceQuotaList = nil - } else { - return "", err - } - } - limitRangeList, err := d.Core().LimitRanges(name).List(metav1.ListOptions{}) - if err != nil { - if errors.IsNotFound(err) { - // Server does not support limit ranges. - // Not an error, will not show limit ranges information. - limitRangeList = nil - } else { - return "", err - } - } - return describeNamespace(ns, resourceQuotaList, limitRangeList) -} - -func describeNamespace(namespace *api.Namespace, resourceQuotaList *api.ResourceQuotaList, limitRangeList *api.LimitRangeList) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", namespace.Name) - printLabelsMultiline(w, "Labels", namespace.Labels) - printAnnotationsMultiline(w, "Annotations", namespace.Annotations) - w.Write(LEVEL_0, "Status:\t%s\n", string(namespace.Status.Phase)) - if resourceQuotaList != nil { - w.Write(LEVEL_0, "\n") - DescribeResourceQuotas(resourceQuotaList, w) - } - if limitRangeList != nil { - w.Write(LEVEL_0, "\n") - DescribeLimitRanges(limitRangeList, w) - } - return nil - }) -} - -func describeLimitRangeSpec(spec api.LimitRangeSpec, prefix string, w PrefixWriter) { - for i := range spec.Limits { - item := spec.Limits[i] - maxResources := item.Max - minResources := item.Min - defaultLimitResources := item.Default - defaultRequestResources := item.DefaultRequest - ratio := item.MaxLimitRequestRatio - - set := map[api.ResourceName]bool{} - for k := range maxResources { - set[k] = true - } - for k := range minResources { - set[k] = true - } - for k := range defaultLimitResources { - set[k] = true - } - for k := range defaultRequestResources { - set[k] = true - } - for k := range ratio { - set[k] = true - } - - for k := range set { - // if no value is set, we output - - maxValue := "-" - minValue := "-" - defaultLimitValue := "-" - defaultRequestValue := "-" - ratioValue := "-" - - maxQuantity, maxQuantityFound := maxResources[k] - if maxQuantityFound { - maxValue = maxQuantity.String() - } - - minQuantity, minQuantityFound := minResources[k] - if minQuantityFound { - minValue = minQuantity.String() - } - - defaultLimitQuantity, defaultLimitQuantityFound := defaultLimitResources[k] - if defaultLimitQuantityFound { - defaultLimitValue = defaultLimitQuantity.String() - } - - defaultRequestQuantity, defaultRequestQuantityFound := defaultRequestResources[k] - if defaultRequestQuantityFound { - defaultRequestValue = defaultRequestQuantity.String() - } - - ratioQuantity, ratioQuantityFound := ratio[k] - if ratioQuantityFound { - ratioValue = ratioQuantity.String() - } - - msg := "%s%s\t%v\t%v\t%v\t%v\t%v\t%v\n" - w.Write(LEVEL_0, msg, prefix, item.Type, k, minValue, maxValue, defaultRequestValue, defaultLimitValue, ratioValue) - } - } -} - -// DescribeLimitRanges merges a set of limit range items into a single tabular description -func DescribeLimitRanges(limitRanges *api.LimitRangeList, w PrefixWriter) { - if len(limitRanges.Items) == 0 { - w.Write(LEVEL_0, "No resource limits.\n") - return - } - w.Write(LEVEL_0, "Resource Limits\n Type\tResource\tMin\tMax\tDefault Request\tDefault Limit\tMax Limit/Request Ratio\n") - w.Write(LEVEL_0, " ----\t--------\t---\t---\t---------------\t-------------\t-----------------------\n") - for _, limitRange := range limitRanges.Items { - describeLimitRangeSpec(limitRange.Spec, " ", w) - } -} - -// DescribeResourceQuotas merges a set of quota items into a single tabular description of all quotas -func DescribeResourceQuotas(quotas *api.ResourceQuotaList, w PrefixWriter) { - if len(quotas.Items) == 0 { - w.Write(LEVEL_0, "No resource quota.\n") - return - } - sort.Sort(SortableResourceQuotas(quotas.Items)) - - w.Write(LEVEL_0, "Resource Quotas") - for _, q := range quotas.Items { - w.Write(LEVEL_0, "\n Name:\t%s\n", q.Name) - if len(q.Spec.Scopes) > 0 { - scopes := make([]string, 0, len(q.Spec.Scopes)) - for _, scope := range q.Spec.Scopes { - scopes = append(scopes, string(scope)) - } - sort.Strings(scopes) - w.Write(LEVEL_0, " Scopes:\t%s\n", strings.Join(scopes, ", ")) - for _, scope := range scopes { - helpText := helpTextForResourceQuotaScope(api.ResourceQuotaScope(scope)) - if len(helpText) > 0 { - w.Write(LEVEL_0, " * %s\n", helpText) - } - } - } - - w.Write(LEVEL_0, " Resource\tUsed\tHard\n") - w.Write(LEVEL_0, " --------\t---\t---\n") - - resources := make([]api.ResourceName, 0, len(q.Status.Hard)) - for resource := range q.Status.Hard { - resources = append(resources, resource) - } - sort.Sort(SortableResourceNames(resources)) - - for _, resource := range resources { - hardQuantity := q.Status.Hard[resource] - usedQuantity := q.Status.Used[resource] - w.Write(LEVEL_0, " %s\t%s\t%s\n", string(resource), usedQuantity.String(), hardQuantity.String()) - } - } -} - -// LimitRangeDescriber generates information about a limit range -type LimitRangeDescriber struct { - clientset.Interface -} - -func (d *LimitRangeDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - lr := d.Core().LimitRanges(namespace) - - limitRange, err := lr.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - return describeLimitRange(limitRange) -} - -func describeLimitRange(limitRange *api.LimitRange) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", limitRange.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", limitRange.Namespace) - w.Write(LEVEL_0, "Type\tResource\tMin\tMax\tDefault Request\tDefault Limit\tMax Limit/Request Ratio\n") - w.Write(LEVEL_0, "----\t--------\t---\t---\t---------------\t-------------\t-----------------------\n") - describeLimitRangeSpec(limitRange.Spec, "", w) - return nil - }) -} - -// ResourceQuotaDescriber generates information about a resource quota -type ResourceQuotaDescriber struct { - clientset.Interface -} - -func (d *ResourceQuotaDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - rq := d.Core().ResourceQuotas(namespace) - - resourceQuota, err := rq.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - return describeQuota(resourceQuota) -} - -func helpTextForResourceQuotaScope(scope api.ResourceQuotaScope) string { - switch scope { - case api.ResourceQuotaScopeTerminating: - return "Matches all pods that have an active deadline. These pods have a limited lifespan on a node before being actively terminated by the system." - case api.ResourceQuotaScopeNotTerminating: - return "Matches all pods that do not have an active deadline. These pods usually include long running pods whose container command is not expected to terminate." - case api.ResourceQuotaScopeBestEffort: - return "Matches all pods that do not have resource requirements set. These pods have a best effort quality of service." - case api.ResourceQuotaScopeNotBestEffort: - return "Matches all pods that have at least one resource requirement set. These pods have a burstable or guaranteed quality of service." - default: - return "" - } -} -func describeQuota(resourceQuota *api.ResourceQuota) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", resourceQuota.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", resourceQuota.Namespace) - if len(resourceQuota.Spec.Scopes) > 0 { - scopes := make([]string, 0, len(resourceQuota.Spec.Scopes)) - for _, scope := range resourceQuota.Spec.Scopes { - scopes = append(scopes, string(scope)) - } - sort.Strings(scopes) - w.Write(LEVEL_0, "Scopes:\t%s\n", strings.Join(scopes, ", ")) - for _, scope := range scopes { - helpText := helpTextForResourceQuotaScope(api.ResourceQuotaScope(scope)) - if len(helpText) > 0 { - w.Write(LEVEL_0, " * %s\n", helpText) - } - } - } - w.Write(LEVEL_0, "Resource\tUsed\tHard\n") - w.Write(LEVEL_0, "--------\t----\t----\n") - - resources := make([]api.ResourceName, 0, len(resourceQuota.Status.Hard)) - for resource := range resourceQuota.Status.Hard { - resources = append(resources, resource) - } - sort.Sort(SortableResourceNames(resources)) - - msg := "%v\t%v\t%v\n" - for i := range resources { - resource := resources[i] - hardQuantity := resourceQuota.Status.Hard[resource] - usedQuantity := resourceQuota.Status.Used[resource] - w.Write(LEVEL_0, msg, resource, usedQuantity.String(), hardQuantity.String()) - } - return nil - }) -} - -// PodDescriber generates information about a pod and the replication controllers that -// create it. -type PodDescriber struct { - clientset.Interface -} - -func (d *PodDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - pod, err := d.Core().Pods(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - if describerSettings.ShowEvents { - eventsInterface := d.Core().Events(namespace) - selector := eventsInterface.GetFieldSelector(&name, &namespace, nil, nil) - options := metav1.ListOptions{FieldSelector: selector.String()} - events, err2 := eventsInterface.List(options) - if describerSettings.ShowEvents && err2 == nil && len(events.Items) > 0 { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Pod '%v': error '%v', but found events.\n", name, err) - DescribeEvents(events, w) - return nil - }) - } - } - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - if ref, err := ref.GetReference(legacyscheme.Scheme, pod); err != nil { - klog.Errorf("Unable to construct reference to '%#v': %v", pod, err) - } else { - ref.Kind = "" - events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, ref) - } - } - - return describePod(pod, events) -} - -func describePod(pod *api.Pod, events *api.EventList) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", pod.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", pod.Namespace) - if pod.Spec.Priority != nil { - w.Write(LEVEL_0, "Priority:\t%d\n", *pod.Spec.Priority) - w.Write(LEVEL_0, "PriorityClassName:\t%s\n", stringOrNone(pod.Spec.PriorityClassName)) - } - if pod.Spec.NodeName == "" { - w.Write(LEVEL_0, "Node:\t\n") - } else { - w.Write(LEVEL_0, "Node:\t%s\n", pod.Spec.NodeName+"/"+pod.Status.HostIP) - } - if pod.Status.StartTime != nil { - w.Write(LEVEL_0, "Start Time:\t%s\n", pod.Status.StartTime.Time.Format(time.RFC1123Z)) - } - printLabelsMultiline(w, "Labels", pod.Labels) - printAnnotationsMultiline(w, "Annotations", pod.Annotations) - if pod.DeletionTimestamp != nil { - w.Write(LEVEL_0, "Status:\tTerminating (lasts %s)\n", translateTimestampUntil(*pod.DeletionTimestamp)) - w.Write(LEVEL_0, "Termination Grace Period:\t%ds\n", *pod.DeletionGracePeriodSeconds) - } else { - w.Write(LEVEL_0, "Status:\t%s\n", string(pod.Status.Phase)) - } - if len(pod.Status.Reason) > 0 { - w.Write(LEVEL_0, "Reason:\t%s\n", pod.Status.Reason) - } - if len(pod.Status.Message) > 0 { - w.Write(LEVEL_0, "Message:\t%s\n", pod.Status.Message) - } - w.Write(LEVEL_0, "IP:\t%s\n", pod.Status.PodIP) - if controlledBy := printController(pod); len(controlledBy) > 0 { - w.Write(LEVEL_0, "Controlled By:\t%s\n", controlledBy) - } - if len(pod.Status.NominatedNodeName) > 0 { - w.Write(LEVEL_0, "NominatedNodeName:\t%s\n", pod.Status.NominatedNodeName) - } - - if len(pod.Spec.InitContainers) > 0 { - describeContainers("Init Containers", pod.Spec.InitContainers, pod.Status.InitContainerStatuses, EnvValueRetriever(pod), w, "") - } - describeContainers("Containers", pod.Spec.Containers, pod.Status.ContainerStatuses, EnvValueRetriever(pod), w, "") - if len(pod.Spec.ReadinessGates) > 0 { - w.Write(LEVEL_0, "Readiness Gates:\n Type\tStatus\n") - for _, g := range pod.Spec.ReadinessGates { - status := "" - for _, c := range pod.Status.Conditions { - if c.Type == g.ConditionType { - status = fmt.Sprintf("%v", c.Status) - break - } - } - w.Write(LEVEL_1, "%v \t%v \n", - g.ConditionType, - status) - } - } - if len(pod.Status.Conditions) > 0 { - w.Write(LEVEL_0, "Conditions:\n Type\tStatus\n") - for _, c := range pod.Status.Conditions { - w.Write(LEVEL_1, "%v \t%v \n", - c.Type, - c.Status) - } - } - describeVolumes(pod.Spec.Volumes, w, "") - if pod.Status.QOSClass != "" { - w.Write(LEVEL_0, "QoS Class:\t%s\n", pod.Status.QOSClass) - } else { - w.Write(LEVEL_0, "QoS Class:\t%s\n", qos.GetPodQOS(pod)) - } - printLabelsMultiline(w, "Node-Selectors", pod.Spec.NodeSelector) - printPodTolerationsMultiline(w, "Tolerations", pod.Spec.Tolerations) - if events != nil { - DescribeEvents(events, w) - } - return nil - }) -} - -func printController(controllee metav1.Object) string { - if controllerRef := metav1.GetControllerOf(controllee); controllerRef != nil { - return fmt.Sprintf("%s/%s", controllerRef.Kind, controllerRef.Name) - } - return "" -} - -func describeVolumes(volumes []api.Volume, w PrefixWriter, space string) { - if volumes == nil || len(volumes) == 0 { - w.Write(LEVEL_0, "%sVolumes:\t\n", space) - return - } - w.Write(LEVEL_0, "%sVolumes:\n", space) - for _, volume := range volumes { - nameIndent := "" - if len(space) > 0 { - nameIndent = " " - } - w.Write(LEVEL_1, "%s%v:\n", nameIndent, volume.Name) - switch { - case volume.VolumeSource.HostPath != nil: - printHostPathVolumeSource(volume.VolumeSource.HostPath, w) - case volume.VolumeSource.EmptyDir != nil: - printEmptyDirVolumeSource(volume.VolumeSource.EmptyDir, w) - case volume.VolumeSource.GCEPersistentDisk != nil: - printGCEPersistentDiskVolumeSource(volume.VolumeSource.GCEPersistentDisk, w) - case volume.VolumeSource.AWSElasticBlockStore != nil: - printAWSElasticBlockStoreVolumeSource(volume.VolumeSource.AWSElasticBlockStore, w) - case volume.VolumeSource.GitRepo != nil: - printGitRepoVolumeSource(volume.VolumeSource.GitRepo, w) - case volume.VolumeSource.Secret != nil: - printSecretVolumeSource(volume.VolumeSource.Secret, w) - case volume.VolumeSource.ConfigMap != nil: - printConfigMapVolumeSource(volume.VolumeSource.ConfigMap, w) - case volume.VolumeSource.NFS != nil: - printNFSVolumeSource(volume.VolumeSource.NFS, w) - case volume.VolumeSource.ISCSI != nil: - printISCSIVolumeSource(volume.VolumeSource.ISCSI, w) - case volume.VolumeSource.Glusterfs != nil: - printGlusterfsVolumeSource(volume.VolumeSource.Glusterfs, w) - case volume.VolumeSource.PersistentVolumeClaim != nil: - printPersistentVolumeClaimVolumeSource(volume.VolumeSource.PersistentVolumeClaim, w) - case volume.VolumeSource.RBD != nil: - printRBDVolumeSource(volume.VolumeSource.RBD, w) - case volume.VolumeSource.Quobyte != nil: - printQuobyteVolumeSource(volume.VolumeSource.Quobyte, w) - case volume.VolumeSource.DownwardAPI != nil: - printDownwardAPIVolumeSource(volume.VolumeSource.DownwardAPI, w) - case volume.VolumeSource.AzureDisk != nil: - printAzureDiskVolumeSource(volume.VolumeSource.AzureDisk, w) - case volume.VolumeSource.VsphereVolume != nil: - printVsphereVolumeSource(volume.VolumeSource.VsphereVolume, w) - case volume.VolumeSource.Cinder != nil: - printCinderVolumeSource(volume.VolumeSource.Cinder, w) - case volume.VolumeSource.PhotonPersistentDisk != nil: - printPhotonPersistentDiskVolumeSource(volume.VolumeSource.PhotonPersistentDisk, w) - case volume.VolumeSource.PortworxVolume != nil: - printPortworxVolumeSource(volume.VolumeSource.PortworxVolume, w) - case volume.VolumeSource.ScaleIO != nil: - printScaleIOVolumeSource(volume.VolumeSource.ScaleIO, w) - case volume.VolumeSource.CephFS != nil: - printCephFSVolumeSource(volume.VolumeSource.CephFS, w) - case volume.VolumeSource.StorageOS != nil: - printStorageOSVolumeSource(volume.VolumeSource.StorageOS, w) - case volume.VolumeSource.FC != nil: - printFCVolumeSource(volume.VolumeSource.FC, w) - case volume.VolumeSource.AzureFile != nil: - printAzureFileVolumeSource(volume.VolumeSource.AzureFile, w) - case volume.VolumeSource.FlexVolume != nil: - printFlexVolumeSource(volume.VolumeSource.FlexVolume, w) - case volume.VolumeSource.Flocker != nil: - printFlockerVolumeSource(volume.VolumeSource.Flocker, w) - case volume.VolumeSource.Projected != nil: - printProjectedVolumeSource(volume.VolumeSource.Projected, w) - default: - w.Write(LEVEL_1, "\n") - } - } -} - -func printHostPathVolumeSource(hostPath *api.HostPathVolumeSource, w PrefixWriter) { - hostPathType := "" - if hostPath.Type != nil { - hostPathType = string(*hostPath.Type) - } - w.Write(LEVEL_2, "Type:\tHostPath (bare host directory volume)\n"+ - " Path:\t%v\n"+ - " HostPathType:\t%v\n", - hostPath.Path, hostPathType) -} - -func printEmptyDirVolumeSource(emptyDir *api.EmptyDirVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tEmptyDir (a temporary directory that shares a pod's lifetime)\n"+ - " Medium:\t%v\n", emptyDir.Medium) -} - -func printGCEPersistentDiskVolumeSource(gce *api.GCEPersistentDiskVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tGCEPersistentDisk (a Persistent Disk resource in Google Compute Engine)\n"+ - " PDName:\t%v\n"+ - " FSType:\t%v\n"+ - " Partition:\t%v\n"+ - " ReadOnly:\t%v\n", - gce.PDName, gce.FSType, gce.Partition, gce.ReadOnly) -} - -func printAWSElasticBlockStoreVolumeSource(aws *api.AWSElasticBlockStoreVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tAWSElasticBlockStore (a Persistent Disk resource in AWS)\n"+ - " VolumeID:\t%v\n"+ - " FSType:\t%v\n"+ - " Partition:\t%v\n"+ - " ReadOnly:\t%v\n", - aws.VolumeID, aws.FSType, aws.Partition, aws.ReadOnly) -} - -func printGitRepoVolumeSource(git *api.GitRepoVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tGitRepo (a volume that is pulled from git when the pod is created)\n"+ - " Repository:\t%v\n"+ - " Revision:\t%v\n", - git.Repository, git.Revision) -} - -func printSecretVolumeSource(secret *api.SecretVolumeSource, w PrefixWriter) { - optional := secret.Optional != nil && *secret.Optional - w.Write(LEVEL_2, "Type:\tSecret (a volume populated by a Secret)\n"+ - " SecretName:\t%v\n"+ - " Optional:\t%v\n", - secret.SecretName, optional) -} - -func printConfigMapVolumeSource(configMap *api.ConfigMapVolumeSource, w PrefixWriter) { - optional := configMap.Optional != nil && *configMap.Optional - w.Write(LEVEL_2, "Type:\tConfigMap (a volume populated by a ConfigMap)\n"+ - " Name:\t%v\n"+ - " Optional:\t%v\n", - configMap.Name, optional) -} - -func printProjectedVolumeSource(projected *api.ProjectedVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tProjected (a volume that contains injected data from multiple sources)\n") - for _, source := range projected.Sources { - if source.Secret != nil { - w.Write(LEVEL_2, "SecretName:\t%v\n"+ - " SecretOptionalName:\t%v\n", - source.Secret.Name, source.Secret.Optional) - } else if source.DownwardAPI != nil { - w.Write(LEVEL_2, "DownwardAPI:\ttrue\n") - } else if source.ConfigMap != nil { - w.Write(LEVEL_2, "ConfigMapName:\t%v\n"+ - " ConfigMapOptional:\t%v\n", - source.ConfigMap.Name, source.ConfigMap.Optional) - } else if source.ServiceAccountToken != nil { - w.Write(LEVEL_2, "TokenExpirationSeconds:\t%v\n", - source.ServiceAccountToken.ExpirationSeconds) - } - } -} - -func printNFSVolumeSource(nfs *api.NFSVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tNFS (an NFS mount that lasts the lifetime of a pod)\n"+ - " Server:\t%v\n"+ - " Path:\t%v\n"+ - " ReadOnly:\t%v\n", - nfs.Server, nfs.Path, nfs.ReadOnly) -} - -func printQuobyteVolumeSource(quobyte *api.QuobyteVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tQuobyte (a Quobyte mount on the host that shares a pod's lifetime)\n"+ - " Registry:\t%v\n"+ - " Volume:\t%v\n"+ - " ReadOnly:\t%v\n", - quobyte.Registry, quobyte.Volume, quobyte.ReadOnly) -} - -func printPortworxVolumeSource(pwxVolume *api.PortworxVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tPortworxVolume (a Portworx Volume resource)\n"+ - " VolumeID:\t%v\n", - pwxVolume.VolumeID) -} - -func printISCSIVolumeSource(iscsi *api.ISCSIVolumeSource, w PrefixWriter) { - initiator := "" - if iscsi.InitiatorName != nil { - initiator = *iscsi.InitiatorName - } - w.Write(LEVEL_2, "Type:\tISCSI (an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod)\n"+ - " TargetPortal:\t%v\n"+ - " IQN:\t%v\n"+ - " Lun:\t%v\n"+ - " ISCSIInterface\t%v\n"+ - " FSType:\t%v\n"+ - " ReadOnly:\t%v\n"+ - " Portals:\t%v\n"+ - " DiscoveryCHAPAuth:\t%v\n"+ - " SessionCHAPAuth:\t%v\n"+ - " SecretRef:\t%v\n"+ - " InitiatorName:\t%v\n", - iscsi.TargetPortal, iscsi.IQN, iscsi.Lun, iscsi.ISCSIInterface, iscsi.FSType, iscsi.ReadOnly, iscsi.Portals, iscsi.DiscoveryCHAPAuth, iscsi.SessionCHAPAuth, iscsi.SecretRef, initiator) -} - -func printISCSIPersistentVolumeSource(iscsi *api.ISCSIPersistentVolumeSource, w PrefixWriter) { - initiatorName := "" - if iscsi.InitiatorName != nil { - initiatorName = *iscsi.InitiatorName - } - w.Write(LEVEL_2, "Type:\tISCSI (an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod)\n"+ - " TargetPortal:\t%v\n"+ - " IQN:\t%v\n"+ - " Lun:\t%v\n"+ - " ISCSIInterface\t%v\n"+ - " FSType:\t%v\n"+ - " ReadOnly:\t%v\n"+ - " Portals:\t%v\n"+ - " DiscoveryCHAPAuth:\t%v\n"+ - " SessionCHAPAuth:\t%v\n"+ - " SecretRef:\t%v\n"+ - " InitiatorName:\t%v\n", - iscsi.TargetPortal, iscsi.IQN, iscsi.Lun, iscsi.ISCSIInterface, iscsi.FSType, iscsi.ReadOnly, iscsi.Portals, iscsi.DiscoveryCHAPAuth, iscsi.SessionCHAPAuth, iscsi.SecretRef, initiatorName) -} - -func printGlusterfsVolumeSource(glusterfs *api.GlusterfsVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tGlusterfs (a Glusterfs mount on the host that shares a pod's lifetime)\n"+ - " EndpointsName:\t%v\n"+ - " Path:\t%v\n"+ - " ReadOnly:\t%v\n", - glusterfs.EndpointsName, glusterfs.Path, glusterfs.ReadOnly) -} - -func printGlusterfsPersistentVolumeSource(glusterfs *api.GlusterfsPersistentVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tGlusterfs (a Glusterfs mount on the host that shares a pod's lifetime)\n"+ - " EndpointsName:\t%v\n"+ - " EndpointsNamespace:\t%v\n"+ - " Path:\t%v\n"+ - " ReadOnly:\t%v\n", - glusterfs.EndpointsName, glusterfs.EndpointsNamespace, glusterfs.Path, glusterfs.ReadOnly) -} - -func printPersistentVolumeClaimVolumeSource(claim *api.PersistentVolumeClaimVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tPersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)\n"+ - " ClaimName:\t%v\n"+ - " ReadOnly:\t%v\n", - claim.ClaimName, claim.ReadOnly) -} - -func printRBDVolumeSource(rbd *api.RBDVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tRBD (a Rados Block Device mount on the host that shares a pod's lifetime)\n"+ - " CephMonitors:\t%v\n"+ - " RBDImage:\t%v\n"+ - " FSType:\t%v\n"+ - " RBDPool:\t%v\n"+ - " RadosUser:\t%v\n"+ - " Keyring:\t%v\n"+ - " SecretRef:\t%v\n"+ - " ReadOnly:\t%v\n", - rbd.CephMonitors, rbd.RBDImage, rbd.FSType, rbd.RBDPool, rbd.RadosUser, rbd.Keyring, rbd.SecretRef, rbd.ReadOnly) -} - -func printRBDPersistentVolumeSource(rbd *api.RBDPersistentVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tRBD (a Rados Block Device mount on the host that shares a pod's lifetime)\n"+ - " CephMonitors:\t%v\n"+ - " RBDImage:\t%v\n"+ - " FSType:\t%v\n"+ - " RBDPool:\t%v\n"+ - " RadosUser:\t%v\n"+ - " Keyring:\t%v\n"+ - " SecretRef:\t%v\n"+ - " ReadOnly:\t%v\n", - rbd.CephMonitors, rbd.RBDImage, rbd.FSType, rbd.RBDPool, rbd.RadosUser, rbd.Keyring, rbd.SecretRef, rbd.ReadOnly) -} - -func printDownwardAPIVolumeSource(d *api.DownwardAPIVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tDownwardAPI (a volume populated by information about the pod)\n Items:\n") - for _, mapping := range d.Items { - if mapping.FieldRef != nil { - w.Write(LEVEL_3, "%v -> %v\n", mapping.FieldRef.FieldPath, mapping.Path) - } - if mapping.ResourceFieldRef != nil { - w.Write(LEVEL_3, "%v -> %v\n", mapping.ResourceFieldRef.Resource, mapping.Path) - } - } -} - -func printAzureDiskVolumeSource(d *api.AzureDiskVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tAzureDisk (an Azure Data Disk mount on the host and bind mount to the pod)\n"+ - " DiskName:\t%v\n"+ - " DiskURI:\t%v\n"+ - " Kind: \t%v\n"+ - " FSType:\t%v\n"+ - " CachingMode:\t%v\n"+ - " ReadOnly:\t%v\n", - d.DiskName, d.DataDiskURI, *d.Kind, *d.FSType, *d.CachingMode, *d.ReadOnly) -} - -func printVsphereVolumeSource(vsphere *api.VsphereVirtualDiskVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tvSphereVolume (a Persistent Disk resource in vSphere)\n"+ - " VolumePath:\t%v\n"+ - " FSType:\t%v\n"+ - " StoragePolicyName:\t%v\n", - vsphere.VolumePath, vsphere.FSType, vsphere.StoragePolicyName) -} - -func printPhotonPersistentDiskVolumeSource(photon *api.PhotonPersistentDiskVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tPhotonPersistentDisk (a Persistent Disk resource in photon platform)\n"+ - " PdID:\t%v\n"+ - " FSType:\t%v\n", - photon.PdID, photon.FSType) -} - -func printCinderVolumeSource(cinder *api.CinderVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tCinder (a Persistent Disk resource in OpenStack)\n"+ - " VolumeID:\t%v\n"+ - " FSType:\t%v\n"+ - " ReadOnly:\t%v\n", - " SecretRef:\t%v\n"+ - cinder.VolumeID, cinder.FSType, cinder.ReadOnly, cinder.SecretRef) -} - -func printCinderPersistentVolumeSource(cinder *api.CinderPersistentVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tCinder (a Persistent Disk resource in OpenStack)\n"+ - " VolumeID:\t%v\n"+ - " FSType:\t%v\n"+ - " ReadOnly:\t%v\n", - " SecretRef:\t%v\n"+ - cinder.VolumeID, cinder.SecretRef, cinder.FSType, cinder.ReadOnly, cinder.SecretRef) -} - -func printScaleIOVolumeSource(sio *api.ScaleIOVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tScaleIO (a persistent volume backed by a block device in ScaleIO)\n"+ - " Gateway:\t%v\n"+ - " System:\t%v\n"+ - " Protection Domain:\t%v\n"+ - " Storage Pool:\t%v\n"+ - " Storage Mode:\t%v\n"+ - " VolumeName:\t%v\n"+ - " FSType:\t%v\n"+ - " ReadOnly:\t%v\n", - sio.Gateway, sio.System, sio.ProtectionDomain, sio.StoragePool, sio.StorageMode, sio.VolumeName, sio.FSType, sio.ReadOnly) -} - -func printScaleIOPersistentVolumeSource(sio *api.ScaleIOPersistentVolumeSource, w PrefixWriter) { - var secretNS, secretName string - if sio.SecretRef != nil { - secretName = sio.SecretRef.Name - secretNS = sio.SecretRef.Namespace - } - w.Write(LEVEL_2, "Type:\tScaleIO (a persistent volume backed by a block device in ScaleIO)\n"+ - " Gateway:\t%v\n"+ - " System:\t%v\n"+ - " Protection Domain:\t%v\n"+ - " Storage Pool:\t%v\n"+ - " Storage Mode:\t%v\n"+ - " VolumeName:\t%v\n"+ - " SecretName:\t%v\n"+ - " SecretNamespace:\t%v\n"+ - " FSType:\t%v\n"+ - " ReadOnly:\t%v\n", - sio.Gateway, sio.System, sio.ProtectionDomain, sio.StoragePool, sio.StorageMode, sio.VolumeName, secretName, secretNS, sio.FSType, sio.ReadOnly) -} - -func printLocalVolumeSource(ls *api.LocalVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tLocalVolume (a persistent volume backed by local storage on a node)\n"+ - " Path:\t%v\n", - ls.Path) -} - -func printCephFSVolumeSource(cephfs *api.CephFSVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tCephFS (a CephFS mount on the host that shares a pod's lifetime)\n"+ - " Monitors:\t%v\n"+ - " Path:\t%v\n"+ - " User:\t%v\n"+ - " SecretFile:\t%v\n"+ - " SecretRef:\t%v\n"+ - " ReadOnly:\t%v\n", - cephfs.Monitors, cephfs.Path, cephfs.User, cephfs.SecretFile, cephfs.SecretRef, cephfs.ReadOnly) -} - -func printCephFSPersistentVolumeSource(cephfs *api.CephFSPersistentVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tCephFS (a CephFS mount on the host that shares a pod's lifetime)\n"+ - " Monitors:\t%v\n"+ - " Path:\t%v\n"+ - " User:\t%v\n"+ - " SecretFile:\t%v\n"+ - " SecretRef:\t%v\n"+ - " ReadOnly:\t%v\n", - cephfs.Monitors, cephfs.Path, cephfs.User, cephfs.SecretFile, cephfs.SecretRef, cephfs.ReadOnly) -} - -func printStorageOSVolumeSource(storageos *api.StorageOSVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tStorageOS (a StorageOS Persistent Disk resource)\n"+ - " VolumeName:\t%v\n"+ - " VolumeNamespace:\t%v\n"+ - " FSType:\t%v\n"+ - " ReadOnly:\t%v\n", - storageos.VolumeName, storageos.VolumeNamespace, storageos.FSType, storageos.ReadOnly) -} - -func printStorageOSPersistentVolumeSource(storageos *api.StorageOSPersistentVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tStorageOS (a StorageOS Persistent Disk resource)\n"+ - " VolumeName:\t%v\n"+ - " VolumeNamespace:\t%v\n"+ - " FSType:\t%v\n"+ - " ReadOnly:\t%v\n", - storageos.VolumeName, storageos.VolumeNamespace, storageos.FSType, storageos.ReadOnly) -} - -func printFCVolumeSource(fc *api.FCVolumeSource, w PrefixWriter) { - lun := "" - if fc.Lun != nil { - lun = strconv.Itoa(int(*fc.Lun)) - } - w.Write(LEVEL_2, "Type:\tFC (a Fibre Channel disk)\n"+ - " TargetWWNs:\t%v\n"+ - " LUN:\t%v\n"+ - " FSType:\t%v\n"+ - " ReadOnly:\t%v\n", - strings.Join(fc.TargetWWNs, ", "), lun, fc.FSType, fc.ReadOnly) -} - -func printAzureFileVolumeSource(azureFile *api.AzureFileVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tAzureFile (an Azure File Service mount on the host and bind mount to the pod)\n"+ - " SecretName:\t%v\n"+ - " ShareName:\t%v\n"+ - " ReadOnly:\t%v\n", - azureFile.SecretName, azureFile.ShareName, azureFile.ReadOnly) -} - -func printAzureFilePersistentVolumeSource(azureFile *api.AzureFilePersistentVolumeSource, w PrefixWriter) { - ns := "" - if azureFile.SecretNamespace != nil { - ns = *azureFile.SecretNamespace - } - w.Write(LEVEL_2, "Type:\tAzureFile (an Azure File Service mount on the host and bind mount to the pod)\n"+ - " SecretName:\t%v\n"+ - " SecretNamespace:\t%v\n"+ - " ShareName:\t%v\n"+ - " ReadOnly:\t%v\n", - azureFile.SecretName, ns, azureFile.ShareName, azureFile.ReadOnly) -} - -func printFlexPersistentVolumeSource(flex *api.FlexPersistentVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tFlexVolume (a generic volume resource that is provisioned/attached using an exec based plugin)\n"+ - " Driver:\t%v\n"+ - " FSType:\t%v\n"+ - " SecretRef:\t%v\n"+ - " ReadOnly:\t%v\n"+ - " Options:\t%v\n", - flex.Driver, flex.FSType, flex.SecretRef, flex.ReadOnly, flex.Options) -} - -func printFlexVolumeSource(flex *api.FlexVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tFlexVolume (a generic volume resource that is provisioned/attached using an exec based plugin)\n"+ - " Driver:\t%v\n"+ - " FSType:\t%v\n"+ - " SecretRef:\t%v\n"+ - " ReadOnly:\t%v\n"+ - " Options:\t%v\n", - flex.Driver, flex.FSType, flex.SecretRef, flex.ReadOnly, flex.Options) -} - -func printFlockerVolumeSource(flocker *api.FlockerVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tFlocker (a Flocker volume mounted by the Flocker agent)\n"+ - " DatasetName:\t%v\n"+ - " DatasetUUID:\t%v\n", - flocker.DatasetName, flocker.DatasetUUID) -} - -func printCSIPersistentVolumeSource(csi *api.CSIPersistentVolumeSource, w PrefixWriter) { - w.Write(LEVEL_2, "Type:\tCSI (a Container Storage Interface (CSI) volume source)\n"+ - " Driver:\t%v\n"+ - " VolumeHandle:\t%v\n"+ - " ReadOnly:\t%v\n", - csi.Driver, csi.VolumeHandle, csi.ReadOnly) - printCSIPersistentVolumeAttributesMultiline(w, "VolumeAttributes", csi.VolumeAttributes) -} - -func printCSIPersistentVolumeAttributesMultiline(w PrefixWriter, title string, annotations map[string]string) { - printCSIPersistentVolumeAttributesMultilineIndent(w, "", title, "\t", annotations, sets.NewString()) -} - -func printCSIPersistentVolumeAttributesMultilineIndent(w PrefixWriter, initialIndent, title, innerIndent string, attributes map[string]string, skip sets.String) { - w.Write(LEVEL_2, "%s%s:%s", initialIndent, title, innerIndent) - - if len(attributes) == 0 { - w.WriteLine("") - return - } - - // to print labels in the sorted order - keys := make([]string, 0, len(attributes)) - for key := range attributes { - if skip.Has(key) { - continue - } - keys = append(keys, key) - } - if len(attributes) == 0 { - w.WriteLine("") - return - } - sort.Strings(keys) - - for i, key := range keys { - if i != 0 { - w.Write(LEVEL_2, initialIndent) - w.Write(LEVEL_2, innerIndent) - } - line := fmt.Sprintf("%s=%s", key, attributes[key]) - if len(line) > maxAnnotationLen { - w.Write(LEVEL_2, "%s...\n", line[:maxAnnotationLen]) - } else { - w.Write(LEVEL_2, "%s\n", line) - } - i++ - } -} - -type PersistentVolumeDescriber struct { - clientset.Interface -} - -func (d *PersistentVolumeDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - c := d.Core().PersistentVolumes() - - pv, err := c.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, pv) - } - - return describePersistentVolume(pv, events) -} - -func printVolumeNodeAffinity(w PrefixWriter, affinity *api.VolumeNodeAffinity) { - w.Write(LEVEL_0, "Node Affinity:\t") - if affinity == nil || affinity.Required == nil { - w.WriteLine("") - return - } - w.WriteLine("") - - if affinity.Required != nil { - w.Write(LEVEL_1, "Required Terms:\t") - if len(affinity.Required.NodeSelectorTerms) == 0 { - w.WriteLine("") - } else { - w.WriteLine("") - for i, term := range affinity.Required.NodeSelectorTerms { - printNodeSelectorTermsMultilineWithIndent(w, LEVEL_2, fmt.Sprintf("Term %v", i), "\t", term.MatchExpressions) - } - } - } -} - -// printLabelsMultiline prints multiple labels with a user-defined alignment. -func printNodeSelectorTermsMultilineWithIndent(w PrefixWriter, indentLevel int, title, innerIndent string, reqs []api.NodeSelectorRequirement) { - w.Write(indentLevel, "%s:%s", title, innerIndent) - - if len(reqs) == 0 { - w.WriteLine("") - return - } - - for i, req := range reqs { - if i != 0 { - w.Write(indentLevel, "%s", innerIndent) - } - exprStr := fmt.Sprintf("%s %s", req.Key, strings.ToLower(string(req.Operator))) - if len(req.Values) > 0 { - exprStr = fmt.Sprintf("%s [%s]", exprStr, strings.Join(req.Values, ", ")) - } - w.Write(LEVEL_0, "%s\n", exprStr) - } -} - -func describePersistentVolume(pv *api.PersistentVolume, events *api.EventList) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", pv.Name) - printLabelsMultiline(w, "Labels", pv.ObjectMeta.Labels) - printAnnotationsMultiline(w, "Annotations", pv.ObjectMeta.Annotations) - w.Write(LEVEL_0, "Finalizers:\t%v\n", pv.ObjectMeta.Finalizers) - w.Write(LEVEL_0, "StorageClass:\t%s\n", helper.GetPersistentVolumeClass(pv)) - if pv.ObjectMeta.DeletionTimestamp != nil { - w.Write(LEVEL_0, "Status:\tTerminating (lasts %s)\n", translateTimestampUntil(*pv.ObjectMeta.DeletionTimestamp)) - } else { - w.Write(LEVEL_0, "Status:\t%v\n", pv.Status.Phase) - } - if pv.Spec.ClaimRef != nil { - w.Write(LEVEL_0, "Claim:\t%s\n", pv.Spec.ClaimRef.Namespace+"/"+pv.Spec.ClaimRef.Name) - } else { - w.Write(LEVEL_0, "Claim:\t%s\n", "") - } - w.Write(LEVEL_0, "Reclaim Policy:\t%v\n", pv.Spec.PersistentVolumeReclaimPolicy) - w.Write(LEVEL_0, "Access Modes:\t%s\n", helper.GetAccessModesAsString(pv.Spec.AccessModes)) - if pv.Spec.VolumeMode != nil { - w.Write(LEVEL_0, "VolumeMode:\t%v\n", *pv.Spec.VolumeMode) - } - storage := pv.Spec.Capacity[api.ResourceStorage] - w.Write(LEVEL_0, "Capacity:\t%s\n", storage.String()) - printVolumeNodeAffinity(w, pv.Spec.NodeAffinity) - w.Write(LEVEL_0, "Message:\t%s\n", pv.Status.Message) - w.Write(LEVEL_0, "Source:\n") - - switch { - case pv.Spec.HostPath != nil: - printHostPathVolumeSource(pv.Spec.HostPath, w) - case pv.Spec.GCEPersistentDisk != nil: - printGCEPersistentDiskVolumeSource(pv.Spec.GCEPersistentDisk, w) - case pv.Spec.AWSElasticBlockStore != nil: - printAWSElasticBlockStoreVolumeSource(pv.Spec.AWSElasticBlockStore, w) - case pv.Spec.NFS != nil: - printNFSVolumeSource(pv.Spec.NFS, w) - case pv.Spec.ISCSI != nil: - printISCSIPersistentVolumeSource(pv.Spec.ISCSI, w) - case pv.Spec.Glusterfs != nil: - printGlusterfsPersistentVolumeSource(pv.Spec.Glusterfs, w) - case pv.Spec.RBD != nil: - printRBDPersistentVolumeSource(pv.Spec.RBD, w) - case pv.Spec.Quobyte != nil: - printQuobyteVolumeSource(pv.Spec.Quobyte, w) - case pv.Spec.VsphereVolume != nil: - printVsphereVolumeSource(pv.Spec.VsphereVolume, w) - case pv.Spec.Cinder != nil: - printCinderPersistentVolumeSource(pv.Spec.Cinder, w) - case pv.Spec.AzureDisk != nil: - printAzureDiskVolumeSource(pv.Spec.AzureDisk, w) - case pv.Spec.PhotonPersistentDisk != nil: - printPhotonPersistentDiskVolumeSource(pv.Spec.PhotonPersistentDisk, w) - case pv.Spec.PortworxVolume != nil: - printPortworxVolumeSource(pv.Spec.PortworxVolume, w) - case pv.Spec.ScaleIO != nil: - printScaleIOPersistentVolumeSource(pv.Spec.ScaleIO, w) - case pv.Spec.Local != nil: - printLocalVolumeSource(pv.Spec.Local, w) - case pv.Spec.CephFS != nil: - printCephFSPersistentVolumeSource(pv.Spec.CephFS, w) - case pv.Spec.StorageOS != nil: - printStorageOSPersistentVolumeSource(pv.Spec.StorageOS, w) - case pv.Spec.FC != nil: - printFCVolumeSource(pv.Spec.FC, w) - case pv.Spec.AzureFile != nil: - printAzureFilePersistentVolumeSource(pv.Spec.AzureFile, w) - case pv.Spec.FlexVolume != nil: - printFlexPersistentVolumeSource(pv.Spec.FlexVolume, w) - case pv.Spec.Flocker != nil: - printFlockerVolumeSource(pv.Spec.Flocker, w) - case pv.Spec.CSI != nil: - printCSIPersistentVolumeSource(pv.Spec.CSI, w) - default: - w.Write(LEVEL_1, "\n") - } - - if events != nil { - DescribeEvents(events, w) - } - - return nil - }) -} - -type PersistentVolumeClaimDescriber struct { - clientset.Interface -} - -func (d *PersistentVolumeClaimDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - c := d.Core().PersistentVolumeClaims(namespace) - - pvc, err := c.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - pc := d.Core().Pods(namespace) - - mountPods, err := getMountPods(pc, pvc.Name) - if err != nil { - return "", err - } - - events, _ := d.Core().Events(namespace).Search(legacyscheme.Scheme, pvc) - - return describePersistentVolumeClaim(pvc, events, mountPods) -} - -func getMountPods(c coreclient.PodInterface, pvcName string) ([]api.Pod, error) { - nsPods, err := c.List(metav1.ListOptions{}) - if err != nil { - return []api.Pod{}, err - } - - var pods []api.Pod - - for _, pod := range nsPods.Items { - pvcs := getPvcs(pod.Spec.Volumes) - - for _, pvc := range pvcs { - if pvc.PersistentVolumeClaim.ClaimName == pvcName { - pods = append(pods, pod) - } - } - } - - return pods, nil -} - -func getPvcs(volumes []api.Volume) []api.Volume { - var pvcs []api.Volume - - for _, volume := range volumes { - if volume.VolumeSource.PersistentVolumeClaim != nil { - pvcs = append(pvcs, volume) - } - } - - return pvcs -} - -func describePersistentVolumeClaim(pvc *api.PersistentVolumeClaim, events *api.EventList, mountPods []api.Pod) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", pvc.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", pvc.Namespace) - w.Write(LEVEL_0, "StorageClass:\t%s\n", helper.GetPersistentVolumeClaimClass(pvc)) - if pvc.ObjectMeta.DeletionTimestamp != nil { - w.Write(LEVEL_0, "Status:\tTerminating (lasts %s)\n", translateTimestampUntil(*pvc.ObjectMeta.DeletionTimestamp)) - } else { - w.Write(LEVEL_0, "Status:\t%v\n", pvc.Status.Phase) - } - w.Write(LEVEL_0, "Volume:\t%s\n", pvc.Spec.VolumeName) - printLabelsMultiline(w, "Labels", pvc.Labels) - printAnnotationsMultiline(w, "Annotations", pvc.Annotations) - w.Write(LEVEL_0, "Finalizers:\t%v\n", pvc.ObjectMeta.Finalizers) - storage := pvc.Spec.Resources.Requests[api.ResourceStorage] - capacity := "" - accessModes := "" - if pvc.Spec.VolumeName != "" { - accessModes = helper.GetAccessModesAsString(pvc.Status.AccessModes) - storage = pvc.Status.Capacity[api.ResourceStorage] - capacity = storage.String() - } - w.Write(LEVEL_0, "Capacity:\t%s\n", capacity) - w.Write(LEVEL_0, "Access Modes:\t%s\n", accessModes) - if pvc.Spec.VolumeMode != nil { - w.Write(LEVEL_0, "VolumeMode:\t%v\n", *pvc.Spec.VolumeMode) - } - if len(pvc.Status.Conditions) > 0 { - w.Write(LEVEL_0, "Conditions:\n") - w.Write(LEVEL_1, "Type\tStatus\tLastProbeTime\tLastTransitionTime\tReason\tMessage\n") - w.Write(LEVEL_1, "----\t------\t-----------------\t------------------\t------\t-------\n") - for _, c := range pvc.Status.Conditions { - w.Write(LEVEL_1, "%v \t%v \t%s \t%s \t%v \t%v\n", - c.Type, - c.Status, - c.LastProbeTime.Time.Format(time.RFC1123Z), - c.LastTransitionTime.Time.Format(time.RFC1123Z), - c.Reason, - c.Message) - } - } - if events != nil { - DescribeEvents(events, w) - } - - printPodsMultiline(w, "Mounted By", mountPods) - - return nil - }) -} - -func describeContainers(label string, containers []api.Container, containerStatuses []api.ContainerStatus, - resolverFn EnvVarResolverFunc, w PrefixWriter, space string) { - statuses := map[string]api.ContainerStatus{} - for _, status := range containerStatuses { - statuses[status.Name] = status - } - - describeContainersLabel(containers, label, space, w) - - for _, container := range containers { - status, ok := statuses[container.Name] - describeContainerBasicInfo(container, status, ok, space, w) - describeContainerCommand(container, w) - if ok { - describeContainerState(status, w) - } - describeContainerResource(container, w) - describeContainerProbe(container, w) - if len(container.EnvFrom) > 0 { - describeContainerEnvFrom(container, resolverFn, w) - } - describeContainerEnvVars(container, resolverFn, w) - describeContainerVolumes(container, w) - } -} - -func describeContainersLabel(containers []api.Container, label, space string, w PrefixWriter) { - none := "" - if len(containers) == 0 { - none = " " - } - w.Write(LEVEL_0, "%s%s:%s\n", space, label, none) -} - -func describeContainerBasicInfo(container api.Container, status api.ContainerStatus, ok bool, space string, w PrefixWriter) { - nameIndent := "" - if len(space) > 0 { - nameIndent = " " - } - w.Write(LEVEL_1, "%s%v:\n", nameIndent, container.Name) - if ok { - w.Write(LEVEL_2, "Container ID:\t%s\n", status.ContainerID) - } - w.Write(LEVEL_2, "Image:\t%s\n", container.Image) - if ok { - w.Write(LEVEL_2, "Image ID:\t%s\n", status.ImageID) - } - portString := describeContainerPorts(container.Ports) - if strings.Contains(portString, ",") { - w.Write(LEVEL_2, "Ports:\t%s\n", portString) - } else { - w.Write(LEVEL_2, "Port:\t%s\n", stringOrNone(portString)) - } - hostPortString := describeContainerHostPorts(container.Ports) - if strings.Contains(hostPortString, ",") { - w.Write(LEVEL_2, "Host Ports:\t%s\n", hostPortString) - } else { - w.Write(LEVEL_2, "Host Port:\t%s\n", stringOrNone(hostPortString)) - } -} - -func describeContainerPorts(cPorts []api.ContainerPort) string { - ports := make([]string, 0, len(cPorts)) - for _, cPort := range cPorts { - ports = append(ports, fmt.Sprintf("%d/%s", cPort.ContainerPort, cPort.Protocol)) - } - return strings.Join(ports, ", ") -} - -func describeContainerHostPorts(cPorts []api.ContainerPort) string { - ports := make([]string, 0, len(cPorts)) - for _, cPort := range cPorts { - ports = append(ports, fmt.Sprintf("%d/%s", cPort.HostPort, cPort.Protocol)) - } - return strings.Join(ports, ", ") -} - -func describeContainerCommand(container api.Container, w PrefixWriter) { - if len(container.Command) > 0 { - w.Write(LEVEL_2, "Command:\n") - for _, c := range container.Command { - for _, s := range strings.Split(c, "\n") { - w.Write(LEVEL_3, "%s\n", s) - } - } - } - if len(container.Args) > 0 { - w.Write(LEVEL_2, "Args:\n") - for _, arg := range container.Args { - for _, s := range strings.Split(arg, "\n") { - w.Write(LEVEL_3, "%s\n", s) - } - } - } -} - -func describeContainerResource(container api.Container, w PrefixWriter) { - resources := container.Resources - if len(resources.Limits) > 0 { - w.Write(LEVEL_2, "Limits:\n") - } - for _, name := range SortedResourceNames(resources.Limits) { - quantity := resources.Limits[name] - w.Write(LEVEL_3, "%s:\t%s\n", name, quantity.String()) - } - - if len(resources.Requests) > 0 { - w.Write(LEVEL_2, "Requests:\n") - } - for _, name := range SortedResourceNames(resources.Requests) { - quantity := resources.Requests[name] - w.Write(LEVEL_3, "%s:\t%s\n", name, quantity.String()) - } -} - -func describeContainerState(status api.ContainerStatus, w PrefixWriter) { - describeStatus("State", status.State, w) - if status.LastTerminationState.Terminated != nil { - describeStatus("Last State", status.LastTerminationState, w) - } - w.Write(LEVEL_2, "Ready:\t%v\n", printBool(status.Ready)) - w.Write(LEVEL_2, "Restart Count:\t%d\n", status.RestartCount) -} - -func describeContainerProbe(container api.Container, w PrefixWriter) { - if container.LivenessProbe != nil { - probe := DescribeProbe(container.LivenessProbe) - w.Write(LEVEL_2, "Liveness:\t%s\n", probe) - } - if container.ReadinessProbe != nil { - probe := DescribeProbe(container.ReadinessProbe) - w.Write(LEVEL_2, "Readiness:\t%s\n", probe) - } -} - -func describeContainerVolumes(container api.Container, w PrefixWriter) { - // Show volumeMounts - none := "" - if len(container.VolumeMounts) == 0 { - none = "\t" - } - w.Write(LEVEL_2, "Mounts:%s\n", none) - sort.Sort(SortableVolumeMounts(container.VolumeMounts)) - for _, mount := range container.VolumeMounts { - flags := []string{} - switch { - case mount.ReadOnly: - flags = append(flags, "ro") - case !mount.ReadOnly: - flags = append(flags, "rw") - case len(mount.SubPath) > 0: - flags = append(flags, fmt.Sprintf("path=%q", mount.SubPath)) - } - w.Write(LEVEL_3, "%s from %s (%s)\n", mount.MountPath, mount.Name, strings.Join(flags, ",")) - } - // Show volumeDevices if exists - if len(container.VolumeDevices) > 0 { - w.Write(LEVEL_2, "Devices:%s\n", none) - sort.Sort(SortableVolumeDevices(container.VolumeDevices)) - for _, device := range container.VolumeDevices { - w.Write(LEVEL_3, "%s from %s\n", device.DevicePath, device.Name) - } - } -} - -func describeContainerEnvVars(container api.Container, resolverFn EnvVarResolverFunc, w PrefixWriter) { - none := "" - if len(container.Env) == 0 { - none = "\t" - } - w.Write(LEVEL_2, "Environment:%s\n", none) - - for _, e := range container.Env { - if e.ValueFrom == nil { - for i, s := range strings.Split(e.Value, "\n") { - if i == 0 { - w.Write(LEVEL_3, "%s:\t%s\n", e.Name, s) - } else { - w.Write(LEVEL_3, "\t%s\n", s) - } - } - continue - } - - switch { - case e.ValueFrom.FieldRef != nil: - var valueFrom string - if resolverFn != nil { - valueFrom = resolverFn(e) - } - w.Write(LEVEL_3, "%s:\t%s (%s:%s)\n", e.Name, valueFrom, e.ValueFrom.FieldRef.APIVersion, e.ValueFrom.FieldRef.FieldPath) - case e.ValueFrom.ResourceFieldRef != nil: - valueFrom, err := resourcehelper.ExtractContainerResourceValue(e.ValueFrom.ResourceFieldRef, &container) - if err != nil { - valueFrom = "" - } - resource := e.ValueFrom.ResourceFieldRef.Resource - if valueFrom == "0" && (resource == "limits.cpu" || resource == "limits.memory") { - valueFrom = "node allocatable" - } - w.Write(LEVEL_3, "%s:\t%s (%s)\n", e.Name, valueFrom, resource) - case e.ValueFrom.SecretKeyRef != nil: - optional := e.ValueFrom.SecretKeyRef.Optional != nil && *e.ValueFrom.SecretKeyRef.Optional - w.Write(LEVEL_3, "%s:\t\tOptional: %t\n", e.Name, e.ValueFrom.SecretKeyRef.Key, e.ValueFrom.SecretKeyRef.Name, optional) - case e.ValueFrom.ConfigMapKeyRef != nil: - optional := e.ValueFrom.ConfigMapKeyRef.Optional != nil && *e.ValueFrom.ConfigMapKeyRef.Optional - w.Write(LEVEL_3, "%s:\t\tOptional: %t\n", e.Name, e.ValueFrom.ConfigMapKeyRef.Key, e.ValueFrom.ConfigMapKeyRef.Name, optional) - } - } -} - -func describeContainerEnvFrom(container api.Container, resolverFn EnvVarResolverFunc, w PrefixWriter) { - none := "" - if len(container.EnvFrom) == 0 { - none = "\t" - } - w.Write(LEVEL_2, "Environment Variables from:%s\n", none) - - for _, e := range container.EnvFrom { - from := "" - name := "" - optional := false - if e.ConfigMapRef != nil { - from = "ConfigMap" - name = e.ConfigMapRef.Name - optional = e.ConfigMapRef.Optional != nil && *e.ConfigMapRef.Optional - } else if e.SecretRef != nil { - from = "Secret" - name = e.SecretRef.Name - optional = e.SecretRef.Optional != nil && *e.SecretRef.Optional - } - if len(e.Prefix) == 0 { - w.Write(LEVEL_3, "%s\t%s\tOptional: %t\n", name, from, optional) - } else { - w.Write(LEVEL_3, "%s\t%s with prefix '%s'\tOptional: %t\n", name, from, e.Prefix, optional) - } - } -} - -// DescribeProbe is exported for consumers in other API groups that have probes -func DescribeProbe(probe *api.Probe) string { - attrs := fmt.Sprintf("delay=%ds timeout=%ds period=%ds #success=%d #failure=%d", probe.InitialDelaySeconds, probe.TimeoutSeconds, probe.PeriodSeconds, probe.SuccessThreshold, probe.FailureThreshold) - switch { - case probe.Exec != nil: - return fmt.Sprintf("exec %v %s", probe.Exec.Command, attrs) - case probe.HTTPGet != nil: - url := &url.URL{} - url.Scheme = strings.ToLower(string(probe.HTTPGet.Scheme)) - if len(probe.HTTPGet.Port.String()) > 0 { - url.Host = net.JoinHostPort(probe.HTTPGet.Host, probe.HTTPGet.Port.String()) - } else { - url.Host = probe.HTTPGet.Host - } - url.Path = probe.HTTPGet.Path - return fmt.Sprintf("http-get %s %s", url.String(), attrs) - case probe.TCPSocket != nil: - return fmt.Sprintf("tcp-socket %s:%s %s", probe.TCPSocket.Host, probe.TCPSocket.Port.String(), attrs) - } - return fmt.Sprintf("unknown %s", attrs) -} - -type EnvVarResolverFunc func(e api.EnvVar) string - -// EnvValueFrom is exported for use by describers in other packages -func EnvValueRetriever(pod *api.Pod) EnvVarResolverFunc { - return func(e api.EnvVar) string { - gv, err := schema.ParseGroupVersion(e.ValueFrom.FieldRef.APIVersion) - if err != nil { - return "" - } - gvk := gv.WithKind("Pod") - internalFieldPath, _, err := legacyscheme.Scheme.ConvertFieldLabel(gvk, e.ValueFrom.FieldRef.FieldPath, "") - if err != nil { - return "" // pod validation should catch this on create - } - - valueFrom, err := fieldpath.ExtractFieldPathAsString(pod, internalFieldPath) - if err != nil { - return "" // pod validation should catch this on create - } - - return valueFrom - } -} - -func describeStatus(stateName string, state api.ContainerState, w PrefixWriter) { - switch { - case state.Running != nil: - w.Write(LEVEL_2, "%s:\tRunning\n", stateName) - w.Write(LEVEL_3, "Started:\t%v\n", state.Running.StartedAt.Time.Format(time.RFC1123Z)) - case state.Waiting != nil: - w.Write(LEVEL_2, "%s:\tWaiting\n", stateName) - if state.Waiting.Reason != "" { - w.Write(LEVEL_3, "Reason:\t%s\n", state.Waiting.Reason) - } - case state.Terminated != nil: - w.Write(LEVEL_2, "%s:\tTerminated\n", stateName) - if state.Terminated.Reason != "" { - w.Write(LEVEL_3, "Reason:\t%s\n", state.Terminated.Reason) - } - if state.Terminated.Message != "" { - w.Write(LEVEL_3, "Message:\t%s\n", state.Terminated.Message) - } - w.Write(LEVEL_3, "Exit Code:\t%d\n", state.Terminated.ExitCode) - if state.Terminated.Signal > 0 { - w.Write(LEVEL_3, "Signal:\t%d\n", state.Terminated.Signal) - } - w.Write(LEVEL_3, "Started:\t%s\n", state.Terminated.StartedAt.Time.Format(time.RFC1123Z)) - w.Write(LEVEL_3, "Finished:\t%s\n", state.Terminated.FinishedAt.Time.Format(time.RFC1123Z)) - default: - w.Write(LEVEL_2, "%s:\tWaiting\n", stateName) - } -} - -func describeVolumeClaimTemplates(templates []api.PersistentVolumeClaim, w PrefixWriter) { - if len(templates) == 0 { - w.Write(LEVEL_0, "Volume Claims:\t\n") - return - } - w.Write(LEVEL_0, "Volume Claims:\n") - for _, pvc := range templates { - w.Write(LEVEL_1, "Name:\t%s\n", pvc.Name) - w.Write(LEVEL_1, "StorageClass:\t%s\n", helper.GetPersistentVolumeClaimClass(&pvc)) - printLabelsMultilineWithIndent(w, " ", "Labels", "\t", pvc.Labels, sets.NewString()) - printLabelsMultilineWithIndent(w, " ", "Annotations", "\t", pvc.Annotations, sets.NewString()) - if capacity, ok := pvc.Spec.Resources.Requests[api.ResourceStorage]; ok { - w.Write(LEVEL_1, "Capacity:\t%s\n", capacity.String()) - } else { - w.Write(LEVEL_1, "Capacity:\t%s\n", "") - } - w.Write(LEVEL_1, "Access Modes:\t%s\n", pvc.Spec.AccessModes) - } -} - -func printBoolPtr(value *bool) string { - if value != nil { - return printBool(*value) - } - - return "" -} - -func printBool(value bool) string { - if value { - return "True" - } - - return "False" -} - -// ReplicationControllerDescriber generates information about a replication controller -// and the pods it has created. -type ReplicationControllerDescriber struct { - clientset.Interface -} - -func (d *ReplicationControllerDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - rc := d.Core().ReplicationControllers(namespace) - pc := d.Core().Pods(namespace) - - controller, err := rc.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - running, waiting, succeeded, failed, err := getPodStatusForController(pc, labels.SelectorFromSet(controller.Spec.Selector), controller.UID) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, controller) - } - - return describeReplicationController(controller, events, running, waiting, succeeded, failed) -} - -func describeReplicationController(controller *api.ReplicationController, events *api.EventList, running, waiting, succeeded, failed int) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", controller.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", controller.Namespace) - w.Write(LEVEL_0, "Selector:\t%s\n", labels.FormatLabels(controller.Spec.Selector)) - printLabelsMultiline(w, "Labels", controller.Labels) - printAnnotationsMultiline(w, "Annotations", controller.Annotations) - w.Write(LEVEL_0, "Replicas:\t%d current / %d desired\n", controller.Status.Replicas, controller.Spec.Replicas) - w.Write(LEVEL_0, "Pods Status:\t%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed) - DescribePodTemplate(controller.Spec.Template, w) - if len(controller.Status.Conditions) > 0 { - w.Write(LEVEL_0, "Conditions:\n Type\tStatus\tReason\n") - w.Write(LEVEL_1, "----\t------\t------\n") - for _, c := range controller.Status.Conditions { - w.Write(LEVEL_1, "%v \t%v\t%v\n", c.Type, c.Status, c.Reason) - } - } - if events != nil { - DescribeEvents(events, w) - } - return nil - }) -} - -func DescribePodTemplate(template *api.PodTemplateSpec, w PrefixWriter) { - w.Write(LEVEL_0, "Pod Template:\n") - if template == nil { - w.Write(LEVEL_1, "") - return - } - printLabelsMultiline(w, " Labels", template.Labels) - if len(template.Annotations) > 0 { - printAnnotationsMultiline(w, " Annotations", template.Annotations) - } - if len(template.Spec.ServiceAccountName) > 0 { - w.Write(LEVEL_1, "Service Account:\t%s\n", template.Spec.ServiceAccountName) - } - if len(template.Spec.InitContainers) > 0 { - describeContainers("Init Containers", template.Spec.InitContainers, nil, nil, w, " ") - } - describeContainers("Containers", template.Spec.Containers, nil, nil, w, " ") - describeVolumes(template.Spec.Volumes, w, " ") -} - -// ReplicaSetDescriber generates information about a ReplicaSet and the pods it has created. -type ReplicaSetDescriber struct { - clientset.Interface -} - -func (d *ReplicaSetDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - rsc := d.Apps().ReplicaSets(namespace) - pc := d.Core().Pods(namespace) - - rs, err := rsc.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector) - if err != nil { - return "", err - } - - running, waiting, succeeded, failed, getPodErr := getPodStatusForController(pc, selector, rs.UID) - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, rs) - } - - return describeReplicaSet(rs, events, running, waiting, succeeded, failed, getPodErr) -} - -func describeReplicaSet(rs *apps.ReplicaSet, events *api.EventList, running, waiting, succeeded, failed int, getPodErr error) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", rs.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", rs.Namespace) - w.Write(LEVEL_0, "Selector:\t%s\n", metav1.FormatLabelSelector(rs.Spec.Selector)) - printLabelsMultiline(w, "Labels", rs.Labels) - printAnnotationsMultiline(w, "Annotations", rs.Annotations) - if controlledBy := printController(rs); len(controlledBy) > 0 { - w.Write(LEVEL_0, "Controlled By:\t%s\n", controlledBy) - } - w.Write(LEVEL_0, "Replicas:\t%d current / %d desired\n", rs.Status.Replicas, rs.Spec.Replicas) - w.Write(LEVEL_0, "Pods Status:\t") - if getPodErr != nil { - w.Write(LEVEL_0, "error in fetching pods: %s\n", getPodErr) - } else { - w.Write(LEVEL_0, "%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed) - } - DescribePodTemplate(&rs.Spec.Template, w) - if len(rs.Status.Conditions) > 0 { - w.Write(LEVEL_0, "Conditions:\n Type\tStatus\tReason\n") - w.Write(LEVEL_1, "----\t------\t------\n") - for _, c := range rs.Status.Conditions { - w.Write(LEVEL_1, "%v \t%v\t%v\n", c.Type, c.Status, c.Reason) - } - } - if events != nil { - DescribeEvents(events, w) - } - return nil - }) -} - -// JobDescriber generates information about a job and the pods it has created. -type JobDescriber struct { - clientset.Interface -} - -func (d *JobDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - job, err := d.Batch().Jobs(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, job) - } - - return describeJob(job, events) -} - -func describeJob(job *batch.Job, events *api.EventList) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", job.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", job.Namespace) - selector, _ := metav1.LabelSelectorAsSelector(job.Spec.Selector) - w.Write(LEVEL_0, "Selector:\t%s\n", selector) - printLabelsMultiline(w, "Labels", job.Labels) - printAnnotationsMultiline(w, "Annotations", job.Annotations) - if controlledBy := printController(job); len(controlledBy) > 0 { - w.Write(LEVEL_0, "Controlled By:\t%s\n", controlledBy) - } - w.Write(LEVEL_0, "Parallelism:\t%d\n", *job.Spec.Parallelism) - if job.Spec.Completions != nil { - w.Write(LEVEL_0, "Completions:\t%d\n", *job.Spec.Completions) - } else { - w.Write(LEVEL_0, "Completions:\t\n") - } - if job.Status.StartTime != nil { - w.Write(LEVEL_0, "Start Time:\t%s\n", job.Status.StartTime.Time.Format(time.RFC1123Z)) - } - if job.Status.CompletionTime != nil { - w.Write(LEVEL_0, "Completed At:\t%s\n", job.Status.CompletionTime.Time.Format(time.RFC1123Z)) - } - if job.Status.StartTime != nil && job.Status.CompletionTime != nil { - w.Write(LEVEL_0, "Duration:\t%s\n", duration.HumanDuration(job.Status.CompletionTime.Sub(job.Status.StartTime.Time))) - } - if job.Spec.ActiveDeadlineSeconds != nil { - w.Write(LEVEL_0, "Active Deadline Seconds:\t%ds\n", *job.Spec.ActiveDeadlineSeconds) - } - w.Write(LEVEL_0, "Pods Statuses:\t%d Running / %d Succeeded / %d Failed\n", job.Status.Active, job.Status.Succeeded, job.Status.Failed) - DescribePodTemplate(&job.Spec.Template, w) - if events != nil { - DescribeEvents(events, w) - } - return nil - }) -} - -// CronJobDescriber generates information about a cron job and the jobs it has created. -type CronJobDescriber struct { - clientset.Interface - external externalclient.Interface -} - -func (d *CronJobDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - cronJob, err := d.external.BatchV1beta1().CronJobs(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, cronJob) - } - - internalCronJob := &batch.CronJob{} - if err := legacyscheme.Scheme.Convert(cronJob, internalCronJob, nil); err != nil { - return "", err - } - - return describeCronJob(internalCronJob, events) -} - -func describeCronJob(cronJob *batch.CronJob, events *api.EventList) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", cronJob.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", cronJob.Namespace) - printLabelsMultiline(w, "Labels", cronJob.Labels) - printAnnotationsMultiline(w, "Annotations", cronJob.Annotations) - w.Write(LEVEL_0, "Schedule:\t%s\n", cronJob.Spec.Schedule) - w.Write(LEVEL_0, "Concurrency Policy:\t%s\n", cronJob.Spec.ConcurrencyPolicy) - w.Write(LEVEL_0, "Suspend:\t%s\n", printBoolPtr(cronJob.Spec.Suspend)) - if cronJob.Spec.StartingDeadlineSeconds != nil { - w.Write(LEVEL_0, "Starting Deadline Seconds:\t%ds\n", *cronJob.Spec.StartingDeadlineSeconds) - } else { - w.Write(LEVEL_0, "Starting Deadline Seconds:\t\n") - } - describeJobTemplate(cronJob.Spec.JobTemplate, w) - if cronJob.Status.LastScheduleTime != nil { - w.Write(LEVEL_0, "Last Schedule Time:\t%s\n", cronJob.Status.LastScheduleTime.Time.Format(time.RFC1123Z)) - } else { - w.Write(LEVEL_0, "Last Schedule Time:\t\n") - } - printActiveJobs(w, "Active Jobs", cronJob.Status.Active) - if events != nil { - DescribeEvents(events, w) - } - return nil - }) -} - -func describeJobTemplate(jobTemplate batch.JobTemplateSpec, w PrefixWriter) { - if jobTemplate.Spec.Selector != nil { - selector, _ := metav1.LabelSelectorAsSelector(jobTemplate.Spec.Selector) - w.Write(LEVEL_0, "Selector:\t%s\n", selector) - } else { - w.Write(LEVEL_0, "Selector:\t\n") - } - if jobTemplate.Spec.Parallelism != nil { - w.Write(LEVEL_0, "Parallelism:\t%d\n", *jobTemplate.Spec.Parallelism) - } else { - w.Write(LEVEL_0, "Parallelism:\t\n") - } - if jobTemplate.Spec.Completions != nil { - w.Write(LEVEL_0, "Completions:\t%d\n", *jobTemplate.Spec.Completions) - } else { - w.Write(LEVEL_0, "Completions:\t\n") - } - if jobTemplate.Spec.ActiveDeadlineSeconds != nil { - w.Write(LEVEL_0, "Active Deadline Seconds:\t%ds\n", *jobTemplate.Spec.ActiveDeadlineSeconds) - } - DescribePodTemplate(&jobTemplate.Spec.Template, w) -} - -func printActiveJobs(w PrefixWriter, title string, jobs []api.ObjectReference) { - w.Write(LEVEL_0, "%s:\t", title) - if len(jobs) == 0 { - w.WriteLine("") - return - } - - for i, job := range jobs { - if i != 0 { - w.Write(LEVEL_0, ", ") - } - w.Write(LEVEL_0, "%s", job.Name) - } - w.WriteLine("") -} - -// DaemonSetDescriber generates information about a daemon set and the pods it has created. -type DaemonSetDescriber struct { - clientset.Interface -} - -func (d *DaemonSetDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - dc := d.Apps().DaemonSets(namespace) - pc := d.Core().Pods(namespace) - - daemon, err := dc.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - selector, err := metav1.LabelSelectorAsSelector(daemon.Spec.Selector) - if err != nil { - return "", err - } - running, waiting, succeeded, failed, err := getPodStatusForController(pc, selector, daemon.UID) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, daemon) - } - - return describeDaemonSet(daemon, events, running, waiting, succeeded, failed) -} - -func describeDaemonSet(daemon *apps.DaemonSet, events *api.EventList, running, waiting, succeeded, failed int) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", daemon.Name) - selector, err := metav1.LabelSelectorAsSelector(daemon.Spec.Selector) - if err != nil { - // this shouldn't happen if LabelSelector passed validation - return err - } - w.Write(LEVEL_0, "Selector:\t%s\n", selector) - w.Write(LEVEL_0, "Node-Selector:\t%s\n", labels.FormatLabels(daemon.Spec.Template.Spec.NodeSelector)) - printLabelsMultiline(w, "Labels", daemon.Labels) - printAnnotationsMultiline(w, "Annotations", daemon.Annotations) - w.Write(LEVEL_0, "Desired Number of Nodes Scheduled: %d\n", daemon.Status.DesiredNumberScheduled) - w.Write(LEVEL_0, "Current Number of Nodes Scheduled: %d\n", daemon.Status.CurrentNumberScheduled) - w.Write(LEVEL_0, "Number of Nodes Scheduled with Up-to-date Pods: %d\n", daemon.Status.UpdatedNumberScheduled) - w.Write(LEVEL_0, "Number of Nodes Scheduled with Available Pods: %d\n", daemon.Status.NumberAvailable) - w.Write(LEVEL_0, "Number of Nodes Misscheduled: %d\n", daemon.Status.NumberMisscheduled) - w.Write(LEVEL_0, "Pods Status:\t%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed) - DescribePodTemplate(&daemon.Spec.Template, w) - if events != nil { - DescribeEvents(events, w) - } - return nil - }) -} - -// SecretDescriber generates information about a secret -type SecretDescriber struct { - clientset.Interface -} - -func (d *SecretDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - c := d.Core().Secrets(namespace) - - secret, err := c.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - return describeSecret(secret) -} - -func describeSecret(secret *api.Secret) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", secret.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", secret.Namespace) - printLabelsMultiline(w, "Labels", secret.Labels) - skipAnnotations := sets.NewString(api.LastAppliedConfigAnnotation) - printAnnotationsMultilineWithFilter(w, "Annotations", secret.Annotations, skipAnnotations) - - w.Write(LEVEL_0, "\nType:\t%s\n", secret.Type) - - w.Write(LEVEL_0, "\nData\n====\n") - for k, v := range secret.Data { - switch { - case k == api.ServiceAccountTokenKey && secret.Type == api.SecretTypeServiceAccountToken: - w.Write(LEVEL_0, "%s:\t%s\n", k, string(v)) - default: - w.Write(LEVEL_0, "%s:\t%d bytes\n", k, len(v)) - } - } - - return nil - }) -} - -type IngressDescriber struct { - clientset.Interface -} - -func (i *IngressDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - c := i.Extensions().Ingresses(namespace) - ing, err := c.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - return i.describeIngress(ing, describerSettings) -} - -func (i *IngressDescriber) describeBackend(ns string, backend *extensions.IngressBackend) string { - endpoints, _ := i.Core().Endpoints(ns).Get(backend.ServiceName, metav1.GetOptions{}) - service, _ := i.Core().Services(ns).Get(backend.ServiceName, metav1.GetOptions{}) - spName := "" - for i := range service.Spec.Ports { - sp := &service.Spec.Ports[i] - switch backend.ServicePort.Type { - case intstr.String: - if backend.ServicePort.StrVal == sp.Name { - spName = sp.Name - } - case intstr.Int: - if int32(backend.ServicePort.IntVal) == sp.Port { - spName = sp.Name - } - } - } - return formatEndpoints(endpoints, sets.NewString(spName)) -} - -func (i *IngressDescriber) describeIngress(ing *extensions.Ingress, describerSettings printers.DescriberSettings) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%v\n", ing.Name) - w.Write(LEVEL_0, "Namespace:\t%v\n", ing.Namespace) - w.Write(LEVEL_0, "Address:\t%v\n", loadBalancerStatusStringer(ing.Status.LoadBalancer, true)) - def := ing.Spec.Backend - ns := ing.Namespace - if def == nil { - // Ingresses that don't specify a default backend inherit the - // default backend in the kube-system namespace. - def = &extensions.IngressBackend{ - ServiceName: "default-http-backend", - ServicePort: intstr.IntOrString{Type: intstr.Int, IntVal: 80}, - } - ns = metav1.NamespaceSystem - } - w.Write(LEVEL_0, "Default backend:\t%s (%s)\n", backendStringer(def), i.describeBackend(ns, def)) - if len(ing.Spec.TLS) != 0 { - describeIngressTLS(w, ing.Spec.TLS) - } - w.Write(LEVEL_0, "Rules:\n Host\tPath\tBackends\n") - w.Write(LEVEL_1, "----\t----\t--------\n") - count := 0 - for _, rules := range ing.Spec.Rules { - if rules.HTTP == nil { - continue - } - count++ - host := rules.Host - if len(host) == 0 { - host = "*" - } - w.Write(LEVEL_1, "%s\t\n", host) - for _, path := range rules.HTTP.Paths { - w.Write(LEVEL_2, "\t%s \t%s (%s)\n", path.Path, backendStringer(&path.Backend), i.describeBackend(ns, &path.Backend)) - } - } - if count == 0 { - w.Write(LEVEL_1, "%s\t%s \t%s (%s)\n", "*", "*", backendStringer(def), i.describeBackend(ns, def)) - } - describeIngressAnnotations(w, ing.Annotations) - - if describerSettings.ShowEvents { - events, _ := i.Core().Events(ing.Namespace).Search(legacyscheme.Scheme, ing) - if events != nil { - DescribeEvents(events, w) - } - } - return nil - }) -} - -func describeIngressTLS(w PrefixWriter, ingTLS []extensions.IngressTLS) { - w.Write(LEVEL_0, "TLS:\n") - for _, t := range ingTLS { - if t.SecretName == "" { - w.Write(LEVEL_1, "SNI routes %v\n", strings.Join(t.Hosts, ",")) - } else { - w.Write(LEVEL_1, "%v terminates %v\n", t.SecretName, strings.Join(t.Hosts, ",")) - } - } - return -} - -// TODO: Move from annotations into Ingress status. -func describeIngressAnnotations(w PrefixWriter, annotations map[string]string) { - w.Write(LEVEL_0, "Annotations:\n") - for k, v := range annotations { - w.Write(LEVEL_1, "%v:\t%s\n", k, v) - } - return -} - -// ServiceDescriber generates information about a service. -type ServiceDescriber struct { - clientset.Interface -} - -func (d *ServiceDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - c := d.Core().Services(namespace) - - service, err := c.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - endpoints, _ := d.Core().Endpoints(namespace).Get(name, metav1.GetOptions{}) - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, service) - } - return describeService(service, endpoints, events) -} - -func buildIngressString(ingress []api.LoadBalancerIngress) string { - var buffer bytes.Buffer - - for i := range ingress { - if i != 0 { - buffer.WriteString(", ") - } - if ingress[i].IP != "" { - buffer.WriteString(ingress[i].IP) - } else { - buffer.WriteString(ingress[i].Hostname) - } - } - return buffer.String() -} - -func describeService(service *api.Service, endpoints *api.Endpoints, events *api.EventList) (string, error) { - if endpoints == nil { - endpoints = &api.Endpoints{} - } - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", service.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", service.Namespace) - printLabelsMultiline(w, "Labels", service.Labels) - printAnnotationsMultiline(w, "Annotations", service.Annotations) - w.Write(LEVEL_0, "Selector:\t%s\n", labels.FormatLabels(service.Spec.Selector)) - w.Write(LEVEL_0, "Type:\t%s\n", service.Spec.Type) - w.Write(LEVEL_0, "IP:\t%s\n", service.Spec.ClusterIP) - if len(service.Spec.ExternalIPs) > 0 { - w.Write(LEVEL_0, "External IPs:\t%v\n", strings.Join(service.Spec.ExternalIPs, ",")) - } - if service.Spec.LoadBalancerIP != "" { - w.Write(LEVEL_0, "IP:\t%s\n", service.Spec.LoadBalancerIP) - } - if service.Spec.ExternalName != "" { - w.Write(LEVEL_0, "External Name:\t%s\n", service.Spec.ExternalName) - } - if len(service.Status.LoadBalancer.Ingress) > 0 { - list := buildIngressString(service.Status.LoadBalancer.Ingress) - w.Write(LEVEL_0, "LoadBalancer Ingress:\t%s\n", list) - } - for i := range service.Spec.Ports { - sp := &service.Spec.Ports[i] - - name := sp.Name - if name == "" { - name = "" - } - w.Write(LEVEL_0, "Port:\t%s\t%d/%s\n", name, sp.Port, sp.Protocol) - if sp.TargetPort.Type == intstr.Type(intstr.Int) { - w.Write(LEVEL_0, "TargetPort:\t%d/%s\n", sp.TargetPort.IntVal, sp.Protocol) - } else { - w.Write(LEVEL_0, "TargetPort:\t%s/%s\n", sp.TargetPort.StrVal, sp.Protocol) - } - if sp.NodePort != 0 { - w.Write(LEVEL_0, "NodePort:\t%s\t%d/%s\n", name, sp.NodePort, sp.Protocol) - } - w.Write(LEVEL_0, "Endpoints:\t%s\n", formatEndpoints(endpoints, sets.NewString(sp.Name))) - } - w.Write(LEVEL_0, "Session Affinity:\t%s\n", service.Spec.SessionAffinity) - if service.Spec.ExternalTrafficPolicy != "" { - w.Write(LEVEL_0, "External Traffic Policy:\t%s\n", service.Spec.ExternalTrafficPolicy) - } - if service.Spec.HealthCheckNodePort != 0 { - w.Write(LEVEL_0, "HealthCheck NodePort:\t%d\n", service.Spec.HealthCheckNodePort) - } - if len(service.Spec.LoadBalancerSourceRanges) > 0 { - w.Write(LEVEL_0, "LoadBalancer Source Ranges:\t%v\n", strings.Join(service.Spec.LoadBalancerSourceRanges, ",")) - } - if events != nil { - DescribeEvents(events, w) - } - return nil - }) -} - -// EndpointsDescriber generates information about an Endpoint. -type EndpointsDescriber struct { - clientset.Interface -} - -func (d *EndpointsDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - c := d.Core().Endpoints(namespace) - - ep, err := c.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, ep) - } - - return describeEndpoints(ep, events) -} - -func describeEndpoints(ep *api.Endpoints, events *api.EventList) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", ep.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", ep.Namespace) - printLabelsMultiline(w, "Labels", ep.Labels) - printAnnotationsMultiline(w, "Annotations", ep.Annotations) - - w.Write(LEVEL_0, "Subsets:\n") - for i := range ep.Subsets { - subset := &ep.Subsets[i] - - addresses := make([]string, 0, len(subset.Addresses)) - for _, addr := range subset.Addresses { - addresses = append(addresses, addr.IP) - } - addressesString := strings.Join(addresses, ",") - if len(addressesString) == 0 { - addressesString = "" - } - w.Write(LEVEL_1, "Addresses:\t%s\n", addressesString) - - notReadyAddresses := make([]string, 0, len(subset.NotReadyAddresses)) - for _, addr := range subset.NotReadyAddresses { - notReadyAddresses = append(notReadyAddresses, addr.IP) - } - notReadyAddressesString := strings.Join(notReadyAddresses, ",") - if len(notReadyAddressesString) == 0 { - notReadyAddressesString = "" - } - w.Write(LEVEL_1, "NotReadyAddresses:\t%s\n", notReadyAddressesString) - - if len(subset.Ports) > 0 { - w.Write(LEVEL_1, "Ports:\n") - w.Write(LEVEL_2, "Name\tPort\tProtocol\n") - w.Write(LEVEL_2, "----\t----\t--------\n") - for _, port := range subset.Ports { - name := port.Name - if len(name) == 0 { - name = "" - } - w.Write(LEVEL_2, "%s\t%d\t%s\n", name, port.Port, port.Protocol) - } - } - w.Write(LEVEL_0, "\n") - } - - if events != nil { - DescribeEvents(events, w) - } - return nil - }) -} - -// ServiceAccountDescriber generates information about a service. -type ServiceAccountDescriber struct { - clientset.Interface -} - -func (d *ServiceAccountDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - c := d.Core().ServiceAccounts(namespace) - - serviceAccount, err := c.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - tokens := []api.Secret{} - - // missingSecrets is the set of all secrets present in the - // serviceAccount but not present in the set of existing secrets. - missingSecrets := sets.NewString() - secrets, err := d.Core().Secrets(namespace).List(metav1.ListOptions{}) - - // errors are tolerated here in order to describe the serviceAccount with all - // of the secrets that it references, even if those secrets cannot be fetched. - if err == nil { - // existingSecrets is the set of all secrets remaining on a - // service account that are not present in the "tokens" slice. - existingSecrets := sets.NewString() - - for _, s := range secrets.Items { - if s.Type == api.SecretTypeServiceAccountToken { - name, _ := s.Annotations[api.ServiceAccountNameKey] - uid, _ := s.Annotations[api.ServiceAccountUIDKey] - if name == serviceAccount.Name && uid == string(serviceAccount.UID) { - tokens = append(tokens, s) - } - } - existingSecrets.Insert(s.Name) - } - - for _, s := range serviceAccount.Secrets { - if !existingSecrets.Has(s.Name) { - missingSecrets.Insert(s.Name) - } - } - for _, s := range serviceAccount.ImagePullSecrets { - if !existingSecrets.Has(s.Name) { - missingSecrets.Insert(s.Name) - } - } - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = d.Core().Events(namespace).Search(legacyscheme.Scheme, serviceAccount) - } - - return describeServiceAccount(serviceAccount, tokens, missingSecrets, events) -} - -func describeServiceAccount(serviceAccount *api.ServiceAccount, tokens []api.Secret, missingSecrets sets.String, events *api.EventList) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", serviceAccount.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", serviceAccount.Namespace) - printLabelsMultiline(w, "Labels", serviceAccount.Labels) - printAnnotationsMultiline(w, "Annotations", serviceAccount.Annotations) - - var ( - emptyHeader = " " - pullHeader = "Image pull secrets:" - mountHeader = "Mountable secrets: " - tokenHeader = "Tokens: " - - pullSecretNames = []string{} - mountSecretNames = []string{} - tokenSecretNames = []string{} - ) - - for _, s := range serviceAccount.ImagePullSecrets { - pullSecretNames = append(pullSecretNames, s.Name) - } - for _, s := range serviceAccount.Secrets { - mountSecretNames = append(mountSecretNames, s.Name) - } - for _, s := range tokens { - tokenSecretNames = append(tokenSecretNames, s.Name) - } - - types := map[string][]string{ - pullHeader: pullSecretNames, - mountHeader: mountSecretNames, - tokenHeader: tokenSecretNames, - } - for _, header := range sets.StringKeySet(types).List() { - names := types[header] - if len(names) == 0 { - w.Write(LEVEL_0, "%s\t\n", header) - } else { - prefix := header - for _, name := range names { - if missingSecrets.Has(name) { - w.Write(LEVEL_0, "%s\t%s (not found)\n", prefix, name) - } else { - w.Write(LEVEL_0, "%s\t%s\n", prefix, name) - } - prefix = emptyHeader - } - } - } - - if events != nil { - DescribeEvents(events, w) - } - - return nil - }) -} - -// RoleDescriber generates information about a node. -type RoleDescriber struct { - externalclient.Interface -} - -func (d *RoleDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - role, err := d.Rbac().Roles(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - breakdownRules := []rbacv1.PolicyRule{} - for _, rule := range role.Rules { - breakdownRules = append(breakdownRules, validation.BreakdownRule(rule)...) - } - - compactRules, err := validation.CompactRules(breakdownRules) - if err != nil { - return "", err - } - sort.Stable(rbacv1helpers.SortableRuleSlice(compactRules)) - - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", role.Name) - printLabelsMultiline(w, "Labels", role.Labels) - printAnnotationsMultiline(w, "Annotations", role.Annotations) - - w.Write(LEVEL_0, "PolicyRule:\n") - w.Write(LEVEL_1, "Resources\tNon-Resource URLs\tResource Names\tVerbs\n") - w.Write(LEVEL_1, "---------\t-----------------\t--------------\t-----\n") - for _, r := range compactRules { - w.Write(LEVEL_1, "%s\t%v\t%v\t%v\n", combineResourceGroup(r.Resources, r.APIGroups), r.NonResourceURLs, r.ResourceNames, r.Verbs) - } - - return nil - }) -} - -// ClusterRoleDescriber generates information about a node. -type ClusterRoleDescriber struct { - externalclient.Interface -} - -func (d *ClusterRoleDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - role, err := d.Rbac().ClusterRoles().Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - breakdownRules := []rbacv1.PolicyRule{} - for _, rule := range role.Rules { - breakdownRules = append(breakdownRules, validation.BreakdownRule(rule)...) - } - - compactRules, err := validation.CompactRules(breakdownRules) - if err != nil { - return "", err - } - sort.Stable(rbacv1helpers.SortableRuleSlice(compactRules)) - - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", role.Name) - printLabelsMultiline(w, "Labels", role.Labels) - printAnnotationsMultiline(w, "Annotations", role.Annotations) - - w.Write(LEVEL_0, "PolicyRule:\n") - w.Write(LEVEL_1, "Resources\tNon-Resource URLs\tResource Names\tVerbs\n") - w.Write(LEVEL_1, "---------\t-----------------\t--------------\t-----\n") - for _, r := range compactRules { - w.Write(LEVEL_1, "%s\t%v\t%v\t%v\n", combineResourceGroup(r.Resources, r.APIGroups), r.NonResourceURLs, r.ResourceNames, r.Verbs) - } - - return nil - }) -} - -func combineResourceGroup(resource, group []string) string { - if len(resource) == 0 { - return "" - } - parts := strings.SplitN(resource[0], "/", 2) - combine := parts[0] - - if len(group) > 0 && group[0] != "" { - combine = combine + "." + group[0] - } - - if len(parts) == 2 { - combine = combine + "/" + parts[1] - } - return combine -} - -// RoleBindingDescriber generates information about a node. -type RoleBindingDescriber struct { - externalclient.Interface -} - -func (d *RoleBindingDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - binding, err := d.Rbac().RoleBindings(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", binding.Name) - printLabelsMultiline(w, "Labels", binding.Labels) - printAnnotationsMultiline(w, "Annotations", binding.Annotations) - - w.Write(LEVEL_0, "Role:\n") - w.Write(LEVEL_1, "Kind:\t%s\n", binding.RoleRef.Kind) - w.Write(LEVEL_1, "Name:\t%s\n", binding.RoleRef.Name) - - w.Write(LEVEL_0, "Subjects:\n") - w.Write(LEVEL_1, "Kind\tName\tNamespace\n") - w.Write(LEVEL_1, "----\t----\t---------\n") - for _, s := range binding.Subjects { - w.Write(LEVEL_1, "%s\t%s\t%s\n", s.Kind, s.Name, s.Namespace) - } - - return nil - }) -} - -// ClusterRoleBindingDescriber generates information about a node. -type ClusterRoleBindingDescriber struct { - externalclient.Interface -} - -func (d *ClusterRoleBindingDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - binding, err := d.Rbac().ClusterRoleBindings().Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", binding.Name) - printLabelsMultiline(w, "Labels", binding.Labels) - printAnnotationsMultiline(w, "Annotations", binding.Annotations) - - w.Write(LEVEL_0, "Role:\n") - w.Write(LEVEL_1, "Kind:\t%s\n", binding.RoleRef.Kind) - w.Write(LEVEL_1, "Name:\t%s\n", binding.RoleRef.Name) - - w.Write(LEVEL_0, "Subjects:\n") - w.Write(LEVEL_1, "Kind\tName\tNamespace\n") - w.Write(LEVEL_1, "----\t----\t---------\n") - for _, s := range binding.Subjects { - w.Write(LEVEL_1, "%s\t%s\t%s\n", s.Kind, s.Name, s.Namespace) - } - - return nil - }) -} - -// NodeDescriber generates information about a node. -type NodeDescriber struct { - clientset.Interface -} - -func (d *NodeDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - mc := d.Core().Nodes() - node, err := mc.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - fieldSelector, err := fields.ParseSelector("spec.nodeName=" + name + ",status.phase!=" + string(api.PodSucceeded) + ",status.phase!=" + string(api.PodFailed)) - if err != nil { - return "", err - } - // in a policy aware setting, users may have access to a node, but not all pods - // in that case, we note that the user does not have access to the pods - canViewPods := true - nodeNonTerminatedPodsList, err := d.Core().Pods(namespace).List(metav1.ListOptions{FieldSelector: fieldSelector.String()}) - if err != nil { - if !errors.IsForbidden(err) { - return "", err - } - canViewPods = false - } - - var events *api.EventList - if describerSettings.ShowEvents { - if ref, err := ref.GetReference(legacyscheme.Scheme, node); err != nil { - klog.Errorf("Unable to construct reference to '%#v': %v", node, err) - } else { - // TODO: We haven't decided the namespace for Node object yet. - ref.UID = types.UID(ref.Name) - events, _ = d.Core().Events("").Search(legacyscheme.Scheme, ref) - } - } - - return describeNode(node, nodeNonTerminatedPodsList, events, canViewPods) -} - -func describeNode(node *api.Node, nodeNonTerminatedPodsList *api.PodList, events *api.EventList, canViewPods bool) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", node.Name) - if roles := findNodeRoles(node); len(roles) > 0 { - w.Write(LEVEL_0, "Roles:\t%s\n", strings.Join(roles, ",")) - } else { - w.Write(LEVEL_0, "Roles:\t%s\n", "") - } - printLabelsMultiline(w, "Labels", node.Labels) - printAnnotationsMultiline(w, "Annotations", node.Annotations) - w.Write(LEVEL_0, "CreationTimestamp:\t%s\n", node.CreationTimestamp.Time.Format(time.RFC1123Z)) - printNodeTaintsMultiline(w, "Taints", node.Spec.Taints) - w.Write(LEVEL_0, "Unschedulable:\t%v\n", node.Spec.Unschedulable) - if len(node.Status.Conditions) > 0 { - w.Write(LEVEL_0, "Conditions:\n Type\tStatus\tLastHeartbeatTime\tLastTransitionTime\tReason\tMessage\n") - w.Write(LEVEL_1, "----\t------\t-----------------\t------------------\t------\t-------\n") - for _, c := range node.Status.Conditions { - w.Write(LEVEL_1, "%v \t%v \t%s \t%s \t%v \t%v\n", - c.Type, - c.Status, - c.LastHeartbeatTime.Time.Format(time.RFC1123Z), - c.LastTransitionTime.Time.Format(time.RFC1123Z), - c.Reason, - c.Message) - } - } - - w.Write(LEVEL_0, "Addresses:\n") - for _, address := range node.Status.Addresses { - w.Write(LEVEL_1, "%s:\t%s\n", address.Type, address.Address) - } - - printResourceList := func(resourceList api.ResourceList) { - resources := make([]api.ResourceName, 0, len(resourceList)) - for resource := range resourceList { - resources = append(resources, resource) - } - sort.Sort(SortableResourceNames(resources)) - for _, resource := range resources { - value := resourceList[resource] - w.Write(LEVEL_0, " %s:\t%s\n", resource, value.String()) - } - } - - if len(node.Status.Capacity) > 0 { - w.Write(LEVEL_0, "Capacity:\n") - printResourceList(node.Status.Capacity) - } - if len(node.Status.Allocatable) > 0 { - w.Write(LEVEL_0, "Allocatable:\n") - printResourceList(node.Status.Allocatable) - } - - w.Write(LEVEL_0, "System Info:\n") - w.Write(LEVEL_0, " Machine ID:\t%s\n", node.Status.NodeInfo.MachineID) - w.Write(LEVEL_0, " System UUID:\t%s\n", node.Status.NodeInfo.SystemUUID) - w.Write(LEVEL_0, " Boot ID:\t%s\n", node.Status.NodeInfo.BootID) - w.Write(LEVEL_0, " Kernel Version:\t%s\n", node.Status.NodeInfo.KernelVersion) - w.Write(LEVEL_0, " OS Image:\t%s\n", node.Status.NodeInfo.OSImage) - w.Write(LEVEL_0, " Operating System:\t%s\n", node.Status.NodeInfo.OperatingSystem) - w.Write(LEVEL_0, " Architecture:\t%s\n", node.Status.NodeInfo.Architecture) - w.Write(LEVEL_0, " Container Runtime Version:\t%s\n", node.Status.NodeInfo.ContainerRuntimeVersion) - w.Write(LEVEL_0, " Kubelet Version:\t%s\n", node.Status.NodeInfo.KubeletVersion) - w.Write(LEVEL_0, " Kube-Proxy Version:\t%s\n", node.Status.NodeInfo.KubeProxyVersion) - - if len(node.Spec.PodCIDR) > 0 { - w.Write(LEVEL_0, "PodCIDR:\t%s\n", node.Spec.PodCIDR) - } - if len(node.Spec.ProviderID) > 0 { - w.Write(LEVEL_0, "ProviderID:\t%s\n", node.Spec.ProviderID) - } - if canViewPods && nodeNonTerminatedPodsList != nil { - describeNodeResource(nodeNonTerminatedPodsList, node, w) - } else { - w.Write(LEVEL_0, "Pods:\tnot authorized\n") - } - if events != nil { - DescribeEvents(events, w) - } - return nil - }) -} - -type StatefulSetDescriber struct { - client clientset.Interface -} - -func (p *StatefulSetDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - ps, err := p.client.Apps().StatefulSets(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - pc := p.client.Core().Pods(namespace) - - selector, err := metav1.LabelSelectorAsSelector(ps.Spec.Selector) - if err != nil { - return "", err - } - - running, waiting, succeeded, failed, err := getPodStatusForController(pc, selector, ps.UID) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = p.client.Core().Events(namespace).Search(legacyscheme.Scheme, ps) - } - - return describeStatefulSet(ps, selector, events, running, waiting, succeeded, failed) -} - -func describeStatefulSet(ps *apps.StatefulSet, selector labels.Selector, events *api.EventList, running, waiting, succeeded, failed int) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", ps.ObjectMeta.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", ps.ObjectMeta.Namespace) - w.Write(LEVEL_0, "CreationTimestamp:\t%s\n", ps.CreationTimestamp.Time.Format(time.RFC1123Z)) - w.Write(LEVEL_0, "Selector:\t%s\n", selector) - printLabelsMultiline(w, "Labels", ps.Labels) - printAnnotationsMultiline(w, "Annotations", ps.Annotations) - w.Write(LEVEL_0, "Replicas:\t%d desired | %d total\n", ps.Spec.Replicas, ps.Status.Replicas) - w.Write(LEVEL_0, "Update Strategy:\t%s\n", ps.Spec.UpdateStrategy.Type) - if ps.Spec.UpdateStrategy.RollingUpdate != nil { - ru := ps.Spec.UpdateStrategy.RollingUpdate - if ru.Partition != 0 { - w.Write(LEVEL_1, "Partition:\t%d\n", ru.Partition) - } - } - - w.Write(LEVEL_0, "Pods Status:\t%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed) - DescribePodTemplate(&ps.Spec.Template, w) - describeVolumeClaimTemplates(ps.Spec.VolumeClaimTemplates, w) - if events != nil { - DescribeEvents(events, w) - } - - return nil - }) -} - -type CertificateSigningRequestDescriber struct { - client clientset.Interface -} - -func (p *CertificateSigningRequestDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - csr, err := p.client.Certificates().CertificateSigningRequests().Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - cr, err := certificates.ParseCSR(csr) - if err != nil { - return "", fmt.Errorf("Error parsing CSR: %v", err) - } - status, err := extractCSRStatus(csr) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = p.client.Core().Events(namespace).Search(legacyscheme.Scheme, csr) - } - - return describeCertificateSigningRequest(csr, cr, status, events) -} - -func describeCertificateSigningRequest(csr *certificates.CertificateSigningRequest, cr *x509.CertificateRequest, status string, events *api.EventList) (string, error) { - printListHelper := func(w PrefixWriter, prefix, name string, values []string) { - if len(values) == 0 { - return - } - w.Write(LEVEL_0, prefix+name+":\t") - w.Write(LEVEL_0, strings.Join(values, "\n"+prefix+"\t")) - w.Write(LEVEL_0, "\n") - } - - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", csr.Name) - w.Write(LEVEL_0, "Labels:\t%s\n", labels.FormatLabels(csr.Labels)) - w.Write(LEVEL_0, "Annotations:\t%s\n", labels.FormatLabels(csr.Annotations)) - w.Write(LEVEL_0, "CreationTimestamp:\t%s\n", csr.CreationTimestamp.Time.Format(time.RFC1123Z)) - w.Write(LEVEL_0, "Requesting User:\t%s\n", csr.Spec.Username) - w.Write(LEVEL_0, "Status:\t%s\n", status) - - w.Write(LEVEL_0, "Subject:\n") - w.Write(LEVEL_0, "\tCommon Name:\t%s\n", cr.Subject.CommonName) - w.Write(LEVEL_0, "\tSerial Number:\t%s\n", cr.Subject.SerialNumber) - printListHelper(w, "\t", "Organization", cr.Subject.Organization) - printListHelper(w, "\t", "Organizational Unit", cr.Subject.OrganizationalUnit) - printListHelper(w, "\t", "Country", cr.Subject.Country) - printListHelper(w, "\t", "Locality", cr.Subject.Locality) - printListHelper(w, "\t", "Province", cr.Subject.Province) - printListHelper(w, "\t", "StreetAddress", cr.Subject.StreetAddress) - printListHelper(w, "\t", "PostalCode", cr.Subject.PostalCode) - - if len(cr.DNSNames)+len(cr.EmailAddresses)+len(cr.IPAddresses) > 0 { - w.Write(LEVEL_0, "Subject Alternative Names:\n") - printListHelper(w, "\t", "DNS Names", cr.DNSNames) - printListHelper(w, "\t", "Email Addresses", cr.EmailAddresses) - var ipaddrs []string - for _, ipaddr := range cr.IPAddresses { - ipaddrs = append(ipaddrs, ipaddr.String()) - } - printListHelper(w, "\t", "IP Addresses", ipaddrs) - } - - if events != nil { - DescribeEvents(events, w) - } - - return nil - }) -} - -// HorizontalPodAutoscalerDescriber generates information about a horizontal pod autoscaler. -type HorizontalPodAutoscalerDescriber struct { - client clientset.Interface -} - -func (d *HorizontalPodAutoscalerDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - hpa, err := d.client.Autoscaling().HorizontalPodAutoscalers(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = d.client.Core().Events(namespace).Search(legacyscheme.Scheme, hpa) - } - - return describeHorizontalPodAutoscaler(hpa, events, d) -} - -func describeHorizontalPodAutoscaler(hpa *autoscaling.HorizontalPodAutoscaler, events *api.EventList, d *HorizontalPodAutoscalerDescriber) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", hpa.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", hpa.Namespace) - printLabelsMultiline(w, "Labels", hpa.Labels) - printAnnotationsMultiline(w, "Annotations", hpa.Annotations) - w.Write(LEVEL_0, "CreationTimestamp:\t%s\n", hpa.CreationTimestamp.Time.Format(time.RFC1123Z)) - w.Write(LEVEL_0, "Reference:\t%s/%s\n", - hpa.Spec.ScaleTargetRef.Kind, - hpa.Spec.ScaleTargetRef.Name) - w.Write(LEVEL_0, "Metrics:\t( current / target )\n") - for i, metric := range hpa.Spec.Metrics { - switch metric.Type { - case autoscaling.ExternalMetricSourceType: - if metric.External.Target.AverageValue != nil { - current := "" - if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].External != nil && - &hpa.Status.CurrentMetrics[i].External.Current.AverageValue != nil { - current = hpa.Status.CurrentMetrics[i].External.Current.AverageValue.String() - } - w.Write(LEVEL_1, "%q (target average value):\t%s / %s\n", metric.External.Metric.Name, current, metric.External.Target.AverageValue.String()) - } else { - current := "" - if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].External != nil { - current = hpa.Status.CurrentMetrics[i].External.Current.Value.String() - } - w.Write(LEVEL_1, "%q (target value):\t%s / %s\n", metric.External.Metric.Name, current, metric.External.Target.Value.String()) - - } - case autoscaling.PodsMetricSourceType: - current := "" - if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Pods != nil { - current = hpa.Status.CurrentMetrics[i].Pods.Current.AverageValue.String() - } - w.Write(LEVEL_1, "%q on pods:\t%s / %s\n", metric.Pods.Metric.Name, current, metric.Pods.Target.AverageValue.String()) - case autoscaling.ObjectMetricSourceType: - current := "" - if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Object != nil { - current = hpa.Status.CurrentMetrics[i].Object.Current.Value.String() - } - w.Write(LEVEL_1, "%q on %s/%s:\t%s / %s\n", metric.Object.Metric.Name, metric.Object.DescribedObject.Kind, metric.Object.DescribedObject.Name, current, metric.Object.Target.Value.String()) - case autoscaling.ResourceMetricSourceType: - w.Write(LEVEL_1, "resource %s on pods", string(metric.Resource.Name)) - if metric.Resource.Target.AverageValue != nil { - current := "" - if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Resource != nil { - current = hpa.Status.CurrentMetrics[i].Resource.Current.AverageValue.String() - } - w.Write(LEVEL_0, ":\t%s / %s\n", current, metric.Resource.Target.AverageValue.String()) - } else { - current := "" - if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Resource != nil && hpa.Status.CurrentMetrics[i].Resource.Current.AverageUtilization != nil { - current = fmt.Sprintf("%d%% (%s)", *hpa.Status.CurrentMetrics[i].Resource.Current.AverageUtilization, hpa.Status.CurrentMetrics[i].Resource.Current.AverageValue.String()) - } - - target := "" - if metric.Resource.Target.AverageUtilization != nil { - target = fmt.Sprintf("%d%%", *metric.Resource.Target.AverageUtilization) - } - w.Write(LEVEL_1, "(as a percentage of request):\t%s / %s\n", current, target) - } - default: - w.Write(LEVEL_1, "", string(metric.Type)) - } - } - minReplicas := "" - if hpa.Spec.MinReplicas != nil { - minReplicas = fmt.Sprintf("%d", *hpa.Spec.MinReplicas) - } - w.Write(LEVEL_0, "Min replicas:\t%s\n", minReplicas) - w.Write(LEVEL_0, "Max replicas:\t%d\n", hpa.Spec.MaxReplicas) - w.Write(LEVEL_0, "%s pods:\t", hpa.Spec.ScaleTargetRef.Kind) - w.Write(LEVEL_0, "%d current / %d desired\n", hpa.Status.CurrentReplicas, hpa.Status.DesiredReplicas) - - if len(hpa.Status.Conditions) > 0 { - w.Write(LEVEL_0, "Conditions:\n") - w.Write(LEVEL_1, "Type\tStatus\tReason\tMessage\n") - w.Write(LEVEL_1, "----\t------\t------\t-------\n") - for _, c := range hpa.Status.Conditions { - w.Write(LEVEL_1, "%v\t%v\t%v\t%v\n", c.Type, c.Status, c.Reason, c.Message) - } - } - - if events != nil { - DescribeEvents(events, w) - } - - return nil - }) -} - -func describeNodeResource(nodeNonTerminatedPodsList *api.PodList, node *api.Node, w PrefixWriter) { - w.Write(LEVEL_0, "Non-terminated Pods:\t(%d in total)\n", len(nodeNonTerminatedPodsList.Items)) - w.Write(LEVEL_1, "Namespace\tName\t\tCPU Requests\tCPU Limits\tMemory Requests\tMemory Limits\tAGE\n") - w.Write(LEVEL_1, "---------\t----\t\t------------\t----------\t---------------\t-------------\t---\n") - allocatable := node.Status.Capacity - if len(node.Status.Allocatable) > 0 { - allocatable = node.Status.Allocatable - } - - for _, pod := range nodeNonTerminatedPodsList.Items { - req, limit := resourcehelper.PodRequestsAndLimits(&pod) - cpuReq, cpuLimit, memoryReq, memoryLimit := req[api.ResourceCPU], limit[api.ResourceCPU], req[api.ResourceMemory], limit[api.ResourceMemory] - fractionCpuReq := float64(cpuReq.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 - fractionCpuLimit := float64(cpuLimit.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 - fractionMemoryReq := float64(memoryReq.Value()) / float64(allocatable.Memory().Value()) * 100 - fractionMemoryLimit := float64(memoryLimit.Value()) / float64(allocatable.Memory().Value()) * 100 - w.Write(LEVEL_1, "%s\t%s\t\t%s (%d%%)\t%s (%d%%)\t%s (%d%%)\t%s (%d%%)\t%s\n", pod.Namespace, pod.Name, - cpuReq.String(), int64(fractionCpuReq), cpuLimit.String(), int64(fractionCpuLimit), - memoryReq.String(), int64(fractionMemoryReq), memoryLimit.String(), int64(fractionMemoryLimit), translateTimestampSince(pod.CreationTimestamp)) - } - - w.Write(LEVEL_0, "Allocated resources:\n (Total limits may be over 100 percent, i.e., overcommitted.)\n") - w.Write(LEVEL_1, "Resource\tRequests\tLimits\n") - w.Write(LEVEL_1, "--------\t--------\t------\n") - reqs, limits := getPodsTotalRequestsAndLimits(nodeNonTerminatedPodsList) - cpuReqs, cpuLimits, memoryReqs, memoryLimits, ephemeralstorageReqs, ephemeralstorageLimits := - reqs[api.ResourceCPU], limits[api.ResourceCPU], reqs[api.ResourceMemory], limits[api.ResourceMemory], reqs[api.ResourceEphemeralStorage], limits[api.ResourceEphemeralStorage] - fractionCpuReqs := float64(0) - fractionCpuLimits := float64(0) - if allocatable.Cpu().MilliValue() != 0 { - fractionCpuReqs = float64(cpuReqs.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 - fractionCpuLimits = float64(cpuLimits.MilliValue()) / float64(allocatable.Cpu().MilliValue()) * 100 - } - fractionMemoryReqs := float64(0) - fractionMemoryLimits := float64(0) - if allocatable.Memory().Value() != 0 { - fractionMemoryReqs = float64(memoryReqs.Value()) / float64(allocatable.Memory().Value()) * 100 - fractionMemoryLimits = float64(memoryLimits.Value()) / float64(allocatable.Memory().Value()) * 100 - } - fractionEphemeralStorageReqs := float64(0) - fractionEphemeralStorageLimits := float64(0) - if allocatable.StorageEphemeral().Value() != 0 { - fractionEphemeralStorageReqs = float64(ephemeralstorageReqs.Value()) / float64(allocatable.StorageEphemeral().Value()) * 100 - fractionEphemeralStorageLimits = float64(ephemeralstorageLimits.Value()) / float64(allocatable.StorageEphemeral().Value()) * 100 - } - w.Write(LEVEL_1, "%s\t%s (%d%%)\t%s (%d%%)\n", - api.ResourceCPU, cpuReqs.String(), int64(fractionCpuReqs), cpuLimits.String(), int64(fractionCpuLimits)) - w.Write(LEVEL_1, "%s\t%s (%d%%)\t%s (%d%%)\n", - api.ResourceMemory, memoryReqs.String(), int64(fractionMemoryReqs), memoryLimits.String(), int64(fractionMemoryLimits)) - w.Write(LEVEL_1, "%s\t%s (%d%%)\t%s (%d%%)\n", - api.ResourceEphemeralStorage, ephemeralstorageReqs.String(), int64(fractionEphemeralStorageReqs), ephemeralstorageLimits.String(), int64(fractionEphemeralStorageLimits)) - extResources := make([]string, 0, len(allocatable)) - for resource := range allocatable { - if !helper.IsStandardContainerResourceName(string(resource)) && resource != api.ResourcePods { - extResources = append(extResources, string(resource)) - } - } - sort.Strings(extResources) - for _, ext := range extResources { - extRequests, extLimits := reqs[api.ResourceName(ext)], limits[api.ResourceName(ext)] - w.Write(LEVEL_1, "%s\t%s\t%s\n", ext, extRequests.String(), extLimits.String()) - } -} - -func getPodsTotalRequestsAndLimits(podList *api.PodList) (reqs map[api.ResourceName]resource.Quantity, limits map[api.ResourceName]resource.Quantity) { - reqs, limits = map[api.ResourceName]resource.Quantity{}, map[api.ResourceName]resource.Quantity{} - for _, pod := range podList.Items { - podReqs, podLimits := resourcehelper.PodRequestsAndLimits(&pod) - for podReqName, podReqValue := range podReqs { - if value, ok := reqs[podReqName]; !ok { - reqs[podReqName] = *podReqValue.Copy() - } else { - value.Add(podReqValue) - reqs[podReqName] = value - } - } - for podLimitName, podLimitValue := range podLimits { - if value, ok := limits[podLimitName]; !ok { - limits[podLimitName] = *podLimitValue.Copy() - } else { - value.Add(podLimitValue) - limits[podLimitName] = value - } - } - } - return -} - -func DescribeEvents(el *api.EventList, w PrefixWriter) { - if len(el.Items) == 0 { - w.Write(LEVEL_0, "Events:\t\n") - return - } - w.Flush() - sort.Sort(events.SortableEvents(el.Items)) - w.Write(LEVEL_0, "Events:\n Type\tReason\tAge\tFrom\tMessage\n") - w.Write(LEVEL_1, "----\t------\t----\t----\t-------\n") - for _, e := range el.Items { - var interval string - if e.Count > 1 { - interval = fmt.Sprintf("%s (x%d over %s)", translateTimestampSince(e.LastTimestamp), e.Count, translateTimestampSince(e.FirstTimestamp)) - } else { - interval = translateTimestampSince(e.FirstTimestamp) - } - w.Write(LEVEL_1, "%v\t%v\t%s\t%v\t%v\n", - e.Type, - e.Reason, - interval, - formatEventSource(e.Source), - strings.TrimSpace(e.Message), - ) - } -} - -// DeploymentDescriber generates information about a deployment. -type DeploymentDescriber struct { - clientset.Interface - external externalclient.Interface -} - -func (dd *DeploymentDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - d, err := dd.external.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - selector, err := metav1.LabelSelectorAsSelector(d.Spec.Selector) - if err != nil { - return "", err - } - internalDeployment := &apps.Deployment{} - if err := legacyscheme.Scheme.Convert(d, internalDeployment, apps.SchemeGroupVersion); err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = dd.Core().Events(namespace).Search(legacyscheme.Scheme, d) - } - - return describeDeployment(d, selector, internalDeployment, events, dd) -} - -func describeDeployment(d *appsv1.Deployment, selector labels.Selector, internalDeployment *apps.Deployment, events *api.EventList, dd *DeploymentDescriber) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", d.ObjectMeta.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", d.ObjectMeta.Namespace) - w.Write(LEVEL_0, "CreationTimestamp:\t%s\n", d.CreationTimestamp.Time.Format(time.RFC1123Z)) - printLabelsMultiline(w, "Labels", d.Labels) - printAnnotationsMultiline(w, "Annotations", d.Annotations) - w.Write(LEVEL_0, "Selector:\t%s\n", selector) - w.Write(LEVEL_0, "Replicas:\t%d desired | %d updated | %d total | %d available | %d unavailable\n", *(d.Spec.Replicas), d.Status.UpdatedReplicas, d.Status.Replicas, d.Status.AvailableReplicas, d.Status.UnavailableReplicas) - w.Write(LEVEL_0, "StrategyType:\t%s\n", d.Spec.Strategy.Type) - w.Write(LEVEL_0, "MinReadySeconds:\t%d\n", d.Spec.MinReadySeconds) - if d.Spec.Strategy.RollingUpdate != nil { - ru := d.Spec.Strategy.RollingUpdate - w.Write(LEVEL_0, "RollingUpdateStrategy:\t%s max unavailable, %s max surge\n", ru.MaxUnavailable.String(), ru.MaxSurge.String()) - } - DescribePodTemplate(&internalDeployment.Spec.Template, w) - if len(d.Status.Conditions) > 0 { - w.Write(LEVEL_0, "Conditions:\n Type\tStatus\tReason\n") - w.Write(LEVEL_1, "----\t------\t------\n") - for _, c := range d.Status.Conditions { - w.Write(LEVEL_1, "%v \t%v\t%v\n", c.Type, c.Status, c.Reason) - } - } - oldRSs, _, newRS, err := deploymentutil.GetAllReplicaSets(d, dd.external.AppsV1()) - if err == nil { - w.Write(LEVEL_0, "OldReplicaSets:\t%s\n", printReplicaSetsByLabels(oldRSs)) - var newRSs []*appsv1.ReplicaSet - if newRS != nil { - newRSs = append(newRSs, newRS) - } - w.Write(LEVEL_0, "NewReplicaSet:\t%s\n", printReplicaSetsByLabels(newRSs)) - } - if events != nil { - DescribeEvents(events, w) - } - - return nil - }) -} - -func printReplicaSetsByLabels(matchingRSs []*appsv1.ReplicaSet) string { - // Format the matching ReplicaSets into strings. - rsStrings := make([]string, 0, len(matchingRSs)) - for _, rs := range matchingRSs { - rsStrings = append(rsStrings, fmt.Sprintf("%s (%d/%d replicas created)", rs.Name, rs.Status.Replicas, *rs.Spec.Replicas)) - } - - list := strings.Join(rsStrings, ", ") - if list == "" { - return "" - } - return list -} - -func getPodStatusForController(c coreclient.PodInterface, selector labels.Selector, uid types.UID) (running, waiting, succeeded, failed int, err error) { - options := metav1.ListOptions{LabelSelector: selector.String()} - rcPods, err := c.List(options) - if err != nil { - return - } - for _, pod := range rcPods.Items { - controllerRef := metav1.GetControllerOf(&pod) - // Skip pods that are orphans or owned by other controllers. - if controllerRef == nil || controllerRef.UID != uid { - continue - } - switch pod.Status.Phase { - case api.PodRunning: - running++ - case api.PodPending: - waiting++ - case api.PodSucceeded: - succeeded++ - case api.PodFailed: - failed++ - } - } - return -} - -// ConfigMapDescriber generates information about a ConfigMap -type ConfigMapDescriber struct { - clientset.Interface -} - -func (d *ConfigMapDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - c := d.Core().ConfigMaps(namespace) - - configMap, err := c.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", configMap.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", configMap.Namespace) - printLabelsMultiline(w, "Labels", configMap.Labels) - printAnnotationsMultiline(w, "Annotations", configMap.Annotations) - - w.Write(LEVEL_0, "\nData\n====\n") - for k, v := range configMap.Data { - w.Write(LEVEL_0, "%s:\n----\n", k) - w.Write(LEVEL_0, "%s\n", string(v)) - } - if describerSettings.ShowEvents { - events, err := d.Core().Events(namespace).Search(legacyscheme.Scheme, configMap) - if err != nil { - return err - } - if events != nil { - DescribeEvents(events, w) - } - } - return nil - }) -} - -// NetworkPolicyDescriber generates information about a networking.NetworkPolicy -type NetworkPolicyDescriber struct { - clientset.Interface -} - -func (d *NetworkPolicyDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - c := d.Networking().NetworkPolicies(namespace) - - networkPolicy, err := c.Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - return describeNetworkPolicy(networkPolicy) -} - -func describeNetworkPolicy(networkPolicy *networking.NetworkPolicy) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", networkPolicy.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", networkPolicy.Namespace) - w.Write(LEVEL_0, "Created on:\t%s\n", networkPolicy.CreationTimestamp) - printLabelsMultiline(w, "Labels", networkPolicy.Labels) - printAnnotationsMultiline(w, "Annotations", networkPolicy.Annotations) - describeNetworkPolicySpec(networkPolicy.Spec, w) - return nil - }) -} - -func describeNetworkPolicySpec(nps networking.NetworkPolicySpec, w PrefixWriter) { - w.Write(LEVEL_0, "Spec:\n") - w.Write(LEVEL_1, "PodSelector: ") - if len(nps.PodSelector.MatchLabels) == 0 && len(nps.PodSelector.MatchExpressions) == 0 { - w.Write(LEVEL_2, " (Allowing the specific traffic to all pods in this namespace)\n") - } else { - w.Write(LEVEL_2, "%s\n", metav1.FormatLabelSelector(&nps.PodSelector)) - } - w.Write(LEVEL_1, "Allowing ingress traffic:\n") - printNetworkPolicySpecIngressFrom(nps.Ingress, " ", w) - w.Write(LEVEL_1, "Allowing egress traffic:\n") - printNetworkPolicySpecEgressTo(nps.Egress, " ", w) - w.Write(LEVEL_1, "Policy Types: %v\n", policyTypesToString(nps.PolicyTypes)) -} - -func printNetworkPolicySpecIngressFrom(npirs []networking.NetworkPolicyIngressRule, initialIndent string, w PrefixWriter) { - if len(npirs) == 0 { - w.Write(LEVEL_0, "%s%s\n", initialIndent, " (Selected pods are isolated for ingress connectivity)") - return - } - for i, npir := range npirs { - if len(npir.Ports) == 0 { - w.Write(LEVEL_0, "%s%s\n", initialIndent, "To Port: (traffic allowed to all ports)") - } else { - for _, port := range npir.Ports { - var proto api.Protocol - if port.Protocol != nil { - proto = *port.Protocol - } else { - proto = api.ProtocolTCP - } - w.Write(LEVEL_0, "%s%s: %s/%s\n", initialIndent, "To Port", port.Port, proto) - } - } - if len(npir.From) == 0 { - w.Write(LEVEL_0, "%s%s\n", initialIndent, "From: (traffic not restricted by source)") - } else { - for _, from := range npir.From { - w.Write(LEVEL_0, "%s%s\n", initialIndent, "From:") - if from.PodSelector != nil && from.NamespaceSelector != nil { - w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "NamespaceSelector", metav1.FormatLabelSelector(from.NamespaceSelector)) - w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "PodSelector", metav1.FormatLabelSelector(from.PodSelector)) - } else if from.PodSelector != nil { - w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "PodSelector", metav1.FormatLabelSelector(from.PodSelector)) - } else if from.NamespaceSelector != nil { - w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "NamespaceSelector", metav1.FormatLabelSelector(from.NamespaceSelector)) - } else if from.IPBlock != nil { - w.Write(LEVEL_1, "%sIPBlock:\n", initialIndent) - w.Write(LEVEL_2, "%sCIDR: %s\n", initialIndent, from.IPBlock.CIDR) - w.Write(LEVEL_2, "%sExcept: %v\n", initialIndent, strings.Join(from.IPBlock.Except, ", ")) - } - } - } - if i != len(npirs)-1 { - w.Write(LEVEL_0, "%s%s\n", initialIndent, "----------") - } - } -} - -func printNetworkPolicySpecEgressTo(npers []networking.NetworkPolicyEgressRule, initialIndent string, w PrefixWriter) { - if len(npers) == 0 { - w.Write(LEVEL_0, "%s%s\n", initialIndent, " (Selected pods are isolated for egress connectivity)") - return - } - for i, nper := range npers { - if len(nper.Ports) == 0 { - w.Write(LEVEL_0, "%s%s\n", initialIndent, "To Port: (traffic allowed to all ports)") - } else { - for _, port := range nper.Ports { - var proto api.Protocol - if port.Protocol != nil { - proto = *port.Protocol - } else { - proto = api.ProtocolTCP - } - w.Write(LEVEL_0, "%s%s: %s/%s\n", initialIndent, "To Port", port.Port, proto) - } - } - if len(nper.To) == 0 { - w.Write(LEVEL_0, "%s%s\n", initialIndent, "To: (traffic not restricted by source)") - } else { - for _, to := range nper.To { - w.Write(LEVEL_0, "%s%s\n", initialIndent, "To:") - if to.PodSelector != nil && to.NamespaceSelector != nil { - w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "NamespaceSelector", metav1.FormatLabelSelector(to.NamespaceSelector)) - w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "PodSelector", metav1.FormatLabelSelector(to.PodSelector)) - } else if to.PodSelector != nil { - w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "PodSelector", metav1.FormatLabelSelector(to.PodSelector)) - } else if to.NamespaceSelector != nil { - w.Write(LEVEL_1, "%s%s: %s\n", initialIndent, "NamespaceSelector", metav1.FormatLabelSelector(to.NamespaceSelector)) - } else if to.IPBlock != nil { - w.Write(LEVEL_1, "%sIPBlock:\n", initialIndent) - w.Write(LEVEL_2, "%sCIDR: %s\n", initialIndent, to.IPBlock.CIDR) - w.Write(LEVEL_2, "%sExcept: %v\n", initialIndent, strings.Join(to.IPBlock.Except, ", ")) - } - } - } - if i != len(npers)-1 { - w.Write(LEVEL_0, "%s%s\n", initialIndent, "----------") - } - } -} - -type StorageClassDescriber struct { - clientset.Interface -} - -func (s *StorageClassDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - sc, err := s.Storage().StorageClasses().Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = s.Core().Events(namespace).Search(legacyscheme.Scheme, sc) - } - - return describeStorageClass(sc, events) -} - -func describeStorageClass(sc *storage.StorageClass, events *api.EventList) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", sc.Name) - w.Write(LEVEL_0, "IsDefaultClass:\t%s\n", storageutil.IsDefaultAnnotationText(sc.ObjectMeta)) - w.Write(LEVEL_0, "Annotations:\t%s\n", labels.FormatLabels(sc.Annotations)) - w.Write(LEVEL_0, "Provisioner:\t%s\n", sc.Provisioner) - w.Write(LEVEL_0, "Parameters:\t%s\n", labels.FormatLabels(sc.Parameters)) - w.Write(LEVEL_0, "AllowVolumeExpansion:\t%s\n", printBoolPtr(sc.AllowVolumeExpansion)) - if len(sc.MountOptions) == 0 { - w.Write(LEVEL_0, "MountOptions:\t\n") - } else { - w.Write(LEVEL_0, "MountOptions:\n") - for _, option := range sc.MountOptions { - w.Write(LEVEL_1, "%s\n", option) - } - } - if sc.ReclaimPolicy != nil { - w.Write(LEVEL_0, "ReclaimPolicy:\t%s\n", *sc.ReclaimPolicy) - } - if sc.VolumeBindingMode != nil { - w.Write(LEVEL_0, "VolumeBindingMode:\t%s\n", *sc.VolumeBindingMode) - } - if sc.AllowedTopologies != nil { - printAllowedTopologies(w, sc.AllowedTopologies) - } - if events != nil { - DescribeEvents(events, w) - } - - return nil - }) -} - -func printAllowedTopologies(w PrefixWriter, topologies []api.TopologySelectorTerm) { - w.Write(LEVEL_0, "AllowedTopologies:\t") - if len(topologies) == 0 { - w.WriteLine("") - return - } - w.WriteLine("") - for i, term := range topologies { - printTopologySelectorTermsMultilineWithIndent(w, LEVEL_1, fmt.Sprintf("Term %d", i), "\t", term.MatchLabelExpressions) - } -} - -func printTopologySelectorTermsMultilineWithIndent(w PrefixWriter, indentLevel int, title, innerIndent string, reqs []api.TopologySelectorLabelRequirement) { - w.Write(indentLevel, "%s:%s", title, innerIndent) - - if len(reqs) == 0 { - w.WriteLine("") - return - } - - for i, req := range reqs { - if i != 0 { - w.Write(indentLevel, "%s", innerIndent) - } - exprStr := fmt.Sprintf("%s %s", req.Key, "in") - if len(req.Values) > 0 { - exprStr = fmt.Sprintf("%s [%s]", exprStr, strings.Join(req.Values, ", ")) - } - w.Write(LEVEL_0, "%s\n", exprStr) - } -} - -type PodDisruptionBudgetDescriber struct { - clientset.Interface -} - -func (p *PodDisruptionBudgetDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - pdb, err := p.Policy().PodDisruptionBudgets(namespace).Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = p.Core().Events(namespace).Search(legacyscheme.Scheme, pdb) - } - - return describePodDisruptionBudget(pdb, events) -} - -func describePodDisruptionBudget(pdb *policy.PodDisruptionBudget, events *api.EventList) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", pdb.Name) - w.Write(LEVEL_0, "Namespace:\t%s\n", pdb.Namespace) - - if pdb.Spec.MinAvailable != nil { - w.Write(LEVEL_0, "Min available:\t%s\n", pdb.Spec.MinAvailable.String()) - } else if pdb.Spec.MaxUnavailable != nil { - w.Write(LEVEL_0, "Max unavailable:\t%s\n", pdb.Spec.MaxUnavailable.String()) - } - - if pdb.Spec.Selector != nil { - w.Write(LEVEL_0, "Selector:\t%s\n", metav1.FormatLabelSelector(pdb.Spec.Selector)) - } else { - w.Write(LEVEL_0, "Selector:\t\n") - } - w.Write(LEVEL_0, "Status:\n") - w.Write(LEVEL_2, "Allowed disruptions:\t%d\n", pdb.Status.PodDisruptionsAllowed) - w.Write(LEVEL_2, "Current:\t%d\n", pdb.Status.CurrentHealthy) - w.Write(LEVEL_2, "Desired:\t%d\n", pdb.Status.DesiredHealthy) - w.Write(LEVEL_2, "Total:\t%d\n", pdb.Status.ExpectedPods) - if events != nil { - DescribeEvents(events, w) - } - - return nil - }) -} - -// PriorityClassDescriber generates information about a PriorityClass. -type PriorityClassDescriber struct { - clientset.Interface -} - -func (s *PriorityClassDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - pc, err := s.Scheduling().PriorityClasses().Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - var events *api.EventList - if describerSettings.ShowEvents { - events, _ = s.Core().Events(namespace).Search(legacyscheme.Scheme, pc) - } - - return describePriorityClass(pc, events) -} - -func describePriorityClass(pc *scheduling.PriorityClass, events *api.EventList) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", pc.Name) - w.Write(LEVEL_0, "Value:\t%v\n", pc.Value) - w.Write(LEVEL_0, "GlobalDefault:\t%v\n", pc.GlobalDefault) - w.Write(LEVEL_0, "Description:\t%s\n", pc.Description) - - w.Write(LEVEL_0, "Annotations:\t%s\n", labels.FormatLabels(pc.Annotations)) - if events != nil { - DescribeEvents(events, w) - } - - return nil - }) -} - -// PodSecurityPolicyDescriber generates information about a PodSecurityPolicy. -type PodSecurityPolicyDescriber struct { - clientset.Interface -} - -func (d *PodSecurityPolicyDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (string, error) { - psp, err := d.Policy().PodSecurityPolicies().Get(name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - return describePodSecurityPolicy(psp) -} - -func describePodSecurityPolicy(psp *policy.PodSecurityPolicy) (string, error) { - return tabbedString(func(out io.Writer) error { - w := NewPrefixWriter(out) - w.Write(LEVEL_0, "Name:\t%s\n", psp.Name) - - w.Write(LEVEL_0, "\nSettings:\n") - - w.Write(LEVEL_1, "Allow Privileged:\t%t\n", psp.Spec.Privileged) - w.Write(LEVEL_1, "Allow Privilege Escalation:\t%v\n", psp.Spec.AllowPrivilegeEscalation) - w.Write(LEVEL_1, "Default Add Capabilities:\t%v\n", capsToString(psp.Spec.DefaultAddCapabilities)) - w.Write(LEVEL_1, "Required Drop Capabilities:\t%s\n", capsToString(psp.Spec.RequiredDropCapabilities)) - w.Write(LEVEL_1, "Allowed Capabilities:\t%s\n", capsToString(psp.Spec.AllowedCapabilities)) - w.Write(LEVEL_1, "Allowed Volume Types:\t%s\n", fsTypeToString(psp.Spec.Volumes)) - - if len(psp.Spec.AllowedFlexVolumes) > 0 { - w.Write(LEVEL_1, "Allowed FlexVolume Types:\t%s\n", flexVolumesToString(psp.Spec.AllowedFlexVolumes)) - } - if len(psp.Spec.AllowedUnsafeSysctls) > 0 { - w.Write(LEVEL_1, "Allowed Unsafe Sysctls:\t%s\n", sysctlsToString(psp.Spec.AllowedUnsafeSysctls)) - } - if len(psp.Spec.ForbiddenSysctls) > 0 { - w.Write(LEVEL_1, "Forbidden Sysctls:\t%s\n", sysctlsToString(psp.Spec.ForbiddenSysctls)) - } - w.Write(LEVEL_1, "Allow Host Network:\t%t\n", psp.Spec.HostNetwork) - w.Write(LEVEL_1, "Allow Host Ports:\t%s\n", hostPortRangeToString(psp.Spec.HostPorts)) - w.Write(LEVEL_1, "Allow Host PID:\t%t\n", psp.Spec.HostPID) - w.Write(LEVEL_1, "Allow Host IPC:\t%t\n", psp.Spec.HostIPC) - w.Write(LEVEL_1, "Read Only Root Filesystem:\t%v\n", psp.Spec.ReadOnlyRootFilesystem) - - w.Write(LEVEL_1, "SELinux Context Strategy: %s\t\n", string(psp.Spec.SELinux.Rule)) - var user, role, seLinuxType, level string - if psp.Spec.SELinux.SELinuxOptions != nil { - user = psp.Spec.SELinux.SELinuxOptions.User - role = psp.Spec.SELinux.SELinuxOptions.Role - seLinuxType = psp.Spec.SELinux.SELinuxOptions.Type - level = psp.Spec.SELinux.SELinuxOptions.Level - } - w.Write(LEVEL_2, "User:\t%s\n", stringOrNone(user)) - w.Write(LEVEL_2, "Role:\t%s\n", stringOrNone(role)) - w.Write(LEVEL_2, "Type:\t%s\n", stringOrNone(seLinuxType)) - w.Write(LEVEL_2, "Level:\t%s\n", stringOrNone(level)) - - w.Write(LEVEL_1, "Run As User Strategy: %s\t\n", string(psp.Spec.RunAsUser.Rule)) - w.Write(LEVEL_2, "Ranges:\t%s\n", idRangeToString(psp.Spec.RunAsUser.Ranges)) - - w.Write(LEVEL_1, "FSGroup Strategy: %s\t\n", string(psp.Spec.FSGroup.Rule)) - w.Write(LEVEL_2, "Ranges:\t%s\n", idRangeToString(psp.Spec.FSGroup.Ranges)) - - w.Write(LEVEL_1, "Supplemental Groups Strategy: %s\t\n", string(psp.Spec.SupplementalGroups.Rule)) - w.Write(LEVEL_2, "Ranges:\t%s\n", idRangeToString(psp.Spec.SupplementalGroups.Ranges)) - - return nil - }) -} - -func stringOrNone(s string) string { - return stringOrDefaultValue(s, "") -} - -func stringOrDefaultValue(s, defaultValue string) string { - if len(s) > 0 { - return s - } - return defaultValue -} - -func fsTypeToString(volumes []policy.FSType) string { - strVolumes := []string{} - for _, v := range volumes { - strVolumes = append(strVolumes, string(v)) - } - return stringOrNone(strings.Join(strVolumes, ",")) -} - -func flexVolumesToString(flexVolumes []policy.AllowedFlexVolume) string { - volumes := []string{} - for _, flexVolume := range flexVolumes { - volumes = append(volumes, "driver="+flexVolume.Driver) - } - return stringOrDefaultValue(strings.Join(volumes, ","), "") -} - -func sysctlsToString(sysctls []string) string { - return stringOrNone(strings.Join(sysctls, ",")) -} - -func hostPortRangeToString(ranges []policy.HostPortRange) string { - formattedString := "" - if ranges != nil { - strRanges := []string{} - for _, r := range ranges { - strRanges = append(strRanges, fmt.Sprintf("%d-%d", r.Min, r.Max)) - } - formattedString = strings.Join(strRanges, ",") - } - return stringOrNone(formattedString) -} - -func idRangeToString(ranges []policy.IDRange) string { - formattedString := "" - if ranges != nil { - strRanges := []string{} - for _, r := range ranges { - strRanges = append(strRanges, fmt.Sprintf("%d-%d", r.Min, r.Max)) - } - formattedString = strings.Join(strRanges, ",") - } - return stringOrNone(formattedString) -} - -func capsToString(caps []api.Capability) string { - formattedString := "" - if caps != nil { - strCaps := []string{} - for _, c := range caps { - strCaps = append(strCaps, string(c)) - } - formattedString = strings.Join(strCaps, ",") - } - return stringOrNone(formattedString) -} - -func policyTypesToString(pts []networking.PolicyType) string { - formattedString := "" - if pts != nil { - strPts := []string{} - for _, p := range pts { - strPts = append(strPts, string(p)) - } - formattedString = strings.Join(strPts, ", ") - } - return stringOrNone(formattedString) -} - -// newErrNoDescriber creates a new ErrNoDescriber with the names of the provided types. -func newErrNoDescriber(types ...reflect.Type) error { - names := make([]string, 0, len(types)) - for _, t := range types { - names = append(names, t.String()) - } - return printers.ErrNoDescriber{Types: names} -} - -// Describers implements ObjectDescriber against functions registered via Add. Those functions can -// be strongly typed. Types are exactly matched (no conversion or assignable checks). -type Describers struct { - searchFns map[reflect.Type][]typeFunc -} - -// DescribeObject implements ObjectDescriber and will attempt to print the provided object to a string, -// if at least one describer function has been registered with the exact types passed, or if any -// describer can print the exact object in its first argument (the remainder will be provided empty -// values). If no function registered with Add can satisfy the passed objects, an ErrNoDescriber will -// be returned -// TODO: reorder and partial match extra. -func (d *Describers) DescribeObject(exact interface{}, extra ...interface{}) (string, error) { - exactType := reflect.TypeOf(exact) - fns, ok := d.searchFns[exactType] - if !ok { - return "", newErrNoDescriber(exactType) - } - if len(extra) == 0 { - for _, typeFn := range fns { - if len(typeFn.Extra) == 0 { - return typeFn.Describe(exact, extra...) - } - } - typeFn := fns[0] - for _, t := range typeFn.Extra { - v := reflect.New(t).Elem() - extra = append(extra, v.Interface()) - } - return fns[0].Describe(exact, extra...) - } - - types := make([]reflect.Type, 0, len(extra)) - for _, obj := range extra { - types = append(types, reflect.TypeOf(obj)) - } - for _, typeFn := range fns { - if typeFn.Matches(types) { - return typeFn.Describe(exact, extra...) - } - } - return "", newErrNoDescriber(append([]reflect.Type{exactType}, types...)...) -} - -// Add adds one or more describer functions to the printers.Describer. The passed function must -// match the signature: -// -// func(...) (string, error) -// -// Any number of arguments may be provided. -func (d *Describers) Add(fns ...interface{}) error { - for _, fn := range fns { - fv := reflect.ValueOf(fn) - ft := fv.Type() - if ft.Kind() != reflect.Func { - return fmt.Errorf("expected func, got: %v", ft) - } - numIn := ft.NumIn() - if numIn == 0 { - return fmt.Errorf("expected at least one 'in' params, got: %v", ft) - } - if ft.NumOut() != 2 { - return fmt.Errorf("expected two 'out' params - (string, error), got: %v", ft) - } - types := make([]reflect.Type, 0, numIn) - for i := 0; i < numIn; i++ { - types = append(types, ft.In(i)) - } - if ft.Out(0) != reflect.TypeOf(string("")) { - return fmt.Errorf("expected string return, got: %v", ft) - } - var forErrorType error - // This convolution is necessary, otherwise TypeOf picks up on the fact - // that forErrorType is nil. - errorType := reflect.TypeOf(&forErrorType).Elem() - if ft.Out(1) != errorType { - return fmt.Errorf("expected error return, got: %v", ft) - } - - exact := types[0] - extra := types[1:] - if d.searchFns == nil { - d.searchFns = make(map[reflect.Type][]typeFunc) - } - fns := d.searchFns[exact] - fn := typeFunc{Extra: extra, Fn: fv} - fns = append(fns, fn) - d.searchFns[exact] = fns - } - return nil -} - -// typeFunc holds information about a describer function and the types it accepts -type typeFunc struct { - Extra []reflect.Type - Fn reflect.Value -} - -// Matches returns true when the passed types exactly match the Extra list. -func (fn typeFunc) Matches(types []reflect.Type) bool { - if len(fn.Extra) != len(types) { - return false - } - // reorder the items in array types and fn.Extra - // convert the type into string and sort them, check if they are matched - varMap := make(map[reflect.Type]bool) - for i := range fn.Extra { - varMap[fn.Extra[i]] = true - } - for i := range types { - if _, found := varMap[types[i]]; !found { - return false - } - } - return true -} - -// Describe invokes the nested function with the exact number of arguments. -func (fn typeFunc) Describe(exact interface{}, extra ...interface{}) (string, error) { - values := []reflect.Value{reflect.ValueOf(exact)} - for _, obj := range extra { - values = append(values, reflect.ValueOf(obj)) - } - out := fn.Fn.Call(values) - s := out[0].Interface().(string) - var err error - if !out[1].IsNil() { - err = out[1].Interface().(error) - } - return s, err -} - -// printLabelsMultiline prints multiple labels with a proper alignment. -func printLabelsMultiline(w PrefixWriter, title string, labels map[string]string) { - printLabelsMultilineWithIndent(w, "", title, "\t", labels, sets.NewString()) -} - -// printLabelsMultiline prints multiple labels with a user-defined alignment. -func printLabelsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerIndent string, labels map[string]string, skip sets.String) { - w.Write(LEVEL_0, "%s%s:%s", initialIndent, title, innerIndent) - - if labels == nil || len(labels) == 0 { - w.WriteLine("") - return - } - - // to print labels in the sorted order - keys := make([]string, 0, len(labels)) - for key := range labels { - if skip.Has(key) { - continue - } - keys = append(keys, key) - } - if len(keys) == 0 { - w.WriteLine("") - return - } - sort.Strings(keys) - - for i, key := range keys { - if i != 0 { - w.Write(LEVEL_0, "%s", initialIndent) - w.Write(LEVEL_0, "%s", innerIndent) - } - w.Write(LEVEL_0, "%s=%s\n", key, labels[key]) - i++ - } -} - -// printTaintsMultiline prints multiple taints with a proper alignment. -func printNodeTaintsMultiline(w PrefixWriter, title string, taints []api.Taint) { - printTaintsMultilineWithIndent(w, "", title, "\t", taints) -} - -// printTaintsMultilineWithIndent prints multiple taints with a user-defined alignment. -func printTaintsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerIndent string, taints []api.Taint) { - w.Write(LEVEL_0, "%s%s:%s", initialIndent, title, innerIndent) - - if taints == nil || len(taints) == 0 { - w.WriteLine("") - return - } - - // to print taints in the sorted order - sort.Slice(taints, func(i, j int) bool { - cmpKey := func(taint api.Taint) string { - return string(taint.Effect) + "," + taint.Key - } - return cmpKey(taints[i]) < cmpKey(taints[j]) - }) - - for i, taint := range taints { - if i != 0 { - w.Write(LEVEL_0, "%s", initialIndent) - w.Write(LEVEL_0, "%s", innerIndent) - } - w.Write(LEVEL_0, "%s\n", taint.ToString()) - } -} - -// printPodsMultiline prints multiple pods with a proper alignment. -func printPodsMultiline(w PrefixWriter, title string, pods []api.Pod) { - printPodsMultilineWithIndent(w, "", title, "\t", pods) -} - -// printPodsMultilineWithIndent prints multiple pods with a user-defined alignment. -func printPodsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerIndent string, pods []api.Pod) { - w.Write(LEVEL_0, "%s%s:%s", initialIndent, title, innerIndent) - - if pods == nil || len(pods) == 0 { - w.WriteLine("") - return - } - - // to print pods in the sorted order - sort.Slice(pods, func(i, j int) bool { - cmpKey := func(pod api.Pod) string { - return pod.Name - } - return cmpKey(pods[i]) < cmpKey(pods[j]) - }) - - for i, pod := range pods { - if i != 0 { - w.Write(LEVEL_0, "%s", initialIndent) - w.Write(LEVEL_0, "%s", innerIndent) - } - w.Write(LEVEL_0, "%s\n", pod.Name) - } -} - -// printPodTolerationsMultiline prints multiple tolerations with a proper alignment. -func printPodTolerationsMultiline(w PrefixWriter, title string, tolerations []api.Toleration) { - printTolerationsMultilineWithIndent(w, "", title, "\t", tolerations) -} - -// printTolerationsMultilineWithIndent prints multiple tolerations with a user-defined alignment. -func printTolerationsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerIndent string, tolerations []api.Toleration) { - w.Write(LEVEL_0, "%s%s:%s", initialIndent, title, innerIndent) - - if tolerations == nil || len(tolerations) == 0 { - w.WriteLine("") - return - } - - // to print tolerations in the sorted order - sort.Slice(tolerations, func(i, j int) bool { - return tolerations[i].Key < tolerations[j].Key - }) - - for i, toleration := range tolerations { - if i != 0 { - w.Write(LEVEL_0, "%s", initialIndent) - w.Write(LEVEL_0, "%s", innerIndent) - } - w.Write(LEVEL_0, "%s", toleration.Key) - if len(toleration.Value) != 0 { - w.Write(LEVEL_0, "=%s", toleration.Value) - } - if len(toleration.Effect) != 0 { - w.Write(LEVEL_0, ":%s", toleration.Effect) - } - if toleration.TolerationSeconds != nil { - w.Write(LEVEL_0, " for %ds", *toleration.TolerationSeconds) - } - w.Write(LEVEL_0, "\n") - } -} - -type flusher interface { - Flush() -} - -func tabbedString(f func(io.Writer) error) (string, error) { - out := new(tabwriter.Writer) - buf := &bytes.Buffer{} - out.Init(buf, 0, 8, 2, ' ', 0) - - err := f(out) - if err != nil { - return "", err - } - - out.Flush() - str := string(buf.String()) - return str, nil -} - -type SortableResourceNames []api.ResourceName - -func (list SortableResourceNames) Len() int { - return len(list) -} - -func (list SortableResourceNames) Swap(i, j int) { - list[i], list[j] = list[j], list[i] -} - -func (list SortableResourceNames) Less(i, j int) bool { - return list[i] < list[j] -} - -// SortedResourceNames returns the sorted resource names of a resource list. -func SortedResourceNames(list api.ResourceList) []api.ResourceName { - resources := make([]api.ResourceName, 0, len(list)) - for res := range list { - resources = append(resources, res) - } - sort.Sort(SortableResourceNames(resources)) - return resources -} - -type SortableResourceQuotas []api.ResourceQuota - -func (list SortableResourceQuotas) Len() int { - return len(list) -} - -func (list SortableResourceQuotas) Swap(i, j int) { - list[i], list[j] = list[j], list[i] -} - -func (list SortableResourceQuotas) Less(i, j int) bool { - return list[i].Name < list[j].Name -} - -type SortableVolumeMounts []api.VolumeMount - -func (list SortableVolumeMounts) Len() int { - return len(list) -} - -func (list SortableVolumeMounts) Swap(i, j int) { - list[i], list[j] = list[j], list[i] -} - -func (list SortableVolumeMounts) Less(i, j int) bool { - return list[i].MountPath < list[j].MountPath -} - -type SortableVolumeDevices []api.VolumeDevice - -func (list SortableVolumeDevices) Len() int { - return len(list) -} - -func (list SortableVolumeDevices) Swap(i, j int) { - list[i], list[j] = list[j], list[i] -} - -func (list SortableVolumeDevices) Less(i, j int) bool { - return list[i].DevicePath < list[j].DevicePath -} - -var maxAnnotationLen = 140 - -// printAnnotationsMultilineWithFilter prints filtered multiple annotations with a proper alignment. -func printAnnotationsMultilineWithFilter(w PrefixWriter, title string, annotations map[string]string, skip sets.String) { - printAnnotationsMultilineWithIndent(w, "", title, "\t", annotations, skip) -} - -// printAnnotationsMultiline prints multiple annotations with a proper alignment. -func printAnnotationsMultiline(w PrefixWriter, title string, annotations map[string]string) { - printAnnotationsMultilineWithIndent(w, "", title, "\t", annotations, sets.NewString()) -} - -// printAnnotationsMultilineWithIndent prints multiple annotations with a user-defined alignment. -// If annotation string is too long, we omit chars more than 200 length. -func printAnnotationsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerIndent string, annotations map[string]string, skip sets.String) { - - w.Write(LEVEL_0, "%s%s:%s", initialIndent, title, innerIndent) - - if len(annotations) == 0 { - w.WriteLine("") - return - } - - // to print labels in the sorted order - keys := make([]string, 0, len(annotations)) - for key := range annotations { - if skip.Has(key) { - continue - } - keys = append(keys, key) - } - if len(annotations) == 0 { - w.WriteLine("") - return - } - sort.Strings(keys) - indent := initialIndent + innerIndent - for i, key := range keys { - if i != 0 { - w.Write(LEVEL_0, indent) - } - value := strings.TrimSuffix(annotations[key], "\n") - if (len(value)+len(key)+2) > maxAnnotationLen || strings.Contains(value, "\n") { - w.Write(LEVEL_0, "%s:\n", key) - for _, s := range strings.Split(value, "\n") { - w.Write(LEVEL_0, "%s %s\n", indent, shorten(s, maxAnnotationLen-2)) - } - } else { - w.Write(LEVEL_0, "%s: %s\n", key, value) - } - i++ - } -} - -func shorten(s string, maxLength int) string { - if len(s) > maxLength { - return s[:maxLength] + "..." - } - return s -} diff --git a/pkg/printers/internalversion/import_known_versions.go b/pkg/printers/internalversion/import_known_versions.go new file mode 100644 index 0000000000..154e6c76a6 --- /dev/null +++ b/pkg/printers/internalversion/import_known_versions.go @@ -0,0 +1,37 @@ +/* +Copyright 2016 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 internalversion + +// These imports are the API groups the client will support. +// TODO: Remove these manual install once we don't need legacy scheme in get comman +import ( + _ "k8s.io/kubernetes/pkg/apis/apps/install" + _ "k8s.io/kubernetes/pkg/apis/authentication/install" + _ "k8s.io/kubernetes/pkg/apis/authorization/install" + _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" + _ "k8s.io/kubernetes/pkg/apis/batch/install" + _ "k8s.io/kubernetes/pkg/apis/certificates/install" + _ "k8s.io/kubernetes/pkg/apis/coordination/install" + _ "k8s.io/kubernetes/pkg/apis/core/install" + _ "k8s.io/kubernetes/pkg/apis/events/install" + _ "k8s.io/kubernetes/pkg/apis/extensions/install" + _ "k8s.io/kubernetes/pkg/apis/policy/install" + _ "k8s.io/kubernetes/pkg/apis/rbac/install" + _ "k8s.io/kubernetes/pkg/apis/scheduling/install" + _ "k8s.io/kubernetes/pkg/apis/settings/install" + _ "k8s.io/kubernetes/pkg/apis/storage/install" +) diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index 9019c70e66..70dadf902c 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -1965,3 +1965,33 @@ func printPriorityClassList(list *scheduling.PriorityClassList, options printers } return rows, nil } + +func printBoolPtr(value *bool) string { + if value != nil { + return printBool(*value) + } + + return "" +} + +func printBool(value bool) string { + if value { + return "True" + } + + return "False" +} + +type SortableResourceNames []api.ResourceName + +func (list SortableResourceNames) Len() int { + return len(list) +} + +func (list SortableResourceNames) Swap(i, j int) { + list[i], list[j] = list[j], list[i] +} + +func (list SortableResourceNames) Less(i, j int) bool { + return list[i] < list[j] +} diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index 4635db90b6..bdc1757d82 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -3569,3 +3569,27 @@ func verifyTable(t *testing.T, table *metav1beta1.Table) { t.Errorf("unexpected panic during deepcopy of table %#v: %v", table, panicErr) } } + +// VerifyDatesInOrder checks the start of each line for a RFC1123Z date +// and posts error if all subsequent dates are not equal or increasing +func VerifyDatesInOrder( + resultToTest, rowDelimiter, columnDelimiter string, t *testing.T) { + lines := strings.Split(resultToTest, rowDelimiter) + var previousTime time.Time + for _, str := range lines { + columns := strings.Split(str, columnDelimiter) + if len(columns) > 0 { + currentTime, err := time.Parse(time.RFC1123Z, columns[0]) + if err == nil { + if previousTime.After(currentTime) { + t.Errorf( + "Output is not sorted by time. %s should be listed after %s. Complete output: %s", + previousTime.Format(time.RFC1123Z), + currentTime.Format(time.RFC1123Z), + resultToTest) + } + previousTime = currentTime + } + } + } +}