vendor update for CSI 0.3.0

This commit is contained in:
gman
2018-07-18 16:47:22 +02:00
parent 6f484f92fc
commit 8ea659f0d5
6810 changed files with 438061 additions and 193861 deletions

View File

@ -121,7 +121,7 @@
"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates",
"k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/util",
"k8s.io/kubernetes/pkg/scheduler/api",
"k8s.io/kubernetes/pkg/scheduler/schedulercache",
"k8s.io/kubernetes/pkg/scheduler/cache",
"k8s.io/kubernetes/pkg/scheduler/util",
"k8s.io/kubernetes/pkg/scheduler/volumebinder",
"k8s.io/kubernetes/pkg/security/apparmor",

View File

@ -12,7 +12,6 @@ go_test(
"autoscale_test.go",
"clusterrolebinding_test.go",
"configmap_test.go",
"delete_test.go",
"deployment_test.go",
"env_file_test.go",
"generate_test.go",
@ -21,7 +20,6 @@ go_test(
"pdb_test.go",
"priorityclass_test.go",
"quota_test.go",
"resource_filter_test.go",
"rolebinding_test.go",
"rollback_test.go",
"rolling_updater_test.go",
@ -34,28 +32,21 @@ go_test(
"service_basic_test.go",
"service_test.go",
"serviceaccount_test.go",
"sorting_printer_test.go",
"sorter_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/api/testing:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/apps/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:go_default_library",
"//pkg/kubectl/util:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/util/pointer:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/apps/v1beta1:go_default_library",
"//vendor/k8s.io/api/apps/v1beta2:go_default_library",
"//vendor/k8s.io/api/autoscaling/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
@ -74,18 +65,13 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
"//vendor/k8s.io/client-go/discovery/fake:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/k8s.io/client-go/scale:go_default_library",
"//vendor/k8s.io/client-go/scale/fake:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
"//vendor/k8s.io/client-go/util/testing:go_default_library",
],
@ -96,23 +82,19 @@ go_library(
srcs = [
"apply.go",
"autoscale.go",
"bash_comp_utils.go",
"clusterrolebinding.go",
"conditions.go",
"configmap.go",
"delete.go",
"deployment.go",
"doc.go",
"env_file.go",
"generate.go",
"history.go",
"interfaces.go",
"kubectl.go",
"namespace.go",
"pdb.go",
"priorityclass.go",
"quota.go",
"resource_filter.go",
"rolebinding.go",
"rollback.go",
"rolling_updater.go",
@ -125,7 +107,7 @@ go_library(
"service.go",
"service_basic.go",
"serviceaccount.go",
"sorting_printer.go",
"sorter.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl",
deps = [
@ -133,21 +115,15 @@ go_library(
"//pkg/api/pod:go_default_library",
"//pkg/api/v1/pod:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/v1:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/apps/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:go_default_library",
"//pkg/controller/daemon:go_default_library",
"//pkg/controller/deployment/util:go_default_library",
"//pkg/controller/statefulset:go_default_library",
"//pkg/credentialprovider:go_default_library",
"//pkg/kubectl/apps:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/util:go_default_library",
"//pkg/kubectl/util/hash:go_default_library",
"//pkg/kubectl/util/slice:go_default_library",
@ -182,14 +158,11 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/apps/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/apps/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/scale:go_default_library",
"//vendor/k8s.io/client-go/util/integer:go_default_library",
@ -212,13 +185,13 @@ filegroup(
":package-srcs",
"//pkg/kubectl/apply:all-srcs",
"//pkg/kubectl/apps:all-srcs",
"//pkg/kubectl/categories:all-srcs",
"//pkg/kubectl/cmd:all-srcs",
"//pkg/kubectl/explain:all-srcs",
"//pkg/kubectl/genericclioptions:all-srcs",
"//pkg/kubectl/metricsutil:all-srcs",
"//pkg/kubectl/plugins:all-srcs",
"//pkg/kubectl/polymorphichelpers:all-srcs",
"//pkg/kubectl/proxy:all-srcs",
"//pkg/kubectl/resource:all-srcs",
"//pkg/kubectl/scheme:all-srcs",
"//pkg/kubectl/util:all-srcs",
"//pkg/kubectl/validation:all-srcs",

View File

@ -20,13 +20,14 @@ import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
var metadataAccessor = meta.NewAccessor()
// GetOriginalConfiguration retrieves the original configuration of the object
// from the annotation, or nil if no annotation was found.
func GetOriginalConfiguration(mapping *meta.RESTMapping, obj runtime.Object) ([]byte, error) {
annots, err := mapping.MetadataAccessor.Annotations(obj)
func GetOriginalConfiguration(obj runtime.Object) ([]byte, error) {
annots, err := metadataAccessor.Annotations(obj)
if err != nil {
return nil, err
}
@ -45,13 +46,12 @@ func GetOriginalConfiguration(mapping *meta.RESTMapping, obj runtime.Object) ([]
// SetOriginalConfiguration sets the original configuration of the object
// as the annotation on the object for later use in computing a three way patch.
func SetOriginalConfiguration(info *resource.Info, original []byte) error {
func setOriginalConfiguration(obj runtime.Object, original []byte) error {
if len(original) < 1 {
return nil
}
accessor := info.Mapping.MetadataAccessor
annots, err := accessor.Annotations(info.Object)
annots, err := metadataAccessor.Annotations(obj)
if err != nil {
return err
}
@ -61,22 +61,21 @@ func SetOriginalConfiguration(info *resource.Info, original []byte) error {
}
annots[v1.LastAppliedConfigAnnotation] = string(original)
return info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annots)
return metadataAccessor.SetAnnotations(obj, annots)
}
// GetModifiedConfiguration retrieves the modified configuration of the object.
// If annotate is true, it embeds the result as an annotation in the modified
// configuration. If an object was read from the command input, it will use that
// version of the object. Otherwise, it will use the version from the server.
func GetModifiedConfiguration(info *resource.Info, annotate bool, codec runtime.Encoder) ([]byte, error) {
func GetModifiedConfiguration(obj runtime.Object, annotate bool, codec runtime.Encoder) ([]byte, error) {
// First serialize the object without the annotation to prevent recursion,
// then add that serialization to it as the annotation and serialize it again.
var modified []byte
// Otherwise, use the server side version of the object.
accessor := info.Mapping.MetadataAccessor
// Get the current annotations from the object.
annots, err := accessor.Annotations(info.Object)
annots, err := metadataAccessor.Annotations(obj)
if err != nil {
return nil, err
}
@ -87,22 +86,22 @@ func GetModifiedConfiguration(info *resource.Info, annotate bool, codec runtime.
original := annots[v1.LastAppliedConfigAnnotation]
delete(annots, v1.LastAppliedConfigAnnotation)
if err := accessor.SetAnnotations(info.Object, annots); err != nil {
if err := metadataAccessor.SetAnnotations(obj, annots); err != nil {
return nil, err
}
modified, err = runtime.Encode(codec, info.Object)
modified, err = runtime.Encode(codec, obj)
if err != nil {
return nil, err
}
if annotate {
annots[v1.LastAppliedConfigAnnotation] = string(modified)
if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annots); err != nil {
if err := metadataAccessor.SetAnnotations(obj, annots); err != nil {
return nil, err
}
modified, err = runtime.Encode(codec, info.Object)
modified, err = runtime.Encode(codec, obj)
if err != nil {
return nil, err
}
@ -110,7 +109,7 @@ func GetModifiedConfiguration(info *resource.Info, annotate bool, codec runtime.
// Restore the object to its original condition.
annots[v1.LastAppliedConfigAnnotation] = original
if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annots); err != nil {
if err := metadataAccessor.SetAnnotations(obj, annots); err != nil {
return nil, err
}
@ -119,28 +118,28 @@ func GetModifiedConfiguration(info *resource.Info, annotate bool, codec runtime.
// UpdateApplyAnnotation calls CreateApplyAnnotation if the last applied
// configuration annotation is already present. Otherwise, it does nothing.
func UpdateApplyAnnotation(info *resource.Info, codec runtime.Encoder) error {
if original, err := GetOriginalConfiguration(info.Mapping, info.Object); err != nil || len(original) <= 0 {
func UpdateApplyAnnotation(obj runtime.Object, codec runtime.Encoder) error {
if original, err := GetOriginalConfiguration(obj); err != nil || len(original) <= 0 {
return err
}
return CreateApplyAnnotation(info, codec)
return CreateApplyAnnotation(obj, codec)
}
// CreateApplyAnnotation gets the modified configuration of the object,
// without embedding it again, and then sets it on the object as the annotation.
func CreateApplyAnnotation(info *resource.Info, codec runtime.Encoder) error {
modified, err := GetModifiedConfiguration(info, false, codec)
func CreateApplyAnnotation(obj runtime.Object, codec runtime.Encoder) error {
modified, err := GetModifiedConfiguration(obj, false, codec)
if err != nil {
return err
}
return SetOriginalConfiguration(info, modified)
return setOriginalConfiguration(obj, modified)
}
// Create the annotation used by kubectl apply only when createAnnotation is true
// Otherwise, only update the annotation when it already exists
func CreateOrUpdateAnnotation(createAnnotation bool, info *resource.Info, codec runtime.Encoder) error {
func CreateOrUpdateAnnotation(createAnnotation bool, obj runtime.Object, codec runtime.Encoder) error {
if createAnnotation {
return CreateApplyAnnotation(info, codec)
return CreateApplyAnnotation(obj, codec)
}
return UpdateApplyAnnotation(info, codec)
return UpdateApplyAnnotation(obj, codec)
}

View File

@ -6,6 +6,7 @@ go_library(
"doc.go",
"element.go",
"empty_element.go",
"error.go",
"list_element.go",
"map_element.go",
"primitive_element.go",

View File

@ -45,7 +45,7 @@ type Element interface {
GetRecorded() interface{}
// HasLocal returns true if the field was explicitly
// present in the recorded source. This is to differentiate between
// present in the local source. This is to differentiate between
// undefined and set to null
HasLocal() bool
@ -88,7 +88,7 @@ type FieldMetaImpl struct {
// Type is the openapi type of the field - "list", "primitive", "map"
Type string
// Name contains of the field
// Name contains name of the field
Name string
}
@ -276,8 +276,7 @@ func (s *CombinedMapSlice) UpsertRecorded(key MergeKeys, l interface{}) error {
if err != nil {
return err
}
item.recorded = l
item.recordedSet = true
item.SetRecorded(l)
return nil
}
@ -289,8 +288,7 @@ func (s *CombinedMapSlice) UpsertLocal(key MergeKeys, l interface{}) error {
if err != nil {
return err
}
item.local = l
item.localSet = true
item.SetLocal(l)
return nil
}
@ -302,8 +300,7 @@ func (s *CombinedMapSlice) UpsertRemote(key MergeKeys, l interface{}) error {
if err != nil {
return err
}
item.remote = l
item.remoteSet = true
item.SetRemote(l)
return nil
}
@ -359,13 +356,13 @@ func (b *RawElementData) SetRecorded(value interface{}) {
b.recordedSet = true
}
// SetLocal sets the recorded value
// SetLocal sets the local value
func (b *RawElementData) SetLocal(value interface{}) {
b.local = value
b.localSet = true
}
// SetRemote sets the recorded value
// SetRemote sets the remote value
func (b *RawElementData) SetRemote(value interface{}) {
b.remote = value
b.remoteSet = true
@ -419,3 +416,8 @@ func (e HasElementData) HasLocal() bool {
func (e HasElementData) HasRemote() bool {
return e.remoteSet
}
// ConflictDetector defines the capability to detect conflict. An element can examine remote/recorded value to detect conflict.
type ConflictDetector interface {
HasConflict() error
}

37
vendor/k8s.io/kubernetes/pkg/kubectl/apply/error.go generated vendored Normal file
View File

@ -0,0 +1,37 @@
/*
Copyright 2017 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 apply
import "fmt"
// ConflictError represents a conflict error occurred during the merge operation.
type ConflictError struct {
element Element
}
// NewConflictError returns a ConflictError with detailed conflict information in element
func NewConflictError(e PrimitiveElement) *ConflictError {
return &ConflictError{
element: e,
}
}
// Error implements error
func (c *ConflictError) Error() string {
return fmt.Sprintf("conflict detected, recorded value (%+v) and remote value (%+v)",
c.element.GetRecorded(), c.element.GetRemote())
}

View File

@ -65,3 +65,17 @@ func sliceCast(i interface{}) []interface{} {
}
return i.([]interface{})
}
// HasConflict returns ConflictError if fields in recorded and remote of ListElement conflict
func (e ListElement) HasConflict() error {
for _, item := range e.Values {
if item, ok := item.(ConflictDetector); ok {
if err := item.HasConflict(); err != nil {
return err
}
}
}
return nil
}
var _ ConflictDetector = &ListElement{}

View File

@ -70,3 +70,17 @@ func mapCast(i interface{}) map[string]interface{} {
}
return i.(map[string]interface{})
}
// HasConflict returns ConflictError if some elements in map conflict.
func (e MapElement) HasConflict() error {
for _, item := range e.GetValues() {
if item, ok := item.(ConflictDetector); ok {
if err := item.HasConflict(); err != nil {
return err
}
}
}
return nil
}
var _ ConflictDetector = &MapElement{}

View File

@ -104,7 +104,7 @@ func getFieldMeta(s proto.Schema, name string) (apply.FieldMetaImpl, error) {
if e, found := ext["x-kubernetes-patch-strategy"]; found {
strategy, ok := e.(string)
if !ok {
return apply.FieldMetaImpl{}, fmt.Errorf("Expected string for x-kubernetes-patch-strategy by got %T", s)
return apply.FieldMetaImpl{}, fmt.Errorf("Expected string for x-kubernetes-patch-strategy by got %T", e)
}
// Take the first strategy if there are substrategies.

View File

@ -16,6 +16,8 @@ limitations under the License.
package apply
import "reflect"
// PrimitiveElement contains the recorded, local and remote values for a field
// of type primitive
type PrimitiveElement struct {
@ -32,3 +34,21 @@ func (e PrimitiveElement) Merge(v Strategy) (Result, error) {
}
var _ Element = &PrimitiveElement{}
// HasConflict returns ConflictError if primitive element has conflict field.
// Conflicts happen when either of the following conditions:
// 1. A field is specified in both recorded and remote values, but does not match.
// 2. A field is specified in recorded values, but missing in remote values.
func (e PrimitiveElement) HasConflict() error {
if e.HasRecorded() && e.HasRemote() {
if !reflect.DeepEqual(e.GetRecorded(), e.GetRemote()) {
return NewConflictError(e)
}
}
if e.HasRecorded() && !e.HasRemote() {
return NewConflictError(e)
}
return nil
}
var _ ConflictDetector = &PrimitiveElement{}

View File

@ -18,6 +18,7 @@ go_library(
go_test(
name = "go_default_xtest",
srcs = [
"merge_conflict_test.go",
"merge_map_list_test.go",
"merge_map_test.go",
"merge_primitive_list_test.go",

View File

@ -0,0 +1,206 @@
/*
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 strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Comparing fields of remote and recorded ", func() {
Context("Test conflict in map fields of remote and recorded", func() {
It("If conflicts found, expected return error", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo1: "key1"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo2: "baz2-1"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo1: "baz1-0"
`)
expect := hasConflict
// map fields have conflict : recorded {foo1 : "key1"}, remote {foo1 : "baz1-0"}
runConflictTest(strategy.Create(strategy.Options{FailOnConflict: true}), recorded, local, remote, expect)
})
})
Context("Test conflict in list fields of remote and recorded ", func() {
It("If conflicts found, expected return false", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
- "d"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
- "c"
`)
expect := hasConflict
// primatie lists have conflicts: recorded [a, b, d], remote [a, b, c]
runConflictTest(strategy.Create(strategy.Options{FailOnConflict: true}), recorded, local, remote, expect)
})
})
Context("Test conflict in Map-List fields of remote and recorded ", func() {
It("should leave the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item1
image: image1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item2
image: image2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item1
image: image3
`)
expect := hasConflict
// map list has conflict : recorded {containers: [ {name: item1, image: image1} ]} , remote {containers: [ {name: item1, image: image3} ]}
runConflictTest(strategy.Create(strategy.Options{FailOnConflict: true}), recorded, local, remote, expect)
})
})
Context("Test conflicts in nested map field", func() {
It("If conflicts found, expected return error", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo1:
name: "key1"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo1:
name: "baz1-0"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo1:
name: "baz1-1"
`)
expect := hasConflict
// nested map has conflict : recorded {foo1: {name: "key1"}}, remote {foo1: {name : "baz1-1"}}
runConflictTest(strategy.Create(strategy.Options{FailOnConflict: true}), recorded, local, remote, expect)
})
})
Context("Test conflicts in complicated map, list", func() {
It("Should catch conflict in key-value in map element", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
- containerPort: 8080
protocol: UDP
hostPort: 2022
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
- containerPort: 8080
protocol: UDP
hostPort: 2022
hostIP: "127.0.0.1"
`)
expect := noConflict
runConflictTest(strategy.Create(strategy.Options{FailOnConflict: true}), recorded, local, remote, expect)
})
})
})

View File

@ -41,7 +41,10 @@ func (v mergeStrategy) MergeList(e apply.ListElement) (apply.Result, error) {
if result, done := v.doAddOrDelete(e); done {
return result, nil
}
// Detect conflict in ListElement
if err := v.doConflictDetect(e); err != nil {
return apply.Result{}, err
}
// Merge each item in the list and append it to the list
merged := []interface{}{}
for _, value := range e.Values {
@ -76,7 +79,10 @@ func (v mergeStrategy) MergeMap(e apply.MapElement) (apply.Result, error) {
if result, done := v.doAddOrDelete(e); done {
return result, nil
}
// Detect conflict in MapElement
if err := v.doConflictDetect(e); err != nil {
return apply.Result{}, err
}
return v.doMergeMap(e.GetValues())
}
@ -86,7 +92,10 @@ func (v mergeStrategy) MergeType(e apply.TypeElement) (apply.Result, error) {
if result, done := v.doAddOrDelete(e); done {
return result, nil
}
// Detect conflict in TypeElement
if err := v.doConflictDetect(e); err != nil {
return apply.Result{}, err
}
return v.doMergeMap(e.GetValues())
}
@ -145,4 +154,9 @@ func (v mergeStrategy) MergeEmpty(diff apply.EmptyElement) (apply.Result, error)
return apply.Result{Operation: apply.SET}, nil
}
// doConflictDetect returns error if element has conflict
func (v mergeStrategy) doConflictDetect(e apply.Element) error {
return v.strategic.doConflictDetect(e)
}
var _ apply.Strategy = &mergeStrategy{}

View File

@ -65,11 +65,13 @@ func (v replaceStrategy) MergeEmpty(e apply.EmptyElement) (apply.Result, error)
// replace returns the local value if specified, otherwise it returns the remote value
// this works regardless of the approach
func (v replaceStrategy) doReplace(e apply.Element) (apply.Result, error) {
// TODO: Check for conflicts
if result, done := v.doAddOrDelete(e); done {
return result, nil
}
if err := v.doConflictDetect(e); err != nil {
return apply.Result{}, err
}
if e.HasLocal() {
// Specified locally, set the local value
return apply.Result{Operation: apply.SET, MergedResult: e.GetLocal()}, nil
@ -97,4 +99,9 @@ func (v replaceStrategy) doAddOrDelete(e apply.Element) (apply.Result, bool) {
return apply.Result{}, false
}
// doConflictDetect returns error if element has conflict
func (v replaceStrategy) doConflictDetect(e apply.Element) error {
return v.strategic.doConflictDetect(e)
}
var _ apply.Strategy = &replaceStrategy{}

View File

@ -96,4 +96,14 @@ func (v delegatingStrategy) MergeEmpty(diff apply.EmptyElement) (apply.Result, e
return v.merge.MergeEmpty(diff)
}
// doConflictDetect detects conflicts in element when option enabled, return error if conflict happened.
func (v delegatingStrategy) doConflictDetect(e apply.Element) error {
if v.options.FailOnConflict {
if e, ok := e.(apply.ConflictDetector); ok {
return e.HasConflict()
}
}
return nil
}
var _ apply.Strategy = &delegatingStrategy{}

View File

@ -81,7 +81,7 @@
"spec": {
"description": "Specification of the desired behavior of the Deployment.",
"$ref": "#/definitions/io.k8s.api.apps.v1beta1.DeploymentSpec"
},
}
},
"x-kubernetes-group-version-kind": [
{

View File

@ -32,6 +32,11 @@ import (
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
)
const (
hasConflict = true
noConflict = false
)
var fakeResources = tst.NewFakeResources(filepath.Join("..", "..", "..", "..", "api", "openapi-spec", "swagger.json"))
// run parses the openapi and runs the tests
@ -64,3 +69,17 @@ func create(config string) map[string]interface{} {
return result
}
func runConflictTest(instance apply.Strategy, recorded, local, remote map[string]interface{}, isConflict bool) {
parseFactory := parse.Factory{Resources: fakeResources}
parsed, err := parseFactory.CreateElement(recorded, local, remote)
Expect(err).Should(Not(HaveOccurred()))
merged, err := parsed.Merge(instance)
if isConflict {
Expect(err).Should(HaveOccurred())
} else {
Expect(err).ShouldNot(HaveOccurred())
Expect(merged.Operation).Should(Equal(apply.SET))
}
}

View File

@ -40,4 +40,17 @@ func (e TypeElement) GetValues() map[string]Element {
return e.Values
}
// HasConflict returns ConflictError if some elements in type conflict.
func (e TypeElement) HasConflict() error {
for _, item := range e.GetValues() {
if item, ok := item.(ConflictDetector); ok {
if err := item.HasConflict(); err != nil {
return err
}
}
}
return nil
}
var _ Element = &TypeElement{}
var _ ConflictDetector = &TypeElement{}

View File

@ -22,6 +22,7 @@ import (
autoscalingv1 "k8s.io/api/autoscaling/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilpointer "k8s.io/kubernetes/pkg/util/pointer"
)
func TestHPAGenerate(t *testing.T) {
@ -51,14 +52,14 @@ func TestHPAGenerate(t *testing.T) {
Name: "foo",
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
TargetCPUUtilizationPercentage: newInt32(80),
TargetCPUUtilizationPercentage: utilpointer.Int32Ptr(80),
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "kind",
Name: "name",
APIVersion: "apiVersion",
},
MaxReplicas: int32(10),
MinReplicas: newInt32(1),
MinReplicas: utilpointer.Int32Ptr(1),
},
},
expectErr: false,
@ -125,8 +126,3 @@ func TestHPAGenerate(t *testing.T) {
}
}
}
func newInt32(value int) *int32 {
v := int32(value)
return &v
}

View File

@ -1,36 +0,0 @@
/*
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.
*/
// A set of common functions needed by cmd/kubectl and pkg/kubectl packages.
package kubectl
import (
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
func AddJsonFilenameFlag(cmd *cobra.Command, value *[]string, usage string) {
cmd.Flags().StringSliceVarP(value, "filename", "f", *value, usage)
annotations := make([]string, 0, len(resource.FileExtensions))
for _, ext := range resource.FileExtensions {
annotations = append(annotations, strings.TrimLeft(ext, "."))
}
cmd.Flags().SetAnnotation("filename", cobra.BashCompFilenameExt, annotations)
}

View File

@ -1,41 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["categories.go"],
importpath = "k8s.io/kubernetes/pkg/kubectl/categories",
visibility = ["//visibility:public"],
deps = [
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["categories_test.go"],
embed = [":go_default_library"],
deps = [
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake: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"],
)

View File

@ -1,192 +0,0 @@
/*
Copyright 2017 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 categories
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
)
// CategoryExpander maps category strings to GroupResouces.
// Categories are classification or 'tag' of a group of resources.
type CategoryExpander interface {
Expand(category string) ([]schema.GroupResource, bool)
}
// SimpleCategoryExpander implements CategoryExpander interface
// using a static mapping of categories to GroupResource mapping.
type SimpleCategoryExpander struct {
Expansions map[string][]schema.GroupResource
}
func (e SimpleCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
ret, ok := e.Expansions[category]
return ret, ok
}
// discoveryCategoryExpander struct lets a REST Client wrapper (discoveryClient) to retrieve list of APIResourceList,
// and then convert to fallbackExpander
type discoveryCategoryExpander struct {
fallbackExpander CategoryExpander
discoveryClient discovery.DiscoveryInterface
}
// NewDiscoveryCategoryExpander returns a category expander that makes use of the "categories" fields from
// the API, found through the discovery client. In case of any error or no category found (which likely
// means we're at a cluster prior to categories support, fallback to the expander provided.
func NewDiscoveryCategoryExpander(fallbackExpander CategoryExpander, client discovery.DiscoveryInterface) (discoveryCategoryExpander, error) {
if client == nil {
panic("Please provide discovery client to shortcut expander")
}
return discoveryCategoryExpander{fallbackExpander: fallbackExpander, discoveryClient: client}, nil
}
func (e discoveryCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
// Get all supported resources for groups and versions from server, if no resource found, fallback anyway.
apiResourceLists, _ := e.discoveryClient.ServerResources()
if len(apiResourceLists) == 0 {
return e.fallbackExpander.Expand(category)
}
discoveredExpansions := map[string][]schema.GroupResource{}
for _, apiResourceList := range apiResourceLists {
gv, err := schema.ParseGroupVersion(apiResourceList.GroupVersion)
if err != nil {
return e.fallbackExpander.Expand(category)
}
// Collect GroupVersions by categories
for _, apiResource := range apiResourceList.APIResources {
if categories := apiResource.Categories; len(categories) > 0 {
for _, category := range categories {
groupResource := schema.GroupResource{
Group: gv.Group,
Resource: apiResource.Name,
}
discoveredExpansions[category] = append(discoveredExpansions[category], groupResource)
}
}
}
}
if len(discoveredExpansions) == 0 {
// We don't know if the server really don't have any resource with categories,
// or we're on a cluster version prior to categories support. Anyways, fallback.
return e.fallbackExpander.Expand(category)
}
ret, ok := discoveredExpansions[category]
return ret, ok
}
// discoveryFilteredExpander expands the given CategoryExpander (delegate) to filter group and resource returned from server
type discoveryFilteredExpander struct {
delegate CategoryExpander
discoveryClient discovery.DiscoveryInterface
}
// NewDiscoveryFilteredExpander returns a category expander that filters the returned groupresources
// by what the server has available
func NewDiscoveryFilteredExpander(delegate CategoryExpander, client discovery.DiscoveryInterface) (discoveryFilteredExpander, error) {
if client == nil {
panic("Please provide discovery client to shortcut expander")
}
return discoveryFilteredExpander{delegate: delegate, discoveryClient: client}, nil
}
func (e discoveryFilteredExpander) Expand(category string) ([]schema.GroupResource, bool) {
delegateExpansion, ok := e.delegate.Expand(category)
// Check if we have access to server resources
apiResources, err := e.discoveryClient.ServerResources()
if err != nil {
return delegateExpansion, ok
}
availableResources, err := discovery.GroupVersionResources(apiResources)
if err != nil {
return delegateExpansion, ok
}
available := []schema.GroupResource{}
for _, requestedResource := range delegateExpansion {
for availableResource := range availableResources {
if requestedResource.Group == availableResource.Group &&
requestedResource.Resource == availableResource.Resource {
available = append(available, requestedResource)
break
}
}
}
return available, ok
}
// UnionCategoryExpander implements CategoryExpander interface.
// It maps given category string to union of expansions returned by all the CategoryExpanders in the list.
type UnionCategoryExpander []CategoryExpander
func (u UnionCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
ret := []schema.GroupResource{}
ok := false
// Expand the category for each CategoryExpander in the list and merge/combine the results.
for _, expansion := range u {
curr, currOk := expansion.Expand(category)
for _, currGR := range curr {
found := false
for _, existing := range ret {
if existing == currGR {
found = true
break
}
}
if !found {
ret = append(ret, currGR)
}
}
ok = ok || currOk
}
return ret, ok
}
// legacyUserResources are the resource names that apply to the primary, user facing resources used by
// client tools. They are in deletion-first order - dependent resources should be last.
// Should remain exported in order to expose a current list of resources to downstream
// composition that wants to build on the concept of 'all' for their CLIs.
var legacyUserResources = []schema.GroupResource{
{Group: "", Resource: "pods"},
{Group: "", Resource: "replicationcontrollers"},
{Group: "", Resource: "services"},
{Group: "apps", Resource: "statefulsets"},
{Group: "autoscaling", Resource: "horizontalpodautoscalers"},
{Group: "batch", Resource: "jobs"},
{Group: "batch", Resource: "cronjobs"},
{Group: "extensions", Resource: "daemonsets"},
{Group: "extensions", Resource: "deployments"},
{Group: "extensions", Resource: "replicasets"},
}
// LegacyCategoryExpander is the old hardcoded expansion
var LegacyCategoryExpander CategoryExpander = SimpleCategoryExpander{
Expansions: map[string][]schema.GroupResource{
"all": legacyUserResources,
},
}

View File

@ -1,186 +0,0 @@
/*
Copyright 2017 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 categories
import (
"reflect"
"testing"
"github.com/googleapis/gnostic/OpenAPIv2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
)
func TestCategoryExpansion(t *testing.T) {
tests := []struct {
name string
arg string
expected []schema.GroupResource
expectedOk bool
}{
{
name: "no-replacement",
arg: "service",
expected: nil,
},
{
name: "all-replacement",
arg: "all",
expected: []schema.GroupResource{
{Resource: "pods"},
{Resource: "replicationcontrollers"},
{Resource: "services"},
{Resource: "statefulsets", Group: "apps"},
{Resource: "horizontalpodautoscalers", Group: "autoscaling"},
{Resource: "jobs", Group: "batch"},
{Resource: "cronjobs", Group: "batch"},
{Resource: "daemonsets", Group: "extensions"},
{Resource: "deployments", Group: "extensions"},
{Resource: "replicasets", Group: "extensions"},
},
expectedOk: true,
},
}
for _, test := range tests {
actual, actualOk := LegacyCategoryExpander.Expand(test.arg)
if e, a := test.expected, actual; !reflect.DeepEqual(e, a) {
t.Errorf("%s: expected %s, got %s", test.name, e, a)
}
if e, a := test.expectedOk, actualOk; e != a {
t.Errorf("%s: expected %v, got %v", test.name, e, a)
}
}
}
func TestDiscoveryCategoryExpander(t *testing.T) {
tests := []struct {
category string
serverResponse []*metav1.APIResourceList
expected []schema.GroupResource
}{
{
category: "all",
serverResponse: []*metav1.APIResourceList{
{
GroupVersion: "batch/v1",
APIResources: []metav1.APIResource{
{
Name: "jobs",
ShortNames: []string{"jz"},
Categories: []string{"all"},
},
},
},
},
expected: []schema.GroupResource{
{
Group: "batch",
Resource: "jobs",
},
},
},
{
category: "all",
serverResponse: []*metav1.APIResourceList{
{
GroupVersion: "batch/v1",
APIResources: []metav1.APIResource{
{
Name: "jobs",
ShortNames: []string{"jz"},
},
},
},
},
},
{
category: "targaryens",
serverResponse: []*metav1.APIResourceList{
{
GroupVersion: "batch/v1",
APIResources: []metav1.APIResource{
{
Name: "jobs",
ShortNames: []string{"jz"},
Categories: []string{"all"},
},
},
},
},
},
}
dc := &fakeDiscoveryClient{}
for _, test := range tests {
dc.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {
return test.serverResponse, nil
}
expander, err := NewDiscoveryCategoryExpander(SimpleCategoryExpander{}, dc)
if err != nil {
t.Fatalf("unexpected error %v", err)
}
expanded, _ := expander.Expand(test.category)
if !reflect.DeepEqual(expanded, test.expected) {
t.Errorf("expected %v, got %v", test.expected, expanded)
}
}
}
type fakeDiscoveryClient struct {
serverResourcesHandler func() ([]*metav1.APIResourceList, error)
}
var _ discovery.DiscoveryInterface = &fakeDiscoveryClient{}
func (c *fakeDiscoveryClient) RESTClient() restclient.Interface {
return &fake.RESTClient{}
}
func (c *fakeDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
return nil, nil
}
func (c *fakeDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
return &metav1.APIResourceList{}, nil
}
func (c *fakeDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
return c.serverResourcesHandler()
}
func (c *fakeDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return nil, nil
}
func (c *fakeDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
return nil, nil
}
func (c *fakeDiscoveryClient) ServerVersion() (*version.Info, error) {
return &version.Info{}, nil
}
func (c *fakeDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
return &openapi_v2.Document{}, nil
}

View File

@ -9,6 +9,7 @@ go_library(
srcs = [
"alpha.go",
"annotate.go",
"apiresources.go",
"apiversions.go",
"apply.go",
"apply_edit_last_applied.go",
@ -23,22 +24,8 @@ go_library(
"completion.go",
"convert.go",
"cp.go",
"create.go",
"create_clusterrole.go",
"create_clusterrolebinding.go",
"create_configmap.go",
"create_deployment.go",
"create_job.go",
"create_namespace.go",
"create_pdb.go",
"create_priorityclass.go",
"create_quota.go",
"create_role.go",
"create_rolebinding.go",
"create_secret.go",
"create_service.go",
"create_serviceaccount.go",
"delete.go",
"delete_flags.go",
"describe.go",
"diff.go",
"drain.go",
@ -74,30 +61,37 @@ go_library(
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/validation:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/apply/parse:go_default_library",
"//pkg/kubectl/apply/strategy:go_default_library",
"//pkg/kubectl/cmd/auth:go_default_library",
"//pkg/kubectl/cmd/config:go_default_library",
"//pkg/kubectl/cmd/resource:go_default_library",
"//pkg/kubectl/cmd/create:go_default_library",
"//pkg/kubectl/cmd/get:go_default_library",
"//pkg/kubectl/cmd/rollout:go_default_library",
"//pkg/kubectl/cmd/scalejob:go_default_library",
"//pkg/kubectl/cmd/set:go_default_library",
"//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/cmd/util/editor:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/cmd/wait:go_default_library",
"//pkg/kubectl/explain:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/metricsutil:go_default_library",
"//pkg/kubectl/plugins:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//pkg/kubectl/proxy:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//pkg/kubectl/util:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/kubectl/util/term:go_default_library",
"//pkg/kubectl/validation:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",
"//pkg/util/interrupt:go_default_library",
"//pkg/util/taints:go_default_library",
"//pkg/version:go_default_library",
@ -111,16 +105,15 @@ go_library(
"//vendor/github.com/renstrom/dedent:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/autoscaling/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
@ -134,6 +127,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
@ -141,11 +135,12 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/batch/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/autoscaling/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/scale:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/portforward:go_default_library",
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
@ -165,24 +160,10 @@ go_test(
"apply_test.go",
"attach_test.go",
"clusterinfo_dump_test.go",
"cmd_printing_test.go",
"cmd_test.go",
"convert_test.go",
"cp_test.go",
"create_clusterrole_test.go",
"create_clusterrolebinding_test.go",
"create_configmap_test.go",
"create_deployment_test.go",
"create_job_test.go",
"create_namespace_test.go",
"create_pdb_test.go",
"create_priorityclass_test.go",
"create_quota_test.go",
"create_role_test.go",
"create_rolebinding_test.go",
"create_secret_test.go",
"create_service_test.go",
"create_serviceaccount_test.go",
"create_test.go",
"delete_test.go",
"describe_test.go",
"diff_test.go",
@ -206,7 +187,6 @@ go_test(
data = [
"testdata",
"//api/openapi-spec:swagger-spec",
"//examples:config",
"//test/e2e/testing-manifests:all-srcs",
"//test/fixtures",
],
@ -218,29 +198,25 @@ go_test(
"//pkg/apis/batch:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/cmd/resource:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/kubectl/cmd/create:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/plugins:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/kubectl/util/term:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/util/strings:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
@ -256,8 +232,7 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch/testing:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/client-go/dynamic/fake:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
@ -281,13 +256,16 @@ filegroup(
":package-srcs",
"//pkg/kubectl/cmd/auth:all-srcs",
"//pkg/kubectl/cmd/config:all-srcs",
"//pkg/kubectl/cmd/resource:all-srcs",
"//pkg/kubectl/cmd/create:all-srcs",
"//pkg/kubectl/cmd/get:all-srcs",
"//pkg/kubectl/cmd/rollout:all-srcs",
"//pkg/kubectl/cmd/scalejob:all-srcs",
"//pkg/kubectl/cmd/set:all-srcs",
"//pkg/kubectl/cmd/templates:all-srcs",
"//pkg/kubectl/cmd/testdata/edit:all-srcs",
"//pkg/kubectl/cmd/testing:all-srcs",
"//pkg/kubectl/cmd/util:all-srcs",
"//pkg/kubectl/cmd/wait:all-srcs",
],
tags = ["automanaged"],
visibility = [

View File

@ -17,17 +17,16 @@ limitations under the License.
package cmd
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
// NewCmdAlpha creates a command that acts as an alternate root command for features in alpha
func NewCmdAlpha(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command {
func NewCmdAlpha(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "alpha",
Short: i18n.T("Commands for features in alpha"),
@ -37,7 +36,7 @@ func NewCmdAlpha(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Com
// Alpha commands should be added here. As features graduate from alpha they should move
// from here to the CommandGroups defined by NewKubeletCommand() in cmd.go.
//cmd.AddCommand(NewCmdDebug(f, in, out, err))
cmd.AddCommand(NewCmdDiff(f, out, err))
cmd.AddCommand(NewCmdDiff(f, streams))
// NewKubeletCommand() will hide the alpha command if it has no subcommands. Overriding
// the help function ensures a reasonable message if someone types the hidden command anyway.

View File

@ -34,32 +34,44 @@ import (
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
// AnnotateOptions have the data required to perform the annotate operation
type AnnotateOptions struct {
PrintFlags *genericclioptions.PrintFlags
PrintObj printers.ResourcePrinterFunc
// Filename options
resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags
// Common user flags
overwrite bool
local bool
dryrun bool
all bool
resourceVersion string
selector string
outputFormat string
recordChangeCause bool
overwrite bool
local bool
dryrun bool
all bool
resourceVersion string
selector string
fieldSelector string
outputFormat string
// results of arg parsing
resources []string
newAnnotations map[string]string
removeAnnotations []string
resources []string
newAnnotations map[string]string
removeAnnotations []string
Recorder genericclioptions.Recorder
namespace string
enforceNamespace bool
builder *resource.Builder
unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
includeUninitialized bool
// Common share fields
out io.Writer
genericclioptions.IOStreams
}
var (
@ -69,7 +81,7 @@ var (
All Kubernetes objects support the ability to store additional data with the object as
annotations. Annotations are key/value pairs that can be larger than labels and include
arbitrary string values such as structured JSON. Tools and system extensions may use
annotations to store their own data.
annotations to store their own data.
Attempting to set an annotation that already exists will fail unless --overwrite is set.
If --resource-version is specified and does not match the current resource version on
@ -97,50 +109,81 @@ var (
kubectl annotate pods foo description-`))
)
func NewCmdAnnotate(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &AnnotateOptions{}
validArgs := cmdutil.ValidArgList(f)
func NewAnnotateOptions(ioStreams genericclioptions.IOStreams) *AnnotateOptions {
return &AnnotateOptions{
PrintFlags: genericclioptions.NewPrintFlags("annotated").WithTypeSetter(scheme.Scheme),
RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{},
IOStreams: ioStreams,
}
}
func NewCmdAnnotate(parent string, f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewAnnotateOptions(ioStreams)
cmd := &cobra.Command{
Use: "annotate [--overwrite] (-f FILENAME | TYPE NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--resource-version=version]",
DisableFlagsInUseLine: true,
Short: i18n.T("Update the annotations on a resource"),
Long: annotateLong + "\n\n" + cmdutil.ValidResourceTypeList(f),
Long: annotateLong + "\n\n" + cmdutil.SuggestApiResources(parent),
Example: annotateExample,
Run: func(cmd *cobra.Command, args []string) {
if err := options.Complete(out, cmd, args); err != nil {
cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, "%v", err))
}
if err := options.Validate(); err != nil {
cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, "%v", err))
}
cmdutil.CheckErr(options.RunAnnotate(f, cmd))
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.RunAnnotate())
},
ValidArgs: validArgs,
ArgAliases: kubectl.ResourceAliases(validArgs),
}
cmdutil.AddPrinterFlags(cmd)
// bind flag structs
o.RecordFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd)
cmd.Flags().BoolVar(&options.overwrite, "overwrite", options.overwrite, "If true, allow annotations to be overwritten, otherwise reject annotation updates that overwrite existing annotations.")
cmd.Flags().BoolVar(&options.local, "local", options.local, "If true, annotation will NOT contact api-server but run locally.")
cmd.Flags().StringVarP(&options.selector, "selector", "l", options.selector, "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2).")
cmd.Flags().BoolVar(&options.all, "all", options.all, "Select all resources, including uninitialized ones, in the namespace of the specified resource types.")
cmd.Flags().StringVar(&options.resourceVersion, "resource-version", options.resourceVersion, i18n.T("If non-empty, the annotation update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource."))
cmd.Flags().BoolVar(&o.overwrite, "overwrite", o.overwrite, "If true, allow annotations to be overwritten, otherwise reject annotation updates that overwrite existing annotations.")
cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, annotation will NOT contact api-server but run locally.")
cmd.Flags().StringVarP(&o.selector, "selector", "l", o.selector, "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2).")
cmd.Flags().StringVar(&o.fieldSelector, "field-selector", o.fieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
cmd.Flags().BoolVar(&o.all, "all", o.all, "Select all resources, including uninitialized ones, in the namespace of the specified resource types.")
cmd.Flags().StringVar(&o.resourceVersion, "resource-version", o.resourceVersion, i18n.T("If non-empty, the annotation update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource."))
usage := "identifying the resource to update the annotation"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
// Complete adapts from the command line args and factory to the data required.
func (o *AnnotateOptions) Complete(out io.Writer, cmd *cobra.Command, args []string) (err error) {
o.out = out
func (o *AnnotateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.RecordFlags.Complete(cmd)
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
o.outputFormat = cmdutil.GetFlagString(cmd, "output")
o.dryrun = cmdutil.GetDryRunFlag(cmd)
o.recordChangeCause = cmdutil.GetRecordFlag(cmd)
if o.dryrun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = func(obj runtime.Object, out io.Writer) error {
return printer.PrintObj(obj, out)
}
o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
o.includeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, false)
o.builder = f.NewBuilder()
o.unstructuredClientForMapping = f.UnstructuredClientForMapping
// retrieves resource and annotation args from args
// also checks args to verify that all resources are specified before annotations
@ -150,7 +193,11 @@ func (o *AnnotateOptions) Complete(out io.Writer, cmd *cobra.Command, args []str
}
o.resources = resources
o.newAnnotations, o.removeAnnotations, err = parseAnnotations(annotationArgs)
return err
if err != nil {
return err
}
return nil
}
// Validate checks to the AnnotateOptions to see if there is sufficient information run the command.
@ -158,6 +205,9 @@ func (o AnnotateOptions) Validate() error {
if o.all && len(o.selector) > 0 {
return fmt.Errorf("cannot set --all and --selector at the same time")
}
if o.all && len(o.fieldSelector) > 0 {
return fmt.Errorf("cannot set --all and --field-selector at the same time")
}
if len(o.resources) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames) {
return fmt.Errorf("one or more resources must be specified as <resource> <name> or <resource>/<name>")
}
@ -168,26 +218,19 @@ func (o AnnotateOptions) Validate() error {
}
// RunAnnotate does the work
func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) error {
namespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
changeCause := f.Command(cmd, false)
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
b := f.NewBuilder().
func (o AnnotateOptions) RunAnnotate() error {
b := o.builder.
Unstructured().
LocalParam(o.local).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
NamespaceParam(o.namespace).DefaultNamespace().
FilenameParam(o.enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(o.includeUninitialized).
Flatten()
if !o.local {
b = b.LabelSelectorParam(o.selector).
FieldSelectorParam(o.fieldSelector).
ResourceTypeOrNameArgs(o.all, o.resources...).
Latest()
}
@ -214,12 +257,7 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
}
var outputObj runtime.Object
var obj runtime.Object
obj, err = info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion())
if err != nil {
return err
}
obj := info.Object
if o.dryrun || o.local {
if err := o.updateAnnotations(obj); err != nil {
@ -232,9 +270,8 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
if err != nil {
return err
}
// If we should record change-cause, add it to new annotations
if cmdutil.ContainsChangeCause(info) || o.recordChangeCause {
o.newAnnotations[kubectl.ChangeCauseAnnotation] = changeCause
if err := o.Recorder.Record(info.Object); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
if err := o.updateAnnotations(obj); err != nil {
return err
@ -250,7 +287,7 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
}
mapping := info.ResourceMapping()
client, err := f.UnstructuredClientForMapping(mapping)
client, err := o.unstructuredClientForMapping(mapping)
if err != nil {
return err
}
@ -266,11 +303,7 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
}
}
if len(o.outputFormat) > 0 {
return cmdutil.PrintObject(cmd, outputObj, o.out)
}
cmdutil.PrintSuccess(false, o.out, info.Object, o.dryrun, "annotated")
return nil
return o.PrintObj(outputObj, o.Out)
})
}

View File

@ -17,7 +17,6 @@ limitations under the License.
package cmd
import (
"bytes"
"net/http"
"reflect"
"strings"
@ -30,6 +29,7 @@ import (
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@ -417,37 +417,45 @@ func TestAnnotateErrors(t *testing.T) {
}
for k, testCase := range testCases {
tf := cmdtesting.NewTestFactory()
tf.Namespace = "test"
tf.ClientConfigVal = defaultClientConfig()
t.Run(k, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(tf, buf)
cmd.SetOutput(buf)
tf.ClientConfigVal = defaultClientConfig()
for k, v := range testCase.flags {
cmd.Flags().Set(k, v)
}
options := &AnnotateOptions{}
err := options.Complete(buf, cmd, testCase.args)
if err == nil {
err = options.Validate()
}
if !testCase.errFn(err) {
t.Errorf("%s: unexpected error: %v", k, err)
continue
}
if buf.Len() > 0 {
t.Errorf("buffer should be empty: %s", string(buf.Bytes()))
}
iostreams, _, bufOut, bufErr := genericclioptions.NewTestIOStreams()
cmd := NewCmdAnnotate("kubectl", tf, iostreams)
cmd.SetOutput(bufOut)
for k, v := range testCase.flags {
cmd.Flags().Set(k, v)
}
options := NewAnnotateOptions(iostreams)
err := options.Complete(tf, cmd, testCase.args)
if err == nil {
err = options.Validate()
}
if !testCase.errFn(err) {
t.Errorf("%s: unexpected error: %v", k, err)
return
}
if bufOut.Len() > 0 {
t.Errorf("buffer should be empty: %s", string(bufOut.Bytes()))
}
if bufErr.Len() > 0 {
t.Errorf("buffer should be empty: %s", string(bufErr.Bytes()))
}
})
}
}
func TestAnnotateObject(t *testing.T) {
pods, _, _ := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
@ -476,21 +484,20 @@ func TestAnnotateObject(t *testing.T) {
}
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(tf, buf)
cmd.SetOutput(buf)
options := &AnnotateOptions{}
iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdAnnotate("kubectl", tf, iostreams)
cmd.SetOutput(bufOut)
options := NewAnnotateOptions(iostreams)
args := []string{"pods/foo", "a=b", "c-"}
if err := options.Complete(buf, cmd, args); err != nil {
if err := options.Complete(tf, cmd, args); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.Validate(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.RunAnnotate(tf, cmd); err != nil {
if err := options.RunAnnotate(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
@ -498,8 +505,10 @@ func TestAnnotateObject(t *testing.T) {
func TestAnnotateObjectFromFile(t *testing.T) {
pods, _, _ := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
@ -528,28 +537,29 @@ func TestAnnotateObjectFromFile(t *testing.T) {
}
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(tf, buf)
cmd.SetOutput(buf)
options := &AnnotateOptions{}
iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdAnnotate("kubectl", tf, iostreams)
cmd.SetOutput(bufOut)
options := NewAnnotateOptions(iostreams)
options.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}
args := []string{"a=b", "c-"}
if err := options.Complete(buf, cmd, args); err != nil {
if err := options.Complete(tf, cmd, args); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.Validate(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.RunAnnotate(tf, cmd); err != nil {
if err := options.RunAnnotate(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestAnnotateLocal(t *testing.T) {
tf := cmdtesting.NewTestFactory()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
NegotiatedSerializer: unstructuredSerializer,
@ -558,21 +568,21 @@ func TestAnnotateLocal(t *testing.T) {
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(tf, buf)
options := &AnnotateOptions{local: true}
iostreams, _, _, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdAnnotate("kubectl", tf, iostreams)
options := NewAnnotateOptions(iostreams)
options.local = true
options.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}
args := []string{"a=b"}
if err := options.Complete(buf, cmd, args); err != nil {
if err := options.Complete(tf, cmd, args); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.Validate(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.RunAnnotate(tf, cmd); err != nil {
if err := options.RunAnnotate(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
@ -580,8 +590,10 @@ func TestAnnotateLocal(t *testing.T) {
func TestAnnotateMultipleObjects(t *testing.T) {
pods, _, _ := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
NegotiatedSerializer: unstructuredSerializer,
@ -611,21 +623,21 @@ func TestAnnotateMultipleObjects(t *testing.T) {
}
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(tf, buf)
cmd.SetOutput(buf)
options := &AnnotateOptions{all: true}
iostreams, _, _, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdAnnotate("kubectl", tf, iostreams)
cmd.SetOutput(iostreams.Out)
options := NewAnnotateOptions(iostreams)
options.all = true
args := []string{"pods", "a=b", "c-"}
if err := options.Complete(buf, cmd, args); err != nil {
if err := options.Complete(tf, cmd, args); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.Validate(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.RunAnnotate(tf, cmd); err != nil {
if err := options.RunAnnotate(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}

View File

@ -0,0 +1,229 @@
/*
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 cmd
import (
"fmt"
"io"
"sort"
"strings"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/printers"
)
var (
apiresourcesExample = templates.Examples(`
# Print the supported API Resources
kubectl api-resources
# Print the supported API Resources with more information
kubectl api-resources -o wide
# Print the supported namespaced resources
kubectl api-resources --namespaced=true
# Print the supported non-namespaced resources
kubectl api-resources --namespaced=false
# Print the supported API Resources with specific APIGroup
kubectl api-resources --api-group=extensions`)
)
// ApiResourcesOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
// referencing the cmd.Flags()
type ApiResourcesOptions struct {
Output string
APIGroup string
Namespaced bool
Verbs []string
NoHeaders bool
Cached bool
genericclioptions.IOStreams
}
// groupResource contains the APIGroup and APIResource
type groupResource struct {
APIGroup string
APIResource metav1.APIResource
}
func NewAPIResourceOptions(ioStreams genericclioptions.IOStreams) *ApiResourcesOptions {
return &ApiResourcesOptions{
IOStreams: ioStreams,
Namespaced: true,
}
}
func NewCmdApiResources(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewAPIResourceOptions(ioStreams)
cmd := &cobra.Command{
Use: "api-resources",
Short: "Print the supported API resources on the server",
Long: "Print the supported API resources on the server",
Example: apiresourcesExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Validate(cmd))
cmdutil.CheckErr(o.RunApiResources(cmd, f))
},
}
cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).")
cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "Output format. One of: wide|name.")
cmd.Flags().StringVar(&o.APIGroup, "api-group", o.APIGroup, "Limit to resources in the specified API group.")
cmd.Flags().BoolVar(&o.Namespaced, "namespaced", o.Namespaced, "If false, non-namespaced resources will be returned, otherwise returning namespaced resources by default.")
cmd.Flags().StringSliceVar(&o.Verbs, "verbs", o.Verbs, "Limit to resources that support the specified verbs.")
cmd.Flags().BoolVar(&o.Cached, "cached", o.Cached, "Use the cached list of resources if available.")
return cmd
}
func (o *ApiResourcesOptions) Validate(cmd *cobra.Command) error {
supportedOutputTypes := sets.NewString("", "wide", "name")
if !supportedOutputTypes.Has(o.Output) {
return fmt.Errorf("--output %v is not available", o.Output)
}
return nil
}
func (o *ApiResourcesOptions) RunApiResources(cmd *cobra.Command, f cmdutil.Factory) error {
w := printers.GetNewTabWriter(o.Out)
defer w.Flush()
discoveryclient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
if !o.Cached {
// Always request fresh data from the server
discoveryclient.Invalidate()
}
lists, err := discoveryclient.ServerPreferredResources()
if err != nil {
return err
}
resources := []groupResource{}
groupChanged := cmd.Flags().Changed("api-group")
nsChanged := cmd.Flags().Changed("namespaced")
for _, list := range lists {
if len(list.APIResources) == 0 {
continue
}
gv, err := schema.ParseGroupVersion(list.GroupVersion)
if err != nil {
continue
}
for _, resource := range list.APIResources {
if len(resource.Verbs) == 0 {
continue
}
// filter apiGroup
if groupChanged && o.APIGroup != gv.Group {
continue
}
// filter namespaced
if nsChanged && o.Namespaced != resource.Namespaced {
continue
}
// filter to resources that support the specified verbs
if len(o.Verbs) > 0 && !sets.NewString(resource.Verbs...).HasAll(o.Verbs...) {
continue
}
resources = append(resources, groupResource{
APIGroup: gv.Group,
APIResource: resource,
})
}
}
if o.NoHeaders == false && o.Output != "name" {
if err = printContextHeaders(w, o.Output); err != nil {
return err
}
}
sort.Stable(sortableGroupResource(resources))
for _, r := range resources {
switch o.Output {
case "name":
name := r.APIResource.Name
if len(r.APIGroup) > 0 {
name += "." + r.APIGroup
}
if _, err := fmt.Fprintf(w, "%s\n", name); err != nil {
return err
}
case "wide":
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\t%v\n",
r.APIResource.Name,
strings.Join(r.APIResource.ShortNames, ","),
r.APIGroup,
r.APIResource.Namespaced,
r.APIResource.Kind,
r.APIResource.Verbs); err != nil {
return err
}
case "":
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\n",
r.APIResource.Name,
strings.Join(r.APIResource.ShortNames, ","),
r.APIGroup,
r.APIResource.Namespaced,
r.APIResource.Kind); err != nil {
return err
}
}
}
return nil
}
func printContextHeaders(out io.Writer, output string) error {
columnNames := []string{"NAME", "SHORTNAMES", "APIGROUP", "NAMESPACED", "KIND"}
if output == "wide" {
columnNames = append(columnNames, "VERBS")
}
_, err := fmt.Fprintf(out, "%s\n", strings.Join(columnNames, "\t"))
return err
}
type sortableGroupResource []groupResource
func (s sortableGroupResource) Len() int { return len(s) }
func (s sortableGroupResource) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s sortableGroupResource) Less(i, j int) bool {
ret := strings.Compare(s[i].APIGroup, s[j].APIGroup)
if ret > 0 {
return false
} else if ret == 0 {
return strings.Compare(s[i].APIResource.Name, s[j].APIResource.Name) < 0
}
return true
}

View File

@ -18,14 +18,15 @@ package cmd
import (
"fmt"
"io"
"sort"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -35,37 +36,54 @@ var (
kubectl api-versions`))
)
func NewCmdApiVersions(f cmdutil.Factory, out io.Writer) *cobra.Command {
type ApiVersionsOptions struct {
discoveryClient discovery.CachedDiscoveryInterface
genericclioptions.IOStreams
}
func NewApiVersionsOptions(ioStreams genericclioptions.IOStreams) *ApiVersionsOptions {
return &ApiVersionsOptions{
IOStreams: ioStreams,
}
}
func NewCmdApiVersions(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewApiVersionsOptions(ioStreams)
cmd := &cobra.Command{
Use: "api-versions",
Short: "Print the supported API versions on the server, in the form of \"group/version\"",
Long: "Print the supported API versions on the server, in the form of \"group/version\"",
Example: apiversionsExample,
Run: func(cmd *cobra.Command, args []string) {
err := RunApiVersions(f, out)
cmdutil.CheckErr(err)
cmdutil.CheckErr(o.Complete(f))
cmdutil.CheckErr(o.RunApiVersions())
},
}
return cmd
}
func RunApiVersions(f cmdutil.Factory, w io.Writer) error {
discoveryclient, err := f.DiscoveryClient()
func (o *ApiVersionsOptions) Complete(f cmdutil.Factory) error {
var err error
o.discoveryClient, err = f.ToDiscoveryClient()
if err != nil {
return err
}
return nil
}
func (o *ApiVersionsOptions) RunApiVersions() error {
// Always request fresh data from the server
discoveryclient.Invalidate()
o.discoveryClient.Invalidate()
groupList, err := discoveryclient.ServerGroups()
groupList, err := o.discoveryClient.ServerGroups()
if err != nil {
return fmt.Errorf("Couldn't get available api versions from server: %v\n", err)
}
apiVersions := metav1.ExtractGroupVersions(groupList)
sort.Strings(apiVersions)
for _, v := range apiVersions {
fmt.Fprintln(w, v)
fmt.Fprintln(o.Out, v)
}
return nil
}

View File

@ -26,9 +26,11 @@ import (
"github.com/jonboulle/clockwork"
"github.com/spf13/cobra"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
@ -37,29 +39,52 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/dynamic"
oapi "k8s.io/kube-openapi/pkg/util/proto"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/kubectl/validation"
)
type ApplyOptions struct {
FilenameOptions resource.FilenameOptions
Selector string
Force bool
Prune bool
Cascade bool
GracePeriod int
PruneResources []pruneResource
Timeout time.Duration
cmdBaseName string
all bool
RecordFlags *genericclioptions.RecordFlags
Recorder genericclioptions.Recorder
PrintFlags *genericclioptions.PrintFlags
ToPrinter func(string) (printers.ResourcePrinter, error)
DeleteFlags *DeleteFlags
DeleteOptions *DeleteOptions
Selector string
DryRun bool
Prune bool
PruneResources []pruneResource
cmdBaseName string
All bool
Overwrite bool
OpenApiPatch bool
PruneWhitelist []string
ShouldIncludeUninitialized bool
Validator validation.Schema
Builder *resource.Builder
Mapper meta.RESTMapper
DynamicClient dynamic.Interface
OpenAPISchema openapi.Resources
Namespace string
EnforceNamespace bool
genericclioptions.IOStreams
}
const (
@ -98,12 +123,27 @@ var (
warningNoLastAppliedConfigAnnotation = "Warning: %[1]s apply should be used on resource created by either %[1]s create --save-config or %[1]s apply\n"
)
func NewCmdApply(baseName string, f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
var options ApplyOptions
func NewApplyOptions(ioStreams genericclioptions.IOStreams) *ApplyOptions {
return &ApplyOptions{
RecordFlags: genericclioptions.NewRecordFlags(),
DeleteFlags: NewDeleteFlags("that contains the configuration to apply"),
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
Overwrite: true,
OpenApiPatch: true,
Recorder: genericclioptions.NoopRecorder{},
IOStreams: ioStreams,
}
}
func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewApplyOptions(ioStreams)
// Store baseName for use in printing warnings / messages involving the base command name.
// This is useful for downstream command that wrap this one.
options.cmdBaseName = baseName
o.cmdBaseName = baseName
cmd := &cobra.Command{
Use: "apply -f FILENAME",
@ -112,40 +152,85 @@ func NewCmdApply(baseName string, f cmdutil.Factory, out, errOut io.Writer) *cob
Long: applyLong,
Example: applyExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(validateArgs(cmd, args))
cmdutil.CheckErr(validatePruneAll(options.Prune, options.all, options.Selector))
cmdutil.CheckErr(RunApply(f, cmd, out, errOut, &options))
cmdutil.CheckErr(validatePruneAll(o.Prune, o.All, o.Selector))
cmdutil.CheckErr(o.Run())
},
}
usage := "that contains the configuration to apply"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
// bind flag structs
o.DeleteFlags.AddFlags(cmd)
o.RecordFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
cmd.MarkFlagRequired("filename")
cmd.Flags().Bool("overwrite", true, "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration")
cmd.Flags().BoolVar(&options.Prune, "prune", false, "Automatically delete resource objects, including the uninitialized ones, that do not appear in the configs and are created by either apply or create --save-config. Should be used with either -l or --all.")
cmd.Flags().BoolVar(&options.Cascade, "cascade", true, "Only relevant during a prune or a force apply. If true, cascade the deletion of the resources managed by pruned or deleted resources (e.g. Pods created by a ReplicationController).")
cmd.Flags().IntVar(&options.GracePeriod, "grace-period", -1, "Only relevant during a prune or a force apply. Period of time in seconds given to pruned or deleted resources to terminate gracefully. Ignored if negative.")
cmd.Flags().BoolVar(&options.Force, "force", false, fmt.Sprintf("Delete and re-create the specified resource, when PATCH encounters conflict and has retried for %d times.", maxPatchRetry))
cmd.Flags().DurationVar(&options.Timeout, "timeout", 0, "Only relevant during a force apply. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).")
cmd.Flags().BoolVar(&o.Overwrite, "overwrite", o.Overwrite, "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration")
cmd.Flags().BoolVar(&o.Prune, "prune", o.Prune, "Automatically delete resource objects, including the uninitialized ones, that do not appear in the configs and are created by either apply or create --save-config. Should be used with either -l or --all.")
cmdutil.AddValidateFlags(cmd)
cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVar(&options.all, "all", options.all, "Select all resources in the namespace of the specified resource types.")
cmd.Flags().StringArray("prune-whitelist", []string{}, "Overwrite the default whitelist with <group/version/kind> for --prune")
cmd.Flags().Bool("openapi-patch", true, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.")
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types.")
cmd.Flags().StringArrayVar(&o.PruneWhitelist, "prune-whitelist", o.PruneWhitelist, "Overwrite the default whitelist with <group/version/kind> for --prune")
cmd.Flags().BoolVar(&o.OpenApiPatch, "openapi-patch", o.OpenApiPatch, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.")
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd)
// apply subcommands
cmd.AddCommand(NewCmdApplyViewLastApplied(f, out, errOut))
cmd.AddCommand(NewCmdApplySetLastApplied(f, out, errOut))
cmd.AddCommand(NewCmdApplyEditLastApplied(f, out, errOut))
cmd.AddCommand(NewCmdApplyViewLastApplied(f, ioStreams))
cmd.AddCommand(NewCmdApplySetLastApplied(f, ioStreams))
cmd.AddCommand(NewCmdApplyEditLastApplied(f, ioStreams))
return cmd
}
func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
o.DryRun = cmdutil.GetDryRunFlag(cmd)
// allow for a success message operation to be specified at print time
o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
o.PrintFlags.NamePrintFlags.Operation = operation
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
return o.PrintFlags.ToPrinter()
}
var err error
o.RecordFlags.Complete(cmd)
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
dynamicClient, err := f.DynamicClient()
if err != nil {
return err
}
o.DeleteOptions = o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams)
o.ShouldIncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, o.Prune)
o.OpenAPISchema, _ = f.OpenAPISchema()
o.Validator, err = f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
o.Builder = f.NewBuilder()
o.Mapper, err = f.ToRESTMapper()
if err != nil {
return err
}
o.DynamicClient, err = f.DynamicClient()
if err != nil {
return err
}
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
return nil
}
func validateArgs(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args)
@ -194,60 +279,44 @@ func parsePruneResources(mapper meta.RESTMapper, gvks []string) ([]pruneResource
return pruneResources, nil
}
func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, options *ApplyOptions) error {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
if err != nil {
return err
}
func (o *ApplyOptions) Run() error {
var openapiSchema openapi.Resources
if cmdutil.GetFlagBool(cmd, "openapi-patch") {
openapiSchema, err = f.OpenAPISchema()
if err != nil {
openapiSchema = nil
}
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
if o.OpenApiPatch {
openapiSchema = o.OpenAPISchema
}
// include the uninitialized objects by default if --prune is true
// unless explicitly set --include-uninitialized=false
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, options.Prune)
r := f.NewBuilder().
r := o.Builder.
Unstructured().
Schema(schema).
Schema(o.Validator).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &options.FilenameOptions).
LabelSelectorParam(options.Selector).
IncludeUninitialized(includeUninitialized).
NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
LabelSelectorParam(o.Selector).
IncludeUninitialized(o.ShouldIncludeUninitialized).
Flatten().
Do()
if err := r.Err(); err != nil {
return err
}
if options.Prune {
options.PruneResources, err = parsePruneResources(r.Mapper().RESTMapper, cmdutil.GetFlagStringArray(cmd, "prune-whitelist"))
var err error
if o.Prune {
o.PruneResources, err = parsePruneResources(o.Mapper, o.PruneWhitelist)
if err != nil {
return err
}
}
dryRun := cmdutil.GetDryRunFlag(cmd)
output := cmdutil.GetFlagString(cmd, "output")
output := *o.PrintFlags.OutputFormat
shortOutput := output == "name"
encoder := scheme.DefaultJSONEncoder()
deserializer := scheme.Codecs.UniversalDeserializer()
mapper := r.Mapper().RESTMapper
visitedUids := sets.NewString()
visitedNamespaces := sets.NewString()
var objs []runtime.Object
count := 0
err = r.Visit(func(info *resource.Info, err error) error {
if err != nil {
@ -258,145 +327,185 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
visitedNamespaces.Insert(info.Namespace)
}
// Add change-cause annotation to resource info if it should be recorded
if cmdutil.ShouldRecord(cmd, info) {
recordInObj := info.Object
if err := cmdutil.RecordChangeCause(recordInObj, f.Command(cmd, false)); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
if err := o.Recorder.Record(info.Object); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
// Get the modified configuration of the object. Embed the result
// as an annotation in the modified configuration, so that it will appear
// in the patch sent to the server.
modified, err := kubectl.GetModifiedConfiguration(info, true, encoder)
modified, err := kubectl.GetModifiedConfiguration(info.Object, true, unstructured.UnstructuredJSONScheme)
if err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving modified configuration from:\n%v\nfor:", info), info.Source, err)
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving modified configuration from:\n%s\nfor:", info.String()), info.Source, err)
}
// Print object only if output format other than "name" is specified
printObject := len(output) > 0 && !shortOutput
if err := info.Get(); err != nil {
if !errors.IsNotFound(err) {
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err)
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
}
// Create the resource if it doesn't exist
// First, update the annotation used by kubectl apply
if err := kubectl.CreateApplyAnnotation(info, encoder); err != nil {
if err := kubectl.CreateApplyAnnotation(info.Object, unstructured.UnstructuredJSONScheme); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
if !dryRun {
if !o.DryRun {
// Then create the resource and skip the three-way merge
if err := createAndRefresh(info); err != nil {
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
if err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
if uid, err := info.Mapping.UID(info.Object); err != nil {
info.Refresh(obj, true)
metadata, err := meta.Accessor(info.Object)
if err != nil {
return err
} else {
visitedUids.Insert(string(uid))
}
visitedUids.Insert(string(metadata.GetUID()))
}
count++
if len(output) > 0 && !shortOutput {
return cmdutil.PrintObject(cmd, info.Object, out)
}
cmdutil.PrintSuccess(shortOutput, out, info.Object, dryRun, "created")
return nil
}
if !dryRun {
annotationMap, err := info.Mapping.MetadataAccessor.Annotations(info.Object)
if printObject {
objs = append(objs, info.Object)
return nil
}
printer, err := o.ToPrinter("created")
if err != nil {
return err
}
if _, ok := annotationMap[api.LastAppliedConfigAnnotation]; !ok {
fmt.Fprintf(errOut, warningNoLastAppliedConfigAnnotation, options.cmdBaseName)
return printer.PrintObj(info.Object, o.Out)
}
if !o.DryRun {
metadata, err := meta.Accessor(info.Object)
if err != nil {
return err
}
overwrite := cmdutil.GetFlagBool(cmd, "overwrite")
annotationMap := metadata.GetAnnotations()
if _, ok := annotationMap[api.LastAppliedConfigAnnotation]; !ok {
fmt.Fprintf(o.ErrOut, warningNoLastAppliedConfigAnnotation, o.cmdBaseName)
}
helper := resource.NewHelper(info.Client, info.Mapping)
patcher := &patcher{
encoder: encoder,
decoder: deserializer,
mapping: info.Mapping,
helper: helper,
clientFunc: f.UnstructuredClientForMapping,
clientsetFunc: f.ClientSet,
overwrite: overwrite,
dynamicClient: o.DynamicClient,
overwrite: o.Overwrite,
backOff: clockwork.NewRealClock(),
force: options.Force,
cascade: options.Cascade,
timeout: options.Timeout,
gracePeriod: options.GracePeriod,
force: o.DeleteOptions.ForceDeletion,
cascade: o.DeleteOptions.Cascade,
timeout: o.DeleteOptions.Timeout,
gracePeriod: o.DeleteOptions.GracePeriod,
openapiSchema: openapiSchema,
}
patchBytes, patchedObject, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name, errOut)
patchBytes, patchedObject, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name, o.ErrOut)
if err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
}
info.Refresh(patchedObject, true)
if uid, err := info.Mapping.UID(info.Object); err != nil {
return err
} else {
visitedUids.Insert(string(uid))
}
visitedUids.Insert(string(metadata.GetUID()))
if string(patchBytes) == "{}" {
if string(patchBytes) == "{}" && !printObject {
count++
cmdutil.PrintSuccess(shortOutput, out, info.Object, false, "unchanged")
return nil
printer, err := o.ToPrinter("unchanged")
if err != nil {
return err
}
return printer.PrintObj(info.Object, o.Out)
}
}
count++
if len(output) > 0 && !shortOutput {
return cmdutil.PrintObject(cmd, info.Object, out)
}
cmdutil.PrintSuccess(shortOutput, out, info.Object, dryRun, "configured")
return nil
})
if printObject {
objs = append(objs, info.Object)
return nil
}
printer, err := o.ToPrinter("configured")
if err != nil {
return err
}
return printer.PrintObj(info.Object, o.Out)
})
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("no objects passed to apply")
}
if !options.Prune {
// print objects
if len(objs) > 0 {
printer, err := o.ToPrinter("")
if err != nil {
return err
}
objToPrint := objs[0]
if len(objs) > 1 {
list := &v1.List{
TypeMeta: metav1.TypeMeta{
Kind: "List",
APIVersion: "v1",
},
ListMeta: metav1.ListMeta{},
}
if err := meta.SetList(list, objs); err != nil {
return err
}
objToPrint = list
}
if err := printer.PrintObj(objToPrint, o.Out); err != nil {
return err
}
}
if !o.Prune {
return nil
}
p := pruner{
mapper: mapper,
clientFunc: f.UnstructuredClientForMapping,
clientsetFunc: f.ClientSet,
mapper: o.Mapper,
dynamicClient: o.DynamicClient,
labelSelector: options.Selector,
labelSelector: o.Selector,
visitedUids: visitedUids,
cascade: options.Cascade,
dryRun: dryRun,
gracePeriod: options.GracePeriod,
cascade: o.DeleteOptions.Cascade,
dryRun: o.DryRun,
gracePeriod: o.DeleteOptions.GracePeriod,
out: out,
toPrinter: o.ToPrinter,
out: o.Out,
}
namespacedRESTMappings, nonNamespacedRESTMappings, err := getRESTMappings(mapper, &(options.PruneResources))
namespacedRESTMappings, nonNamespacedRESTMappings, err := getRESTMappings(o.Mapper, &(o.PruneResources))
if err != nil {
return fmt.Errorf("error retrieving RESTMappings to prune: %v", err)
}
for n := range visitedNamespaces {
for _, m := range namespacedRESTMappings {
if err := p.prune(f, n, m, shortOutput, includeUninitialized); err != nil {
if err := p.prune(n, m, o.ShouldIncludeUninitialized); err != nil {
return fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err)
}
}
}
for _, m := range nonNamespacedRESTMappings {
if err := p.prune(f, metav1.NamespaceNone, m, shortOutput, includeUninitialized); err != nil {
if err := p.prune(metav1.NamespaceNone, m, o.ShouldIncludeUninitialized); err != nil {
return fmt.Errorf("error pruning nonNamespaced object %v: %v", m.GroupVersionKind, err)
}
}
@ -430,6 +539,7 @@ func getRESTMappings(mapper meta.RESTMapper, pruneResources *[]pruneResource) (n
{"", "v1", "Secret", true},
{"", "v1", "Service", true},
{"batch", "v1", "Job", true},
{"batch", "v1beta1", "CronJob", true},
{"extensions", "v1beta1", "DaemonSet", true},
{"extensions", "v1beta1", "Deployment", true},
{"extensions", "v1beta1", "Ingress", true},
@ -456,8 +566,7 @@ func getRESTMappings(mapper meta.RESTMapper, pruneResources *[]pruneResource) (n
type pruner struct {
mapper meta.RESTMapper
clientFunc resource.ClientMapperFunc
clientsetFunc func() (internalclientset.Interface, error)
dynamicClient dynamic.Interface
visitedUids sets.String
labelSelector string
@ -467,117 +576,83 @@ type pruner struct {
dryRun bool
gracePeriod int
toPrinter func(string) (printers.ResourcePrinter, error)
out io.Writer
}
func (p *pruner) prune(f cmdutil.Factory, namespace string, mapping *meta.RESTMapping, shortOutput, includeUninitialized bool) error {
c, err := p.clientFunc(mapping)
func (p *pruner) prune(namespace string, mapping *meta.RESTMapping, includeUninitialized bool) error {
objList, err := p.dynamicClient.Resource(mapping.Resource).
Namespace(namespace).
List(metav1.ListOptions{
LabelSelector: p.labelSelector,
FieldSelector: p.fieldSelector,
IncludeUninitialized: includeUninitialized,
})
if err != nil {
return err
}
objList, err := resource.NewHelper(c, mapping).List(
namespace,
mapping.GroupVersionKind.Version,
false,
&metav1.ListOptions{
LabelSelector: p.labelSelector,
FieldSelector: p.fieldSelector,
IncludeUninitialized: includeUninitialized,
},
)
if err != nil {
return err
}
objs, err := meta.ExtractList(objList)
if err != nil {
return err
}
for _, obj := range objs {
annots, err := mapping.MetadataAccessor.Annotations(obj)
metadata, err := meta.Accessor(obj)
if err != nil {
return err
}
annots := metadata.GetAnnotations()
if _, ok := annots[api.LastAppliedConfigAnnotation]; !ok {
// don't prune resources not created with apply
continue
}
uid, err := mapping.UID(obj)
if err != nil {
return err
}
uid := metadata.GetUID()
if p.visitedUids.Has(string(uid)) {
continue
}
name, err := mapping.Name(obj)
if err != nil {
return err
}
name := metadata.GetName()
if !p.dryRun {
if err := p.delete(namespace, name, mapping); err != nil {
return err
}
}
cmdutil.PrintSuccess(shortOutput, p.out, obj, p.dryRun, "pruned")
printer, err := p.toPrinter("pruned")
if err != nil {
return err
}
printer.PrintObj(obj, p.out)
}
return nil
}
func (p *pruner) delete(namespace, name string, mapping *meta.RESTMapping) error {
c, err := p.clientFunc(mapping)
if err != nil {
return err
}
return runDelete(namespace, name, mapping, c, nil, p.cascade, p.gracePeriod, p.clientsetFunc)
return runDelete(namespace, name, mapping, p.dynamicClient, p.cascade, p.gracePeriod)
}
func runDelete(namespace, name string, mapping *meta.RESTMapping, c resource.RESTClient, helper *resource.Helper, cascade bool, gracePeriod int, clientsetFunc func() (internalclientset.Interface, error)) error {
if !cascade {
if helper == nil {
helper = resource.NewHelper(c, mapping)
}
return helper.Delete(namespace, name)
}
cs, err := clientsetFunc()
if err != nil {
return err
}
r, err := kubectl.ReaperFor(mapping.GroupVersionKind.GroupKind(), cs)
if err != nil {
if _, ok := err.(*kubectl.NoSuchReaperError); !ok {
return err
}
return resource.NewHelper(c, mapping).Delete(namespace, name)
}
var options *metav1.DeleteOptions
func runDelete(namespace, name string, mapping *meta.RESTMapping, c dynamic.Interface, cascade bool, gracePeriod int) error {
options := &metav1.DeleteOptions{}
if gracePeriod >= 0 {
options = metav1.NewDeleteOptions(int64(gracePeriod))
}
if err := r.Stop(namespace, name, 2*time.Minute, options); err != nil {
return err
policy := metav1.DeletePropagationForeground
if !cascade {
policy = metav1.DeletePropagationOrphan
}
return nil
options.PropagationPolicy = &policy
return c.Resource(mapping.Resource).Namespace(namespace).Delete(name, options)
}
func (p *patcher) delete(namespace, name string) error {
c, err := p.clientFunc(p.mapping)
if err != nil {
return err
}
return runDelete(namespace, name, p.mapping, c, p.helper, p.cascade, p.gracePeriod, p.clientsetFunc)
return runDelete(namespace, name, p.mapping, p.dynamicClient, p.cascade, p.gracePeriod)
}
type patcher struct {
encoder runtime.Encoder
decoder runtime.Decoder
mapping *meta.RESTMapping
helper *resource.Helper
clientFunc resource.ClientMapperFunc
clientsetFunc func() (internalclientset.Interface, error)
dynamicClient dynamic.Interface
overwrite bool
backOff clockwork.Clock
@ -592,13 +667,13 @@ type patcher struct {
func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
// Serialize the current configuration of the object from the server.
current, err := runtime.Encode(p.encoder, obj)
current, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("serializing current configuration from:\n%v\nfor:", obj), source, err)
}
// Retrieve the original configuration of the object from the annotation.
original, err := kubectl.GetOriginalConfiguration(p.mapping, obj)
original, err := kubectl.GetOriginalConfiguration(obj)
if err != nil {
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("retrieving original configuration from:\n%v\nfor:", obj), source, err)
}
@ -685,20 +760,19 @@ func (p *patcher) patch(current runtime.Object, modified []byte, source, namespa
}
func (p *patcher) deleteAndCreate(original runtime.Object, modified []byte, namespace, name string) ([]byte, runtime.Object, error) {
err := p.delete(namespace, name)
if err != nil {
if err := p.delete(namespace, name); err != nil {
return modified, nil, err
}
err = wait.PollImmediate(kubectl.Interval, p.timeout, func() (bool, error) {
// TODO: use wait
if err := wait.PollImmediate(1*time.Second, p.timeout, func() (bool, error) {
if _, err := p.helper.Get(namespace, name, false); !errors.IsNotFound(err) {
return false, err
}
return true, nil
})
if err != nil {
}); err != nil {
return modified, nil, err
}
versionedObject, _, err := p.decoder.Decode(modified, nil, nil)
versionedObject, _, err := unstructured.UnstructuredJSONScheme.Decode(modified, nil, nil)
if err != nil {
return modified, nil, err
}

View File

@ -17,15 +17,12 @@ limitations under the License.
package cmd
import (
"io"
"runtime"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
var (
@ -58,11 +55,8 @@ var (
kubectl apply edit-last-applied -f deploy.yaml -o json`)
)
func NewCmdApplyEditLastApplied(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := &editor.EditOptions{
EditMode: editor.ApplyEditMode,
}
validArgs := cmdutil.ValidArgList(f)
func NewCmdApplyEditLastApplied(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := editor.NewEditOptions(editor.ApplyEditMode, ioStreams)
cmd := &cobra.Command{
Use: "edit-last-applied (RESOURCE/NAME | -f FILENAME)",
@ -71,24 +65,23 @@ func NewCmdApplyEditLastApplied(f cmdutil.Factory, out, errOut io.Writer) *cobra
Long: applyEditLastAppliedLong,
Example: applyEditLastAppliedExample,
Run: func(cmd *cobra.Command, args []string) {
options.ChangeCause = f.Command(cmd, false)
if err := options.Complete(f, out, errOut, args, cmd); err != nil {
if err := o.Complete(f, args, cmd); err != nil {
cmdutil.CheckErr(err)
}
if err := options.Run(); err != nil {
if err := o.Run(); err != nil {
cmdutil.CheckErr(err)
}
},
ValidArgs: validArgs,
ArgAliases: kubectl.ResourceAliases(validArgs),
}
// bind flag structs
o.RecordFlags.AddFlags(cmd)
usage := "to use to edit the resource"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.Flags().StringVarP(&options.Output, "output", "o", "yaml", "Output format. One of: yaml|json.")
cmd.Flags().BoolVar(&options.WindowsLineEndings, "windows-line-endings", runtime.GOOS == "windows",
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "Output format. One of: yaml|json.")
cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings,
"Defaults to the line ending native to your platform.")
cmdutil.AddRecordVarFlag(cmd, &options.Record)
cmdutil.AddIncludeUninitializedFlag(cmd)
return cmd

View File

@ -18,44 +18,44 @@ package cmd
import (
"bytes"
"encoding/json"
"fmt"
"io"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
apijson "k8s.io/apimachinery/pkg/util/json"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
type SetLastAppliedOptions struct {
FilenameOptions resource.FilenameOptions
Selector string
InfoList []*resource.Info
Mapper meta.RESTMapper
Typer runtime.ObjectTyper
Namespace string
EnforceNamespace bool
DryRun bool
ShortOutput bool
CreateAnnotation bool
Output string
PatchBufferList []PatchBuffer
Factory cmdutil.Factory
Out io.Writer
ErrOut io.Writer
PrintFlags *genericclioptions.PrintFlags
PrintObj printers.ResourcePrinterFunc
FilenameOptions resource.FilenameOptions
infoList []*resource.Info
namespace string
enforceNamespace bool
dryRun bool
shortOutput bool
output string
patchBufferList []PatchBuffer
builder *resource.Builder
unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
genericclioptions.IOStreams
}
type PatchBuffer struct {
@ -81,8 +81,15 @@ var (
`))
)
func NewCmdApplySetLastApplied(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
options := &SetLastAppliedOptions{Out: out, ErrOut: err}
func NewSetLastAppliedOptions(ioStreams genericclioptions.IOStreams) *SetLastAppliedOptions {
return &SetLastAppliedOptions{
PrintFlags: genericclioptions.NewPrintFlags("configured").WithTypeSetter(scheme.Scheme),
IOStreams: ioStreams,
}
}
func NewCmdApplySetLastApplied(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewSetLastAppliedOptions(ioStreams)
cmd := &cobra.Command{
Use: "set-last-applied -f FILENAME",
DisableFlagsInUseLine: true,
@ -90,42 +97,55 @@ func NewCmdApplySetLastApplied(f cmdutil.Factory, out, err io.Writer) *cobra.Com
Long: applySetLastAppliedLong,
Example: applySetLastAppliedExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd))
cmdutil.CheckErr(options.Validate(f, cmd))
cmdutil.CheckErr(options.RunSetLastApplied(f, cmd))
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.RunSetLastApplied())
},
}
o.PrintFlags.AddFlags(cmd)
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().BoolVar(&options.CreateAnnotation, "create-annotation", false, "Will create 'last-applied-configuration' annotations if current objects doesn't have one")
usage := "that contains the last-applied-configuration annotations"
kubectl.AddJsonFilenameFlag(cmd, &options.FilenameOptions.Filenames, "Filename, directory, or URL to files "+usage)
cmd.Flags().BoolVar(&o.CreateAnnotation, "create-annotation", o.CreateAnnotation, "Will create 'last-applied-configuration' annotations if current objects doesn't have one")
cmdutil.AddJsonFilenameFlag(cmd.Flags(), &o.FilenameOptions.Filenames, "Filename, directory, or URL to files that contains the last-applied-configuration annotations")
return cmd
}
func (o *SetLastAppliedOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
o.DryRun = cmdutil.GetDryRunFlag(cmd)
o.Output = cmdutil.GetFlagString(cmd, "output")
o.ShortOutput = o.Output == "name"
o.dryRun = cmdutil.GetDryRunFlag(cmd)
o.output = cmdutil.GetFlagString(cmd, "output")
o.shortOutput = o.output == "name"
var err error
o.Mapper, o.Typer = f.Object()
o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
o.builder = f.NewBuilder()
o.unstructuredClientForMapping = f.UnstructuredClientForMapping
o.Namespace, o.EnforceNamespace, err = f.DefaultNamespace()
return err
if o.dryRun {
// TODO(juanvallejo): This can be cleaned up even further by creating
// a PrintFlags struct that binds the --dry-run flag, and whose
// ToPrinter method returns a printer that understands how to print
// this success message.
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = printer.PrintObj
return nil
}
func (o *SetLastAppliedOptions) Validate(f cmdutil.Factory, cmd *cobra.Command) error {
r := f.NewBuilder().
func (o *SetLastAppliedOptions) Validate() error {
r := o.builder.
Unstructured().
NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(o.EnforceNamespace, &o.FilenameOptions).
NamespaceParam(o.namespace).DefaultNamespace().
FilenameParam(o.enforceNamespace, &o.FilenameOptions).
Flatten().
Do()
@ -133,7 +153,7 @@ func (o *SetLastAppliedOptions) Validate(f cmdutil.Factory, cmd *cobra.Command)
if err != nil {
return err
}
patchBuf, diffBuf, patchType, err := editor.GetApplyPatch(info.Object, scheme.DefaultJSONEncoder())
patchBuf, diffBuf, patchType, err := editor.GetApplyPatch(info.Object.(runtime.Unstructured))
if err != nil {
return err
}
@ -143,22 +163,22 @@ func (o *SetLastAppliedOptions) Validate(f cmdutil.Factory, cmd *cobra.Command)
if errors.IsNotFound(err) {
return err
} else {
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err)
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
}
}
originalBuf, err := kubectl.GetOriginalConfiguration(info.Mapping, info.Object)
originalBuf, err := kubectl.GetOriginalConfiguration(info.Object)
if err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err)
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
}
if originalBuf == nil && !o.CreateAnnotation {
return cmdutil.UsageErrorf(cmd, "no last-applied-configuration annotation found on resource: %s, to create the annotation, run the command with --create-annotation", info.Name)
return fmt.Errorf("no last-applied-configuration annotation found on resource: %s, to create the annotation, run the command with --create-annotation", info.Name)
}
//only add to PatchBufferList when changed
if !bytes.Equal(cmdutil.StripComments(originalBuf), cmdutil.StripComments(diffBuf)) {
p := PatchBuffer{Patch: patchBuf, PatchType: patchType}
o.PatchBufferList = append(o.PatchBufferList, p)
o.InfoList = append(o.InfoList, info)
o.patchBufferList = append(o.patchBufferList, p)
o.infoList = append(o.infoList, info)
} else {
fmt.Fprintf(o.Out, "set-last-applied %s: no changes required.\n", info.Name)
}
@ -168,68 +188,26 @@ func (o *SetLastAppliedOptions) Validate(f cmdutil.Factory, cmd *cobra.Command)
return err
}
func (o *SetLastAppliedOptions) RunSetLastApplied(f cmdutil.Factory, cmd *cobra.Command) error {
for i, patch := range o.PatchBufferList {
info := o.InfoList[i]
if !o.DryRun {
func (o *SetLastAppliedOptions) RunSetLastApplied() error {
for i, patch := range o.patchBufferList {
info := o.infoList[i]
finalObj := info.Object
if !o.dryRun {
mapping := info.ResourceMapping()
client, err := f.UnstructuredClientForMapping(mapping)
client, err := o.unstructuredClientForMapping(mapping)
if err != nil {
return err
}
helper := resource.NewHelper(client, mapping)
patchedObj, err := helper.Patch(o.Namespace, info.Name, patch.PatchType, patch.Patch)
finalObj, err = helper.Patch(o.namespace, info.Name, patch.PatchType, patch.Patch)
if err != nil {
return err
}
if len(o.Output) > 0 && !o.ShortOutput {
info.Refresh(patchedObj, false)
return cmdutil.PrintObject(cmd, info.Object, o.Out)
}
cmdutil.PrintSuccess(o.ShortOutput, o.Out, info.Object, o.DryRun, "configured")
} else {
err := o.formatPrinter(o.Output, patch.Patch, o.Out)
if err != nil {
return err
}
cmdutil.PrintSuccess(o.ShortOutput, o.Out, info.Object, o.DryRun, "configured")
}
}
return nil
}
func (o *SetLastAppliedOptions) formatPrinter(output string, buf []byte, w io.Writer) error {
yamlOutput, err := yaml.JSONToYAML(buf)
if err != nil {
return err
}
switch output {
case "json":
jsonBuffer := &bytes.Buffer{}
err = json.Indent(jsonBuffer, buf, "", " ")
if err != nil {
if err := o.PrintObj(finalObj, o.Out); err != nil {
return err
}
fmt.Fprintf(w, "%s\n", jsonBuffer.String())
case "yaml":
fmt.Fprintf(w, "%s\n", string(yamlOutput))
}
return nil
}
func (o *SetLastAppliedOptions) getPatch(info *resource.Info) ([]byte, []byte, error) {
objMap := map[string]map[string]map[string]string{}
metadataMap := map[string]map[string]string{}
annotationsMap := map[string]string{}
localFile, err := runtime.Encode(scheme.DefaultJSONEncoder(), info.Object)
if err != nil {
return nil, localFile, err
}
annotationsMap[api.LastAppliedConfigAnnotation] = string(localFile)
metadataMap["annotations"] = annotationsMap
objMap["metadata"] = metadataMap
jsonString, err := apijson.Marshal(objMap)
return jsonString, localFile, err
}

File diff suppressed because it is too large Load Diff

View File

@ -20,14 +20,14 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -38,8 +38,8 @@ type ViewLastAppliedOptions struct {
OutputFormat string
All bool
Factory cmdutil.Factory
Out io.Writer
ErrOut io.Writer
genericclioptions.IOStreams
}
var (
@ -57,8 +57,17 @@ var (
kubectl apply view-last-applied -f deploy.yaml -o json`))
)
func NewCmdApplyViewLastApplied(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
options := &ViewLastAppliedOptions{Out: out, ErrOut: err}
func NewViewLastAppliedOptions(ioStreams genericclioptions.IOStreams) *ViewLastAppliedOptions {
return &ViewLastAppliedOptions{
OutputFormat: "yaml",
IOStreams: ioStreams,
}
}
func NewCmdApplyViewLastApplied(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := NewViewLastAppliedOptions(ioStreams)
cmd := &cobra.Command{
Use: "view-last-applied (TYPE [NAME | -l label] | TYPE/NAME | -f FILENAME)",
DisableFlagsInUseLine: true,
@ -66,24 +75,23 @@ func NewCmdApplyViewLastApplied(f cmdutil.Factory, out, err io.Writer) *cobra.Co
Long: applyViewLastAppliedLong,
Example: applyViewLastAppliedExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.ValidateOutputArgs(cmd))
cmdutil.CheckErr(options.Complete(f, args))
cmdutil.CheckErr(options.Complete(cmd, f, args))
cmdutil.CheckErr(options.Validate(cmd))
cmdutil.CheckErr(options.RunApplyViewLastApplied())
cmdutil.CheckErr(options.RunApplyViewLastApplied(cmd))
},
}
cmd.Flags().StringP("output", "o", "", "Output format. Must be one of yaml|json")
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVar(&options.All, "all", false, "Select all resources in the namespace of the specified resource types")
cmd.Flags().StringVarP(&options.OutputFormat, "output", "o", options.OutputFormat, "Output format. Must be one of yaml|json")
cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVar(&options.All, "all", options.All, "Select all resources in the namespace of the specified resource types")
usage := "that contains the last-applied-configuration annotations"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
return cmd
}
func (o *ViewLastAppliedOptions) Complete(f cmdutil.Factory, args []string) error {
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
func (o *ViewLastAppliedOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args []string) error {
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
@ -108,7 +116,7 @@ func (o *ViewLastAppliedOptions) Complete(f cmdutil.Factory, args []string) erro
return err
}
configString, err := kubectl.GetOriginalConfiguration(info.Mapping, info.Object)
configString, err := kubectl.GetOriginalConfiguration(info.Object)
if err != nil {
return err
}
@ -130,7 +138,7 @@ func (o *ViewLastAppliedOptions) Validate(cmd *cobra.Command) error {
return nil
}
func (o *ViewLastAppliedOptions) RunApplyViewLastApplied() error {
func (o *ViewLastAppliedOptions) RunApplyViewLastApplied(cmd *cobra.Command) error {
for _, str := range o.LastAppliedConfigurationList {
switch o.OutputFormat {
case "json":
@ -146,23 +154,13 @@ func (o *ViewLastAppliedOptions) RunApplyViewLastApplied() error {
return err
}
fmt.Fprint(o.Out, string(yamlOutput))
default:
return cmdutil.UsageErrorf(
cmd,
"Unexpected -o output mode: %s, the flag 'output' must be one of yaml|json",
o.OutputFormat)
}
}
return nil
}
func (o *ViewLastAppliedOptions) ValidateOutputArgs(cmd *cobra.Command) error {
format := cmdutil.GetFlagString(cmd, "output")
switch format {
case "json":
o.OutputFormat = "json"
return nil
// If flag -o is not specified, use yaml as default
case "yaml", "":
o.OutputFormat = "yaml"
return nil
default:
return cmdutil.UsageErrorf(cmd, "Unexpected -o output mode: %s, the flag 'output' must be one of yaml|json", format)
}
}

View File

@ -35,6 +35,8 @@ import (
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -60,12 +62,10 @@ const (
defaultPodLogsTimeout = 20 * time.Second
)
func NewCmdAttach(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command {
options := &AttachOptions{
func NewCmdAttach(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := &AttachOptions{
StreamOptions: StreamOptions{
In: cmdIn,
Out: cmdOut,
Err: cmdErr,
IOStreams: streams,
},
Attach: &DefaultRemoteAttach{},
@ -77,15 +77,15 @@ func NewCmdAttach(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer)
Long: "Attach to a process that is already running inside an existing container.",
Example: attachExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.Run())
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
}
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodAttachTimeout)
cmd.Flags().StringVarP(&options.ContainerName, "container", "c", "", "Container name. If omitted, the first container in the pod will be chosen")
cmd.Flags().BoolVarP(&options.Stdin, "stdin", "i", false, "Pass stdin to the container")
cmd.Flags().BoolVarP(&options.TTY, "tty", "t", false, "Stdin is a TTY")
cmd.Flags().StringVarP(&o.ContainerName, "container", "c", o.ContainerName, "Container name. If omitted, the first container in the pod will be chosen")
cmd.Flags().BoolVarP(&o.Stdin, "stdin", "i", o.Stdin, "Pass stdin to the container")
cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, "Stdin is a TTY")
return cmd
}
@ -135,7 +135,7 @@ func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn [
return cmdutil.UsageErrorf(cmd, "expected POD, TYPE/NAME, or TYPE NAME, (at most 2 arguments) saw %d: %v", len(argsIn), argsIn)
}
namespace, _, err := f.DefaultNamespace()
namespace, _, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
@ -146,7 +146,7 @@ func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn [
}
builder := f.NewBuilder().
Internal().
WithScheme(legacyscheme.Scheme).
NamespaceParam(namespace).DefaultNamespace()
switch len(argsIn) {
@ -161,7 +161,7 @@ func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn [
return err
}
attachablePod, err := f.AttachablePodForObject(obj, p.GetPodTimeout)
attachablePod, err := polymorphichelpers.AttachablePodForObjectFn(f, obj, p.GetPodTimeout)
if err != nil {
return err
}
@ -178,7 +178,7 @@ func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn [
p.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe pod/%s -n %s' to see all of the containers in this pod.", fullCmdName, p.PodName, p.Namespace)
}
config, err := f.ClientConfig()
config, err := f.ToRESTConfig()
if err != nil {
return err
}
@ -188,6 +188,7 @@ func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn [
if err != nil {
return err
}
p.PodClient = clientset.Core()
if p.CommandName == "" {
@ -203,7 +204,7 @@ func (p *AttachOptions) Validate() error {
if len(p.PodName) == 0 {
allErrs = append(allErrs, errors.New("pod name must be specified"))
}
if p.Out == nil || p.Err == nil {
if p.Out == nil || p.ErrOut == nil {
allErrs = append(allErrs, errors.New("both output and error output must be provided"))
}
if p.Attach == nil || p.PodClient == nil || p.Config == nil {
@ -236,8 +237,8 @@ func (p *AttachOptions) Run() error {
}
if p.TTY && !containerToAttach.TTY {
p.TTY = false
if p.Err != nil {
fmt.Fprintf(p.Err, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name)
if p.ErrOut != nil {
fmt.Fprintf(p.ErrOut, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name)
}
} else if !p.TTY && containerToAttach.TTY {
// the container was launched with a TTY, so we have to force a TTY here, otherwise you'll get
@ -249,7 +250,7 @@ func (p *AttachOptions) Run() error {
t := p.setupTTY()
// save p.Err so we can print the command prompt message below
stderr := p.Err
stderr := p.ErrOut
var sizeQueue remotecommand.TerminalSizeQueue
if t.Raw {
@ -266,7 +267,7 @@ func (p *AttachOptions) Run() error {
// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
// true
p.Err = nil
p.ErrOut = nil
}
fn := func() error {
@ -284,11 +285,11 @@ func (p *AttachOptions) Run() error {
Container: containerToAttach.Name,
Stdin: p.Stdin,
Stdout: p.Out != nil,
Stderr: p.Err != nil,
Stderr: p.ErrOut != nil,
TTY: t.Raw,
}, legacyscheme.ParameterCodec)
return p.Attach.Attach("POST", req.URL(), p.Config, p.In, p.Out, p.Err, t.Raw, sizeQueue)
return p.Attach.Attach("POST", req.URL(), p.Config, p.In, p.Out, p.ErrOut, t.Raw, sizeQueue)
}
if !p.Quiet && stderr != nil {
@ -322,8 +323,8 @@ func (p *AttachOptions) containerToAttachTo(pod *api.Pod) (*api.Container, error
}
if len(p.SuggestedCmdUsage) > 0 {
fmt.Fprintf(p.Err, "Defaulting container name to %s.\n", pod.Spec.Containers[0].Name)
fmt.Fprintf(p.Err, "%s\n", p.SuggestedCmdUsage)
fmt.Fprintf(p.ErrOut, "Defaulting container name to %s.\n", pod.Spec.Containers[0].Name)
fmt.Fprintf(p.ErrOut, "%s\n", p.SuggestedCmdUsage)
}
glog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name)

View File

@ -17,7 +17,6 @@ limitations under the License.
package cmd
import (
"bytes"
"fmt"
"io"
"net/http"
@ -38,6 +37,7 @@ import (
api "k8s.io/kubernetes/pkg/apis/core"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@ -139,48 +139,51 @@ func TestPodAndContainerAttach(t *testing.T) {
}
for _, test := range tests {
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
ns := legacyscheme.Codecs
t.Run(test.name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion,
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
if test.obj != nil {
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, test.obj)}, nil
}
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = defaultClientConfig()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
cmd := &cobra.Command{}
options := test.p
cmdutil.AddPodRunningTimeoutFlag(cmd, test.timeout)
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
if test.obj != nil {
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, test.obj)}, nil
}
return nil, nil
}),
}
tf.ClientConfigVal = defaultClientConfig()
err := options.Complete(tf, cmd, test.args)
if test.expectError && err == nil {
t.Errorf("%s: unexpected non-error", test.name)
}
if !test.expectError && err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
}
if err != nil {
continue
}
if options.PodName != test.expectedPod {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedPod, options.PodName)
}
if options.ContainerName != test.expectedContainer {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedContainer, options.ContainerName)
}
cmd := &cobra.Command{}
options := test.p
cmdutil.AddPodRunningTimeoutFlag(cmd, test.timeout)
err := options.Complete(tf, cmd, test.args)
if test.expectError && err == nil {
t.Errorf("%s: unexpected non-error", test.name)
}
if !test.expectError && err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
}
if err != nil {
return
}
if options.PodName != test.expectedPod {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedPod, options.PodName)
}
if options.ContainerName != test.expectedContainer {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedContainer, options.ContainerName)
}
})
}
}
func TestAttach(t *testing.T) {
version := legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version
version := "v1"
tests := []struct {
name, version, podPath, fetchPodPath, attachPath, container string
pod *api.Pod
@ -219,79 +222,76 @@ func TestAttach(t *testing.T) {
},
}
for _, test := range tests {
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
ns := legacyscheme.Codecs
t.Run(test.name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion,
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
case p == test.fetchPodPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
// Ensures no GET is performed when deleting by name
t.Errorf("%s: unexpected request: %s %#v\n%#v", p, req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
bufOut := bytes.NewBuffer([]byte{})
bufErr := bytes.NewBuffer([]byte{})
bufIn := bytes.NewBuffer([]byte{})
remoteAttach := &fakeRemoteAttach{}
if test.remoteAttachErr {
remoteAttach.err = fmt.Errorf("attach error")
}
params := &AttachOptions{
StreamOptions: StreamOptions{
ContainerName: test.container,
In: bufIn,
Out: bufOut,
Err: bufErr,
},
Attach: remoteAttach,
GetPodTimeout: 1000,
}
cmd := &cobra.Command{}
cmdutil.AddPodRunningTimeoutFlag(cmd, 1000)
if err := params.Complete(tf, cmd, []string{"foo"}); err != nil {
t.Fatal(err)
}
err := params.Run()
if test.exepctedErr != "" && err.Error() != test.exepctedErr {
t.Errorf("%s: Unexpected exec error: %v", test.name, err)
continue
}
if test.exepctedErr == "" && err != nil {
t.Errorf("%s: Unexpected error: %v", test.name, err)
continue
}
if test.exepctedErr != "" {
continue
}
if remoteAttach.url.Path != test.attachPath {
t.Errorf("%s: Did not get expected path for exec request: %q %q", test.name, test.attachPath, remoteAttach.url.Path)
continue
}
if remoteAttach.method != "POST" {
t.Errorf("%s: Did not get method for attach request: %s", test.name, remoteAttach.method)
}
if remoteAttach.url.Query().Get("container") != "bar" {
t.Errorf("%s: Did not have query parameters: %s", test.name, remoteAttach.url.Query())
}
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
case p == test.fetchPodPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", p, req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
}),
}
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
remoteAttach := &fakeRemoteAttach{}
if test.remoteAttachErr {
remoteAttach.err = fmt.Errorf("attach error")
}
params := &AttachOptions{
StreamOptions: StreamOptions{
ContainerName: test.container,
IOStreams: genericclioptions.NewTestIOStreamsDiscard(),
},
Attach: remoteAttach,
GetPodTimeout: 1000,
}
cmd := &cobra.Command{}
cmdutil.AddPodRunningTimeoutFlag(cmd, 1000)
if err := params.Complete(tf, cmd, []string{"foo"}); err != nil {
t.Fatal(err)
}
err := params.Run()
if test.exepctedErr != "" && err.Error() != test.exepctedErr {
t.Errorf("%s: Unexpected exec error: %v", test.name, err)
return
}
if test.exepctedErr == "" && err != nil {
t.Errorf("%s: Unexpected error: %v", test.name, err)
return
}
if test.exepctedErr != "" {
return
}
if remoteAttach.url.Path != test.attachPath {
t.Errorf("%s: Did not get expected path for exec request: %q %q", test.name, test.attachPath, remoteAttach.url.Path)
return
}
if remoteAttach.method != "POST" {
t.Errorf("%s: Did not get method for attach request: %s", test.name, remoteAttach.method)
}
if remoteAttach.url.Query().Get("container") != "bar" {
t.Errorf("%s: Did not have query parameters: %s", test.name, remoteAttach.url.Query())
}
})
}
}
func TestAttachWarnings(t *testing.T) {
version := legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version
version := "v1"
tests := []struct {
name, container, version, podPath, fetchPodPath, expectedErr string
pod *api.Pod
@ -309,62 +309,61 @@ func TestAttachWarnings(t *testing.T) {
},
}
for _, test := range tests {
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
ns := legacyscheme.Codecs
t.Run(test.name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion,
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
case p == test.fetchPodPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
bufOut := bytes.NewBuffer([]byte{})
bufErr := bytes.NewBuffer([]byte{})
bufIn := bytes.NewBuffer([]byte{})
ex := &fakeRemoteAttach{}
params := &AttachOptions{
StreamOptions: StreamOptions{
ContainerName: test.container,
In: bufIn,
Out: bufOut,
Err: bufErr,
Stdin: test.stdin,
TTY: test.tty,
},
Attach: ex,
GetPodTimeout: 1000,
}
cmd := &cobra.Command{}
cmdutil.AddPodRunningTimeoutFlag(cmd, 1000)
if err := params.Complete(tf, cmd, []string{"foo"}); err != nil {
t.Fatal(err)
}
if err := params.Run(); err != nil {
t.Fatal(err)
}
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
if test.stdin && test.tty {
if !test.pod.Spec.Containers[0].TTY {
if !strings.Contains(bufErr.String(), test.expectedErr) {
t.Errorf("%s: Expected TTY fallback warning for attach request: %s", test.name, bufErr.String())
continue
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
case p == test.fetchPodPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
}),
}
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
streams, _, _, bufErr := genericclioptions.NewTestIOStreams()
ex := &fakeRemoteAttach{}
params := &AttachOptions{
StreamOptions: StreamOptions{
ContainerName: test.container,
IOStreams: streams,
Stdin: test.stdin,
TTY: test.tty,
},
Attach: ex,
GetPodTimeout: 1000,
}
cmd := &cobra.Command{}
cmdutil.AddPodRunningTimeoutFlag(cmd, 1000)
if err := params.Complete(tf, cmd, []string{"foo"}); err != nil {
t.Fatal(err)
}
if err := params.Run(); err != nil {
t.Fatal(err)
}
if test.stdin && test.tty {
if !test.pod.Spec.Containers[0].TTY {
if !strings.Contains(bufErr.String(), test.expectedErr) {
t.Errorf("%s: Expected TTY fallback warning for attach request: %s", test.name, bufErr.String())
return
}
}
}
}
})
}
}

View File

@ -17,18 +17,21 @@ go_library(
],
deps = [
"//pkg/apis/authorization:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library",
"//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//pkg/registry/rbac/reconciliation:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
],
)

View File

@ -17,24 +17,23 @@ limitations under the License.
package auth
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
func NewCmdAuth(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
func NewCmdAuth(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
// Parent command to which all subcommands are added.
cmds := &cobra.Command{
Use: "auth",
Short: "Inspect authorization",
Long: `Inspect authorization`,
Run: cmdutil.DefaultSubCommandRun(errOut),
Run: cmdutil.DefaultSubCommandRun(streams.ErrOut),
}
cmds.AddCommand(NewCmdCanI(f, out, errOut))
cmds.AddCommand(NewCmdReconcile(f, out, errOut))
cmds.AddCommand(NewCmdCanI(f, streams))
cmds.AddCommand(NewCmdReconcile(f, streams))
return cmds
}

View File

@ -19,12 +19,12 @@ package auth
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -48,8 +48,7 @@ type CanIOptions struct {
Subresource string
ResourceName string
Out io.Writer
Err io.Writer
genericclioptions.IOStreams
}
var (
@ -81,10 +80,9 @@ var (
kubectl auth can-i get /logs/`)
)
func NewCmdCanI(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
func NewCmdCanI(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := &CanIOptions{
Out: out,
Err: err,
IOStreams: streams,
}
cmd := &cobra.Command{
@ -99,7 +97,7 @@ func NewCmdCanI(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
allowed, err := o.RunAccessCheck()
if err == nil {
if o.Quiet && !allowed {
if !allowed {
os.Exit(1)
}
}
@ -110,7 +108,7 @@ func NewCmdCanI(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", o.AllNamespaces, "If true, check the specified action in all namespaces.")
cmd.Flags().BoolVarP(&o.Quiet, "quiet", "q", o.Quiet, "If true, suppress output and just return the exit code.")
cmd.Flags().StringVar(&o.Subresource, "subresource", "", "SubResource such as pod/log or deployment/scale")
cmd.Flags().StringVar(&o.Subresource, "subresource", o.Subresource, "SubResource such as pod/log or deployment/scale")
return cmd
}
@ -127,7 +125,10 @@ func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
break
}
resourceTokens := strings.SplitN(args[1], "/", 2)
restMapper, _ := f.Object()
restMapper, err := f.ToRESTMapper()
if err != nil {
return err
}
o.Resource = o.resourceFor(restMapper, resourceTokens[0])
if len(resourceTokens) > 1 {
o.ResourceName = resourceTokens[1]
@ -145,7 +146,7 @@ func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
o.Namespace = ""
if !o.AllNamespaces {
o.Namespace, _, err = f.DefaultNamespace()
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
@ -229,9 +230,9 @@ func (o *CanIOptions) resourceFor(mapper meta.RESTMapper, resourceArg string) sc
gvr, err = mapper.ResourceFor(groupResource.WithVersion(""))
if err != nil {
if len(groupResource.Group) == 0 {
fmt.Fprintf(o.Err, "Warning: the server doesn't have a resource type '%s'\n", groupResource.Resource)
fmt.Fprintf(o.ErrOut, "Warning: the server doesn't have a resource type '%s'\n", groupResource.Resource)
} else {
fmt.Fprintf(o.Err, "Warning: the server doesn't have a resource type '%s' in group '%s'\n", groupResource.Resource, groupResource.Group)
fmt.Fprintf(o.ErrOut, "Warning: the server doesn't have a resource type '%s' in group '%s'\n", groupResource.Resource, groupResource.Group)
}
return schema.GroupVersionResource{Resource: resourceArg}
}

View File

@ -117,64 +117,67 @@ func TestRunAccessCheck(t *testing.T) {
}
for _, test := range tests {
test.o.Out = ioutil.Discard
test.o.Err = ioutil.Discard
t.Run(test.name, func(t *testing.T) {
test.o.Out = ioutil.Discard
test.o.ErrOut = ioutil.Discard
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
expectPath := "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews"
if req.URL.Path != expectPath {
t.Errorf("%s: expected %v, got %v", test.name, expectPath, req.URL.Path)
return nil, nil
}
bodyBits, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Errorf("%s: %v", test.name, err)
return nil, nil
}
body := string(bodyBits)
ns := legacyscheme.Codecs
for _, expectedBody := range test.expectedBodyStrings {
if !strings.Contains(body, expectedBody) {
t.Errorf("%s expecting %s in %s", test.name, expectedBody, body)
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
expectPath := "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews"
if req.URL.Path != expectPath {
t.Errorf("%s: expected %v, got %v", test.name, expectPath, req.URL.Path)
return nil, nil
}
}
bodyBits, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Errorf("%s: %v", test.name, err)
return nil, nil
}
body := string(bodyBits)
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewBufferString(
fmt.Sprintf(`{"kind":"SelfSubjectAccessReview","apiVersion":"authorization.k8s.io/v1","status":{"allowed":%v}}`, test.allowed),
)),
},
test.serverErr
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}}
for _, expectedBody := range test.expectedBodyStrings {
if !strings.Contains(body, expectedBody) {
t.Errorf("%s expecting %s in %s", test.name, expectedBody, body)
}
}
if err := test.o.Complete(tf, test.args); err != nil {
t.Errorf("%s: %v", test.name, err)
continue
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewBufferString(
fmt.Sprintf(`{"kind":"SelfSubjectAccessReview","apiVersion":"authorization.k8s.io/v1","status":{"allowed":%v}}`, test.allowed),
)),
},
test.serverErr
}),
}
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}}
actualAllowed, err := test.o.RunAccessCheck()
switch {
case test.serverErr == nil && err == nil:
// pass
case err != nil && test.serverErr != nil && strings.Contains(err.Error(), test.serverErr.Error()):
// pass
default:
t.Errorf("%s: expected %v, got %v", test.name, test.serverErr, err)
continue
}
if actualAllowed != test.allowed {
t.Errorf("%s: expected %v, got %v", test.name, test.allowed, actualAllowed)
continue
}
if err := test.o.Complete(tf, test.args); err != nil {
t.Errorf("%s: %v", test.name, err)
return
}
actualAllowed, err := test.o.RunAccessCheck()
switch {
case test.serverErr == nil && err == nil:
// pass
case err != nil && test.serverErr != nil && strings.Contains(err.Error(), test.serverErr.Error()):
// pass
default:
t.Errorf("%s: expected %v, got %v", test.name, test.serverErr, err)
return
}
if actualAllowed != test.allowed {
t.Errorf("%s: expected %v, got %v", test.name, test.allowed, actualAllowed)
return
}
})
}
}

View File

@ -18,31 +18,36 @@ package auth
import (
"errors"
"io"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/apis/rbac"
internalcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
internalrbacclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
rbacv1 "k8s.io/api/rbac/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
rbacv1client "k8s.io/client-go/kubernetes/typed/rbac/v1"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/registry/rbac/reconciliation"
)
// ReconcileOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
// referencing the cmd.Flags()
type ReconcileOptions struct {
PrintFlags *genericclioptions.PrintFlags
FilenameOptions *resource.FilenameOptions
DryRun bool
Visitor resource.Visitor
RBACClient internalrbacclient.RbacInterface
NamespaceClient internalcoreclient.NamespaceInterface
RBACClient rbacv1client.RbacV1Interface
NamespaceClient corev1client.CoreV1Interface
Print func(*resource.Info) error
PrintObject printers.ResourcePrinterFunc
Out io.Writer
Err io.Writer
genericclioptions.IOStreams
}
var (
@ -56,12 +61,16 @@ var (
kubectl auth reconcile -f my-rbac-rules.yaml`)
)
func NewCmdReconcile(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
fileOptions := &resource.FilenameOptions{}
o := &ReconcileOptions{
Out: out,
Err: err,
func NewReconcileOptions(ioStreams genericclioptions.IOStreams) *ReconcileOptions {
return &ReconcileOptions{
FilenameOptions: &resource.FilenameOptions{},
PrintFlags: genericclioptions.NewPrintFlags("reconciled").WithTypeSetter(scheme.Scheme),
IOStreams: ioStreams,
}
}
func NewCmdReconcile(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewReconcileOptions(streams)
cmd := &cobra.Command{
Use: "reconcile -f FILENAME",
@ -70,35 +79,36 @@ func NewCmdReconcile(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
Long: reconcileLong,
Example: reconcileExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(cmd, f, args, fileOptions))
cmdutil.CheckErr(o.Complete(cmd, f, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.RunReconcile())
},
}
cmdutil.AddPrinterFlags(cmd)
usage := "identifying the resource to reconcile."
cmdutil.AddFilenameOptionFlags(cmd, fileOptions, usage)
o.PrintFlags.AddFlags(cmd)
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to reconcile.")
cmd.Flags().BoolVar(&o.DryRun, "dry-run", o.DryRun, "If true, display results but do not submit changes")
cmd.MarkFlagRequired("filename")
return cmd
}
func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args []string, options *resource.FilenameOptions) error {
func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args []string) error {
if len(args) > 0 {
return errors.New("no arguments are allowed")
}
namespace, enforceNamespace, err := f.DefaultNamespace()
namespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
r := f.NewBuilder().
Internal().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
FilenameParam(enforceNamespace, o.FilenameOptions).
Flatten().
Do()
@ -107,24 +117,28 @@ func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args
}
o.Visitor = r
client, err := f.ClientSet()
clientConfig, err := f.ToRESTConfig()
if err != nil {
return err
}
o.RBACClient = client.Rbac()
o.NamespaceClient = client.Core().Namespaces()
dryRun := false
output := cmdutil.GetFlagString(cmd, "output")
shortOutput := output == "name"
o.Print = func(info *resource.Info) error {
if len(output) > 0 && !shortOutput {
return cmdutil.PrintObject(cmd, info.Object, o.Out)
}
cmdutil.PrintSuccess(shortOutput, o.Out, info.Object, dryRun, "reconciled")
return nil
o.RBACClient, err = rbacv1client.NewForConfig(clientConfig)
if err != nil {
return err
}
o.NamespaceClient, err = corev1client.NewForConfig(clientConfig)
if err != nil {
return err
}
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObject = printer.PrintObj
return nil
}
@ -138,13 +152,13 @@ func (o *ReconcileOptions) Validate() error {
if o.NamespaceClient == nil {
return errors.New("ReconcileOptions.NamespaceClient must be set")
}
if o.Print == nil {
if o.PrintObject == nil {
return errors.New("ReconcileOptions.Print must be set")
}
if o.Out == nil {
return errors.New("ReconcileOptions.Out must be set")
}
if o.Err == nil {
if o.ErrOut == nil {
return errors.New("ReconcileOptions.Err must be set")
}
return nil
@ -156,18 +170,14 @@ func (o *ReconcileOptions) RunReconcile() error {
return err
}
// shallowInfoCopy this is used to later twiddle the Object for printing
// we really need more straightforward printing options
shallowInfoCopy := *info
switch t := info.Object.(type) {
case *rbac.Role:
case *rbacv1.Role:
reconcileOptions := reconciliation.ReconcileRoleOptions{
Confirm: true,
Confirm: !o.DryRun,
RemoveExtraPermissions: false,
Role: reconciliation.RoleRuleOwner{Role: t},
Client: reconciliation.RoleModifier{
NamespaceClient: o.NamespaceClient,
NamespaceClient: o.NamespaceClient.Namespaces(),
Client: o.RBACClient,
},
}
@ -175,12 +185,11 @@ func (o *ReconcileOptions) RunReconcile() error {
if err != nil {
return err
}
shallowInfoCopy.Object = result.Role.GetObject()
o.Print(&shallowInfoCopy)
o.PrintObject(result.Role.GetObject(), o.Out)
case *rbac.ClusterRole:
case *rbacv1.ClusterRole:
reconcileOptions := reconciliation.ReconcileRoleOptions{
Confirm: true,
Confirm: !o.DryRun,
RemoveExtraPermissions: false,
Role: reconciliation.ClusterRoleRuleOwner{ClusterRole: t},
Client: reconciliation.ClusterRoleModifier{
@ -191,29 +200,27 @@ func (o *ReconcileOptions) RunReconcile() error {
if err != nil {
return err
}
shallowInfoCopy.Object = result.Role.GetObject()
o.Print(&shallowInfoCopy)
o.PrintObject(result.Role.GetObject(), o.Out)
case *rbac.RoleBinding:
case *rbacv1.RoleBinding:
reconcileOptions := reconciliation.ReconcileRoleBindingOptions{
Confirm: true,
Confirm: !o.DryRun,
RemoveExtraSubjects: false,
RoleBinding: reconciliation.RoleBindingAdapter{RoleBinding: t},
Client: reconciliation.RoleBindingClientAdapter{
Client: o.RBACClient,
NamespaceClient: o.NamespaceClient,
NamespaceClient: o.NamespaceClient.Namespaces(),
},
}
result, err := reconcileOptions.Run()
if err != nil {
return err
}
shallowInfoCopy.Object = result.RoleBinding.GetObject()
o.Print(&shallowInfoCopy)
o.PrintObject(result.RoleBinding.GetObject(), o.Out)
case *rbac.ClusterRoleBinding:
case *rbacv1.ClusterRoleBinding:
reconcileOptions := reconciliation.ReconcileRoleBindingOptions{
Confirm: true,
Confirm: !o.DryRun,
RemoveExtraSubjects: false,
RoleBinding: reconciliation.ClusterRoleBindingAdapter{ClusterRoleBinding: t},
Client: reconciliation.ClusterRoleBindingClientAdapter{
@ -224,8 +231,7 @@ func (o *ReconcileOptions) RunReconcile() error {
if err != nil {
return err
}
shallowInfoCopy.Object = result.RoleBinding.GetObject()
o.Print(&shallowInfoCopy)
o.PrintObject(result.RoleBinding.GetObject(), o.Out)
default:
glog.V(1).Infof("skipping %#v", info.Object.GetObjectKind())

View File

@ -18,16 +18,23 @@ package cmd
import (
"fmt"
"io"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"github.com/golang/glog"
"github.com/spf13/cobra"
autoscalingv1 "k8s.io/api/autoscaling/v1"
"k8s.io/apimachinery/pkg/api/meta"
autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"github.com/spf13/cobra"
)
var (
@ -45,11 +52,50 @@ var (
kubectl autoscale rc foo --max=5 --cpu-percent=80`))
)
func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}
type AutoscaleOptions struct {
FilenameOptions *resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags
Recorder genericclioptions.Recorder
PrintFlags *genericclioptions.PrintFlags
ToPrinter func(string) (printers.ResourcePrinter, error)
Name string
Generator string
Min int32
Max int32
CpuPercent int32
createAnnotation bool
args []string
enforceNamespace bool
namespace string
dryRun bool
builder *resource.Builder
canBeAutoscaled polymorphichelpers.CanBeAutoscaledFunc
generatorFunc func(string, *meta.RESTMapping) (kubectl.StructuredGenerator, error)
HPAClient autoscalingv1client.HorizontalPodAutoscalersGetter
genericclioptions.IOStreams
}
func NewAutoscaleOptions(ioStreams genericclioptions.IOStreams) *AutoscaleOptions {
return &AutoscaleOptions{
PrintFlags: genericclioptions.NewPrintFlags("autoscaled").WithTypeSetter(scheme.Scheme),
FilenameOptions: &resource.FilenameOptions{},
RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{},
IOStreams: ioStreams,
}
}
func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewAutoscaleOptions(ioStreams)
validArgs := []string{"deployment", "replicaset", "replicationcontroller"}
argAliases := kubectl.ResourceAliases(validArgs)
cmd := &cobra.Command{
Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]",
@ -58,78 +104,122 @@ func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command {
Long: autoscaleLong,
Example: autoscaleExample,
Run: func(cmd *cobra.Command, args []string) {
err := RunAutoscale(f, out, cmd, args, options)
cmdutil.CheckErr(err)
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
ValidArgs: validArgs,
ArgAliases: argAliases,
ValidArgs: validArgs,
}
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().String("generator", cmdutil.HorizontalPodAutoscalerV1GeneratorName, i18n.T("The name of the API generator to use. Currently there is only 1 generator."))
cmd.Flags().Int32("min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.")
cmd.Flags().Int32("max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
// bind flag structs
o.RecordFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
cmd.Flags().StringVar(&o.Generator, "generator", cmdutil.HorizontalPodAutoscalerV1GeneratorName, i18n.T("The name of the API generator to use. Currently there is only 1 generator."))
cmd.Flags().Int32Var(&o.Min, "min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.")
cmd.Flags().Int32Var(&o.Max, "max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
cmd.MarkFlagRequired("max")
cmd.Flags().Int32("cpu-percent", -1, fmt.Sprintf("The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used."))
cmd.Flags().String("name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
cmd.Flags().Int32Var(&o.CpuPercent, "cpu-percent", -1, fmt.Sprintf("The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used."))
cmd.Flags().StringVar(&o.Name, "name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
cmdutil.AddDryRunFlag(cmd)
usage := "identifying the resource to autoscale."
cmdutil.AddFilenameOptionFlags(cmd, options, usage)
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.")
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
func RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error {
namespace, enforceNamespace, err := f.DefaultNamespace()
func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.dryRun = cmdutil.GetFlagBool(cmd, "dry-run")
o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
o.builder = f.NewBuilder()
o.canBeAutoscaled = polymorphichelpers.CanBeAutoscaledFn
o.args = args
o.RecordFlags.Complete(cmd)
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
// validate flags
if err := validateFlags(cmd); err != nil {
kubeClient, err := f.KubernetesClientSet()
if err != nil {
return err
}
o.HPAClient = kubeClient.AutoscalingV1()
// get the generator
o.generatorFunc = func(name string, mapping *meta.RESTMapping) (kubectl.StructuredGenerator, error) {
switch o.Generator {
case cmdutil.HorizontalPodAutoscalerV1GeneratorName:
return &kubectl.HorizontalPodAutoscalerGeneratorV1{
Name: name,
MinReplicas: o.Min,
MaxReplicas: o.Max,
CPUPercent: o.CpuPercent,
ScaleRefName: name,
ScaleRefKind: mapping.GroupVersionKind.Kind,
ScaleRefApiVersion: mapping.GroupVersionKind.GroupVersion().String(),
}, nil
default:
return nil, cmdutil.UsageErrorf(cmd, "Generator %s not supported. ", o.Generator)
}
}
o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
r := f.NewBuilder().
Internal().
o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
o.PrintFlags.NamePrintFlags.Operation = operation
if o.dryRun {
o.PrintFlags.Complete("%s (dry run)")
}
return o.PrintFlags.ToPrinter()
}
return nil
}
func (o *AutoscaleOptions) Validate() error {
if o.Max < 1 {
return fmt.Errorf("--max=MAXPODS is required and must be at least 1, max: %d", o.Max)
}
if o.Max < o.Min {
return fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", o.Max, o.Min)
}
return nil
}
func (o *AutoscaleOptions) Run() error {
r := o.builder.
WithScheme(legacyscheme.Scheme).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
ResourceTypeOrNameArgs(false, args...).
NamespaceParam(o.namespace).DefaultNamespace().
FilenameParam(o.enforceNamespace, o.FilenameOptions).
ResourceTypeOrNameArgs(false, o.args...).
Flatten().
Do()
err = r.Err()
if err != nil {
if err := r.Err(); err != nil {
return err
}
count := 0
err = r.Visit(func(info *resource.Info, err error) error {
err := r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
mapping := info.ResourceMapping()
if err := f.CanBeAutoscaled(mapping.GroupVersionKind.GroupKind()); err != nil {
if err := o.canBeAutoscaled(mapping.GroupVersionKind.GroupKind()); err != nil {
return err
}
// get the generator
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.HorizontalPodAutoscalerV1GeneratorName:
generator = &kubectl.HorizontalPodAutoscalerGeneratorV1{
Name: info.Name,
MinReplicas: cmdutil.GetFlagInt32(cmd, "min"),
MaxReplicas: cmdutil.GetFlagInt32(cmd, "max"),
CPUPercent: cmdutil.GetFlagInt32(cmd, "cpu-percent"),
ScaleRefName: info.Name,
ScaleRefKind: mapping.GroupVersionKind.Kind,
ScaleRefApiVersion: mapping.GroupVersionKind.GroupVersion().String(),
}
default:
return errUnsupportedGenerator(cmd, generatorName)
generator, err := o.generatorFunc(info.Name, mapping)
if err != nil {
return err
}
// Generate new object
@ -137,44 +227,40 @@ func RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s
if err != nil {
return err
}
hpa, ok := object.(*autoscalingv1.HorizontalPodAutoscaler)
if !ok {
return fmt.Errorf("generator made %T, not autoscalingv1.HorizontalPodAutoscaler", object)
}
mapper, typer := f.Object()
resourceMapper := &resource.Mapper{
ObjectTyper: typer,
RESTMapper: mapper,
ClientMapper: resource.ClientMapperFunc(f.ClientForMapping),
Decoder: cmdutil.InternalVersionDecoder(),
if err := o.Recorder.Record(hpa); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
hpa, err := resourceMapper.InfoForObject(object, nil)
if err != nil {
return err
}
if cmdutil.ShouldRecord(cmd, hpa) {
if err := cmdutil.RecordChangeCause(hpa.Object, f.Command(cmd, false)); err != nil {
if o.dryRun {
count++
printer, err := o.ToPrinter("created")
if err != nil {
return err
}
object = hpa.Object
}
if cmdutil.GetDryRunFlag(cmd) {
return cmdutil.PrintObject(cmd, object, out)
return printer.PrintObj(hpa, o.Out)
}
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), hpa, cmdutil.InternalVersionJSONEncoder()); err != nil {
if err := kubectl.CreateOrUpdateAnnotation(o.createAnnotation, hpa, cmdutil.InternalVersionJSONEncoder()); err != nil {
return err
}
object, err = resource.NewHelper(hpa.Client, hpa.Mapping).Create(namespace, false, object)
actualHPA, err := o.HPAClient.HorizontalPodAutoscalers(o.namespace).Create(hpa)
if err != nil {
return err
}
count++
if len(cmdutil.GetFlagString(cmd, "output")) > 0 {
return cmdutil.PrintObject(cmd, object, out)
printer, err := o.ToPrinter("autoscaled")
if err != nil {
return err
}
cmdutil.PrintSuccess(false, out, info.Object, cmdutil.GetDryRunFlag(cmd), "autoscaled")
return nil
return printer.PrintObj(actualHPA, o.Out)
})
if err != nil {
return err
@ -184,15 +270,3 @@ func RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s
}
return nil
}
func validateFlags(cmd *cobra.Command) error {
errs := []error{}
max, min := cmdutil.GetFlagInt32(cmd, "max"), cmdutil.GetFlagInt32(cmd, "min")
if max < 1 {
errs = append(errs, fmt.Errorf("--max=MAXPODS is required and must be at least 1, max: %d", max))
}
if max < min {
errs = append(errs, fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", max, min))
}
return utilerrors.NewAggregate(errs)
}

View File

@ -20,17 +20,24 @@ import (
"fmt"
"io"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/certificates"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"github.com/spf13/cobra"
)
func NewCmdCertificate(f cmdutil.Factory, out io.Writer) *cobra.Command {
func NewCmdCertificate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "certificate SUBCOMMAND",
DisableFlagsInUseLine: true,
@ -41,33 +48,61 @@ func NewCmdCertificate(f cmdutil.Factory, out io.Writer) *cobra.Command {
},
}
cmd.AddCommand(NewCmdCertificateApprove(f, out))
cmd.AddCommand(NewCmdCertificateDeny(f, out))
cmd.AddCommand(NewCmdCertificateApprove(f, ioStreams))
cmd.AddCommand(NewCmdCertificateDeny(f, ioStreams))
return cmd
}
type CertificateOptions struct {
resource.FilenameOptions
PrintFlags *genericclioptions.PrintFlags
PrintObj printers.ResourcePrinterFunc
csrNames []string
outputStyle string
clientSet internalclientset.Interface
builder *resource.Builder
genericclioptions.IOStreams
}
func (options *CertificateOptions) Complete(cmd *cobra.Command, args []string) error {
options.csrNames = args
options.outputStyle = cmdutil.GetFlagString(cmd, "output")
func (o *CertificateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
o.csrNames = args
o.outputStyle = cmdutil.GetFlagString(cmd, "output")
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = func(obj runtime.Object, out io.Writer) error {
return printer.PrintObj(obj, out)
}
o.builder = f.NewBuilder()
o.clientSet, err = f.ClientSet()
if err != nil {
return err
}
return nil
}
func (options *CertificateOptions) Validate() error {
if len(options.csrNames) < 1 && cmdutil.IsFilenameSliceEmpty(options.Filenames) {
func (o *CertificateOptions) Validate() error {
if len(o.csrNames) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames) {
return fmt.Errorf("one or more CSRs must be specified as <name> or -f <filename>")
}
return nil
}
func NewCmdCertificateApprove(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := CertificateOptions{}
func NewCmdCertificateApprove(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := CertificateOptions{
PrintFlags: genericclioptions.NewPrintFlags("approved").WithTypeSetter(scheme.Scheme),
IOStreams: ioStreams,
}
cmd := &cobra.Command{
Use: "approve (-f FILENAME | NAME)",
DisableFlagsInUseLine: true,
@ -85,19 +120,22 @@ func NewCmdCertificateApprove(f cmdutil.Factory, out io.Writer) *cobra.Command {
signed certificate can do.
`),
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(cmd, args))
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.RunCertificateApprove(f, out))
cmdutil.CheckErr(options.RunCertificateApprove(cmdutil.GetFlagBool(cmd, "force")))
},
}
cmdutil.AddOutputFlagsForMutation(cmd)
options.PrintFlags.AddFlags(cmd)
cmd.Flags().Bool("force", false, "Update the CSR even if it is already approved.")
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, "identifying the resource to update")
return cmd
}
func (options *CertificateOptions) RunCertificateApprove(f cmdutil.Factory, out io.Writer) error {
return options.modifyCertificateCondition(f, out, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, string) {
func (o *CertificateOptions) RunCertificateApprove(force bool) error {
return o.modifyCertificateCondition(o.builder, o.clientSet, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool) {
var alreadyApproved bool
for _, c := range csr.Status.Conditions {
if c.Type == certificates.CertificateApproved {
@ -105,7 +143,7 @@ func (options *CertificateOptions) RunCertificateApprove(f cmdutil.Factory, out
}
}
if alreadyApproved {
return csr, "approved"
return csr, true
}
csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateApproved,
@ -113,12 +151,15 @@ func (options *CertificateOptions) RunCertificateApprove(f cmdutil.Factory, out
Message: "This CSR was approved by kubectl certificate approve.",
LastUpdateTime: metav1.Now(),
})
return csr, "approved"
return csr, false
})
}
func NewCmdCertificateDeny(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := CertificateOptions{}
func NewCmdCertificateDeny(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := CertificateOptions{
PrintFlags: genericclioptions.NewPrintFlags("denied").WithTypeSetter(scheme.Scheme),
IOStreams: ioStreams,
}
cmd := &cobra.Command{
Use: "deny (-f FILENAME | NAME)",
DisableFlagsInUseLine: true,
@ -131,19 +172,22 @@ func NewCmdCertificateDeny(f cmdutil.Factory, out io.Writer) *cobra.Command {
not to issue a certificate to the requestor.
`),
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(cmd, args))
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.RunCertificateDeny(f, out))
cmdutil.CheckErr(options.RunCertificateDeny(cmdutil.GetFlagBool(cmd, "force")))
},
}
cmdutil.AddOutputFlagsForMutation(cmd)
options.PrintFlags.AddFlags(cmd)
cmd.Flags().Bool("force", false, "Update the CSR even if it is already denied.")
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, "identifying the resource to update")
return cmd
}
func (options *CertificateOptions) RunCertificateDeny(f cmdutil.Factory, out io.Writer) error {
return options.modifyCertificateCondition(f, out, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, string) {
func (o *CertificateOptions) RunCertificateDeny(force bool) error {
return o.modifyCertificateCondition(o.builder, o.clientSet, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool) {
var alreadyDenied bool
for _, c := range csr.Status.Conditions {
if c.Type == certificates.CertificateDenied {
@ -151,7 +195,7 @@ func (options *CertificateOptions) RunCertificateDeny(f cmdutil.Factory, out io.
}
}
if alreadyDenied {
return csr, "denied"
return csr, true
}
csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateDenied,
@ -159,18 +203,14 @@ func (options *CertificateOptions) RunCertificateDeny(f cmdutil.Factory, out io.
Message: "This CSR was approved by kubectl certificate deny.",
LastUpdateTime: metav1.Now(),
})
return csr, "denied"
return csr, false
})
}
func (options *CertificateOptions) modifyCertificateCondition(f cmdutil.Factory, out io.Writer, modify func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, string)) error {
func (options *CertificateOptions) modifyCertificateCondition(builder *resource.Builder, clientSet internalclientset.Interface, force bool, modify func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool)) error {
var found int
c, err := f.ClientSet()
if err != nil {
return err
}
r := f.NewBuilder().
Internal().
r := builder.
WithScheme(legacyscheme.Scheme).
ContinueOnError().
FilenameParam(false, &options.FilenameOptions).
ResourceNames("certificatesigningrequest", options.csrNames...).
@ -178,24 +218,35 @@ func (options *CertificateOptions) modifyCertificateCondition(f cmdutil.Factory,
Flatten().
Latest().
Do()
err = r.Visit(func(info *resource.Info, err error) error {
err := r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
csr := info.Object.(*certificates.CertificateSigningRequest)
csr, verb := modify(csr)
csr, err = c.Certificates().
CertificateSigningRequests().
UpdateApproval(csr)
if err != nil {
return err
for i := 0; ; i++ {
csr := info.Object.(*certificates.CertificateSigningRequest)
csr, hasCondition := modify(csr)
if !hasCondition || force {
csr, err = clientSet.Certificates().
CertificateSigningRequests().
UpdateApproval(csr)
if errors.IsConflict(err) && i < 10 {
if err := info.Get(); err != nil {
return err
}
continue
}
if err != nil {
return err
}
}
break
}
found++
cmdutil.PrintSuccess(options.outputStyle == "name", out, info.Object, false, verb)
return nil
return options.PrintObj(cmdutil.AsDefaultVersionedOrOriginal(info.Object, info.Mapping), options.Out)
})
if found == 0 {
fmt.Fprintf(out, "No resources found\n")
fmt.Fprintf(options.Out, "No resources found\n")
}
return err
}

View File

@ -23,10 +23,13 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilnet "k8s.io/apimachinery/pkg/util/net"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
ct "github.com/daviddengcn/go-colortext"
@ -43,42 +46,62 @@ var (
kubectl cluster-info`))
)
func NewCmdClusterInfo(f cmdutil.Factory, out io.Writer) *cobra.Command {
type ClusterInfoOptions struct {
genericclioptions.IOStreams
Namespace string
Builder *resource.Builder
Client *restclient.Config
}
func NewCmdClusterInfo(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := &ClusterInfoOptions{
IOStreams: ioStreams,
}
cmd := &cobra.Command{
Use: "cluster-info",
Short: i18n.T("Display cluster info"),
Long: longDescr,
Example: clusterinfoExample,
Run: func(cmd *cobra.Command, args []string) {
err := RunClusterInfo(f, out, cmd)
cmdutil.CheckErr(err)
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(o.Run())
},
}
cmdutil.AddInclude3rdPartyFlags(cmd)
cmd.AddCommand(NewCmdClusterInfoDump(f, out))
cmd.AddCommand(NewCmdClusterInfoDump(f, ioStreams))
return cmd
}
func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error {
client, err := f.ClientConfig()
func (o *ClusterInfoOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
var err error
o.Client, err = f.ToRESTConfig()
if err != nil {
return err
}
printService(out, "Kubernetes master", client.Host)
cmdNamespace := cmdutil.GetFlagString(cmd, "namespace")
if cmdNamespace == "" {
cmdNamespace = metav1.NamespaceSystem
}
o.Namespace = cmdNamespace
o.Builder = f.NewBuilder()
return nil
}
func (o *ClusterInfoOptions) Run() error {
printService(o.Out, "Kubernetes master", o.Client.Host)
// TODO use generalized labels once they are implemented (#341)
b := f.NewBuilder().
Internal().
NamespaceParam(cmdNamespace).DefaultNamespace().
b := o.Builder.
WithScheme(legacyscheme.Scheme).
NamespaceParam(o.Namespace).DefaultNamespace().
LabelSelectorParam("kubernetes.io/cluster-service=true").
ResourceTypeOrNameArgs(false, []string{"services"}...).
Latest()
err = b.Do().Visit(func(r *resource.Info, err error) error {
err := b.Do().Visit(func(r *resource.Info, err error) error {
if err != nil {
return err
}
@ -110,10 +133,10 @@ func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error
name = utilnet.JoinSchemeNamePort(scheme, service.ObjectMeta.Name, port.Name)
}
if len(client.GroupVersion.Group) == 0 {
link = client.Host + "/api/" + client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"
if len(o.Client.GroupVersion.Group) == 0 {
link = o.Client.Host + "/api/" + o.Client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"
} else {
link = client.Host + "/api/" + client.GroupVersion.Group + "/" + client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"
link = o.Client.Host + "/api/" + o.Client.GroupVersion.Group + "/" + o.Client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"
}
}
@ -121,11 +144,11 @@ func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error
if len(name) == 0 {
name = service.ObjectMeta.Name
}
printService(out, name, link)
printService(o.Out, name, link)
}
return nil
})
out.Write([]byte("\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\n"))
o.Out.Write([]byte("\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\n"))
return err
// TODO consider printing more information about cluster

View File

@ -21,31 +21,60 @@ import (
"io"
"os"
"path"
"time"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
)
type ClusterInfoDumpOptions struct {
PrintFlags *genericclioptions.PrintFlags
PrintObj printers.ResourcePrinterFunc
OutputDir string
AllNamespaces bool
Namespaces []string
Timeout time.Duration
Clientset internalclientset.Interface
Namespace string
RESTClientGetter genericclioptions.RESTClientGetter
LogsForObject polymorphichelpers.LogsForObjectFunc
genericclioptions.IOStreams
}
// NewCmdCreateSecret groups subcommands to create various types of secrets
func NewCmdClusterInfoDump(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdClusterInfoDump(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := &ClusterInfoDumpOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme),
IOStreams: ioStreams,
}
cmd := &cobra.Command{
Use: "dump",
Short: i18n.T("Dump lots of relevant info for debugging and diagnosis"),
Long: dumpLong,
Example: dumpExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(dumpClusterInfo(f, cmd, cmdOut))
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(o.Run())
},
}
cmd.Flags().String("output-directory", "", i18n.T("Where to output the files. If empty or '-' uses stdout, otherwise creates a directory hierarchy in that directory"))
cmd.Flags().StringSlice("namespaces", []string{}, "A comma separated list of namespaces to dump.")
cmd.Flags().Bool("all-namespaces", false, "If true, dump all namespaces. If true, --namespaces is ignored.")
cmd.Flags().StringVar(&o.OutputDir, "output-directory", o.OutputDir, i18n.T("Where to output the files. If empty or '-' uses stdout, otherwise creates a directory hierarchy in that directory"))
cmd.Flags().StringSliceVar(&o.Namespaces, "namespaces", o.Namespaces, "A comma separated list of namespaces to dump.")
cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", o.AllNamespaces, "If true, dump all namespaces. If true, --namespaces is ignored.")
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodLogsTimeout)
return cmd
}
@ -74,8 +103,7 @@ var (
kubectl cluster-info dump --namespaces default,kube-system --output-directory=/path/to/cluster-state`))
)
func setupOutputWriter(cmd *cobra.Command, defaultWriter io.Writer, filename string) io.Writer {
dir := cmdutil.GetFlagString(cmd, "output-directory")
func setupOutputWriter(dir string, defaultWriter io.Writer, filename string) io.Writer {
if len(dir) == 0 || dir == "-" {
return defaultWriter
}
@ -88,31 +116,48 @@ func setupOutputWriter(cmd *cobra.Command, defaultWriter io.Writer, filename str
return file
}
func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error {
timeout, err := cmdutil.GetPodRunningTimeoutFlag(cmd)
if err != nil {
return cmdutil.UsageErrorf(cmd, err.Error())
}
clientset, err := f.ClientSet()
func (o *ClusterInfoDumpOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
printer := &printers.JSONPrinter{}
jsonOutputFmt := "json"
o.PrintFlags.OutputFormat = &jsonOutputFmt
o.PrintObj = printer.PrintObj
nodes, err := clientset.Core().Nodes().List(metav1.ListOptions{})
o.Timeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
if err != nil {
return err
}
o.Clientset, err = f.ClientSet()
if err != nil {
return err
}
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
// TODO this should eventually just be the completed kubeconfigflag struct
o.RESTClientGetter = f
o.LogsForObject = polymorphichelpers.LogsForObjectFn
return nil
}
func (o *ClusterInfoDumpOptions) Run() error {
nodes, err := o.Clientset.Core().Nodes().List(metav1.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(nodes, setupOutputWriter(cmd, out, "nodes.json")); err != nil {
if err := o.PrintObj(nodes, setupOutputWriter(o.OutputDir, o.Out, "nodes.json")); err != nil {
return err
}
var namespaces []string
if cmdutil.GetFlagBool(cmd, "all-namespaces") {
namespaceList, err := clientset.Core().Namespaces().List(metav1.ListOptions{})
if o.AllNamespaces {
namespaceList, err := o.Clientset.Core().Namespaces().List(metav1.ListOptions{})
if err != nil {
return err
}
@ -120,75 +165,70 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
namespaces = append(namespaces, namespaceList.Items[ix].Name)
}
} else {
namespaces = cmdutil.GetFlagStringSlice(cmd, "namespaces")
if len(namespaces) == 0 {
cmdNamespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}
if len(o.Namespaces) == 0 {
namespaces = []string{
metav1.NamespaceSystem,
cmdNamespace,
o.Namespace,
}
}
}
for _, namespace := range namespaces {
// TODO: this is repetitive in the extreme. Use reflection or
// something to make this a for loop.
events, err := clientset.Core().Events(namespace).List(metav1.ListOptions{})
events, err := o.Clientset.Core().Events(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(events, setupOutputWriter(cmd, out, path.Join(namespace, "events.json"))); err != nil {
if err := o.PrintObj(events, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "events.json"))); err != nil {
return err
}
rcs, err := clientset.Core().ReplicationControllers(namespace).List(metav1.ListOptions{})
rcs, err := o.Clientset.Core().ReplicationControllers(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(rcs, setupOutputWriter(cmd, out, path.Join(namespace, "replication-controllers.json"))); err != nil {
if err := o.PrintObj(rcs, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "replication-controllers.json"))); err != nil {
return err
}
svcs, err := clientset.Core().Services(namespace).List(metav1.ListOptions{})
svcs, err := o.Clientset.Core().Services(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(svcs, setupOutputWriter(cmd, out, path.Join(namespace, "services.json"))); err != nil {
if err := o.PrintObj(svcs, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "services.json"))); err != nil {
return err
}
sets, err := clientset.Extensions().DaemonSets(namespace).List(metav1.ListOptions{})
sets, err := o.Clientset.Extensions().DaemonSets(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(sets, setupOutputWriter(cmd, out, path.Join(namespace, "daemonsets.json"))); err != nil {
if err := o.PrintObj(sets, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "daemonsets.json"))); err != nil {
return err
}
deps, err := clientset.Extensions().Deployments(namespace).List(metav1.ListOptions{})
deps, err := o.Clientset.Extensions().Deployments(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(deps, setupOutputWriter(cmd, out, path.Join(namespace, "deployments.json"))); err != nil {
if err := o.PrintObj(deps, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "deployments.json"))); err != nil {
return err
}
rps, err := clientset.Extensions().ReplicaSets(namespace).List(metav1.ListOptions{})
rps, err := o.Clientset.Extensions().ReplicaSets(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(rps, setupOutputWriter(cmd, out, path.Join(namespace, "replicasets.json"))); err != nil {
if err := o.PrintObj(rps, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "replicasets.json"))); err != nil {
return err
}
pods, err := clientset.Core().Pods(namespace).List(metav1.ListOptions{})
pods, err := o.Clientset.Core().Pods(namespace).List(metav1.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(pods, setupOutputWriter(cmd, out, path.Join(namespace, "pods.json"))); err != nil {
if err := o.PrintObj(pods, setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, "pods.json"))); err != nil {
return err
}
@ -196,7 +236,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
writer.Write([]byte(fmt.Sprintf("==== START logs for container %s of pod %s/%s ====\n", container.Name, pod.Namespace, pod.Name)))
defer writer.Write([]byte(fmt.Sprintf("==== END logs for container %s of pod %s/%s ====\n", container.Name, pod.Namespace, pod.Name)))
request, err := f.LogsForObject(pod, &api.PodLogOptions{Container: container.Name}, timeout)
request, err := o.LogsForObject(o.RESTClientGetter, pod, &api.PodLogOptions{Container: container.Name}, timeout)
if err != nil {
// Print error and return.
writer.Write([]byte(fmt.Sprintf("Create log request error: %s\n", err.Error())))
@ -215,19 +255,15 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
for ix := range pods.Items {
pod := &pods.Items[ix]
containers := pod.Spec.Containers
writer := setupOutputWriter(cmd, out, path.Join(namespace, pod.Name, "logs.txt"))
writer := setupOutputWriter(o.OutputDir, o.Out, path.Join(namespace, pod.Name, "logs.txt"))
for i := range containers {
printContainer(writer, containers[i], pod)
}
}
}
dir := cmdutil.GetFlagString(cmd, "output-directory")
if len(dir) == 0 {
dir = "standard output"
}
if dir != "-" {
fmt.Fprintf(out, "Cluster info dumped to %s\n", dir)
if o.OutputDir != "-" {
fmt.Fprintf(o.Out, "Cluster info dumped to %s\n", o.OutputDir)
}
return nil
}

View File

@ -17,25 +17,25 @@ limitations under the License.
package cmd
import (
"bytes"
"io/ioutil"
"os"
"path"
"testing"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestSetupOutputWriterNoOp(t *testing.T) {
tests := []string{"", "-"}
for _, test := range tests {
out := &bytes.Buffer{}
_, _, buf, _ := genericclioptions.NewTestIOStreams()
f := cmdtesting.NewTestFactory()
cmd := NewCmdClusterInfoDump(f, os.Stdout)
cmd.Flag("output-directory").Value.Set(test)
writer := setupOutputWriter(cmd, out, "/some/file/that/should/be/ignored")
if writer != out {
t.Errorf("expected: %v, saw: %v", out, writer)
defer f.Cleanup()
writer := setupOutputWriter(test, buf, "/some/file/that/should/be/ignored")
if writer != buf {
t.Errorf("expected: %v, saw: %v", buf, writer)
}
}
}
@ -49,13 +49,13 @@ func TestSetupOutputWriterFile(t *testing.T) {
fullPath := path.Join(dir, file)
defer os.RemoveAll(dir)
out := &bytes.Buffer{}
_, _, buf, _ := genericclioptions.NewTestIOStreams()
f := cmdtesting.NewTestFactory()
cmd := NewCmdClusterInfoDump(f, os.Stdout)
cmd.Flag("output-directory").Value.Set(dir)
writer := setupOutputWriter(cmd, out, file)
if writer == out {
t.Errorf("expected: %v, saw: %v", out, writer)
defer f.Cleanup()
writer := setupOutputWriter(dir, buf, file)
if writer == buf {
t.Errorf("expected: %v, saw: %v", buf, writer)
}
output := "some data here"
writer.Write([]byte(output))

View File

@ -17,22 +17,27 @@ limitations under the License.
package cmd
import (
"flag"
"fmt"
"io"
"os"
"k8s.io/apiserver/pkg/util/flag"
"k8s.io/apimachinery/pkg/api/meta"
utilflag "k8s.io/apiserver/pkg/util/flag"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/auth"
cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config"
"k8s.io/kubernetes/pkg/kubectl/cmd/resource"
"k8s.io/kubernetes/pkg/kubectl/cmd/create"
"k8s.io/kubernetes/pkg/kubectl/cmd/get"
"k8s.io/kubernetes/pkg/kubectl/cmd/rollout"
"k8s.io/kubernetes/pkg/kubectl/cmd/set"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/wait"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
const (
@ -57,9 +62,6 @@ __kubectl_override_flags()
;;
esac
done
if [ "${w}" == "--all-namespaces" ]; then
namespace="--all-namespaces"
fi
done
for var in "${__kubectl_override_flag_list[@]##*-}"; do
if eval "test -n \"\$${var}\""; then
@ -93,20 +95,27 @@ __kubectl_parse_config()
fi
}
# $1 is the name of resource (required)
# $2 is template string for kubectl get (optional)
__kubectl_parse_get()
{
local template
template="{{ range .items }}{{ .metadata.name }} {{ end }}"
template="${2:-"{{ range .items }}{{ .metadata.name }} {{ end }}"}"
local kubectl_out
if kubectl_out=$(kubectl get $(__kubectl_override_flags) -o template --template="${template}" "$1" 2>/dev/null); then
COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
COMPREPLY+=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
fi
}
__kubectl_get_resource()
{
if [[ ${#nouns[@]} -eq 0 ]]; then
return 1
local kubectl_out
if kubectl_out=$(kubectl api-resources $(__kubectl_override_flags) -o name --cached --request-timeout=5s --verbs=get 2>/dev/null); then
COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
return 0
fi
return 1
fi
__kubectl_parse_get "${nouns[${#nouns[@]} -1]}"
}
@ -140,8 +149,8 @@ __kubectl_get_resource_clusterrole()
__kubectl_get_containers()
{
local template
template="{{ range .spec.containers }}{{ .name }} {{ end }}"
__debug "${FUNCNAME} nouns are ${nouns[*]}"
template="{{ range .spec.initContainers }}{{ .name }} {{end}}{{ range .spec.containers }}{{ .name }} {{ end }}"
__kubectl_debug "${FUNCNAME} nouns are ${nouns[*]}"
local len="${#nouns[@]}"
if [[ ${len} -ne 1 ]]; then
@ -165,10 +174,41 @@ __kubectl_require_pod_and_container()
return 0
}
__kubectl_cp()
{
if [[ $(type -t compopt) = "builtin" ]]; then
compopt -o nospace
fi
case "$cur" in
/*|[.~]*) # looks like a path
return
;;
*:*) # TODO: complete remote files in the pod
return
;;
*/*) # complete <namespace>/<pod>
local template namespace kubectl_out
template="{{ range .items }}{{ .metadata.namespace }}/{{ .metadata.name }}: {{ end }}"
namespace="${cur%%/*}"
if kubectl_out=( $(kubectl get $(__kubectl_override_flags) --namespace "${namespace}" -o template --template="${template}" pods 2>/dev/null) ); then
COMPREPLY=( $(compgen -W "${kubectl_out[*]}" -- "${cur}") )
fi
return
;;
*) # complete namespaces, pods, and filedirs
__kubectl_parse_get "namespace" "{{ range .items }}{{ .metadata.name }}/ {{ end }}"
__kubectl_parse_get "pod" "{{ range .items }}{{ .metadata.name }}: {{ end }}"
_filedir
;;
esac
}
__custom_func() {
case ${last_command} in
kubectl_get | kubectl_describe | kubectl_delete | kubectl_label | kubectl_edit | kubectl_patch |\
kubectl_annotate | kubectl_expose | kubectl_scale | kubectl_autoscale | kubectl_taint | kubectl_rollout_*)
kubectl_annotate | kubectl_expose | kubectl_scale | kubectl_autoscale | kubectl_taint | kubectl_rollout_* |\
kubectl_apply_edit-last-applied | kubectl_apply_view-last-applied)
__kubectl_get_resource
return
;;
@ -196,6 +236,10 @@ __custom_func() {
__kubectl_config_get_clusters
return
;;
kubectl_cp)
__kubectl_cp
return
;;
*)
;;
esac
@ -213,11 +257,11 @@ var (
)
func NewDefaultKubectlCommand() *cobra.Command {
return NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr)
return NewKubectlCommand(os.Stdin, os.Stdout, os.Stderr)
}
// NewKubectlCommand creates the `kubectl` command and its nested children.
func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command {
func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
// Parent command to which all subcommands are added.
cmds := &cobra.Command{
Use: "kubectl",
@ -231,8 +275,21 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
BashCompletionFunction: bashCompletionFunc,
}
f.BindFlags(cmds.PersistentFlags())
f.BindExternalFlags(cmds.PersistentFlags())
flags := cmds.PersistentFlags()
flags.SetNormalizeFunc(utilflag.WarnWordSepNormalizeFunc) // Warn for "_" flags
// Normalize all flags that are coming from other packages or pre-configurations
// a.k.a. change all "_" to "-". e.g. glog package
flags.SetNormalizeFunc(utilflag.WordSepNormalizeFunc)
kubeConfigFlags := genericclioptions.NewConfigFlags()
kubeConfigFlags.AddFlags(flags)
matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)
matchVersionKubeConfigFlags.AddFlags(cmds.PersistentFlags())
cmds.PersistentFlags().AddGoFlagSet(flag.CommandLine)
f := cmdutil.NewFactory(matchVersionKubeConfigFlags)
// Sending in 'nil' for the getLanguageFn() results in using
// the LANG environment variable.
@ -242,77 +299,80 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
i18n.LoadTranslations("kubectl", nil)
// From this point and forward we get warnings on flags that contain "_" separators
cmds.SetGlobalNormalizationFunc(flag.WarnWordSepNormalizeFunc)
cmds.SetGlobalNormalizationFunc(utilflag.WarnWordSepNormalizeFunc)
ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err}
groups := templates.CommandGroups{
{
Message: "Basic Commands (Beginner):",
Commands: []*cobra.Command{
NewCmdCreate(f, out, err),
NewCmdExposeService(f, out),
NewCmdRun(f, in, out, err),
set.NewCmdSet(f, in, out, err),
deprecatedAlias("run-container", NewCmdRun(f, in, out, err)),
create.NewCmdCreate(f, ioStreams),
NewCmdExposeService(f, ioStreams),
NewCmdRun(f, ioStreams),
set.NewCmdSet(f, ioStreams),
deprecatedAlias("run-container", NewCmdRun(f, ioStreams)),
},
},
{
Message: "Basic Commands (Intermediate):",
Commands: []*cobra.Command{
resource.NewCmdGet(f, out, err),
NewCmdExplain(f, out, err),
NewCmdEdit(f, out, err),
NewCmdDelete(f, out, err),
NewCmdExplain("kubectl", f, ioStreams),
get.NewCmdGet("kubectl", f, ioStreams),
NewCmdEdit(f, ioStreams),
NewCmdDelete(f, ioStreams),
},
},
{
Message: "Deploy Commands:",
Commands: []*cobra.Command{
rollout.NewCmdRollout(f, out, err),
NewCmdRollingUpdate(f, out),
NewCmdScale(f, out, err),
NewCmdAutoscale(f, out),
rollout.NewCmdRollout(f, ioStreams),
NewCmdRollingUpdate(f, ioStreams),
NewCmdScale(f, ioStreams),
NewCmdAutoscale(f, ioStreams),
},
},
{
Message: "Cluster Management Commands:",
Commands: []*cobra.Command{
NewCmdCertificate(f, out),
NewCmdClusterInfo(f, out),
NewCmdTop(f, out, err),
NewCmdCordon(f, out),
NewCmdUncordon(f, out),
NewCmdDrain(f, out, err),
NewCmdTaint(f, out),
NewCmdCertificate(f, ioStreams),
NewCmdClusterInfo(f, ioStreams),
NewCmdTop(f, ioStreams),
NewCmdCordon(f, ioStreams),
NewCmdUncordon(f, ioStreams),
NewCmdDrain(f, ioStreams),
NewCmdTaint(f, ioStreams),
},
},
{
Message: "Troubleshooting and Debugging Commands:",
Commands: []*cobra.Command{
NewCmdDescribe(f, out, err),
NewCmdLogs(f, out, err),
NewCmdAttach(f, in, out, err),
NewCmdExec(f, in, out, err),
NewCmdPortForward(f, out, err),
NewCmdProxy(f, out),
NewCmdCp(f, out, err),
auth.NewCmdAuth(f, out, err),
NewCmdDescribe("kubectl", f, ioStreams),
NewCmdLogs(f, ioStreams),
NewCmdAttach(f, ioStreams),
NewCmdExec(f, ioStreams),
NewCmdPortForward(f, ioStreams),
NewCmdProxy(f, ioStreams),
NewCmdCp(f, ioStreams),
auth.NewCmdAuth(f, ioStreams),
},
},
{
Message: "Advanced Commands:",
Commands: []*cobra.Command{
NewCmdApply("kubectl", f, out, err),
NewCmdPatch(f, out),
NewCmdReplace(f, out),
NewCmdConvert(f, out),
NewCmdApply("kubectl", f, ioStreams),
NewCmdPatch(f, ioStreams),
NewCmdReplace(f, ioStreams),
wait.NewCmdWait(f, ioStreams),
NewCmdConvert(f, ioStreams),
},
},
{
Message: "Settings Commands:",
Commands: []*cobra.Command{
NewCmdLabel(f, out),
NewCmdAnnotate(f, out),
NewCmdCompletion(out, ""),
NewCmdLabel(f, ioStreams),
NewCmdAnnotate("kubectl", f, ioStreams),
NewCmdCompletion(ioStreams.Out, ""),
},
},
}
@ -321,7 +381,7 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
filters := []string{"options"}
// Hide the "alpha" subcommand if there are no alpha commands in this build.
alpha := NewCmdAlpha(f, in, out, err)
alpha := NewCmdAlpha(f, ioStreams)
if !alpha.HasSubCommands() {
filters = append(filters, alpha.Name())
}
@ -341,11 +401,12 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
}
cmds.AddCommand(alpha)
cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), out, err))
cmds.AddCommand(NewCmdPlugin(f, in, out, err))
cmds.AddCommand(NewCmdVersion(f, out))
cmds.AddCommand(NewCmdApiVersions(f, out))
cmds.AddCommand(NewCmdOptions(out))
cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), ioStreams))
cmds.AddCommand(NewCmdPlugin(f, ioStreams))
cmds.AddCommand(NewCmdVersion(f, ioStreams))
cmds.AddCommand(NewCmdApiVersions(f, ioStreams))
cmds.AddCommand(NewCmdApiResources(f, ioStreams))
cmds.AddCommand(NewCmdOptions(ioStreams.Out))
return cmds
}
@ -355,7 +416,7 @@ func runHelp(cmd *cobra.Command, args []string) {
}
func printDeprecationWarning(errOut io.Writer, command, alias string) {
fmt.Fprintf(errOut, "%s is DEPRECATED and will be removed in a future version. Use %s instead.", alias, command)
fmt.Fprintf(errOut, "%s is DEPRECATED and will be removed in a future version. Use %s instead.\n", alias, command)
}
// deprecatedAlias is intended to be used to create a "wrapper" command around
@ -372,3 +433,5 @@ func deprecatedAlias(deprecatedVersion string, cmd *cobra.Command) *cobra.Comman
cmd.Hidden = true
return cmd
}
var metadataAccessor = meta.NewAccessor()

View File

@ -0,0 +1,234 @@
/*
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 cmd
import (
"bytes"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
genericprinters "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/printers"
)
func TestIllegalPackageSourceCheckerThroughPrintFlags(t *testing.T) {
testCases := []struct {
name string
expectInternalObjErr bool
output string
obj runtime.Object
expectedOutput string
}{
{
name: "success printer: object containing package path beginning with forbidden prefix is rejected",
expectInternalObjErr: true,
obj: internalPod(),
},
{
name: "success printer: object containing package path with no forbidden prefix returns no error",
expectInternalObjErr: false,
obj: externalPod(),
output: "",
expectedOutput: "pod/foo succeeded\n",
},
{
name: "name printer: object containing package path beginning with forbidden prefix is rejected",
expectInternalObjErr: true,
output: "name",
obj: internalPod(),
},
{
name: "json printer: object containing package path beginning with forbidden prefix is rejected",
expectInternalObjErr: true,
output: "json",
obj: internalPod(),
},
{
name: "json printer: object containing package path with no forbidden prefix returns no error",
expectInternalObjErr: false,
obj: externalPod(),
output: "json",
},
{
name: "yaml printer: object containing package path beginning with forbidden prefix is rejected",
expectInternalObjErr: true,
output: "yaml",
obj: internalPod(),
},
{
name: "yaml printer: object containing package path with no forbidden prefix returns no error",
expectInternalObjErr: false,
obj: externalPod(),
output: "yaml",
},
}
for _, tc := range testCases {
printFlags := genericclioptions.NewPrintFlags("succeeded").WithTypeSetter(scheme.Scheme)
printFlags.OutputFormat = &tc.output
printer, err := printFlags.ToPrinter()
if err != nil {
t.Fatalf("unexpected error %v", err)
}
output := bytes.NewBuffer([]byte{})
err = printer.PrintObj(tc.obj, output)
if err != nil {
if !tc.expectInternalObjErr {
t.Fatalf("unexpected error %v", err)
}
if !genericprinters.IsInternalObjectError(err) {
t.Fatalf("unexpected error - expecting internal object printer error, got %q", err)
}
continue
}
if tc.expectInternalObjErr {
t.Fatalf("expected internal object printer error, but got no error")
}
if len(tc.expectedOutput) == 0 {
continue
}
if tc.expectedOutput != output.String() {
t.Fatalf("unexpected output: expecting %q, got %q", tc.expectedOutput, output.String())
}
}
}
func TestIllegalPackageSourceCheckerDirectlyThroughPrinters(t *testing.T) {
jsonPathPrinter, err := genericprinters.NewJSONPathPrinter("{ .metadata.name }")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
goTemplatePrinter, err := genericprinters.NewGoTemplatePrinter([]byte("{{ .metadata.name }}"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
customColumns, err := printers.NewCustomColumnsPrinterFromSpec("NAME:.metadata.name", scheme.Codecs.UniversalDecoder(), true)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
testCases := []struct {
name string
expectInternalObjErr bool
printer genericprinters.ResourcePrinter
obj runtime.Object
expectedOutput string
}{
{
name: "json printer: object containing package path beginning with forbidden prefix is rejected",
expectInternalObjErr: true,
printer: &genericprinters.JSONPrinter{},
obj: internalPod(),
},
{
name: "yaml printer: object containing package path beginning with forbidden prefix is rejected",
expectInternalObjErr: true,
printer: &genericprinters.YAMLPrinter{},
obj: internalPod(),
},
{
name: "jsonpath printer: object containing package path beginning with forbidden prefix is rejected",
expectInternalObjErr: true,
printer: jsonPathPrinter,
obj: internalPod(),
},
{
name: "go-template printer: object containing package path beginning with forbidden prefix is rejected",
expectInternalObjErr: true,
printer: goTemplatePrinter,
obj: internalPod(),
},
{
name: "go-template printer: object containing package path beginning with forbidden prefix is rejected",
expectInternalObjErr: true,
printer: goTemplatePrinter,
obj: internalPod(),
},
{
name: "custom-columns printer: object containing package path beginning with forbidden prefix is rejected",
expectInternalObjErr: true,
printer: customColumns,
obj: internalPod(),
},
}
for _, tc := range testCases {
output := bytes.NewBuffer([]byte{})
err := tc.printer.PrintObj(tc.obj, output)
if err != nil {
if !tc.expectInternalObjErr {
t.Fatalf("unexpected error %v", err)
}
if !genericprinters.IsInternalObjectError(err) {
t.Fatalf("unexpected error - expecting internal object printer error, got %q", err)
}
continue
}
if tc.expectInternalObjErr {
t.Fatalf("expected internal object printer error, but got no error")
}
if len(tc.expectedOutput) == 0 {
continue
}
if tc.expectedOutput != output.String() {
t.Fatalf("unexpected output: expecting %q, got %q", tc.expectedOutput, output.String())
}
}
}
func internalPod() *api.Pod {
return &api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "bar",
},
},
},
Status: api.PodStatus{
Phase: api.PodRunning,
},
}
}
func externalPod() *v1.Pod {
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
}
}

View File

@ -19,7 +19,6 @@ package cmd
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
@ -35,17 +34,11 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
apitesting "k8s.io/kubernetes/pkg/api/testing"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/cmd/resource"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/printers"
"k8s.io/kubernetes/pkg/util/strings"
)
// This init should be removed after switching this command and its tests to user external types.
@ -122,44 +115,6 @@ func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList)
return pods, svc, rc
}
type testPrinter struct {
Objects []runtime.Object
Err error
GenericPrinter bool
}
func (t *testPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
t.Objects = append(t.Objects, obj)
fmt.Fprintf(out, "%#v", obj)
return t.Err
}
// TODO: implement HandledResources()
func (t *testPrinter) HandledResources() []string {
return []string{}
}
func (t *testPrinter) AfterPrint(output io.Writer, res string) error {
return nil
}
func (t *testPrinter) IsGeneric() bool {
return t.GenericPrinter
}
type testDescriber struct {
Name, Namespace string
Settings printers.DescriberSettings
Output string
Err error
}
func (t *testDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (output string, err error) {
t.Namespace, t.Name = namespace, name
t.Settings = describerSettings
return t.Output, t.Err
}
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
}
@ -176,184 +131,6 @@ func stringBody(body string) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(body)))
}
func Example_printMultiContainersReplicationControllerWithWide() {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
ctrl := &api.ReplicationController{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"foo": "bar"},
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.ReplicationControllerSpec{
Replicas: 1,
Selector: map[string]string{"foo": "bar"},
Template: &api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "foo",
Image: "someimage",
},
{
Name: "foo2",
Image: "someimage2",
},
},
},
},
},
Status: api.ReplicationControllerStatus{
Replicas: 1,
},
}
cmd.Flags().Set("output", "wide")
err := cmdutil.PrintObject(cmd, ctrl, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
// foo 1 1 0 10y foo,foo2 someimage,someimage2 foo=bar
}
func Example_printReplicationController() {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
ctrl := &api.ReplicationController{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"foo": "bar"},
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.ReplicationControllerSpec{
Replicas: 1,
Selector: map[string]string{"foo": "bar"},
Template: &api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "foo",
Image: "someimage",
},
{
Name: "foo2",
Image: "someimage",
},
},
},
},
},
Status: api.ReplicationControllerStatus{
Replicas: 1,
},
}
err := cmdutil.PrintObject(cmd, ctrl, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// NAME DESIRED CURRENT READY AGE
// foo 1 1 0 10y
}
func Example_printPodWithWideFormat() {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
nodeName := "kubernetes-node-abcd"
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
pod := &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test1",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: "podPhase",
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
PodIP: "10.1.1.3",
},
}
cmd.Flags().Set("output", "wide")
err := cmdutil.PrintObject(cmd, pod, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// NAME READY STATUS RESTARTS AGE IP NODE
// test1 1/2 podPhase 6 10y 10.1.1.3 kubernetes-node-abcd
}
func Example_printPodWithShowLabels() {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
nodeName := "kubernetes-node-abcd"
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
pod := &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test1",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
Labels: map[string]string{
"l1": "key",
"l2": "value",
},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: "podPhase",
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
}
cmd.Flags().Set("show-labels", "true")
err := cmdutil.PrintObject(cmd, pod, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// NAME READY STATUS RESTARTS AGE LABELS
// test1 1/2 podPhase 6 10y l1=key,l2=value
}
func newAllPhasePodList() *api.PodList {
nodeName := "kubernetes-node-abcd"
return &api.PodList{
@ -446,138 +223,9 @@ func newAllPhasePodList() *api.PodList {
}
}
func Example_printPodShowTerminated() {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
podList := newAllPhasePodList()
// filter pods
filterFuncs := tf.DefaultResourceFilterFunc()
filterOpts := cmdutil.ExtractCmdPrintOptions(cmd, false)
_, filteredPodList, errs := cmdutil.FilterResourceList(podList, filterFuncs, filterOpts)
if errs != nil {
fmt.Printf("Unexpected filter error: %v\n", errs)
}
printer, err := cmdutil.PrinterForOptions(cmdutil.ExtractCmdPrintOptions(cmd, false))
if err != nil {
fmt.Printf("Unexpected printer get error: %v\n", errs)
}
for _, pod := range filteredPodList {
err := printer.PrintObj(pod, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
}
// Output:
// NAME READY STATUS RESTARTS AGE
// test1 1/2 Pending 6 10y
// test2 1/2 Running 6 10y
// test3 1/2 Succeeded 6 10y
// test4 1/2 Failed 6 10y
// test5 1/2 Unknown 6 10y
}
func Example_printPodShowAll() {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
podList := newAllPhasePodList()
err := cmdutil.PrintObject(cmd, podList, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// NAME READY STATUS RESTARTS AGE
// test1 1/2 Pending 6 10y
// test2 1/2 Running 6 10y
// test3 1/2 Succeeded 6 10y
// test4 1/2 Failed 6 10y
// test5 1/2 Unknown 6 10y
}
func Example_printServiceWithLabels() {
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := resource.NewCmdGet(tf, os.Stdout, os.Stderr)
svc := &api.ServiceList{
Items: []api.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc1",
Namespace: "ns1",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
Labels: map[string]string{
"l1": "value",
},
},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Protocol: "UDP", Port: 53},
{Protocol: "TCP", Port: 53},
},
Selector: map[string]string{
"s": "magic",
},
ClusterIP: "10.1.1.1",
Type: api.ServiceTypeClusterIP,
},
Status: api.ServiceStatus{},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc2",
Namespace: "ns2",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
Labels: map[string]string{
"l1": "dolla-bill-yall",
},
},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Protocol: "TCP", Port: 80},
{Protocol: "TCP", Port: 8080},
},
Selector: map[string]string{
"s": "kazam",
},
ClusterIP: "10.1.1.2",
Type: api.ServiceTypeClusterIP,
},
Status: api.ServiceStatus{},
}},
}
ld := strings.NewLineDelimiter(os.Stdout, "|")
defer ld.Flush()
cmd.Flags().Set("label-columns", "l1")
err := cmdutil.PrintObject(cmd, svc, ld)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// |NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE L1|
// |svc1 ClusterIP 10.1.1.1 <none> 53/UDP,53/TCP 10y value|
// |svc2 ClusterIP 10.1.1.2 <none> 80/TCP,8080/TCP 10y dolla-bill-yall|
// ||
}
func TestNormalizationFuncGlobalExistence(t *testing.T) {
// This test can be safely deleted when we will not support multiple flag formats
root := NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr)
root := NewKubectlCommand(os.Stdin, os.Stdout, os.Stderr)
if root.Parent() != nil {
t.Fatal("We expect the root command to be returned")

View File

@ -223,7 +223,7 @@ __kubectl_get_comp_words_by_ref() {
__kubectl_filedir() {
local RET OLD_IFS w qw
__debug "_filedir $@ cur=$cur"
__kubectl_debug "_filedir $@ cur=$cur"
if [[ "$1" = \~* ]]; then
# somehow does not work. Maybe, zsh does not call this at all
eval echo "$1"
@ -240,7 +240,7 @@ __kubectl_filedir() {
fi
IFS="$OLD_IFS"
IFS="," __debug "RET=${RET[@]} len=${#RET[@]}"
IFS="," __kubectl_debug "RET=${RET[@]} len=${#RET[@]}"
for w in ${RET[@]}; do
if [[ ! "${w}" = "${cur}"* ]]; then

View File

@ -28,8 +28,10 @@ go_library(
"//build/visible_to:pkg_kubectl_cmd_config_CONSUMERS",
],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/printers:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
@ -64,6 +66,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",

View File

@ -18,7 +18,6 @@ package config
import (
"fmt"
"io"
"path"
"strconv"
@ -27,11 +26,12 @@ import (
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
// NewCmdConfig creates a command object for the "config" action, and adds all child commands to it.
func NewCmdConfig(f cmdutil.Factory, pathOptions *clientcmd.PathOptions, out, errOut io.Writer) *cobra.Command {
func NewCmdConfig(f cmdutil.Factory, pathOptions *clientcmd.PathOptions, streams genericclioptions.IOStreams) *cobra.Command {
if len(pathOptions.ExplicitFileFlag) == 0 {
pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag
}
@ -48,25 +48,26 @@ func NewCmdConfig(f cmdutil.Factory, pathOptions *clientcmd.PathOptions, out, er
1. If the --` + pathOptions.ExplicitFileFlag + ` flag is set, then only that file is loaded. The flag may only be set once and no merging takes place.
2. If $` + pathOptions.EnvVar + ` environment variable is set, then it is used a list of paths (normal path delimitting rules for your system). These paths are merged. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list.
3. Otherwise, ` + path.Join("${HOME}", pathOptions.GlobalFileSubpath) + ` is used and no merging takes place.`),
Run: cmdutil.DefaultSubCommandRun(errOut),
Run: cmdutil.DefaultSubCommandRun(streams.ErrOut),
}
// file paths are common to all sub commands
cmd.PersistentFlags().StringVar(&pathOptions.LoadingRules.ExplicitPath, pathOptions.ExplicitFileFlag, pathOptions.LoadingRules.ExplicitPath, "use a particular kubeconfig file")
cmd.AddCommand(NewCmdConfigView(f, out, errOut, pathOptions))
cmd.AddCommand(NewCmdConfigSetCluster(out, pathOptions))
cmd.AddCommand(NewCmdConfigSetAuthInfo(out, pathOptions))
cmd.AddCommand(NewCmdConfigSetContext(out, pathOptions))
cmd.AddCommand(NewCmdConfigSet(out, pathOptions))
cmd.AddCommand(NewCmdConfigUnset(out, pathOptions))
cmd.AddCommand(NewCmdConfigCurrentContext(out, pathOptions))
cmd.AddCommand(NewCmdConfigUseContext(out, pathOptions))
cmd.AddCommand(NewCmdConfigGetContexts(out, pathOptions))
cmd.AddCommand(NewCmdConfigGetClusters(out, pathOptions))
cmd.AddCommand(NewCmdConfigDeleteCluster(out, pathOptions))
cmd.AddCommand(NewCmdConfigDeleteContext(out, errOut, pathOptions))
cmd.AddCommand(NewCmdConfigRenameContext(out, pathOptions))
// TODO(juanvallejo): update all subcommands to work with genericclioptions.IOStreams
cmd.AddCommand(NewCmdConfigView(f, streams, pathOptions))
cmd.AddCommand(NewCmdConfigSetCluster(streams.Out, pathOptions))
cmd.AddCommand(NewCmdConfigSetAuthInfo(streams.Out, pathOptions))
cmd.AddCommand(NewCmdConfigSetContext(streams.Out, pathOptions))
cmd.AddCommand(NewCmdConfigSet(streams.Out, pathOptions))
cmd.AddCommand(NewCmdConfigUnset(streams.Out, pathOptions))
cmd.AddCommand(NewCmdConfigCurrentContext(streams.Out, pathOptions))
cmd.AddCommand(NewCmdConfigUseContext(streams.Out, pathOptions))
cmd.AddCommand(NewCmdConfigGetContexts(streams, pathOptions))
cmd.AddCommand(NewCmdConfigGetClusters(streams.Out, pathOptions))
cmd.AddCommand(NewCmdConfigDeleteCluster(streams.Out, pathOptions))
cmd.AddCommand(NewCmdConfigDeleteContext(streams.Out, streams.ErrOut, pathOptions))
cmd.AddCommand(NewCmdConfigRenameContext(streams.Out, pathOptions))
return cmd
}

View File

@ -17,7 +17,6 @@ limitations under the License.
package config
import (
"bytes"
"fmt"
"io/ioutil"
"os"
@ -31,6 +30,7 @@ import (
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func newRedFederalCowHammerConfig() clientcmdapi.Config {
@ -863,15 +863,14 @@ func testConfigCommand(args []string, startingConfig clientcmdapi.Config, t *tes
argsToUse = append(argsToUse, "--kubeconfig="+fakeKubeFile.Name())
argsToUse = append(argsToUse, args...)
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfig(cmdutil.NewFactory(nil), clientcmd.NewDefaultPathOptions(), buf, buf)
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdConfig(cmdutil.NewFactory(genericclioptions.NewTestConfigFlags()), clientcmd.NewDefaultPathOptions(), streams)
// "context" is a global flag, inherited from base kubectl command in the real world
cmd.PersistentFlags().String("context", "", "The name of the kubeconfig context to use")
cmd.SetArgs(argsToUse)
cmd.Execute()
// outBytes, _ := ioutil.ReadFile(fakeKubeFile.Name())
config := clientcmd.GetConfigFromFileOrDie(fakeKubeFile.Name())
return buf.String(), *config
}

View File

@ -142,7 +142,7 @@ func TestCreateAuthInfoOptions(t *testing.T) {
},
{
flags: []string{
// No name for authinfo provided.
// No name for authinfo provided.
},
wantCompleteErr: true,
},

View File

@ -31,6 +31,7 @@ import (
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
)
@ -41,7 +42,8 @@ type GetContextsOptions struct {
nameOnly bool
showHeaders bool
contextNames []string
out io.Writer
genericclioptions.IOStreams
}
var (
@ -57,8 +59,12 @@ var (
// NewCmdConfigGetContexts creates a command object for the "get-contexts" action, which
// retrieves one or more contexts from a kubeconfig.
func NewCmdConfigGetContexts(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &GetContextsOptions{configAccess: configAccess}
func NewCmdConfigGetContexts(streams genericclioptions.IOStreams, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &GetContextsOptions{
configAccess: configAccess,
IOStreams: streams,
}
cmd := &cobra.Command{
Use: "get-contexts [(-o|--output=)name)]",
@ -74,22 +80,22 @@ func NewCmdConfigGetContexts(out io.Writer, configAccess clientcmd.ConfigAccess)
cmdutil.CheckErr(fmt.Errorf("output must be one of '' or 'name': %v", outputFormat))
}
if !supportedOutputTypes.Has(outputFormat) {
fmt.Fprintf(out, "--output %v is not available in kubectl config get-contexts; resetting to default output format\n", outputFormat)
fmt.Fprintf(options.Out, "--output %v is not available in kubectl config get-contexts; resetting to default output format\n", outputFormat)
cmd.Flags().Set("output", "")
}
cmdutil.CheckErr(options.Complete(cmd, args, out))
cmdutil.CheckErr(options.Complete(cmd, args))
cmdutil.CheckErr(options.RunGetContexts())
},
}
cmdutil.AddOutputFlags(cmd)
cmdutil.AddNoHeadersFlags(cmd)
cmd.Flags().Bool("no-headers", false, "When using the default or custom-column output format, don't print headers (default print headers).")
cmd.Flags().StringP("output", "o", "", "Output format. One of: name")
return cmd
}
// Complete assigns GetContextsOptions from the args.
func (o *GetContextsOptions) Complete(cmd *cobra.Command, args []string, out io.Writer) error {
func (o *GetContextsOptions) Complete(cmd *cobra.Command, args []string) error {
o.contextNames = args
o.out = out
o.nameOnly = false
if cmdutil.GetFlagString(cmd, "output") == "name" {
o.nameOnly = true
@ -109,9 +115,9 @@ func (o GetContextsOptions) RunGetContexts() error {
return err
}
out, found := o.out.(*tabwriter.Writer)
out, found := o.Out.(*tabwriter.Writer)
if !found {
out = printers.GetNewTabWriter(o.out)
out = printers.GetNewTabWriter(o.Out)
defer out.Flush()
}

View File

@ -17,13 +17,13 @@ limitations under the License.
package config
import (
"bytes"
"io/ioutil"
"os"
"testing"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
type getContextsTest struct {
@ -157,11 +157,11 @@ func (test getContextsTest) run(t *testing.T) {
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = fakeKubeFile.Name()
pathOptions.EnvVar = ""
buf := bytes.NewBuffer([]byte{})
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
options := GetContextsOptions{
configAccess: pathOptions,
}
cmd := NewCmdConfigGetContexts(buf, options.configAccess)
cmd := NewCmdConfigGetContexts(streams, options.configAccess)
if test.nameOnly {
cmd.Flags().Set("output", "name")
}

View File

@ -33,11 +33,6 @@ import (
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
const (
cannotHaveStepsAfterError = "Cannot have steps after %v. %v are remaining"
additionStepRequiredUnlessUnsettingError = "Must have additional steps after %v unless you are unsetting it"
)
type setOptions struct {
configAccess clientcmd.ConfigAccess
propertyName string

View File

@ -18,8 +18,6 @@ package config
import (
"errors"
"fmt"
"io"
"github.com/spf13/cobra"
@ -27,18 +25,28 @@ import (
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/tools/clientcmd/api/latest"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
)
type ViewOptions struct {
PrintFlags *genericclioptions.PrintFlags
PrintObject printers.ResourcePrinterFunc
ConfigAccess clientcmd.ConfigAccess
Merge flag.Tristate
Flatten bool
Minify bool
RawByteData bool
Context string
OutputFormat string
genericclioptions.IOStreams
}
var (
@ -48,17 +56,25 @@ var (
You can use --output jsonpath={...} to extract specific values using a jsonpath expression.`)
view_example = templates.Examples(`
# Show Merged kubeconfig settings.
# Show merged kubeconfig settings.
kubectl config view
# Show merged kubeconfig settings and raw certificate data.
kubectl config view --raw
# Get the password for the e2e user
kubectl config view -o jsonpath='{.users[?(@.name == "e2e")].user.password}'`)
defaultOutputFormat = "yaml"
)
func NewCmdConfigView(f cmdutil.Factory, out, errOut io.Writer, ConfigAccess clientcmd.ConfigAccess) *cobra.Command {
options := &ViewOptions{ConfigAccess: ConfigAccess}
// Default to yaml
defaultOutputFormat := "yaml"
func NewCmdConfigView(f cmdutil.Factory, streams genericclioptions.IOStreams, ConfigAccess clientcmd.ConfigAccess) *cobra.Command {
o := &ViewOptions{
PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(legacyscheme.Scheme).WithDefaultOutput("yaml"),
ConfigAccess: ConfigAccess,
IOStreams: streams,
}
cmd := &cobra.Command{
Use: "view",
@ -66,46 +82,58 @@ func NewCmdConfigView(f cmdutil.Factory, out, errOut io.Writer, ConfigAccess cli
Long: view_long,
Example: view_example,
Run: func(cmd *cobra.Command, args []string) {
options.Complete()
outputFormat := cmdutil.GetFlagString(cmd, "output")
if outputFormat == "wide" {
fmt.Fprintf(errOut, "--output wide is not available in kubectl config view; reset to default output format (%s)\n\n", defaultOutputFormat)
// TODO: once printing is abstracted, this should be handled at flag declaration time
cmd.Flags().Set("output", defaultOutputFormat)
}
if outputFormat == "" {
fmt.Fprintf(errOut, "Reset to default output format (%s) as --output is empty\n", defaultOutputFormat)
// TODO: once printing is abstracted, this should be handled at flag declaration time
cmd.Flags().Set("output", defaultOutputFormat)
}
printOpts := cmdutil.ExtractCmdPrintOptions(cmd, false)
printer, err := cmdutil.PrinterForOptions(printOpts)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Run(out, printer))
cmdutil.CheckErr(o.Complete(cmd))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
}
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().Set("output", defaultOutputFormat)
o.PrintFlags.AddFlags(cmd)
options.Merge.Default(true)
mergeFlag := cmd.Flags().VarPF(&options.Merge, "merge", "", "Merge the full hierarchy of kubeconfig files")
o.Merge.Default(true)
mergeFlag := cmd.Flags().VarPF(&o.Merge, "merge", "", "Merge the full hierarchy of kubeconfig files")
mergeFlag.NoOptDefVal = "true"
cmd.Flags().BoolVar(&options.RawByteData, "raw", false, "Display raw byte data")
cmd.Flags().BoolVar(&options.Flatten, "flatten", false, "Flatten the resulting kubeconfig file into self-contained output (useful for creating portable kubeconfig files)")
cmd.Flags().BoolVar(&options.Minify, "minify", false, "Remove all information not used by current-context from the output")
cmd.Flags().BoolVar(&o.RawByteData, "raw", o.RawByteData, "Display raw byte data")
cmd.Flags().BoolVar(&o.Flatten, "flatten", o.Flatten, "Flatten the resulting kubeconfig file into self-contained output (useful for creating portable kubeconfig files)")
cmd.Flags().BoolVar(&o.Minify, "minify", o.Minify, "Remove all information not used by current-context from the output")
return cmd
}
func (o ViewOptions) Run(out io.Writer, printer printers.ResourcePrinter) error {
func (o *ViewOptions) Complete(cmd *cobra.Command) error {
if o.ConfigAccess.IsExplicitFile() {
if !o.Merge.Provided() {
o.Merge.Set("false")
}
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObject = printer.PrintObj
o.Context = cmdutil.GetFlagString(cmd, "context")
return nil
}
func (o ViewOptions) Validate() error {
if !o.Merge.Value() && !o.ConfigAccess.IsExplicitFile() {
return errors.New("if merge==false a precise file must to specified")
}
return nil
}
func (o ViewOptions) Run() error {
config, err := o.loadConfig()
if err != nil {
return err
}
if o.Minify {
if len(o.Context) > 0 {
config.CurrentContext = o.Context
}
if err := clientcmdapi.MinifyConfig(config); err != nil {
return err
}
@ -124,22 +152,7 @@ func (o ViewOptions) Run(out io.Writer, printer printers.ResourcePrinter) error
return err
}
err = printer.PrintObj(convertedObj, out)
if err != nil {
return err
}
return nil
}
func (o *ViewOptions) Complete() bool {
if o.ConfigAccess.IsExplicitFile() {
if !o.Merge.Provided() {
o.Merge.Set("false")
}
}
return true
return o.PrintObject(convertedObj, o.Out)
}
func (o ViewOptions) loadConfig() (*clientcmdapi.Config, error) {
@ -152,14 +165,6 @@ func (o ViewOptions) loadConfig() (*clientcmdapi.Config, error) {
return config, err
}
func (o ViewOptions) Validate() error {
if !o.Merge.Value() && !o.ConfigAccess.IsExplicitFile() {
return errors.New("if merge==false a precise file must to specified")
}
return nil
}
// getStartingConfig returns the Config object built from the sources specified by the options, the filename read (only if it was a single file), and an error if something goes wrong
func (o *ViewOptions) getStartingConfig() (*clientcmdapi.Config, error) {
switch {

View File

@ -17,7 +17,6 @@ limitations under the License.
package config
import (
"bytes"
"io/ioutil"
"os"
"testing"
@ -25,6 +24,7 @@ import (
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
type viewClusterTest struct {
@ -43,19 +43,19 @@ func TestViewCluster(t *testing.T) {
"my-cluster": {Server: "https://192.168.0.1:3434"},
},
Contexts: map[string]*clientcmdapi.Context{
"minikube": {AuthInfo: "minikube", Cluster: "minikube"},
"my-cluser": {AuthInfo: "mu-cluster", Cluster: "my-cluster"},
"minikube": {AuthInfo: "minikube", Cluster: "minikube"},
"my-cluster": {AuthInfo: "mu-cluster", Cluster: "my-cluster"},
},
CurrentContext: "minikube",
AuthInfos: map[string]*clientcmdapi.AuthInfo{
"minikube": {Token: "minikube-token"},
"my-cluser": {Token: "minikube-token"},
"minikube": {Token: "minikube-token"},
"mu-cluster": {Token: "minikube-token"},
},
}
test := viewClusterTest{
description: "Testing for kubectl config view",
config: conf,
flags: []string{},
expected: `apiVersion: v1
clusters:
- cluster:
@ -72,7 +72,7 @@ contexts:
- context:
cluster: my-cluster
user: mu-cluster
name: my-cluser
name: my-cluster
current-context: minikube
kind: Config
preferences: {}
@ -80,10 +80,13 @@ users:
- name: minikube
user:
token: minikube-token
- name: my-cluser
- name: mu-cluster
user:
token: minikube-token` + "\n"}
token: minikube-token` + "\n",
}
test.run(t)
}
func TestViewClusterMinify(t *testing.T) {
@ -95,20 +98,27 @@ func TestViewClusterMinify(t *testing.T) {
"my-cluster": {Server: "https://192.168.0.1:3434"},
},
Contexts: map[string]*clientcmdapi.Context{
"minikube": {AuthInfo: "minikube", Cluster: "minikube"},
"my-cluser": {AuthInfo: "mu-cluster", Cluster: "my-cluster"},
"minikube": {AuthInfo: "minikube", Cluster: "minikube"},
"my-cluster": {AuthInfo: "mu-cluster", Cluster: "my-cluster"},
},
CurrentContext: "minikube",
AuthInfos: map[string]*clientcmdapi.AuthInfo{
"minikube": {Token: "minikube-token"},
"my-cluser": {Token: "minikube-token"},
"minikube": {Token: "minikube-token"},
"mu-cluster": {Token: "minikube-token"},
},
}
test := viewClusterTest{
description: "Testing for kubectl config view --minify=true",
config: conf,
flags: []string{"--minify=true"},
expected: `apiVersion: v1
testCases := []struct {
description string
config clientcmdapi.Config
flags []string
expected string
}{
{
description: "Testing for kubectl config view --minify=true",
config: conf,
flags: []string{"--minify=true"},
expected: `apiVersion: v1
clusters:
- cluster:
server: https://192.168.99.100:8443
@ -124,8 +134,41 @@ preferences: {}
users:
- name: minikube
user:
token: minikube-token` + "\n"}
test.run(t)
token: minikube-token` + "\n",
},
{
description: "Testing for kubectl config view --minify=true --context=my-cluster",
config: conf,
flags: []string{"--minify=true", "--context=my-cluster"},
expected: `apiVersion: v1
clusters:
- cluster:
server: https://192.168.0.1:3434
name: my-cluster
contexts:
- context:
cluster: my-cluster
user: mu-cluster
name: my-cluster
current-context: my-cluster
kind: Config
preferences: {}
users:
- name: mu-cluster
user:
token: minikube-token` + "\n",
},
}
for _, test := range testCases {
cmdTest := viewClusterTest{
description: test.description,
config: test.config,
flags: test.flags,
expected: test.expected,
}
cmdTest.run(t)
}
}
func (test viewClusterTest) run(t *testing.T) {
@ -141,10 +184,12 @@ func (test viewClusterTest) run(t *testing.T) {
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = fakeKubeFile.Name()
pathOptions.EnvVar = ""
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfigView(cmdutil.NewFactory(nil), buf, errBuf, pathOptions)
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdConfigView(cmdutil.NewFactory(genericclioptions.NewTestConfigFlags()), streams, pathOptions)
// "context" is a global flag, inherited from base kubectl command in the real world
cmd.Flags().String("context", "", "The name of the kubeconfig context to use")
cmd.Flags().Parse(test.flags)
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpected error executing command: %v,kubectl config view flags: %v", err, test.flags)
}

View File

@ -18,7 +18,6 @@ package cmd
import (
"fmt"
"io"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
@ -27,9 +26,10 @@ import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
"github.com/golang/glog"
"github.com/spf13/cobra"
@ -61,8 +61,8 @@ var (
// NewCmdConvert creates a command object for the generic "convert" action, which
// translates the config file into a given version.
func NewCmdConvert(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &ConvertOptions{}
func NewCmdConvert(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := NewConvertOptions(ioStreams)
cmd := &cobra.Command{
Use: "convert -f FILENAME",
@ -71,37 +71,44 @@ func NewCmdConvert(f cmdutil.Factory, out io.Writer) *cobra.Command {
Long: convert_long,
Example: convert_example,
Run: func(cmd *cobra.Command, args []string) {
err := options.Complete(f, out, cmd)
cmdutil.CheckErr(err)
err = options.RunConvert()
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd))
cmdutil.CheckErr(options.RunConvert())
},
}
options.PrintFlags.AddFlags(cmd)
usage := "to need to get converted."
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.MarkFlagRequired("filename")
cmdutil.AddValidateFlags(cmd)
cmdutil.AddNonDeprecatedPrinterFlags(cmd)
cmd.Flags().BoolVar(&options.local, "local", true, "If true, convert will NOT try to contact api-server but run locally.")
cmd.Flags().BoolVar(&options.local, "local", options.local, "If true, convert will NOT try to contact api-server but run locally.")
cmd.Flags().String("output-version", "", i18n.T("Output the formatted object with the given group version (for ex: 'extensions/v1beta1').)"))
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
// ConvertOptions have the data required to perform the convert operation
type ConvertOptions struct {
PrintFlags *genericclioptions.PrintFlags
PrintObj printers.ResourcePrinterFunc
resource.FilenameOptions
builder *resource.Builder
local bool
out io.Writer
printer printers.ResourcePrinter
genericclioptions.IOStreams
specifiedOutputVersion schema.GroupVersion
}
func NewConvertOptions(ioStreams genericclioptions.IOStreams) *ConvertOptions {
return &ConvertOptions{
PrintFlags: genericclioptions.NewPrintFlags("converted").WithTypeSetter(scheme.Scheme).WithDefaultOutput("yaml"),
local: true,
IOStreams: ioStreams,
}
}
// outputVersion returns the preferred output version for generic content (JSON, YAML, or templates)
// defaultVersion is never mutated. Nil simply allows clean passing in common usage from client.Config
func outputVersion(cmd *cobra.Command) (schema.GroupVersion, error) {
@ -114,7 +121,7 @@ func outputVersion(cmd *cobra.Command) (schema.GroupVersion, error) {
}
// Complete collects information required to run Convert command from command line.
func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) (err error) {
func (o *ConvertOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) (err error) {
o.specifiedOutputVersion, err = outputVersion(cmd)
if err != nil {
return err
@ -122,7 +129,7 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.C
// build the builder
o.builder = f.NewBuilder().
Internal().
WithScheme(scheme.Scheme).
LocalParam(o.local)
if !o.local {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
@ -132,7 +139,7 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.C
o.builder.Schema(schema)
}
cmdNamespace, _, err := f.DefaultNamespace()
cmdNamespace, _, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
@ -142,20 +149,12 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.C
Flatten()
// build the printer
o.out = out
outputFormat := cmdutil.GetFlagString(cmd, "output")
templateFile := cmdutil.GetFlagString(cmd, "template")
if len(outputFormat) == 0 {
if len(templateFile) == 0 {
outputFormat = "yaml"
} else {
outputFormat = "template"
}
// TODO: once printing is abstracted, this should be handled at flag declaration time
cmd.Flags().Set("output", outputFormat)
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.printer, err = cmdutil.PrinterForOptions(cmdutil.ExtractCmdPrintOptions(cmd, false))
return err
o.PrintObj = printer.PrintObj
return nil
}
// RunConvert implements the generic Convert command
@ -182,18 +181,14 @@ func (o *ConvertOptions) RunConvert() error {
}
if meta.IsListType(objects) {
_, items, err := cmdutil.FilterResourceList(objects, nil, nil)
obj, err := objectListToVersionedObject([]runtime.Object{objects}, o.specifiedOutputVersion)
if err != nil {
return err
}
filteredObj, err := objectListToVersionedObject(items, o.specifiedOutputVersion)
if err != nil {
return err
}
return o.printer.PrintObj(filteredObj, o.out)
return o.PrintObj(obj, o.Out)
}
return o.printer.PrintObj(objects, o.out)
return o.PrintObj(objects, o.Out)
}
// objectListToVersionedObject receives a list of api objects and a group version
@ -204,7 +199,7 @@ func objectListToVersionedObject(objects []runtime.Object, specifiedOutputVersio
if !specifiedOutputVersion.Empty() {
targetVersions = append(targetVersions, specifiedOutputVersion)
}
targetVersions = append(targetVersions, scheme.Registry.GroupOrDie(api.GroupName).GroupVersion)
targetVersions = append(targetVersions, schema.GroupVersion{Group: "", Version: "v1"})
converted, err := tryConvert(scheme.Scheme, objectList, targetVersions...)
if err != nil {
return nil, err
@ -231,7 +226,7 @@ func asVersionedObject(infos []*resource.Info, forceList bool, specifiedOutputVe
if !specifiedOutputVersion.Empty() {
targetVersions = append(targetVersions, specifiedOutputVersion)
}
targetVersions = append(targetVersions, scheme.Registry.GroupOrDie(api.GroupName).GroupVersion)
targetVersions = append(targetVersions, schema.GroupVersion{Group: "", Version: "v1"})
converted, err := tryConvert(scheme.Scheme, object, targetVersions...)
if err != nil {
@ -280,14 +275,14 @@ func asVersionedObjects(infos []*resource.Info, specifiedOutputVersion schema.Gr
gvks, _, err := scheme.Scheme.ObjectKinds(info.Object)
if err == nil {
for _, gvk := range gvks {
for _, version := range scheme.Registry.EnabledVersionsForGroup(gvk.Group) {
for _, version := range scheme.Scheme.PrioritizedVersionsForGroup(gvk.Group) {
targetVersions = append(targetVersions, version)
}
}
}
}
converted, err := tryConvert(info.Mapping.ObjectConvertor, info.Object, targetVersions...)
converted, err := tryConvert(scheme.Scheme, info.Object, targetVersions...)
if err != nil {
return nil, err
}

View File

@ -20,10 +20,12 @@ import (
"bytes"
"fmt"
"net/http"
"strings"
"testing"
"k8s.io/client-go/rest/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
type testcase struct {
@ -34,7 +36,6 @@ type testcase struct {
}
type checkField struct {
template string
expected string
}
@ -46,8 +47,7 @@ func TestConvertObject(t *testing.T) {
outputVersion: "extensions/v1beta1",
fields: []checkField{
{
template: "{{.apiVersion}}",
expected: "extensions/v1beta1",
expected: "apiVersion: extensions/v1beta1",
},
},
},
@ -57,8 +57,7 @@ func TestConvertObject(t *testing.T) {
outputVersion: "apps/v1beta2",
fields: []checkField{
{
template: "{{.apiVersion}}",
expected: "apps/v1beta2",
expected: "apiVersion: apps/v1beta2",
},
},
},
@ -68,16 +67,13 @@ func TestConvertObject(t *testing.T) {
outputVersion: "autoscaling/v2beta1",
fields: []checkField{
{
template: "{{.apiVersion}}",
expected: "autoscaling/v2beta1",
expected: "apiVersion: autoscaling/v2beta1",
},
{
template: "{{(index .spec.metrics 0).resource.name}}",
expected: "cpu",
expected: "name: cpu",
},
{
template: "{{(index .spec.metrics 0).resource.targetAverageUtilization}}",
expected: "50",
expected: "targetAverageUtilization: 50",
},
},
},
@ -87,12 +83,10 @@ func TestConvertObject(t *testing.T) {
outputVersion: "autoscaling/v1",
fields: []checkField{
{
template: "{{.apiVersion}}",
expected: "autoscaling/v1",
expected: "apiVersion: autoscaling/v1",
},
{
template: "{{.spec.targetCPUUtilizationPercentage}}",
expected: "50",
expected: "targetCPUUtilizationPercentage: 50",
},
},
},
@ -101,23 +95,24 @@ func TestConvertObject(t *testing.T) {
for _, tc := range testcases {
for _, field := range tc.fields {
t.Run(fmt.Sprintf("%s %s", tc.name, field), func(t *testing.T) {
tf := cmdtesting.NewTestFactory()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.UnstructuredClient = &fake.RESTClient{
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConvert(tf, buf)
cmd := NewCmdConvert(tf, genericclioptions.IOStreams{Out: buf, ErrOut: buf})
cmd.Flags().Set("filename", tc.file)
cmd.Flags().Set("output-version", tc.outputVersion)
cmd.Flags().Set("local", "true")
cmd.Flags().Set("output", "go-template="+field.template)
cmd.Flags().Set("output", "yaml")
cmd.Run(cmd, []string{})
if buf.String() != field.expected {
if !strings.Contains(buf.String(), field.expected) {
t.Errorf("unexpected output when converting %s to %q, expected: %q, but got %q", tc.file, tc.outputVersion, field.expected, buf.String())
}
})

View File

@ -18,7 +18,6 @@ package cmd
import (
"archive/tar"
"bytes"
"errors"
"fmt"
"io"
@ -28,10 +27,15 @@ import (
"path/filepath"
"strings"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"bytes"
"github.com/renstrom/dedent"
"github.com/spf13/cobra"
)
@ -61,8 +65,26 @@ var (
/file/path for a local file`)
)
type CopyOptions struct {
Container string
Namespace string
ClientConfig *restclient.Config
Clientset internalclientset.Interface
genericclioptions.IOStreams
}
func NewCopyOptions(ioStreams genericclioptions.IOStreams) *CopyOptions {
return &CopyOptions{
IOStreams: ioStreams,
}
}
// NewCmdCp creates a new Copy command.
func NewCmdCp(f cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.Command {
func NewCmdCp(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewCopyOptions(ioStreams)
cmd := &cobra.Command{
Use: "cp <file-spec-src> <file-spec-dest>",
DisableFlagsInUseLine: true,
@ -70,10 +92,11 @@ func NewCmdCp(f cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.Command {
Long: "Copy files and directories to and from containers.",
Example: cpExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(runCopy(f, cmd, cmdOut, cmdErr, args))
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(o.Run(args))
},
}
cmd.Flags().StringP("container", "c", "", "Container name. If omitted, the first container in the pod will be chosen")
cmd.Flags().StringVarP(&o.Container, "container", "c", o.Container, "Container name. If omitted, the first container in the pod will be chosen")
return cmd
}
@ -90,39 +113,60 @@ var (
)
func extractFileSpec(arg string) (fileSpec, error) {
pieces := strings.Split(arg, ":")
if len(pieces) == 1 {
if i := strings.Index(arg, ":"); i == -1 {
return fileSpec{File: arg}, nil
}
if len(pieces) != 2 {
// FIXME Kubernetes can't copy files that contain a ':'
// character.
return fileSpec{}, errFileSpecDoesntMatchFormat
}
file := pieces[1]
pieces = strings.Split(pieces[0], "/")
if len(pieces) == 1 {
return fileSpec{
PodName: pieces[0],
File: file,
}, nil
}
if len(pieces) == 2 {
return fileSpec{
PodNamespace: pieces[0],
PodName: pieces[1],
File: file,
}, nil
} else if i > 0 {
file := arg[i+1:]
pod := arg[:i]
pieces := strings.Split(pod, "/")
if len(pieces) == 1 {
return fileSpec{
PodName: pieces[0],
File: file,
}, nil
}
if len(pieces) == 2 {
return fileSpec{
PodNamespace: pieces[0],
PodName: pieces[1],
File: file,
}, nil
}
}
return fileSpec{}, errFileSpecDoesntMatchFormat
}
func runCopy(f cmdutil.Factory, cmd *cobra.Command, out, cmderr io.Writer, args []string) error {
func (o *CopyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
var err error
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
o.Clientset, err = f.ClientSet()
if err != nil {
return err
}
o.ClientConfig, err = f.ToRESTConfig()
if err != nil {
return err
}
return nil
}
func (o *CopyOptions) Validate(cmd *cobra.Command, args []string) error {
if len(args) != 2 {
return cmdutil.UsageErrorf(cmd, cpUsageStr)
}
return nil
}
func (o *CopyOptions) Run(args []string) error {
if len(args) < 2 {
return fmt.Errorf("source and destination are required")
}
srcSpec, err := extractFileSpec(args[0])
if err != nil {
return err
@ -131,24 +175,34 @@ func runCopy(f cmdutil.Factory, cmd *cobra.Command, out, cmderr io.Writer, args
if err != nil {
return err
}
if len(srcSpec.PodName) != 0 && len(destSpec.PodName) != 0 {
if _, err := os.Stat(args[0]); err == nil {
return o.copyToPod(fileSpec{File: args[0]}, destSpec)
}
return fmt.Errorf("src doesn't exist in local filesystem")
}
if len(srcSpec.PodName) != 0 {
return copyFromPod(f, cmd, cmderr, srcSpec, destSpec)
return o.copyFromPod(srcSpec, destSpec)
}
if len(destSpec.PodName) != 0 {
return copyToPod(f, cmd, out, cmderr, srcSpec, destSpec)
return o.copyToPod(srcSpec, destSpec)
}
return cmdutil.UsageErrorf(cmd, "One of src or dest must be a remote file specification")
return fmt.Errorf("one of src or dest must be a remote file specification")
}
// checkDestinationIsDir receives a destination fileSpec and
// determines if the provided destination path exists on the
// pod. If the destination path does not exist or is _not_ a
// directory, an error is returned with the exit code received.
func checkDestinationIsDir(dest fileSpec, f cmdutil.Factory, cmd *cobra.Command) error {
func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error {
options := &ExecOptions{
StreamOptions: StreamOptions{
Out: bytes.NewBuffer([]byte{}),
Err: bytes.NewBuffer([]byte{}),
IOStreams: genericclioptions.IOStreams{
Out: bytes.NewBuffer([]byte{}),
ErrOut: bytes.NewBuffer([]byte{}),
},
Namespace: dest.PodNamespace,
PodName: dest.PodName,
@ -158,21 +212,21 @@ func checkDestinationIsDir(dest fileSpec, f cmdutil.Factory, cmd *cobra.Command)
Executor: &DefaultRemoteExecutor{},
}
return execute(f, cmd, options)
return o.execute(options)
}
func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer, src, dest fileSpec) error {
if len(src.File) == 0 {
func (o *CopyOptions) copyToPod(src, dest fileSpec) error {
if len(src.File) == 0 || len(dest.File) == 0 {
return errFileCannotBeEmpty
}
reader, writer := io.Pipe()
// strip trailing slash (if any)
if strings.HasSuffix(string(dest.File[len(dest.File)-1]), "/") {
if dest.File != "/" && strings.HasSuffix(string(dest.File[len(dest.File)-1]), "/") {
dest.File = dest.File[:len(dest.File)-1]
}
if err := checkDestinationIsDir(dest, f, cmd); err == nil {
if err := o.checkDestinationIsDir(dest); err == nil {
// If no error, dest.File was found to be a directory.
// Copy specified src into it
dest.File = dest.File + "/" + path.Base(src.File)
@ -193,9 +247,11 @@ func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer,
options := &ExecOptions{
StreamOptions: StreamOptions{
In: reader,
Out: stdout,
Err: stderr,
IOStreams: genericclioptions.IOStreams{
In: reader,
Out: o.Out,
ErrOut: o.ErrOut,
},
Stdin: true,
Namespace: dest.PodNamespace,
@ -205,20 +261,22 @@ func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer,
Command: cmdArr,
Executor: &DefaultRemoteExecutor{},
}
return execute(f, cmd, options)
return o.execute(options)
}
func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, cmderr io.Writer, src, dest fileSpec) error {
if len(src.File) == 0 {
func (o *CopyOptions) copyFromPod(src, dest fileSpec) error {
if len(src.File) == 0 || len(dest.File) == 0 {
return errFileCannotBeEmpty
}
reader, outStream := io.Pipe()
options := &ExecOptions{
StreamOptions: StreamOptions{
In: nil,
Out: outStream,
Err: cmderr,
IOStreams: genericclioptions.IOStreams{
In: nil,
Out: outStream,
ErrOut: o.Out,
},
Namespace: src.PodNamespace,
PodName: src.PodName,
@ -231,13 +289,26 @@ func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, cmderr io.Writer, src, d
go func() {
defer outStream.Close()
execute(f, cmd, options)
o.execute(options)
}()
prefix := getPrefix(src.File)
prefix = path.Clean(prefix)
// remove extraneous path shortcuts - these could occur if a path contained extra "../"
// and attempted to navigate beyond "/" in a remote filesystem
prefix = stripPathShortcuts(prefix)
return untarAll(reader, dest.File, prefix)
}
// stripPathShortcuts removes any leading or trailing "../" from a given path
func stripPathShortcuts(p string) string {
newPath := path.Clean(p)
if len(newPath) > 0 && string(newPath[0]) == "/" {
return newPath[1:]
}
return newPath
}
func makeTar(srcPath, destPath string, writer io.Writer) error {
// TODO: use compression here?
tarWriter := tar.NewWriter(writer)
@ -312,6 +383,12 @@ func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) e
return nil
}
// clean prevents path traversals by stripping them out.
// This is adapted from https://golang.org/src/net/http/fs.go#L74
func clean(fileName string) string {
return path.Clean(string(os.PathSeparator) + fileName)
}
func untarAll(reader io.Reader, destFile, prefix string) error {
entrySeq := -1
@ -327,7 +404,7 @@ func untarAll(reader io.Reader, destFile, prefix string) error {
}
entrySeq++
mode := header.FileInfo().Mode()
outFileName := path.Join(destFile, header.Name[len(prefix):])
outFileName := path.Join(destFile, clean(header.Name[len(prefix):]))
baseName := path.Dir(outFileName)
if err := os.MkdirAll(baseName, 0755); err != nil {
return err
@ -346,7 +423,7 @@ func untarAll(reader io.Reader, destFile, prefix string) error {
return err
}
if exists {
outFileName = filepath.Join(outFileName, path.Base(header.Name))
outFileName = filepath.Join(outFileName, path.Base(clean(header.Name)))
}
}
@ -383,31 +460,17 @@ func getPrefix(file string) string {
return strings.TrimLeft(file, "/")
}
func execute(f cmdutil.Factory, cmd *cobra.Command, options *ExecOptions) error {
func (o *CopyOptions) execute(options *ExecOptions) error {
if len(options.Namespace) == 0 {
namespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}
options.Namespace = namespace
options.Namespace = o.Namespace
}
container := cmdutil.GetFlagString(cmd, "container")
if len(container) > 0 {
options.ContainerName = container
if len(o.Container) > 0 {
options.ContainerName = o.Container
}
config, err := f.ClientConfig()
if err != nil {
return err
}
options.Config = config
clientset, err := f.ClientSet()
if err != nil {
return err
}
options.PodClient = clientset.Core()
options.Config = o.ClientConfig
options.PodClient = o.Clientset.Core()
if err := options.Validate(); err != nil {
return err

View File

@ -21,12 +21,23 @@ import (
"bytes"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
type FileType int
@ -60,9 +71,18 @@ func TestExtractFileSpec(t *testing.T) {
expectedFile: "/some/file",
},
{
spec: "some:bad:spec",
spec: ":file:not:exist:in:local:filesystem",
expectErr: true,
},
{
spec: "namespace/pod/invalid:/some/file",
expectErr: true,
},
{
spec: "pod:/some/filenamewith:in",
expectedPod: "pod",
expectedFile: "/some/filenamewith:in",
},
}
for _, test := range tests {
spec, err := extractFileSpec(test.spec)
@ -422,3 +442,180 @@ func TestTarDestinationName(t *testing.T) {
}
}
}
func TestBadTar(t *testing.T) {
dir, err := ioutil.TempDir(os.TempDir(), "dest")
if err != nil {
t.Errorf("unexpected error: %v ", err)
t.FailNow()
}
defer os.RemoveAll(dir)
// More or less cribbed from https://golang.org/pkg/archive/tar/#example__minimal
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
var files = []struct {
name string
body string
}{
{"/prefix/../../../tmp/foo", "Up to temp"},
{"/prefix/foo/bar/../../home/bburns/names.txt", "Down and back"},
}
for _, file := range files {
hdr := &tar.Header{
Name: file.name,
Mode: 0600,
Size: int64(len(file.body)),
}
if err := tw.WriteHeader(hdr); err != nil {
t.Errorf("unexpected error: %v ", err)
t.FailNow()
}
if _, err := tw.Write([]byte(file.body)); err != nil {
t.Errorf("unexpected error: %v ", err)
t.FailNow()
}
}
if err := tw.Close(); err != nil {
t.Errorf("unexpected error: %v ", err)
t.FailNow()
}
if err := untarAll(&buf, dir, "/prefix"); err != nil {
t.Errorf("unexpected error: %v ", err)
t.FailNow()
}
for _, file := range files {
_, err := os.Stat(path.Join(dir, path.Clean(file.name[len("/prefix"):])))
if err != nil {
t.Errorf("Error finding file: %v", err)
}
}
}
func TestClean(t *testing.T) {
tests := []struct {
input string
cleaned string
}{
{
"../../../tmp/foo",
"/tmp/foo",
},
{
"/../../../tmp/foo",
"/tmp/foo",
},
}
for _, test := range tests {
out := clean(test.input)
if out != test.cleaned {
t.Errorf("Expected: %s, saw %s", test.cleaned, out)
}
}
}
func TestCopyToPod(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
ns := legacyscheme.Codecs
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
responsePod := &v1.Pod{}
return &http.Response{StatusCode: http.StatusNotFound, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, responsePod))))}, nil
}),
}
tf.ClientConfigVal = defaultClientConfig()
ioStreams, _, _, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCp(tf, ioStreams)
srcFile, err := ioutil.TempDir("", "test")
if err != nil {
t.Errorf("unexpected error: %v", err)
t.FailNow()
}
defer os.RemoveAll(srcFile)
tests := map[string]struct {
dest string
expectedErr bool
}{
"copy to directory": {
dest: "/tmp/",
expectedErr: false,
},
"copy to root": {
dest: "/",
expectedErr: false,
},
"copy to empty file name": {
dest: "",
expectedErr: true,
},
}
for name, test := range tests {
opts := NewCopyOptions(ioStreams)
src := fileSpec{
File: srcFile,
}
dest := fileSpec{
PodNamespace: "pod-ns",
PodName: "pod-name",
File: test.dest,
}
opts.Complete(tf, cmd)
t.Run(name, func(t *testing.T) {
err = opts.copyToPod(src, dest)
//If error is NotFound error , it indicates that the
//request has been sent correctly.
//Treat this as no error.
if test.expectedErr && errors.IsNotFound(err) {
t.Errorf("expected error but didn't get one")
}
if !test.expectedErr && !errors.IsNotFound(err) {
t.Errorf("unexpected error: %v", err)
}
})
}
}
func TestValidate(t *testing.T) {
tests := []struct {
name string
args []string
expectedErr bool
}{
{
name: "Validate Succeed",
args: []string{"1", "2"},
expectedErr: false,
},
{
name: "Validate Fail",
args: []string{"1", "2", "3"},
expectedErr: true,
},
}
tf := cmdtesting.NewTestFactory()
ioStreams, _, _, _ := genericclioptions.NewTestIOStreams()
opts := NewCopyOptions(ioStreams)
cmd := NewCmdCp(tf, ioStreams)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := opts.Validate(cmd, test.args)
if (err != nil) != test.expectedErr {
t.Errorf("expected error: %v, saw: %v, error: %v", test.expectedErr, err != nil, err)
}
})
}
}

View File

@ -1,362 +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 cmd
import (
"fmt"
"io"
"os"
"runtime"
"strings"
"github.com/spf13/cobra"
"net/url"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
type CreateOptions struct {
FilenameOptions resource.FilenameOptions
Selector string
EditBeforeCreate bool
Raw string
Out io.Writer
ErrOut io.Writer
}
var (
createLong = templates.LongDesc(i18n.T(`
Create a resource from a file or from stdin.
JSON and YAML formats are accepted.`))
createExample = templates.Examples(i18n.T(`
# Create a pod using the data in pod.json.
kubectl create -f ./pod.json
# Create a pod based on the JSON passed into stdin.
cat pod.json | kubectl create -f -
# Edit the data in docker-registry.yaml in JSON using the v1 API format then create the resource using the edited data.
kubectl create -f docker-registry.yaml --edit --output-version=v1 -o json`))
)
func NewCmdCreate(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := &CreateOptions{
Out: out,
ErrOut: errOut,
}
cmd := &cobra.Command{
Use: "create -f FILENAME",
DisableFlagsInUseLine: true,
Short: i18n.T("Create a resource from a file or from stdin."),
Long: createLong,
Example: createExample,
Run: func(cmd *cobra.Command, args []string) {
if cmdutil.IsFilenameSliceEmpty(options.FilenameOptions.Filenames) {
defaultRunFunc := cmdutil.DefaultSubCommandRun(errOut)
defaultRunFunc(cmd, args)
return
}
cmdutil.CheckErr(options.ValidateArgs(cmd, args))
cmdutil.CheckErr(options.RunCreate(f, cmd))
},
}
usage := "to use to create the resource"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.MarkFlagRequired("filename")
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().BoolVar(&options.EditBeforeCreate, "edit", false, "Edit the API resource before creating")
cmd.Flags().Bool("windows-line-endings", runtime.GOOS == "windows",
"Only relevant if --edit=true. Defaults to the line ending native to your platform.")
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().StringVar(&options.Raw, "raw", options.Raw, "Raw URI to POST to the server. Uses the transport specified by the kubeconfig file.")
// create subcommands
cmd.AddCommand(NewCmdCreateNamespace(f, out))
cmd.AddCommand(NewCmdCreateQuota(f, out))
cmd.AddCommand(NewCmdCreateSecret(f, out, errOut))
cmd.AddCommand(NewCmdCreateConfigMap(f, out))
cmd.AddCommand(NewCmdCreateServiceAccount(f, out))
cmd.AddCommand(NewCmdCreateService(f, out, errOut))
cmd.AddCommand(NewCmdCreateDeployment(f, out, errOut))
cmd.AddCommand(NewCmdCreateClusterRole(f, out))
cmd.AddCommand(NewCmdCreateClusterRoleBinding(f, out))
cmd.AddCommand(NewCmdCreateRole(f, out))
cmd.AddCommand(NewCmdCreateRoleBinding(f, out))
cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, out))
cmd.AddCommand(NewCmdCreatePriorityClass(f, out))
cmd.AddCommand(NewCmdCreateJob(f, out))
return cmd
}
func (o *CreateOptions) ValidateArgs(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args)
}
if len(o.Raw) > 0 {
if o.EditBeforeCreate {
return cmdutil.UsageErrorf(cmd, "--raw and --edit are mutually exclusive")
}
if len(o.FilenameOptions.Filenames) != 1 {
return cmdutil.UsageErrorf(cmd, "--raw can only use a single local file or stdin")
}
if strings.Index(o.FilenameOptions.Filenames[0], "http://") == 0 || strings.Index(o.FilenameOptions.Filenames[0], "https://") == 0 {
return cmdutil.UsageErrorf(cmd, "--raw cannot read from a url")
}
if o.FilenameOptions.Recursive {
return cmdutil.UsageErrorf(cmd, "--raw and --recursive are mutually exclusive")
}
if len(o.Selector) > 0 {
return cmdutil.UsageErrorf(cmd, "--raw and --selector (-l) are mutually exclusive")
}
if len(cmdutil.GetFlagString(cmd, "output")) > 0 {
return cmdutil.UsageErrorf(cmd, "--raw and --output are mutually exclusive")
}
if _, err := url.ParseRequestURI(o.Raw); err != nil {
return cmdutil.UsageErrorf(cmd, "--raw must be a valid URL path: %v", err)
}
}
return nil
}
func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error {
// raw only makes sense for a single file resource multiple objects aren't likely to do what you want.
// the validator enforces this, so
if len(o.Raw) > 0 {
return o.raw(f)
}
if o.EditBeforeCreate {
return RunEditOnCreate(f, o.Out, o.ErrOut, cmd, &o.FilenameOptions)
}
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
if err != nil {
return err
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
r := f.NewBuilder().
Unstructured().
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
LabelSelectorParam(o.Selector).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
dryRun := cmdutil.GetDryRunFlag(cmd)
output := cmdutil.GetFlagString(cmd, "output")
count := 0
err = r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, cmdutil.InternalVersionJSONEncoder()); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
if cmdutil.ShouldRecord(cmd, info) {
if err := cmdutil.RecordChangeCause(info.Object, f.Command(cmd, false)); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
}
if !dryRun {
if err := createAndRefresh(info); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
}
count++
shortOutput := output == "name"
if len(output) > 0 && !shortOutput {
return cmdutil.PrintObject(cmd, info.Object, o.Out)
}
cmdutil.PrintSuccess(shortOutput, o.Out, info.Object, dryRun, "created")
return nil
})
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("no objects passed to create")
}
return nil
}
// raw makes a simple HTTP request to the provided path on the server using the default
// credentials.
func (o *CreateOptions) raw(f cmdutil.Factory) error {
restClient, err := f.RESTClient()
if err != nil {
return err
}
var data io.ReadCloser
if o.FilenameOptions.Filenames[0] == "-" {
data = os.Stdin
} else {
data, err = os.Open(o.FilenameOptions.Filenames[0])
if err != nil {
return err
}
}
// TODO post content with stream. Right now it ignores body content
bytes, err := restClient.Post().RequestURI(o.Raw).Body(data).DoRaw()
if err != nil {
return err
}
fmt.Fprintf(o.Out, "%v", string(bytes))
return nil
}
func RunEditOnCreate(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, options *resource.FilenameOptions) error {
editOptions := &editor.EditOptions{
EditMode: editor.EditBeforeCreateMode,
FilenameOptions: *options,
ValidateOptions: cmdutil.ValidateOptions{
EnableValidation: cmdutil.GetFlagBool(cmd, "validate"),
},
Output: cmdutil.GetFlagString(cmd, "output"),
WindowsLineEndings: cmdutil.GetFlagBool(cmd, "windows-line-endings"),
ApplyAnnotation: cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag),
Record: cmdutil.GetFlagBool(cmd, "record"),
ChangeCause: f.Command(cmd, false),
Include3rdParty: cmdutil.GetFlagBool(cmd, "include-extended-apis"),
}
err := editOptions.Complete(f, out, errOut, []string{}, cmd)
if err != nil {
return err
}
return editOptions.Run()
}
// createAndRefresh creates an object from input info and refreshes info with that object
func createAndRefresh(info *resource.Info) error {
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
if err != nil {
return err
}
info.Refresh(obj, true)
return nil
}
// NameFromCommandArgs is a utility function for commands that assume the first argument is a resource name
func NameFromCommandArgs(cmd *cobra.Command, args []string) (string, error) {
if len(args) != 1 {
return "", cmdutil.UsageErrorf(cmd, "exactly one NAME is required, got %d", len(args))
}
return args[0], nil
}
// CreateSubcommandOptions is an options struct to support create subcommands
type CreateSubcommandOptions struct {
// Name of resource being created
Name string
// StructuredGenerator is the resource generator for the object being created
StructuredGenerator kubectl.StructuredGenerator
// DryRun is true if the command should be simulated but not run against the server
DryRun bool
OutputFormat string
}
// RunCreateSubcommand executes a create subcommand using the specified options
func RunCreateSubcommand(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *CreateSubcommandOptions) error {
namespace, nsOverriden, err := f.DefaultNamespace()
if err != nil {
return err
}
obj, err := options.StructuredGenerator.StructuredGenerate()
if err != nil {
return err
}
mapper, typer := f.Object()
gvks, _, err := typer.ObjectKinds(obj)
if err != nil {
return err
}
gvk := gvks[0]
mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version)
if err != nil {
return err
}
client, err := f.ClientForMapping(mapping)
if err != nil {
return err
}
resourceMapper := &resource.Mapper{
ObjectTyper: typer,
RESTMapper: mapper,
ClientMapper: resource.ClientMapperFunc(f.ClientForMapping),
}
info, err := resourceMapper.InfoForObject(obj, nil)
if err != nil {
return err
}
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, cmdutil.InternalVersionJSONEncoder()); err != nil {
return err
}
obj = info.Object
if !options.DryRun {
obj, err = resource.NewHelper(client, mapping).Create(namespace, false, info.Object)
if err != nil {
return err
}
} else {
if meta, err := meta.Accessor(obj); err == nil && nsOverriden {
meta.SetNamespace(namespace)
}
}
if useShortOutput := options.OutputFormat == "name"; useShortOutput || len(options.OutputFormat) == 0 {
cmdutil.PrintSuccess(useShortOutput, out, info.Object, options.DryRun, "created")
return nil
}
return cmdutil.PrintObject(cmd, obj, out)
}

117
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create/BUILD generated vendored Normal file
View File

@ -0,0 +1,117 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"create.go",
"create_clusterrole.go",
"create_clusterrolebinding.go",
"create_configmap.go",
"create_deployment.go",
"create_job.go",
"create_namespace.go",
"create_pdb.go",
"create_priorityclass.go",
"create_quota.go",
"create_role.go",
"create_rolebinding.go",
"create_secret.go",
"create_service.go",
"create_serviceaccount.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/create",
visibility = ["//build/visible_to:pkg_kubectl_cmd_create_CONSUMERS"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/cmd/util/editor:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/printers:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/batch/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"create_clusterrole_test.go",
"create_clusterrolebinding_test.go",
"create_configmap_test.go",
"create_deployment_test.go",
"create_job_test.go",
"create_namespace_test.go",
"create_pdb_test.go",
"create_priorityclass_test.go",
"create_quota_test.go",
"create_role_test.go",
"create_rolebinding_test.go",
"create_secret_test.go",
"create_service_test.go",
"create_serviceaccount_test.go",
"create_test.go",
],
data = [
"//test/e2e/testing-manifests:all-srcs",
],
embed = [":go_default_library"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testing:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,451 @@
/*
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 create
import (
"fmt"
"io"
"net/url"
"os"
"runtime"
"strings"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
kruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
)
type CreateOptions struct {
PrintFlags *genericclioptions.PrintFlags
RecordFlags *genericclioptions.RecordFlags
DryRun bool
FilenameOptions resource.FilenameOptions
Selector string
EditBeforeCreate bool
Raw string
Recorder genericclioptions.Recorder
PrintObj func(obj kruntime.Object) error
genericclioptions.IOStreams
}
var (
createLong = templates.LongDesc(i18n.T(`
Create a resource from a file or from stdin.
JSON and YAML formats are accepted.`))
createExample = templates.Examples(i18n.T(`
# Create a pod using the data in pod.json.
kubectl create -f ./pod.json
# Create a pod based on the JSON passed into stdin.
cat pod.json | kubectl create -f -
# Edit the data in docker-registry.yaml in JSON then create the resource using the edited data.
kubectl create -f docker-registry.yaml --edit -o json`))
)
func NewCreateOptions(ioStreams genericclioptions.IOStreams) *CreateOptions {
return &CreateOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme),
RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{},
IOStreams: ioStreams,
}
}
func NewCmdCreate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewCreateOptions(ioStreams)
cmd := &cobra.Command{
Use: "create -f FILENAME",
DisableFlagsInUseLine: true,
Short: i18n.T("Create a resource from a file or from stdin."),
Long: createLong,
Example: createExample,
Run: func(cmd *cobra.Command, args []string) {
if cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames) {
defaultRunFunc := cmdutil.DefaultSubCommandRun(ioStreams.ErrOut)
defaultRunFunc(cmd, args)
return
}
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(o.ValidateArgs(cmd, args))
cmdutil.CheckErr(o.RunCreate(f, cmd))
},
}
// bind flag structs
o.RecordFlags.AddFlags(cmd)
usage := "to use to create the resource"
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmd.MarkFlagRequired("filename")
cmdutil.AddValidateFlags(cmd)
cmd.Flags().BoolVar(&o.EditBeforeCreate, "edit", o.EditBeforeCreate, "Edit the API resource before creating")
cmd.Flags().Bool("windows-line-endings", runtime.GOOS == "windows",
"Only relevant if --edit=true. Defaults to the line ending native to your platform.")
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddDryRunFlag(cmd)
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to POST to the server. Uses the transport specified by the kubeconfig file.")
o.PrintFlags.AddFlags(cmd)
// create subcommands
cmd.AddCommand(NewCmdCreateNamespace(f, ioStreams))
cmd.AddCommand(NewCmdCreateQuota(f, ioStreams))
cmd.AddCommand(NewCmdCreateSecret(f, ioStreams))
cmd.AddCommand(NewCmdCreateConfigMap(f, ioStreams))
cmd.AddCommand(NewCmdCreateServiceAccount(f, ioStreams))
cmd.AddCommand(NewCmdCreateService(f, ioStreams))
cmd.AddCommand(NewCmdCreateDeployment(f, ioStreams))
cmd.AddCommand(NewCmdCreateClusterRole(f, ioStreams))
cmd.AddCommand(NewCmdCreateClusterRoleBinding(f, ioStreams))
cmd.AddCommand(NewCmdCreateRole(f, ioStreams))
cmd.AddCommand(NewCmdCreateRoleBinding(f, ioStreams))
cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, ioStreams))
cmd.AddCommand(NewCmdCreatePriorityClass(f, ioStreams))
cmd.AddCommand(NewCmdCreateJob(f, ioStreams))
return cmd
}
func (o *CreateOptions) ValidateArgs(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args)
}
if len(o.Raw) > 0 {
if o.EditBeforeCreate {
return cmdutil.UsageErrorf(cmd, "--raw and --edit are mutually exclusive")
}
if len(o.FilenameOptions.Filenames) != 1 {
return cmdutil.UsageErrorf(cmd, "--raw can only use a single local file or stdin")
}
if strings.Index(o.FilenameOptions.Filenames[0], "http://") == 0 || strings.Index(o.FilenameOptions.Filenames[0], "https://") == 0 {
return cmdutil.UsageErrorf(cmd, "--raw cannot read from a url")
}
if o.FilenameOptions.Recursive {
return cmdutil.UsageErrorf(cmd, "--raw and --recursive are mutually exclusive")
}
if len(o.Selector) > 0 {
return cmdutil.UsageErrorf(cmd, "--raw and --selector (-l) are mutually exclusive")
}
if len(cmdutil.GetFlagString(cmd, "output")) > 0 {
return cmdutil.UsageErrorf(cmd, "--raw and --output are mutually exclusive")
}
if _, err := url.ParseRequestURI(o.Raw); err != nil {
return cmdutil.UsageErrorf(cmd, "--raw must be a valid URL path: %v", err)
}
}
return nil
}
func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
var err error
o.RecordFlags.Complete(cmd)
o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil {
return err
}
o.DryRun = cmdutil.GetDryRunFlag(cmd)
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = func(obj kruntime.Object) error {
return printer.PrintObj(obj, o.Out)
}
return nil
}
func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error {
// raw only makes sense for a single file resource multiple objects aren't likely to do what you want.
// the validator enforces this, so
if len(o.Raw) > 0 {
return o.raw(f)
}
if o.EditBeforeCreate {
return RunEditOnCreate(f, o.RecordFlags, o.IOStreams, cmd, &o.FilenameOptions)
}
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
if err != nil {
return err
}
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
r := f.NewBuilder().
Unstructured().
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
LabelSelectorParam(o.Selector).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
count := 0
err = r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, cmdutil.InternalVersionJSONEncoder()); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
if err := o.Recorder.Record(info.Object); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
if !o.DryRun {
if err := createAndRefresh(info); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
}
count++
return o.PrintObj(info.Object)
})
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("no objects passed to create")
}
return nil
}
// raw makes a simple HTTP request to the provided path on the server using the default
// credentials.
func (o *CreateOptions) raw(f cmdutil.Factory) error {
restClient, err := f.RESTClient()
if err != nil {
return err
}
var data io.ReadCloser
if o.FilenameOptions.Filenames[0] == "-" {
data = os.Stdin
} else {
data, err = os.Open(o.FilenameOptions.Filenames[0])
if err != nil {
return err
}
}
// TODO post content with stream. Right now it ignores body content
result := restClient.Post().RequestURI(o.Raw).Body(data).Do()
if err := result.Error(); err != nil {
return err
}
body, err := result.Raw()
if err != nil {
return err
}
fmt.Fprintf(o.Out, "%v", string(body))
return nil
}
func RunEditOnCreate(f cmdutil.Factory, recordFlags *genericclioptions.RecordFlags, ioStreams genericclioptions.IOStreams, cmd *cobra.Command, options *resource.FilenameOptions) error {
editOptions := editor.NewEditOptions(editor.EditBeforeCreateMode, ioStreams)
editOptions.FilenameOptions = *options
editOptions.ValidateOptions = cmdutil.ValidateOptions{
EnableValidation: cmdutil.GetFlagBool(cmd, "validate"),
}
editOptions.Output = cmdutil.GetFlagString(cmd, "output")
editOptions.ApplyAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
editOptions.RecordFlags = recordFlags
err := editOptions.Complete(f, []string{}, cmd)
if err != nil {
return err
}
return editOptions.Run()
}
// createAndRefresh creates an object from input info and refreshes info with that object
func createAndRefresh(info *resource.Info) error {
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
if err != nil {
return err
}
info.Refresh(obj, true)
return nil
}
// NameFromCommandArgs is a utility function for commands that assume the first argument is a resource name
func NameFromCommandArgs(cmd *cobra.Command, args []string) (string, error) {
if len(args) != 1 {
return "", cmdutil.UsageErrorf(cmd, "exactly one NAME is required, got %d", len(args))
}
return args[0], nil
}
// CreateSubcommandOptions is an options struct to support create subcommands
type CreateSubcommandOptions struct {
// PrintFlags holds options necessary for obtaining a printer
PrintFlags *genericclioptions.PrintFlags
// Name of resource being created
Name string
// StructuredGenerator is the resource generator for the object being created
StructuredGenerator kubectl.StructuredGenerator
// DryRun is true if the command should be simulated but not run against the server
DryRun bool
CreateAnnotation bool
Namespace string
EnforceNamespace bool
Mapper meta.RESTMapper
DynamicClient dynamic.Interface
PrintObj printers.ResourcePrinterFunc
genericclioptions.IOStreams
}
func NewCreateSubcommandOptions(ioStreams genericclioptions.IOStreams) *CreateSubcommandOptions {
return &CreateSubcommandOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme),
IOStreams: ioStreams,
}
}
func (o *CreateSubcommandOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string, generator kubectl.StructuredGenerator) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
o.Name = name
o.StructuredGenerator = generator
o.DryRun = cmdutil.GetDryRunFlag(cmd)
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = func(obj kruntime.Object, out io.Writer) error {
return printer.PrintObj(obj, out)
}
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
o.DynamicClient, err = f.DynamicClient()
if err != nil {
return err
}
o.Mapper, err = f.ToRESTMapper()
if err != nil {
return err
}
return nil
}
// RunCreateSubcommand executes a create subcommand using the specified options
func (o *CreateSubcommandOptions) Run() error {
obj, err := o.StructuredGenerator.StructuredGenerate()
if err != nil {
return err
}
if !o.DryRun {
// create subcommands have compiled knowledge of things they create, so type them directly
gvks, _, err := legacyscheme.Scheme.ObjectKinds(obj)
if err != nil {
return err
}
gvk := gvks[0]
mapping, err := o.Mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version)
if err != nil {
return err
}
if err := kubectl.CreateOrUpdateAnnotation(o.CreateAnnotation, obj, cmdutil.InternalVersionJSONEncoder()); err != nil {
return err
}
asUnstructured := &unstructured.Unstructured{}
if err := legacyscheme.Scheme.Convert(obj, asUnstructured, nil); err != nil {
return err
}
if mapping.Scope.Name() == meta.RESTScopeNameRoot {
o.Namespace = ""
}
actualObject, err := o.DynamicClient.Resource(mapping.Resource).Namespace(o.Namespace).Create(asUnstructured)
if err != nil {
return err
}
// ensure we pass a versioned object to the printer
obj = actualObject
} else {
if meta, err := meta.Accessor(obj); err == nil && o.EnforceNamespace {
meta.SetNamespace(o.Namespace)
}
}
return o.PrintObj(obj, o.Out)
}

View File

@ -14,18 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"fmt"
"io"
"strings"
"github.com/spf13/cobra"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilflag "k8s.io/apiserver/pkg/util/flag"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -38,7 +40,7 @@ var (
kubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods
# Create a ClusterRole named "pod-reader" with ResourceName specified
kubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods --resource-name=readablepod --resource-name=anotherpod
kubectl create clusterrole pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
# Create a ClusterRole named "foo" with API Group specified
kubectl create clusterrole foo --verb=get,list,watch --resource=rs.extensions
@ -47,7 +49,10 @@ var (
kubectl create clusterrole foo --verb=get,list,watch --resource=pods,pods/status
# Create a ClusterRole name "foo" with NonResourceURL specified
kubectl create clusterrole "foo" --verb=get --non-resource-url=/logs/*`))
kubectl create clusterrole "foo" --verb=get --non-resource-url=/logs/*
# Create a ClusterRole name "monitoring" with AggregationRule specified
kubectl create clusterrole monitoring --aggregation-rule="rbac.example.com/aggregate-to-monitoring=true"`))
// Valid nonResource verb list for validation.
validNonResourceVerbs = []string{"*", "get", "post", "put", "delete", "patch", "head", "options"}
@ -56,14 +61,14 @@ var (
type CreateClusterRoleOptions struct {
*CreateRoleOptions
NonResourceURLs []string
AggregationRule map[string]string
}
// ClusterRole is a command to ease creating ClusterRoles.
func NewCmdCreateClusterRole(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateClusterRole(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
c := &CreateClusterRoleOptions{
CreateRoleOptions: &CreateRoleOptions{
Out: cmdOut,
},
CreateRoleOptions: NewCreateRoleOptions(ioStreams),
AggregationRule: map[string]string{},
}
cmd := &cobra.Command{
Use: "clusterrole NAME --verb=verb --resource=resource.group [--resource-name=resourcename] [--dry-run]",
@ -77,14 +82,17 @@ func NewCmdCreateClusterRole(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command
cmdutil.CheckErr(c.RunCreateRole())
},
}
c.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddDryRunFlag(cmd)
cmd.Flags().StringSliceVar(&c.Verbs, "verb", []string{}, "Verb that applies to the resources contained in the rule")
cmd.Flags().StringSliceVar(&c.NonResourceURLs, "non-resource-url", []string{}, "A partial url that user should have access to.")
cmd.Flags().StringSliceVar(&c.Verbs, "verb", c.Verbs, "Verb that applies to the resources contained in the rule")
cmd.Flags().StringSliceVar(&c.NonResourceURLs, "non-resource-url", c.NonResourceURLs, "A partial url that user should have access to.")
cmd.Flags().StringSlice("resource", []string{}, "Resource that the rule applies to")
cmd.Flags().StringArrayVar(&c.ResourceNames, "resource-name", []string{}, "Resource in the white list that the rule applies to, repeat this flag for multiple items")
cmd.Flags().StringArrayVar(&c.ResourceNames, "resource-name", c.ResourceNames, "Resource in the white list that the rule applies to, repeat this flag for multiple items")
cmd.Flags().Var(utilflag.NewMapStringString(&c.AggregationRule), "aggregation-rule", "An aggregation label selector for combining ClusterRoles.")
return cmd
}
@ -107,6 +115,13 @@ func (c *CreateClusterRoleOptions) Validate() error {
return fmt.Errorf("name must be specified")
}
if len(c.AggregationRule) > 0 {
if len(c.NonResourceURLs) > 0 || len(c.Verbs) > 0 || len(c.Resources) > 0 || len(c.ResourceNames) > 0 {
return fmt.Errorf("aggregation rule must be specified without nonResourceURLs, verbs, resources or resourceNames")
}
return nil
}
// validate verbs.
if len(c.Verbs) == 0 {
return fmt.Errorf("at least one verb must be specified")
@ -156,26 +171,36 @@ func (c *CreateClusterRoleOptions) Validate() error {
}
func (c *CreateClusterRoleOptions) RunCreateRole() error {
clusterRole := &rbacv1.ClusterRole{}
clusterRole.Name = c.Name
rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames, c.NonResourceURLs)
if err != nil {
return err
clusterRole := &rbacv1.ClusterRole{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "ClusterRole"},
}
clusterRole.Name = c.Name
var err error
if len(c.AggregationRule) == 0 {
rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames, c.NonResourceURLs)
if err != nil {
return err
}
clusterRole.Rules = rules
} else {
clusterRole.AggregationRule = &rbacv1.AggregationRule{
ClusterRoleSelectors: []metav1.LabelSelector{
{
MatchLabels: c.AggregationRule,
},
},
}
}
clusterRole.Rules = rules
// Create ClusterRole.
if !c.DryRun {
_, err = c.Client.ClusterRoles().Create(clusterRole)
clusterRole, err = c.Client.ClusterRoles().Create(clusterRole)
if err != nil {
return err
}
}
if useShortOutput := c.OutputFormat == "name"; useShortOutput || len(c.OutputFormat) == 0 {
cmdutil.PrintSuccess(useShortOutput, c.Out, clusterRole, c.DryRun, "created")
return nil
}
return c.PrintObject(clusterRole)
return c.PrintObj(clusterRole)
}

View File

@ -14,26 +14,31 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
"testing"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestCreateClusterRole(t *testing.T) {
clusterRoleName := "my-cluster-role"
tf := cmdtesting.NewTestFactory()
tf.Namespace = "test"
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{}
tf.ClientConfigVal = defaultClientConfig()
@ -42,12 +47,14 @@ func TestCreateClusterRole(t *testing.T) {
resources string
nonResourceURL string
resourceNames string
aggregationRule string
expectedClusterRole *rbac.ClusterRole
}{
"test-duplicate-resources": {
verbs: "get,watch,list",
resources: "pods,pods",
expectedClusterRole: &rbac.ClusterRole{
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"},
ObjectMeta: v1.ObjectMeta{
Name: clusterRoleName,
},
@ -65,6 +72,7 @@ func TestCreateClusterRole(t *testing.T) {
verbs: "get,watch,list",
resources: "pods,deployments.extensions",
expectedClusterRole: &rbac.ClusterRole{
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"},
ObjectMeta: v1.ObjectMeta{
Name: clusterRoleName,
},
@ -88,6 +96,7 @@ func TestCreateClusterRole(t *testing.T) {
verbs: "get",
nonResourceURL: "/logs/,/healthz",
expectedClusterRole: &rbac.ClusterRole{
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"},
ObjectMeta: v1.ObjectMeta{
Name: clusterRoleName,
},
@ -104,6 +113,7 @@ func TestCreateClusterRole(t *testing.T) {
nonResourceURL: "/logs/,/healthz",
resources: "pods",
expectedClusterRole: &rbac.ClusterRole{
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"},
ObjectMeta: v1.ObjectMeta{
Name: clusterRoleName,
},
@ -121,16 +131,36 @@ func TestCreateClusterRole(t *testing.T) {
},
},
},
"test-aggregation-rules": {
aggregationRule: "foo1=foo2,foo3=foo4",
expectedClusterRole: &rbac.ClusterRole{
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"},
ObjectMeta: v1.ObjectMeta{
Name: clusterRoleName,
},
AggregationRule: &rbac.AggregationRule{
ClusterRoleSelectors: []metav1.LabelSelector{
{
MatchLabels: map[string]string{
"foo1": "foo2",
"foo3": "foo4",
},
},
},
},
},
},
}
for name, test := range tests {
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateClusterRole(tf, buf)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateClusterRole(tf, ioStreams)
cmd.Flags().Set("dry-run", "true")
cmd.Flags().Set("output", "yaml")
cmd.Flags().Set("verb", test.verbs)
cmd.Flags().Set("resource", test.resources)
cmd.Flags().Set("non-resource-url", test.nonResourceURL)
cmd.Flags().Set("aggregation-rule", test.aggregationRule)
if test.resourceNames != "" {
cmd.Flags().Set("resource-name", test.resourceNames)
}
@ -147,8 +177,8 @@ func TestCreateClusterRole(t *testing.T) {
}
func TestClusterRoleValidate(t *testing.T) {
tf := cmdtesting.NewTestFactory()
tf.Namespace = "test"
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tests := map[string]struct {
clusterRoleOptions *CreateClusterRoleOptions
@ -422,12 +452,60 @@ func TestClusterRoleValidate(t *testing.T) {
},
expectErr: false,
},
"test-aggregation-rule-with-verb": {
clusterRoleOptions: &CreateClusterRoleOptions{
CreateRoleOptions: &CreateRoleOptions{
Name: "my-clusterrole",
Verbs: []string{"get"},
},
AggregationRule: map[string]string{"foo-key": "foo-vlue"},
},
expectErr: true,
},
"test-aggregation-rule-with-resource": {
clusterRoleOptions: &CreateClusterRoleOptions{
CreateRoleOptions: &CreateRoleOptions{
Name: "my-clusterrole",
Resources: []ResourceOptions{
{
Resource: "replicasets",
SubResource: "scale",
},
},
},
AggregationRule: map[string]string{"foo-key": "foo-vlue"},
},
expectErr: true,
},
"test-aggregation-rule-with-no-resource-url": {
clusterRoleOptions: &CreateClusterRoleOptions{
CreateRoleOptions: &CreateRoleOptions{
Name: "my-clusterrole",
},
NonResourceURLs: []string{"/logs/"},
AggregationRule: map[string]string{"foo-key": "foo-vlue"},
},
expectErr: true,
},
"test-aggregation-rule": {
clusterRoleOptions: &CreateClusterRoleOptions{
CreateRoleOptions: &CreateRoleOptions{
Name: "my-clusterrole",
},
AggregationRule: map[string]string{"foo-key": "foo-vlue"},
},
expectErr: false,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
test.clusterRoleOptions.Mapper, _ = tf.Object()
err := test.clusterRoleOptions.Validate()
var err error
test.clusterRoleOptions.Mapper, err = tf.ToRESTMapper()
if err != nil {
t.Fatal(err)
}
err = test.clusterRoleOptions.Validate()
if test.expectErr && err == nil {
t.Errorf("%s: expect error happens, but validate passes.", name)
}
@ -437,3 +515,14 @@ func TestClusterRoleValidate(t *testing.T) {
})
}
}
func defaultClientConfig() *restclient.Config {
return &restclient.Config{
APIPath: "/api",
ContentConfig: restclient.ContentConfig{
NegotiatedSerializer: scheme.Codecs,
ContentType: runtime.ContentTypeJSON,
GroupVersion: &schema.GroupVersion{Version: "v1"},
},
}
}

View File

@ -14,16 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -36,8 +35,16 @@ var (
kubectl create clusterrolebinding cluster-admin --clusterrole=cluster-admin --user=user1 --user=user2 --group=group1`))
)
type ClusterRoleBindingOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// ClusterRoleBinding is a command to ease creating ClusterRoleBindings.
func NewCmdCreateClusterRoleBinding(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateClusterRoleBinding(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &ClusterRoleBindingOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "clusterrolebinding NAME --clusterrole=NAME [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run]",
DisableFlagsInUseLine: true,
@ -45,13 +52,15 @@ func NewCmdCreateClusterRoleBinding(f cmdutil.Factory, cmdOut io.Writer) *cobra.
Long: clusterRoleBindingLong,
Example: clusterRoleBindingExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateClusterRoleBinding(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ClusterRoleBindingV1GeneratorName)
cmd.Flags().String("clusterrole", "", i18n.T("ClusterRole this ClusterRoleBinding should reference"))
cmd.MarkFlagCustom("clusterrole", "__kubectl_get_resource_clusterrole")
@ -61,12 +70,12 @@ func NewCmdCreateClusterRoleBinding(f cmdutil.Factory, cmdOut io.Writer) *cobra.
return cmd
}
// CreateClusterRoleBinding is the implementation of the create clusterrolebinding command.
func CreateClusterRoleBinding(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *ClusterRoleBindingOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.ClusterRoleBindingV1GeneratorName:
@ -80,10 +89,11 @@ func CreateClusterRoleBinding(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Co
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreateClusterRoleBinding is the implementation of the create clusterrolebinding command.
func (o *ClusterRoleBindingOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
@ -32,6 +32,7 @@ import (
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestCreateClusterRoleBinding(t *testing.T) {
@ -67,14 +68,15 @@ func TestCreateClusterRoleBinding(t *testing.T) {
},
}
tf := cmdtesting.NewTestFactory()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
ns := legacyscheme.Codecs
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
encoder := ns.EncoderForVersion(info.Serializer, groupVersion)
decoder := ns.DecoderToVersion(info.Serializer, groupVersion)
tf.Namespace = "test"
tf.Client = &ClusterRoleBindingRESTClient{
RESTClient: &fake.RESTClient{
NegotiatedSerializer: ns,
@ -109,8 +111,8 @@ func TestCreateClusterRoleBinding(t *testing.T) {
}
expectedOutput := "clusterrolebinding.rbac.authorization.k8s.io/" + expectBinding.Name + "\n"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateClusterRoleBinding(tf, buf)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateClusterRoleBinding(tf, ioStreams)
cmd.Flags().Set("clusterrole", "fake-clusterrole")
cmd.Flags().Set("user", "fake-user")
cmd.Flags().Set("group", "fake-group")
@ -143,3 +145,9 @@ func (c *ClusterRoleBindingRESTClient) Post() *restclient.Request {
}
return restclient.NewRequest(c, "POST", &url.URL{Host: "localhost"}, c.VersionedAPIPath, config, serializers, nil, nil, 0)
}
func defaultHeader() http.Header {
header := http.Header{}
header.Set("Content-Type", runtime.ContentTypeJSON)
return header
}

View File

@ -14,16 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -57,8 +56,16 @@ var (
kubectl create configmap my-config --from-env-file=path/to/bar.env`))
)
type ConfigMapOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// ConfigMap is a command to ease creating ConfigMaps.
func NewCmdCreateConfigMap(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateConfigMap(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &ConfigMapOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run]",
DisableFlagsInUseLine: true,
@ -67,13 +74,15 @@ func NewCmdCreateConfigMap(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
Long: configMapLong,
Example: configMapExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateConfigMap(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ConfigMapV1GeneratorName)
cmd.Flags().StringSlice("from-file", []string{}, "Key file can be specified using its file path, in which case file basename will be used as configmap key, or optionally with a key and file path, in which case the given key will be used. Specifying a directory will iterate each named file in the directory whose basename is a valid configmap key.")
cmd.Flags().StringArray("from-literal", []string{}, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
@ -82,12 +91,12 @@ func NewCmdCreateConfigMap(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
return cmd
}
// CreateConfigMap is the implementation of the create configmap command.
func CreateConfigMap(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *ConfigMapOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.ConfigMapV1GeneratorName:
@ -101,10 +110,11 @@ func CreateConfigMap(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, ar
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreateConfigMap is the implementation of the create configmap command.
func (o *ConfigMapOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}

View File

@ -14,26 +14,32 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestCreateConfigMap(t *testing.T) {
configMap := &v1.ConfigMap{}
configMap.Name = "my-configmap"
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
@ -49,9 +55,8 @@ func TestCreateConfigMap(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateConfigMap(tf, buf)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateConfigMap(tf, ioStreams)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{configMap.Name})
expectedOutput := "configmap/" + configMap.Name + "\n"
@ -59,3 +64,7 @@ func TestCreateConfigMap(t *testing.T) {
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
}
}
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
}

View File

@ -14,16 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -36,10 +35,18 @@ var (
kubectl create deployment my-dep --image=busybox`))
)
type DeploymentOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateDeployment is a macro command to create a new deployment.
// This command is better known to users as `kubectl create deployment`.
// Note that this command overlaps significantly with the `kubectl run` command.
func NewCmdCreateDeployment(f cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.Command {
func NewCmdCreateDeployment(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &DeploymentOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "deployment NAME --image=image [--dry-run]",
DisableFlagsInUseLine: true,
@ -48,14 +55,16 @@ func NewCmdCreateDeployment(f cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.
Long: deploymentLong,
Example: deploymentExample,
Run: func(cmd *cobra.Command, args []string) {
err := createDeployment(f, cmdOut, cmdErr, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.DeploymentBasicV1Beta1GeneratorName)
cmdutil.AddGeneratorFlags(cmd, "")
cmd.Flags().StringSlice("image", []string{}, "Image name to run.")
cmd.MarkFlagRequired("image")
return cmd
@ -71,7 +80,7 @@ func generatorFromName(
) (kubectl.StructuredGenerator, bool) {
switch generatorName {
case cmdutil.DeploymentBasicAppsV1Beta1GeneratorName:
case cmdutil.DeploymentBasicAppsV1GeneratorName:
generator := &kubectl.DeploymentBasicAppsGeneratorV1{
BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
Name: deploymentName,
@ -80,6 +89,15 @@ func generatorFromName(
}
return generator, true
case cmdutil.DeploymentBasicAppsV1Beta1GeneratorName:
generator := &kubectl.DeploymentBasicAppsGeneratorV1Beta1{
BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
Name: deploymentName,
Images: imageNames,
},
}
return generator, true
case cmdutil.DeploymentBasicV1Beta1GeneratorName:
generator := &kubectl.DeploymentBasicGeneratorV1{
BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
@ -93,14 +111,8 @@ func generatorFromName(
return nil, false
}
// createDeployment
// 1. Reads user config values from Cobra.
// 2. Sets up the correct Generator object.
// 3. Calls RunCreateSubcommand.
func createDeployment(f cmdutil.Factory, cmdOut, cmdErr io.Writer,
cmd *cobra.Command, args []string) error {
deploymentName, err := NameFromCommandArgs(cmd, args)
func (o *DeploymentOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
@ -112,23 +124,32 @@ func createDeployment(f cmdutil.Factory, cmdOut, cmdErr io.Writer,
generatorName := cmdutil.GetFlagString(cmd, "generator")
// It is possible we have to modify the user-provided generator name if
// the server does not have support for the requested generator.
generatorName, err = cmdutil.FallbackGeneratorNameIfNecessary(generatorName, clientset.Discovery(), cmdErr)
if err != nil {
return err
if len(generatorName) == 0 {
generatorName = cmdutil.DeploymentBasicAppsV1GeneratorName
generatorNameTemp, err := cmdutil.FallbackGeneratorNameIfNecessary(generatorName, clientset.Discovery(), o.CreateSubcommandOptions.ErrOut)
if err != nil {
return err
}
if generatorNameTemp != generatorName {
cmdutil.Warning(o.CreateSubcommandOptions.ErrOut, generatorName, generatorNameTemp)
} else {
generatorName = generatorNameTemp
}
}
imageNames := cmdutil.GetFlagStringSlice(cmd, "image")
generator, ok := generatorFromName(generatorName, imageNames, deploymentName)
generator, ok := generatorFromName(generatorName, imageNames, name)
if !ok {
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: deploymentName,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// createDeployment
// 1. Reads user config values from Cobra.
// 2. Sets up the correct Generator object.
// 3. Calls RunCreateSubcommand.
func (o *DeploymentOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
@ -30,14 +30,16 @@ import (
"k8s.io/kubernetes/pkg/kubectl"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func Test_generatorFromName(t *testing.T) {
const (
nonsenseName = "not-a-real-generator-name"
basicName = cmdutil.DeploymentBasicV1Beta1GeneratorName
basicAppsName = cmdutil.DeploymentBasicAppsV1Beta1GeneratorName
deploymentName = "deployment-name"
nonsenseName = "not-a-real-generator-name"
basicName = cmdutil.DeploymentBasicV1Beta1GeneratorName
basicAppsV1Beta1Name = cmdutil.DeploymentBasicAppsV1Beta1GeneratorName
basicAppsV1Name = cmdutil.DeploymentBasicAppsV1GeneratorName
deploymentName = "deployment-name"
)
imageNames := []string{"image-1", "image-2"}
@ -58,7 +60,20 @@ func Test_generatorFromName(t *testing.T) {
assert.Equal(t, expectedGenerator, generator)
}
generator, ok = generatorFromName(basicAppsName, imageNames, deploymentName)
generator, ok = generatorFromName(basicAppsV1Beta1Name, imageNames, deploymentName)
assert.True(t, ok)
{
expectedGenerator := &kubectl.DeploymentBasicAppsGeneratorV1Beta1{
BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
Name: deploymentName,
Images: imageNames,
},
}
assert.Equal(t, expectedGenerator, generator)
}
generator, ok = generatorFromName(basicAppsV1Name, imageNames, deploymentName)
assert.True(t, ok)
{
@ -74,28 +89,29 @@ func Test_generatorFromName(t *testing.T) {
func TestCreateDeployment(t *testing.T) {
depName := "jonny-dep"
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
ns := legacyscheme.Codecs
fakeDiscovery := "{\"kind\":\"APIResourceList\",\"apiVersion\":\"v1\",\"groupVersion\":\"apps/v1\",\"resources\":[{\"name\":\"deployments\",\"singularName\":\"\",\"namespaced\":true,\"kind\":\"Deployment\",\"verbs\":[\"create\",\"delete\",\"deletecollection\",\"get\",\"list\",\"patch\",\"update\",\"watch\"],\"shortNames\":[\"deploy\"],\"categories\":[\"all\"]}]}"
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewBuffer([]byte("{}"))),
Body: ioutil.NopCloser(bytes.NewBuffer([]byte(fakeDiscovery))),
}, nil
}),
}
tf.ClientConfigVal = &restclient.Config{}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateDeployment(tf, buf, buf)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateDeployment(tf, ioStreams)
cmd.Flags().Set("dry-run", "true")
cmd.Flags().Set("output", "name")
cmd.Flags().Set("image", "hollywood/jonny.depp:v2")
cmd.Run(cmd, []string{depName})
expectedOutput := "deployment.extensions/" + depName + "\n"
expectedOutput := "deployment.apps/" + depName + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
}
@ -103,25 +119,38 @@ func TestCreateDeployment(t *testing.T) {
func TestCreateDeploymentNoImage(t *testing.T) {
depName := "jonny-dep"
tf := cmdtesting.NewTestFactory()
ns := legacyscheme.Codecs
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
ns := legacyscheme.Codecs
fakeDiscovery := "{\"kind\":\"APIResourceList\",\"apiVersion\":\"v1\",\"groupVersion\":\"apps/v1\",\"resources\":[{\"name\":\"deployments\",\"singularName\":\"\",\"namespaced\":true,\"kind\":\"Deployment\",\"verbs\":[\"create\",\"delete\",\"deletecollection\",\"get\",\"list\",\"patch\",\"update\",\"watch\"],\"shortNames\":[\"deploy\"],\"categories\":[\"all\"]}]}"
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(&bytes.Buffer{}),
Body: ioutil.NopCloser(bytes.NewBuffer([]byte(fakeDiscovery))),
}, nil
}),
}
tf.ClientConfigVal = &restclient.Config{}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateDeployment(tf, buf, buf)
cmd.Flags().Set("dry-run", "true")
ioStreams := genericclioptions.NewTestIOStreamsDiscard()
cmd := NewCmdCreateDeployment(tf, ioStreams)
cmd.Flags().Set("output", "name")
err := createDeployment(tf, buf, buf, cmd, []string{depName})
options := &DeploymentOpts{
CreateSubcommandOptions: &CreateSubcommandOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme),
DryRun: true,
IOStreams: ioStreams,
},
}
err := options.Complete(tf, cmd, []string{depName})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = options.Run()
assert.Error(t, err, "at least one image must be specified")
}

View File

@ -0,0 +1,186 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package create
import (
"fmt"
"github.com/spf13/cobra"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
clientbatchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
var (
jobLong = templates.LongDesc(i18n.T(`
Create a job with the specified name.`))
jobExample = templates.Examples(i18n.T(`
# Create a job from a CronJob named "a-cronjob"
kubectl create job test-job --from=cronjob/a-cronjob`))
)
type CreateJobOptions struct {
PrintFlags *genericclioptions.PrintFlags
PrintObj func(obj runtime.Object) error
Name string
From string
Namespace string
OutputFormat string
Client clientbatchv1.BatchV1Interface
DryRun bool
Builder *resource.Builder
Cmd *cobra.Command
genericclioptions.IOStreams
}
func NewCreateJobOptions(ioStreams genericclioptions.IOStreams) *CreateJobOptions {
return &CreateJobOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme),
IOStreams: ioStreams,
}
}
// NewCmdCreateJob is a command to ease creating Jobs from CronJobs.
func NewCmdCreateJob(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewCreateJobOptions(ioStreams)
cmd := &cobra.Command{
Use: "job NAME [--from=CRONJOB]",
Short: jobLong,
Long: jobLong,
Example: jobExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.RunCreateJob())
},
}
o.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddDryRunFlag(cmd)
cmd.Flags().StringVar(&o.From, "from", o.From, "The name of the resource to create a Job from (only cronjob is supported).")
return cmd
}
func (o *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) (err error) {
if len(args) == 0 {
return cmdutil.UsageErrorf(cmd, "NAME is required")
}
o.Name = args[0]
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
clientset, err := f.KubernetesClientSet()
if err != nil {
return err
}
o.Client = clientset.BatchV1()
o.Builder = f.NewBuilder()
o.DryRun = cmdutil.GetDryRunFlag(cmd)
o.Cmd = cmd
o.OutputFormat = cmdutil.GetFlagString(cmd, "output")
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = func(obj runtime.Object) error {
return printer.PrintObj(obj, o.Out)
}
return nil
}
func (o *CreateJobOptions) RunCreateJob() error {
infos, err := o.Builder.
Unstructured().
NamespaceParam(o.Namespace).DefaultNamespace().
ResourceTypeOrNameArgs(false, o.From).
Flatten().
Latest().
Do().
Infos()
if err != nil {
return err
}
if len(infos) != 1 {
return fmt.Errorf("from must be an existing cronjob")
}
uncastVersionedObj, err := scheme.Scheme.ConvertToVersion(infos[0].Object, batchv1beta1.SchemeGroupVersion)
if err != nil {
return fmt.Errorf("from must be an existing cronjob: %v", err)
}
cronJob, ok := uncastVersionedObj.(*batchv1beta1.CronJob)
if !ok {
return fmt.Errorf("from must be an existing cronjob")
}
return o.createJob(cronJob)
}
func (o *CreateJobOptions) createJob(cronJob *batchv1beta1.CronJob) error {
annotations := make(map[string]string)
annotations["cronjob.kubernetes.io/instantiate"] = "manual"
for k, v := range cronJob.Spec.JobTemplate.Annotations {
annotations[k] = v
}
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: o.Name,
Namespace: o.Namespace,
Annotations: annotations,
Labels: cronJob.Spec.JobTemplate.Labels,
},
Spec: cronJob.Spec.JobTemplate.Spec,
}
if !o.DryRun {
var err error
job, err = o.Client.Jobs(o.Namespace).Create(job)
if err != nil {
return fmt.Errorf("failed to create job: %v", err)
}
}
return o.PrintObj(job)
}

View File

@ -14,10 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
"testing"
batchv1 "k8s.io/api/batch/v1"
@ -27,7 +26,9 @@ import (
"k8s.io/apimachinery/pkg/runtime"
fake "k8s.io/client-go/kubernetes/fake"
clienttesting "k8s.io/client-go/testing"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestCreateJobFromCronJob(t *testing.T) {
@ -82,13 +83,26 @@ func TestCreateJobFromCronJob(t *testing.T) {
return true, expectJob, nil
})
f := cmdtesting.NewTestFactory()
buf := bytes.NewBuffer([]byte{})
defer f.Cleanup()
printFlags := genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmdOptions := &CreateJobOptions{
Name: testJobName,
Namespace: testNamespaceName,
Client: clientset.BatchV1(),
Out: buf,
Cmd: NewCmdCreateJob(f, buf),
PrintFlags: printFlags,
Name: testJobName,
Namespace: testNamespaceName,
Client: clientset.BatchV1(),
Cmd: NewCmdCreateJob(f, ioStreams),
PrintObj: func(obj runtime.Object) error {
p, err := printFlags.ToPrinter()
if err != nil {
return err
}
return p.PrintObj(obj, buf)
},
IOStreams: ioStreams,
}
err := cmdOptions.createJob(cronJob)

View File

@ -14,16 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -36,8 +35,16 @@ var (
kubectl create namespace my-namespace`))
)
type NamespaceOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateNamespace is a macro command to create a new namespace
func NewCmdCreateNamespace(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateNamespace(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &NamespaceOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "namespace NAME [--dry-run]",
DisableFlagsInUseLine: true,
@ -46,24 +53,26 @@ func NewCmdCreateNamespace(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
Long: namespaceLong,
Example: namespaceExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateNamespace(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.NamespaceV1GeneratorName)
return cmd
}
// CreateNamespace implements the behavior to run the create namespace command
func CreateNamespace(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *NamespaceOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.NamespaceV1GeneratorName:
@ -71,10 +80,11 @@ func CreateNamespace(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, ar
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreateNamespace implements the behavior to run the create namespace command
func (o *NamespaceOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}

View File

@ -14,10 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
"net/http"
"testing"
@ -26,6 +25,7 @@ import (
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@ -33,7 +33,9 @@ func TestCreateNamespace(t *testing.T) {
namespaceObject := &v1.Namespace{}
namespaceObject.Name = "my-namespace"
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
@ -49,8 +51,8 @@ func TestCreateNamespace(t *testing.T) {
}
}),
}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateNamespace(tf, buf)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateNamespace(tf, ioStreams)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{namespaceObject.Name})
expectedOutput := "namespace/" + namespaceObject.Name + "\n"

View File

@ -14,16 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -41,8 +40,16 @@ var (
kubectl create pdb my-pdb --selector=app=nginx --min-available=50%`))
)
type PodDisruptionBudgetOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreatePodDisruptionBudget is a macro command to create a new pod disruption budget.
func NewCmdCreatePodDisruptionBudget(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreatePodDisruptionBudget(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &PodDisruptionBudgetOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "poddisruptionbudget NAME --selector=SELECTOR --min-available=N [--dry-run]",
DisableFlagsInUseLine: true,
@ -51,14 +58,15 @@ func NewCmdCreatePodDisruptionBudget(f cmdutil.Factory, cmdOut io.Writer) *cobra
Long: pdbLong,
Example: pdbExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreatePodDisruptionBudget(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.PodDisruptionBudgetV2GeneratorName)
cmd.Flags().String("min-available", "", i18n.T("The minimum number or percentage of available pods this budget requires."))
@ -67,12 +75,12 @@ func NewCmdCreatePodDisruptionBudget(f cmdutil.Factory, cmdOut io.Writer) *cobra
return cmd
}
// CreatePodDisruptionBudget implements the behavior to run the create pdb command.
func CreatePodDisruptionBudget(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *PodDisruptionBudgetOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.PodDisruptionBudgetV1GeneratorName:
@ -91,10 +99,11 @@ func CreatePodDisruptionBudget(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.C
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreatePodDisruptionBudget implements the behavior to run the create pdb command.
func (o *PodDisruptionBudgetOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
@ -27,11 +27,14 @@ import (
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestCreatePdb(t *testing.T) {
pdbName := "my-pdb"
tf := cmdtesting.NewTestFactory()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
@ -45,15 +48,36 @@ func TestCreatePdb(t *testing.T) {
}),
}
tf.ClientConfigVal = &restclient.Config{}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreatePodDisruptionBudget(tf, buf)
outputFormat := "name"
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreatePodDisruptionBudget(tf, ioStreams)
cmd.Flags().Set("min-available", "1")
cmd.Flags().Set("selector", "app=rails")
cmd.Flags().Set("dry-run", "true")
cmd.Flags().Set("output", "name")
CreatePodDisruptionBudget(tf, buf, cmd, []string{pdbName})
cmd.Flags().Set("output", outputFormat)
printFlags := genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme)
printFlags.OutputFormat = &outputFormat
options := &PodDisruptionBudgetOpts{
CreateSubcommandOptions: &CreateSubcommandOptions{
PrintFlags: printFlags,
Name: pdbName,
IOStreams: ioStreams,
},
}
err := options.Complete(tf, cmd, []string{pdbName})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = options.Run()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expectedOutput := "poddisruptionbudget.policy/" + pdbName + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())

View File

@ -14,16 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -39,8 +38,16 @@ var (
kubectl create priorityclass default-priority --value=1000 --global-default=true --description="default priority"`))
)
type PriorityClassOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreatePriorityClass is a macro command to create a new priorityClass.
func NewCmdCreatePriorityClass(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreatePriorityClass(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &PriorityClassOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "priorityclass NAME --value=VALUE --global-default=BOOL [--dry-run]",
DisableFlagsInUseLine: true,
@ -49,13 +56,15 @@ func NewCmdCreatePriorityClass(f cmdutil.Factory, cmdOut io.Writer) *cobra.Comma
Long: pcLong,
Example: pcExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(CreatePriorityClass(f, cmdOut, cmd, args))
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.PriorityClassV1Alpha1GeneratorName)
cmd.Flags().Int32("value", 0, i18n.T("the value of this priority class."))
@ -64,12 +73,12 @@ func NewCmdCreatePriorityClass(f cmdutil.Factory, cmdOut io.Writer) *cobra.Comma
return cmd
}
// CreatePriorityClass implements the behavior to run the create priorityClass command.
func CreatePriorityClass(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *PriorityClassOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.PriorityClassV1Alpha1GeneratorName:
@ -82,10 +91,11 @@ func CreatePriorityClass(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreatePriorityClass implements the behavior to run the create priorityClass command.
func (o *PriorityClassOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
@ -27,15 +27,18 @@ import (
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestCreatePriorityClass(t *testing.T) {
pcName := "my-pc"
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "scheduling.k8s.io", Version: "v1alpha1"},
GroupVersion: schema.GroupVersion{Group: "scheduling.k8s.io", Version: "v1beta1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
return &http.Response{
@ -45,15 +48,37 @@ func TestCreatePriorityClass(t *testing.T) {
}),
}
tf.ClientConfigVal = &restclient.Config{}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreatePriorityClass(tf, buf)
outputFormat := "name"
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreatePriorityClass(tf, ioStreams)
cmd.Flags().Set("value", "1000")
cmd.Flags().Set("global-default", "true")
cmd.Flags().Set("description", "my priority")
cmd.Flags().Set("dry-run", "true")
cmd.Flags().Set("output", "name")
CreatePriorityClass(tf, buf, cmd, []string{pcName})
cmd.Flags().Set("output", outputFormat)
printFlags := genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme)
printFlags.OutputFormat = &outputFormat
options := &PriorityClassOpts{
CreateSubcommandOptions: &CreateSubcommandOptions{
PrintFlags: printFlags,
Name: pcName,
IOStreams: ioStreams,
},
}
err := options.Complete(tf, cmd, []string{pcName})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = options.Run()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expectedOutput := "priorityclass.scheduling.k8s.io/" + pcName + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())

View File

@ -14,16 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -39,8 +38,16 @@ var (
kubectl create quota best-effort --hard=pods=100 --scopes=BestEffort`))
)
type QuotaOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateQuota is a macro command to create a new quota
func NewCmdCreateQuota(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateQuota(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &QuotaOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "quota NAME [--hard=key1=value1,key2=value2] [--scopes=Scope1,Scope2] [--dry-run=bool]",
DisableFlagsInUseLine: true,
@ -49,26 +56,27 @@ func NewCmdCreateQuota(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
Long: quotaLong,
Example: quotaExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateQuota(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ResourceQuotaV1GeneratorName)
cmd.Flags().String("hard", "", i18n.T("A comma-delimited set of resource=quantity pairs that define a hard limit."))
cmd.Flags().String("scopes", "", i18n.T("A comma-delimited set of quota scopes that must all match each object tracked by the quota."))
return cmd
}
// CreateQuota implements the behavior to run the create quota command
func CreateQuota(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *QuotaOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.ResourceQuotaV1GeneratorName:
@ -80,10 +88,11 @@ func CreateQuota(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args [
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreateQuota implements the behavior to run the create quota command
func (o *QuotaOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}

View File

@ -14,42 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
"net/http"
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestCreateQuota(t *testing.T) {
resourceQuotaObject := &v1.ResourceQuota{}
resourceQuotaObject.Name = "my-quota"
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/resourcequotas" && m == "POST":
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, resourceQuotaObject)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
tests := map[string]struct {
flags []string
@ -73,14 +50,19 @@ func TestCreateQuota(t *testing.T) {
},
}
for name, test := range tests {
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateQuota(tf, buf)
cmd.Flags().Parse(test.flags)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{resourceQuotaObject.Name})
t.Run(name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
if buf.String() != test.expectedOutput {
t.Errorf("%s: expected output: %s, but got: %s", name, test.expectedOutput, buf.String())
}
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateQuota(tf, ioStreams)
cmd.Flags().Parse(test.flags)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{resourceQuotaObject.Name})
if buf.String() != test.expectedOutput {
t.Errorf("%s: expected output: %s, but got: %s", name, test.expectedOutput, buf.String())
}
})
}
}

View File

@ -14,23 +14,25 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"fmt"
"io"
"strings"
"github.com/spf13/cobra"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
clientgorbacv1 "k8s.io/client-go/kubernetes/typed/rbac/v1"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -43,7 +45,7 @@ var (
kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods
# Create a Role named "pod-reader" with ResourceName specified
kubectl create role pod-reader --verb=get,list,watch --resource=pods --resource-name=readablepod --resource-name=anotherpod
kubectl create role pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
# Create a Role named "foo" with API Group specified
kubectl create role foo --verb=get,list,watch --resource=rs.extensions
@ -100,6 +102,8 @@ type ResourceOptions struct {
}
type CreateRoleOptions struct {
PrintFlags *genericclioptions.PrintFlags
Name string
Verbs []string
Resources []ResourceOptions
@ -110,15 +114,23 @@ type CreateRoleOptions struct {
Namespace string
Client clientgorbacv1.RbacV1Interface
Mapper meta.RESTMapper
Out io.Writer
PrintObject func(obj runtime.Object) error
PrintObj func(obj runtime.Object) error
genericclioptions.IOStreams
}
func NewCreateRoleOptions(ioStreams genericclioptions.IOStreams) *CreateRoleOptions {
return &CreateRoleOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme),
IOStreams: ioStreams,
}
}
// Role is a command to ease creating Roles.
func NewCmdCreateRole(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
c := &CreateRoleOptions{
Out: cmdOut,
}
func NewCmdCreateRole(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewCreateRoleOptions(ioStreams)
cmd := &cobra.Command{
Use: "role NAME --verb=verb --resource=resource.group/subresource [--resource-name=resourcename] [--dry-run]",
DisableFlagsInUseLine: true,
@ -126,32 +138,34 @@ func NewCmdCreateRole(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
Long: roleLong,
Example: roleExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(c.Complete(f, cmd, args))
cmdutil.CheckErr(c.Validate())
cmdutil.CheckErr(c.RunCreateRole())
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.RunCreateRole())
},
}
o.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddDryRunFlag(cmd)
cmd.Flags().StringSliceVar(&c.Verbs, "verb", []string{}, "Verb that applies to the resources contained in the rule")
cmd.Flags().StringSliceVar(&o.Verbs, "verb", o.Verbs, "Verb that applies to the resources contained in the rule")
cmd.Flags().StringSlice("resource", []string{}, "Resource that the rule applies to")
cmd.Flags().StringArrayVar(&c.ResourceNames, "resource-name", []string{}, "Resource in the white list that the rule applies to, repeat this flag for multiple items")
cmd.Flags().StringArrayVar(&o.ResourceNames, "resource-name", o.ResourceNames, "Resource in the white list that the rule applies to, repeat this flag for multiple items")
return cmd
}
func (c *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
func (o *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
c.Name = name
o.Name = name
// Remove duplicate verbs.
verbs := []string{}
for _, v := range c.Verbs {
for _, v := range o.Verbs {
// VerbAll respresents all kinds of verbs.
if v == "*" {
verbs = []string{"*"}
@ -161,7 +175,7 @@ func (c *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
verbs = append(verbs, v)
}
}
c.Verbs = verbs
o.Verbs = verbs
// Support resource.group pattern. If no API Group specified, use "" as core API Group.
// e.g. --resource=pods,deployments.extensions
@ -180,79 +194,89 @@ func (c *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
}
resource.Resource = parts[0]
c.Resources = append(c.Resources, *resource)
o.Resources = append(o.Resources, *resource)
}
// Remove duplicate resource names.
resourceNames := []string{}
for _, n := range c.ResourceNames {
for _, n := range o.ResourceNames {
if !arrayContains(resourceNames, n) {
resourceNames = append(resourceNames, n)
}
}
c.ResourceNames = resourceNames
o.ResourceNames = resourceNames
// Complete other options for Run.
c.Mapper, _ = f.Object()
c.DryRun = cmdutil.GetDryRunFlag(cmd)
c.OutputFormat = cmdutil.GetFlagString(cmd, "output")
c.Namespace, _, err = f.DefaultNamespace()
o.Mapper, err = f.ToRESTMapper()
if err != nil {
return err
}
c.PrintObject = func(obj runtime.Object) error {
return cmdutil.PrintObject(cmd, obj, c.Out)
o.DryRun = cmdutil.GetDryRunFlag(cmd)
o.OutputFormat = cmdutil.GetFlagString(cmd, "output")
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = func(obj runtime.Object) error {
return printer.PrintObj(obj, o.Out)
}
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
clientset, err := f.KubernetesClientSet()
if err != nil {
return err
}
c.Client = clientset.RbacV1()
o.Client = clientset.RbacV1()
return nil
}
func (c *CreateRoleOptions) Validate() error {
if c.Name == "" {
func (o *CreateRoleOptions) Validate() error {
if o.Name == "" {
return fmt.Errorf("name must be specified")
}
// validate verbs.
if len(c.Verbs) == 0 {
if len(o.Verbs) == 0 {
return fmt.Errorf("at least one verb must be specified")
}
for _, v := range c.Verbs {
for _, v := range o.Verbs {
if !arrayContains(validResourceVerbs, v) {
return fmt.Errorf("invalid verb: '%s'", v)
}
}
// validate resources.
if len(c.Resources) == 0 {
if len(o.Resources) == 0 {
return fmt.Errorf("at least one resource must be specified")
}
return c.validateResource()
return o.validateResource()
}
func (c *CreateRoleOptions) validateResource() error {
for _, r := range c.Resources {
func (o *CreateRoleOptions) validateResource() error {
for _, r := range o.Resources {
if len(r.Resource) == 0 {
return fmt.Errorf("resource must be specified if apiGroup/subresource specified")
}
resource := schema.GroupVersionResource{Resource: r.Resource, Group: r.Group}
groupVersionResource, err := c.Mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group})
groupVersionResource, err := o.Mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group})
if err == nil {
resource = groupVersionResource
}
for _, v := range c.Verbs {
for _, v := range o.Verbs {
if groupResources, ok := specialVerbs[v]; ok {
match := false
for _, extra := range groupResources {
@ -275,29 +299,27 @@ func (c *CreateRoleOptions) validateResource() error {
return nil
}
func (c *CreateRoleOptions) RunCreateRole() error {
role := &rbacv1.Role{}
role.Name = c.Name
rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames, []string{})
func (o *CreateRoleOptions) RunCreateRole() error {
role := &rbacv1.Role{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "Role"},
}
role.Name = o.Name
rules, err := generateResourcePolicyRules(o.Mapper, o.Verbs, o.Resources, o.ResourceNames, []string{})
if err != nil {
return err
}
role.Rules = rules
// Create role.
if !c.DryRun {
_, err = c.Client.Roles(c.Namespace).Create(role)
if !o.DryRun {
role, err = o.Client.Roles(o.Namespace).Create(role)
if err != nil {
return err
}
}
if useShortOutput := c.OutputFormat == "name"; useShortOutput || len(c.OutputFormat) == 0 {
cmdutil.PrintSuccess(useShortOutput, c.Out, role, c.DryRun, "created")
return nil
}
return c.PrintObject(role)
return o.PrintObj(role)
}
func arrayContains(s []string, e string) bool {

View File

@ -14,10 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
"reflect"
"testing"
@ -29,13 +28,15 @@ import (
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestCreateRole(t *testing.T) {
roleName := "my-role"
tf := cmdtesting.NewTestFactory()
tf.Namespace = "test"
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{}
tf.ClientConfigVal = defaultClientConfig()
@ -49,6 +50,7 @@ func TestCreateRole(t *testing.T) {
verbs: "get,watch,list",
resources: "pods,pods",
expectedRole: &rbac.Role{
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
ObjectMeta: v1.ObjectMeta{
Name: roleName,
},
@ -66,6 +68,7 @@ func TestCreateRole(t *testing.T) {
verbs: "get,watch,list",
resources: "replicasets/scale",
expectedRole: &rbac.Role{
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
ObjectMeta: v1.ObjectMeta{
Name: roleName,
},
@ -83,6 +86,7 @@ func TestCreateRole(t *testing.T) {
verbs: "get,watch,list",
resources: "replicasets.extensions/scale",
expectedRole: &rbac.Role{
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
ObjectMeta: v1.ObjectMeta{
Name: roleName,
},
@ -100,6 +104,7 @@ func TestCreateRole(t *testing.T) {
verbs: "get,watch,list",
resources: "pods,deployments.extensions",
expectedRole: &rbac.Role{
TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
ObjectMeta: v1.ObjectMeta{
Name: roleName,
},
@ -123,8 +128,8 @@ func TestCreateRole(t *testing.T) {
for name, test := range tests {
t.Run(name, func(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateRole(tf, buf)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateRole(tf, ioStreams)
cmd.Flags().Set("dry-run", "true")
cmd.Flags().Set("output", "yaml")
cmd.Flags().Set("verb", test.verbs)
@ -146,8 +151,8 @@ func TestCreateRole(t *testing.T) {
}
func TestValidate(t *testing.T) {
tf := cmdtesting.NewTestFactory()
tf.Namespace = "test"
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tests := map[string]struct {
roleOptions *CreateRoleOptions
@ -331,8 +336,12 @@ func TestValidate(t *testing.T) {
}
for name, test := range tests {
test.roleOptions.Mapper, _ = tf.Object()
err := test.roleOptions.Validate()
var err error
test.roleOptions.Mapper, err = tf.ToRESTMapper()
if err != nil {
t.Fatal(err)
}
err = test.roleOptions.Validate()
if test.expectErr && err == nil {
t.Errorf("%s: expect error happens but validate passes.", name)
}
@ -345,13 +354,13 @@ func TestValidate(t *testing.T) {
func TestComplete(t *testing.T) {
roleName := "my-role"
tf := cmdtesting.NewTestFactory()
tf.Namespace = "test"
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.Client = &fake.RESTClient{}
tf.ClientConfigVal = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateRole(tf, buf)
cmd := NewCmdCreateRole(tf, genericclioptions.NewTestIOStreamsDiscard())
cmd.Flags().Set("resource", "pods,deployments.extensions")
tests := map[string]struct {
@ -361,14 +370,17 @@ func TestComplete(t *testing.T) {
expectErr bool
}{
"test-missing-name": {
params: []string{},
roleOptions: &CreateRoleOptions{},
expectErr: true,
params: []string{},
roleOptions: &CreateRoleOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme),
},
expectErr: true,
},
"test-duplicate-verbs": {
params: []string{roleName},
roleOptions: &CreateRoleOptions{
Name: roleName,
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme),
Name: roleName,
Verbs: []string{
"get",
"watch",
@ -400,7 +412,8 @@ func TestComplete(t *testing.T) {
"test-verball": {
params: []string{roleName},
roleOptions: &CreateRoleOptions{
Name: roleName,
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme),
Name: roleName,
Verbs: []string{
"get",
"watch",
@ -428,6 +441,7 @@ func TestComplete(t *testing.T) {
"test-duplicate-resourcenames": {
params: []string{roleName},
roleOptions: &CreateRoleOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme),
Name: roleName,
Verbs: []string{"*"},
ResourceNames: []string{"foo", "foo"},
@ -452,6 +466,7 @@ func TestComplete(t *testing.T) {
"test-valid-complete-case": {
params: []string{roleName},
roleOptions: &CreateRoleOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(legacyscheme.Scheme),
Name: roleName,
Verbs: []string{"*"},
ResourceNames: []string{"foo"},

View File

@ -14,16 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -36,8 +35,16 @@ var (
kubectl create rolebinding admin --clusterrole=admin --user=user1 --user=user2 --group=group1`))
)
type RoleBindingOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// RoleBinding is a command to ease creating RoleBindings.
func NewCmdCreateRoleBinding(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateRoleBinding(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &RoleBindingOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "rolebinding NAME --clusterrole=NAME|--role=NAME [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run]",
DisableFlagsInUseLine: true,
@ -45,13 +52,15 @@ func NewCmdCreateRoleBinding(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command
Long: roleBindingLong,
Example: roleBindingExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateRoleBinding(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.RoleBindingV1GeneratorName)
cmd.Flags().String("clusterrole", "", i18n.T("ClusterRole this RoleBinding should reference"))
cmd.Flags().String("role", "", i18n.T("Role this RoleBinding should reference"))
@ -61,11 +70,12 @@ func NewCmdCreateRoleBinding(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command
return cmd
}
func CreateRoleBinding(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *RoleBindingOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.RoleBindingV1GeneratorName:
@ -80,10 +90,10 @@ func CreateRoleBinding(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command,
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
func (o *RoleBindingOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
@ -32,6 +32,7 @@ import (
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
var groupVersion = schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1"}
@ -69,14 +70,15 @@ func TestCreateRoleBinding(t *testing.T) {
},
}
tf := cmdtesting.NewTestFactory()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
ns := legacyscheme.Codecs
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
encoder := ns.EncoderForVersion(info.Serializer, groupVersion)
decoder := ns.DecoderToVersion(info.Serializer, groupVersion)
tf.Namespace = "test"
tf.Client = &RoleBindingRESTClient{
RESTClient: &fake.RESTClient{
NegotiatedSerializer: ns,
@ -110,8 +112,7 @@ func TestCreateRoleBinding(t *testing.T) {
},
}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateRoleBinding(tf, buf)
cmd := NewCmdCreateRoleBinding(tf, genericclioptions.NewTestIOStreamsDiscard())
cmd.Flags().Set("role", "fake-role")
cmd.Flags().Set("user", "fake-user")
cmd.Flags().Set("group", "fake-group")

View File

@ -14,30 +14,29 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
// NewCmdCreateSecret groups subcommands to create various types of secrets
func NewCmdCreateSecret(f cmdutil.Factory, cmdOut, errOut io.Writer) *cobra.Command {
func NewCmdCreateSecret(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "secret",
Short: i18n.T("Create a secret using specified subcommand"),
Long: "Create a secret using specified subcommand.",
Run: cmdutil.DefaultSubCommandRun(errOut),
Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut),
}
cmd.AddCommand(NewCmdCreateSecretDockerRegistry(f, cmdOut))
cmd.AddCommand(NewCmdCreateSecretTLS(f, cmdOut))
cmd.AddCommand(NewCmdCreateSecretGeneric(f, cmdOut))
cmd.AddCommand(NewCmdCreateSecretDockerRegistry(f, ioStreams))
cmd.AddCommand(NewCmdCreateSecretTLS(f, ioStreams))
cmd.AddCommand(NewCmdCreateSecretGeneric(f, ioStreams))
return cmd
}
@ -73,8 +72,16 @@ var (
kubectl create secret generic my-secret --from-env-file=path/to/bar.env`))
)
type SecretGenericOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateSecretGeneric is a command to create generic secrets from files, directories, or literal values
func NewCmdCreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateSecretGeneric(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &SecretGenericOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "generic NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run]",
DisableFlagsInUseLine: true,
@ -82,13 +89,15 @@ func NewCmdCreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer) *cobra.Comma
Long: secretLong,
Example: secretExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateSecretGeneric(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.SecretV1GeneratorName)
cmd.Flags().StringSlice("from-file", []string{}, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid secret key.")
cmd.Flags().StringArray("from-literal", []string{}, "Specify a key and literal value to insert in secret (i.e. mykey=somevalue)")
@ -98,12 +107,12 @@ func NewCmdCreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer) *cobra.Comma
return cmd
}
// CreateSecretGeneric is the implementation of the create secret generic command
func CreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *SecretGenericOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.SecretV1GeneratorName:
@ -118,12 +127,13 @@ func CreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreateSecretGeneric is the implementation of the create secret generic command
func (o *SecretGenericOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}
var (
@ -135,7 +145,7 @@ var (
When using the Docker command line to push images, you can authenticate to a given registry by running:
'$ docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'.
That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to
That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to
authenticate to the registry. The email address is optional.
When creating applications, you may have a Docker registry that requires authentication. In order for the
@ -147,8 +157,16 @@ var (
kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL`))
)
type SecretDockerRegistryOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateSecretDockerRegistry is a macro command for creating secrets to work with Docker registries
func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &SecretDockerRegistryOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "docker-registry NAME --docker-username=user --docker-password=password --docker-email=email [--docker-server=string] [--from-literal=key1=value1] [--dry-run]",
DisableFlagsInUseLine: true,
@ -156,13 +174,15 @@ func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, cmdOut io.Writer) *cobr
Long: secretForDockerRegistryLong,
Example: secretForDockerRegistryExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateSecretDockerRegistry(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.SecretForDockerRegistryV1GeneratorName)
cmd.Flags().String("docker-username", "", i18n.T("Username for Docker registry authentication"))
cmd.MarkFlagRequired("docker-username")
@ -171,43 +191,49 @@ func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, cmdOut io.Writer) *cobr
cmd.Flags().String("docker-email", "", i18n.T("Email for Docker registry"))
cmd.Flags().String("docker-server", "https://index.docker.io/v1/", i18n.T("Server location for Docker registry"))
cmd.Flags().Bool("append-hash", false, "Append a hash of the secret to its name.")
cmd.Flags().StringSlice("from-file", []string{}, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid secret key.")
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
// CreateSecretDockerRegistry is the implementation of the create secret docker-registry command
func CreateSecretDockerRegistry(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *SecretDockerRegistryOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
requiredFlags := []string{"docker-username", "docker-password", "docker-email", "docker-server"}
for _, requiredFlag := range requiredFlags {
if value := cmdutil.GetFlagString(cmd, requiredFlag); len(value) == 0 {
return cmdutil.UsageErrorf(cmd, "flag %s is required", requiredFlag)
fromFileFlag := cmdutil.GetFlagStringSlice(cmd, "from-file")
if len(fromFileFlag) == 0 {
requiredFlags := []string{"docker-username", "docker-password", "docker-server"}
for _, requiredFlag := range requiredFlags {
if value := cmdutil.GetFlagString(cmd, requiredFlag); len(value) == 0 {
return cmdutil.UsageErrorf(cmd, "flag %s is required", requiredFlag)
}
}
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.SecretForDockerRegistryV1GeneratorName:
generator = &kubectl.SecretForDockerRegistryGeneratorV1{
Name: name,
Username: cmdutil.GetFlagString(cmd, "docker-username"),
Email: cmdutil.GetFlagString(cmd, "docker-email"),
Password: cmdutil.GetFlagString(cmd, "docker-password"),
Server: cmdutil.GetFlagString(cmd, "docker-server"),
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
Name: name,
Username: cmdutil.GetFlagString(cmd, "docker-username"),
Email: cmdutil.GetFlagString(cmd, "docker-email"),
Password: cmdutil.GetFlagString(cmd, "docker-password"),
Server: cmdutil.GetFlagString(cmd, "docker-server"),
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
}
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreateSecretDockerRegistry is the implementation of the create secret docker-registry command
func (o *SecretDockerRegistryOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}
var (
@ -222,8 +248,16 @@ var (
kubectl create secret tls tls-secret --cert=path/to/tls.cert --key=path/to/tls.key`))
)
type SecretTLSOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateSecretTLS is a macro command for creating secrets to work with Docker registries
func NewCmdCreateSecretTLS(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateSecretTLS(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &SecretTLSOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "tls NAME --cert=path/to/cert/file --key=path/to/key/file [--dry-run]",
DisableFlagsInUseLine: true,
@ -231,13 +265,15 @@ func NewCmdCreateSecretTLS(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
Long: secretForTLSLong,
Example: secretForTLSExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateSecretTLS(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.SecretForTLSV1GeneratorName)
cmd.Flags().String("cert", "", i18n.T("Path to PEM encoded public key certificate."))
cmd.Flags().String("key", "", i18n.T("Path to private key associated with given certificate."))
@ -245,12 +281,12 @@ func NewCmdCreateSecretTLS(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
return cmd
}
// CreateSecretTLS is the implementation of the create secret tls command
func CreateSecretTLS(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *SecretTLSOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
requiredFlags := []string{"cert", "key"}
for _, requiredFlag := range requiredFlags {
if value := cmdutil.GetFlagString(cmd, requiredFlag); len(value) == 0 {
@ -269,10 +305,11 @@ func CreateSecretTLS(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, ar
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreateSecretTLS is the implementation of the create secret tls command
func (o *SecretTLSOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}

View File

@ -14,10 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
"net/http"
"testing"
@ -26,6 +25,7 @@ import (
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@ -37,8 +37,10 @@ func TestCreateSecretGeneric(t *testing.T) {
},
}
secretObject.Name = "my-secret"
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
@ -54,9 +56,8 @@ func TestCreateSecretGeneric(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateSecretGeneric(tf, buf)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateSecretGeneric(tf, ioStreams)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("from-literal", "password=includes,comma")
cmd.Flags().Set("from-literal", "username=test_user")
@ -70,8 +71,8 @@ func TestCreateSecretGeneric(t *testing.T) {
func TestCreateSecretDockerRegistry(t *testing.T) {
secretObject := &v1.Secret{}
secretObject.Name = "my-secret"
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
@ -87,9 +88,8 @@ func TestCreateSecretDockerRegistry(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateSecretDockerRegistry(tf, buf)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateSecretDockerRegistry(tf, ioStreams)
cmd.Flags().Set("docker-username", "test-user")
cmd.Flags().Set("docker-password", "test-pass")
cmd.Flags().Set("docker-email", "test-email")

View File

@ -14,33 +14,32 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"io"
"github.com/spf13/cobra"
"k8s.io/api/core/v1"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
// NewCmdCreateService is a macro command to create a new service
func NewCmdCreateService(f cmdutil.Factory, cmdOut, errOut io.Writer) *cobra.Command {
func NewCmdCreateService(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "service",
Aliases: []string{"svc"},
Short: i18n.T("Create a service using specified subcommand."),
Long: "Create a service using specified subcommand.",
Run: cmdutil.DefaultSubCommandRun(errOut),
Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut),
}
cmd.AddCommand(NewCmdCreateServiceClusterIP(f, cmdOut))
cmd.AddCommand(NewCmdCreateServiceNodePort(f, cmdOut))
cmd.AddCommand(NewCmdCreateServiceLoadBalancer(f, cmdOut))
cmd.AddCommand(NewCmdCreateServiceExternalName(f, cmdOut))
cmd.AddCommand(NewCmdCreateServiceClusterIP(f, ioStreams))
cmd.AddCommand(NewCmdCreateServiceNodePort(f, ioStreams))
cmd.AddCommand(NewCmdCreateServiceLoadBalancer(f, ioStreams))
cmd.AddCommand(NewCmdCreateServiceExternalName(f, ioStreams))
return cmd
}
@ -61,8 +60,16 @@ func addPortFlags(cmd *cobra.Command) {
cmd.Flags().StringSlice("tcp", []string{}, "Port pairs can be specified as '<port>:<targetPort>'.")
}
type ServiceClusterIPOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateServiceClusterIP is a command to create a ClusterIP service
func NewCmdCreateServiceClusterIP(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateServiceClusterIP(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &ServiceClusterIPOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "clusterip NAME [--tcp=<port>:<targetPort>] [--dry-run]",
DisableFlagsInUseLine: true,
@ -70,13 +77,15 @@ func NewCmdCreateServiceClusterIP(f cmdutil.Factory, cmdOut io.Writer) *cobra.Co
Long: serviceClusterIPLong,
Example: serviceClusterIPExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateServiceClusterIP(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceClusterIPGeneratorV1Name)
addPortFlags(cmd)
cmd.Flags().String("clusterip", "", i18n.T("Assign your own ClusterIP or set to 'None' for a 'headless' service (no loadbalancing)."))
@ -87,12 +96,12 @@ func errUnsupportedGenerator(cmd *cobra.Command, generatorName string) error {
return cmdutil.UsageErrorf(cmd, "Generator %s not supported. ", generatorName)
}
// CreateServiceClusterIP is the implementation of the create service clusterip command
func CreateServiceClusterIP(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *ServiceClusterIPOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.ServiceClusterIPGeneratorV1Name:
@ -105,12 +114,13 @@ func CreateServiceClusterIP(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Comm
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreateServiceClusterIP is the implementation of the create service clusterip command
func (o *ServiceClusterIPOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}
var (
@ -122,8 +132,16 @@ var (
kubectl create service nodeport my-ns --tcp=5678:8080`))
)
type ServiceNodePortOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateServiceNodePort is a macro command for creating a NodePort service
func NewCmdCreateServiceNodePort(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateServiceNodePort(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &ServiceNodePortOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "nodeport NAME [--tcp=port:targetPort] [--dry-run]",
DisableFlagsInUseLine: true,
@ -131,25 +149,27 @@ func NewCmdCreateServiceNodePort(f cmdutil.Factory, cmdOut io.Writer) *cobra.Com
Long: serviceNodePortLong,
Example: serviceNodePortExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateServiceNodePort(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceNodePortGeneratorV1Name)
cmd.Flags().Int("node-port", 0, "Port used to expose the service on each node in a cluster.")
addPortFlags(cmd)
return cmd
}
// CreateServiceNodePort is the implementation of the create service nodeport command
func CreateServiceNodePort(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *ServiceNodePortOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.ServiceNodePortGeneratorV1Name:
@ -163,12 +183,13 @@ func CreateServiceNodePort(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Comma
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreateServiceNodePort is the implementation of the create service nodeport command
func (o *ServiceNodePortOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}
var (
@ -180,8 +201,16 @@ var (
kubectl create service loadbalancer my-lbs --tcp=5678:8080`))
)
type ServiceLoadBalancerOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateServiceLoadBalancer is a macro command for creating a LoadBalancer service
func NewCmdCreateServiceLoadBalancer(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateServiceLoadBalancer(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &ServiceLoadBalancerOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "loadbalancer NAME [--tcp=port:targetPort] [--dry-run]",
DisableFlagsInUseLine: true,
@ -189,24 +218,26 @@ func NewCmdCreateServiceLoadBalancer(f cmdutil.Factory, cmdOut io.Writer) *cobra
Long: serviceLoadBalancerLong,
Example: serviceLoadBalancerExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateServiceLoadBalancer(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceLoadBalancerGeneratorV1Name)
addPortFlags(cmd)
return cmd
}
// CreateServiceLoadBalancer is the implementation of the create service loadbalancer command
func CreateServiceLoadBalancer(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *ServiceLoadBalancerOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.ServiceLoadBalancerGeneratorV1Name:
@ -219,12 +250,13 @@ func CreateServiceLoadBalancer(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.C
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreateServiceLoadBalancer is the implementation of the create service loadbalancer command
func (o *ServiceLoadBalancerOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}
var (
@ -236,12 +268,20 @@ var (
that exist off platform, on other clusters, or locally.`))
serviceExternalNameExample = templates.Examples(i18n.T(`
# Create a new ExternalName service named my-ns
# Create a new ExternalName service named my-ns
kubectl create service externalname my-ns --external-name bar.com`))
)
type ServiceExternalNameOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateServiceExternalName is a macro command for creating an ExternalName service
func NewCmdCreateServiceExternalName(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateServiceExternalName(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &ServiceExternalNameOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "externalname NAME --external-name external.name [--dry-run]",
DisableFlagsInUseLine: true,
@ -249,13 +289,15 @@ func NewCmdCreateServiceExternalName(f cmdutil.Factory, cmdOut io.Writer) *cobra
Long: serviceExternalNameLong,
Example: serviceExternalNameExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateExternalNameService(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceExternalNameGeneratorV1Name)
addPortFlags(cmd)
cmd.Flags().String("external-name", "", i18n.T("External name of service"))
@ -263,12 +305,12 @@ func NewCmdCreateServiceExternalName(f cmdutil.Factory, cmdOut io.Writer) *cobra
return cmd
}
// CreateExternalNameService is the implementation of the create service externalname command
func CreateExternalNameService(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *ServiceExternalNameOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.ServiceExternalNameGeneratorV1Name:
@ -281,10 +323,11 @@ func CreateExternalNameService(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.C
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreateExternalNameService is the implementation of the create service externalname command
func (o *ServiceExternalNameOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}

View File

@ -14,10 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
"net/http"
"testing"
@ -26,14 +25,17 @@ import (
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestCreateService(t *testing.T) {
service := &v1.Service{}
service.Name = "my-service"
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
negSer := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
@ -49,9 +51,8 @@ func TestCreateService(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateServiceClusterIP(tf, buf)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateServiceClusterIP(tf, ioStreams)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("tcp", "8080:8000")
cmd.Run(cmd, []string{service.Name})
@ -64,8 +65,10 @@ func TestCreateService(t *testing.T) {
func TestCreateServiceNodePort(t *testing.T) {
service := &v1.Service{}
service.Name = "my-node-port-service"
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
negSer := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
@ -81,9 +84,8 @@ func TestCreateServiceNodePort(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateServiceNodePort(tf, buf)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateServiceNodePort(tf, ioStreams)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("tcp", "30000:8000")
cmd.Run(cmd, []string{service.Name})
@ -96,8 +98,10 @@ func TestCreateServiceNodePort(t *testing.T) {
func TestCreateServiceExternalName(t *testing.T) {
service := &v1.Service{}
service.Name = "my-external-name-service"
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
negSer := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
@ -113,9 +117,8 @@ func TestCreateServiceExternalName(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateServiceExternalName(tf, buf)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateServiceExternalName(tf, ioStreams)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("external-name", "name")
cmd.Run(cmd, []string{service.Name})

View File

@ -14,16 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -36,8 +35,16 @@ var (
kubectl create serviceaccount my-service-account`))
)
type ServiceAccountOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateServiceAccount is a macro command to create a new service account
func NewCmdCreateServiceAccount(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
func NewCmdCreateServiceAccount(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &ServiceAccountOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
cmd := &cobra.Command{
Use: "serviceaccount NAME [--dry-run]",
DisableFlagsInUseLine: true,
@ -46,24 +53,25 @@ func NewCmdCreateServiceAccount(f cmdutil.Factory, cmdOut io.Writer) *cobra.Comm
Long: serviceAccountLong,
Example: serviceAccountExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateServiceAccount(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceAccountV1GeneratorName)
return cmd
}
// CreateServiceAccount implements the behavior to run the create service account command
func CreateServiceAccount(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
func (o *ServiceAccountOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.ServiceAccountV1GeneratorName:
@ -71,10 +79,11 @@ func CreateServiceAccount(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Comman
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
}
// CreateServiceAccount implements the behavior to run the create service account command
func (o *ServiceAccountOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}

View File

@ -14,10 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
"net/http"
"testing"
@ -26,14 +25,17 @@ import (
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestCreateServiceAccount(t *testing.T) {
serviceAccountObject := &v1.ServiceAccount{}
serviceAccountObject.Name = "my-service-account"
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
@ -49,9 +51,8 @@ func TestCreateServiceAccount(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateServiceAccount(tf, buf)
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateServiceAccount(tf, ioStreams)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{serviceAccountObject.Name})
expectedOutput := "serviceaccount/" + serviceAccountObject.Name + "\n"

View File

@ -14,27 +14,32 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package create
import (
"bytes"
"net/http"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
apitesting "k8s.io/kubernetes/pkg/api/testing"
api "k8s.io/kubernetes/pkg/apis/core"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestExtraArgsFail(t *testing.T) {
initTestErrorHandler(t)
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
f := cmdtesting.NewTestFactory()
c := NewCmdCreate(f, buf, errBuf)
defer f.Cleanup()
c := NewCmdCreate(f, genericclioptions.NewTestIOStreamsDiscard())
options := CreateOptions{}
if options.ValidateArgs(c, []string{"rc"}) == nil {
t.Errorf("unexpected non-error")
@ -46,8 +51,10 @@ func TestCreateObject(t *testing.T) {
_, _, rc := testData()
rc.Items[0].Name = "redis-master-controller"
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
@ -62,12 +69,10 @@ func TestCreateObject(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreate(tf, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreate(tf, ioStreams)
cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
@ -81,8 +86,10 @@ func TestCreateMultipleObject(t *testing.T) {
initTestErrorHandler(t)
_, svc, rc := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
@ -99,13 +106,11 @@ func TestCreateMultipleObject(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreate(tf, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml")
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreate(tf, ioStreams)
cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/frontend-service.yaml")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
@ -120,8 +125,10 @@ func TestCreateDirectory(t *testing.T) {
_, _, rc := testData()
rc.Items[0].Name = "name"
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
@ -136,12 +143,10 @@ func TestCreateDirectory(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreate(tf, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy")
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreate(tf, ioStreams)
cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/guestbook/legacy")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
@ -149,3 +154,57 @@ func TestCreateDirectory(t *testing.T) {
t.Errorf("unexpected output: %s", buf.String())
}
}
var unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer
func initTestErrorHandler(t *testing.T) {
cmdutil.BehaviorOnFatal(func(str string, code int) {
t.Errorf("Error running command (exit code %d): %s", code, str)
})
}
func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList) {
pods := &api.PodList{
ListMeta: metav1.ListMeta{
ResourceVersion: "15",
},
Items: []api.Pod{
{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: apitesting.DeepEqualSafePodSpec(),
},
{
ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"},
Spec: apitesting.DeepEqualSafePodSpec(),
},
},
}
svc := &api.ServiceList{
ListMeta: metav1.ListMeta{
ResourceVersion: "16",
},
Items: []api.Service{
{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
SessionAffinity: "None",
Type: api.ServiceTypeClusterIP,
},
},
},
}
rc := &api.ReplicationControllerList{
ListMeta: metav1.ListMeta{
ResourceVersion: "17",
},
Items: []api.ReplicationController{
{
ObjectMeta: metav1.ObjectMeta{Name: "rc1", Namespace: "test", ResourceVersion: "18"},
Spec: api.ReplicationControllerSpec{
Replicas: 1,
},
},
},
}
return pods, svc, rc
}

View File

@ -1,146 +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 cmd
import (
"fmt"
"io"
"github.com/spf13/cobra"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientbatchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
var (
jobLong = templates.LongDesc(i18n.T(`
Create a job with the specified name.`))
jobExample = templates.Examples(i18n.T(`
# Create a job from a CronJob named "a-cronjob"
kubectl create job --from=cronjob/a-cronjob`))
)
type CreateJobOptions struct {
Name string
From string
Namespace string
Client clientbatchv1.BatchV1Interface
Out io.Writer
DryRun bool
Builder *resource.Builder
Cmd *cobra.Command
}
// NewCmdCreateJob is a command to ease creating Jobs from CronJobs.
func NewCmdCreateJob(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
c := &CreateJobOptions{
Out: cmdOut,
}
cmd := &cobra.Command{
Use: "job NAME [--from-cronjob=CRONJOB]",
Short: jobLong,
Long: jobLong,
Example: jobExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(c.Complete(f, cmd, args))
cmdutil.CheckErr(c.RunCreateJob())
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().String("from", "", "The name of the resource to create a Job from (only cronjob is supported).")
return cmd
}
func (c *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) (err error) {
if len(args) == 0 {
return cmdutil.UsageErrorf(cmd, "NAME is required")
}
c.Name = args[0]
c.From = cmdutil.GetFlagString(cmd, "from")
c.Namespace, _, err = f.DefaultNamespace()
if err != nil {
return err
}
clientset, err := f.KubernetesClientSet()
if err != nil {
return err
}
c.Client = clientset.BatchV1()
c.Builder = f.NewBuilder()
c.Cmd = cmd
return nil
}
func (c *CreateJobOptions) RunCreateJob() error {
infos, err := c.Builder.
Unstructured().
NamespaceParam(c.Namespace).DefaultNamespace().
ResourceTypeOrNameArgs(false, c.From).
Flatten().
Latest().
Do().
Infos()
if err != nil {
return err
}
if len(infos) != 1 {
return fmt.Errorf("from must be an existing cronjob")
}
cronJob, ok := infos[0].AsVersioned().(*batchv1beta1.CronJob)
if !ok {
return fmt.Errorf("from must be an existing cronjob")
}
return c.createJob(cronJob)
}
func (c *CreateJobOptions) createJob(cronJob *batchv1beta1.CronJob) error {
annotations := make(map[string]string)
annotations["cronjob.kubernetes.io/instantiate"] = "manual"
for k, v := range cronJob.Spec.JobTemplate.Annotations {
annotations[k] = v
}
jobToCreate := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: c.Name,
Namespace: c.Namespace,
Annotations: annotations,
Labels: cronJob.Spec.JobTemplate.Labels,
},
Spec: cronJob.Spec.JobTemplate.Spec,
}
job, err := c.Client.Jobs(c.Namespace).Create(jobToCreate)
if err != nil {
return fmt.Errorf("failed to create job: %v", err)
}
return cmdutil.PrintObject(c.Cmd, job, c.Out)
}

View File

@ -18,20 +18,28 @@ package cmd
import (
"fmt"
"io"
"strings"
"time"
"github.com/golang/glog"
"github.com/spf13/cobra"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/client-go/dynamic"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
kubectlwait "k8s.io/kubernetes/pkg/kubectl/cmd/wait"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -90,7 +98,8 @@ var (
type DeleteOptions struct {
resource.FilenameOptions
Selector string
LabelSelector string
FieldSelector string
DeleteAll bool
IgnoreNotFound bool
Cascade bool
@ -101,20 +110,17 @@ type DeleteOptions struct {
GracePeriod int
Timeout time.Duration
Include3rdParty bool
Output string
Output string
Mapper meta.RESTMapper
Result *resource.Result
DynamicClient dynamic.Interface
Mapper meta.RESTMapper
Result *resource.Result
f cmdutil.Factory
Out io.Writer
ErrOut io.Writer
genericclioptions.IOStreams
}
func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := &DeleteOptions{Include3rdParty: true}
validArgs := cmdutil.ValidArgList(f)
func NewCmdDelete(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
deleteFlags := NewDeleteCommandFlags("containing the resource to delete.")
cmd := &cobra.Command{
Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
@ -123,50 +129,52 @@ func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
Long: delete_long,
Example: delete_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
if err := options.Complete(f, out, errOut, args, cmd); err != nil {
cmdutil.CheckErr(err)
}
if err := options.Validate(cmd); err != nil {
cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, err.Error()))
}
if err := options.RunDelete(); err != nil {
cmdutil.CheckErr(err)
}
o := deleteFlags.ToOptions(nil, streams)
cmdutil.CheckErr(o.Complete(f, args, cmd))
cmdutil.CheckErr(o.Validate(cmd))
cmdutil.CheckErr(o.RunDelete())
},
SuggestFor: []string{"rm"},
ValidArgs: validArgs,
ArgAliases: kubectl.ResourceAliases(validArgs),
}
usage := "containing the resource to delete."
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, not including uninitialized ones.")
cmd.Flags().BoolVar(&options.DeleteAll, "all", options.DeleteAll, "Delete all resources, including uninitialized ones, in the namespace of the specified resource types.")
cmd.Flags().BoolVar(&options.IgnoreNotFound, "ignore-not-found", false, "Treat \"resource not found\" as a successful delete. Defaults to \"true\" when --all is specified.")
cmd.Flags().BoolVar(&options.Cascade, "cascade", true, "If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.")
cmd.Flags().IntVar(&options.GracePeriod, "grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.")
cmd.Flags().BoolVar(&options.DeleteNow, "now", false, "If true, resources are signaled for immediate shutdown (same as --grace-period=1).")
cmd.Flags().BoolVar(&options.ForceDeletion, "force", false, "Immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
cmd.Flags().DurationVar(&options.Timeout, "timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object")
cmdutil.AddOutputVarFlagsForMutation(cmd, &options.Output)
cmdutil.AddInclude3rdPartyVarFlags(cmd, &options.Include3rdParty)
deleteFlags.AddFlags(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd)
return cmd
}
func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args []string, cmd *cobra.Command) error {
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error {
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
if o.DeleteAll || len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0 {
if f := cmd.Flags().Lookup("ignore-not-found"); f != nil && !f.Changed {
// If the user didn't explicitly set the option, default to ignoring NotFound errors when used with --all, -l, or --field-selector
o.IgnoreNotFound = true
}
}
if o.DeleteNow {
if o.GracePeriod != -1 {
return fmt.Errorf("--now and --grace-period cannot be specified together")
}
o.GracePeriod = 1
}
if o.GracePeriod == 0 && !o.ForceDeletion {
// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
// into --grace-period=1. Users may provide --force to bypass this conversion.
o.GracePeriod = 1
}
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
r := f.NewBuilder().
Unstructured().
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
LabelSelectorParam(o.Selector).
LabelSelectorParam(o.LabelSelector).
FieldSelectorParam(o.FieldSelector).
IncludeUninitialized(includeUninitialized).
SelectAllParam(o.DeleteAll).
ResourceTypeOrNameArgs(false, args...).RequireObject(false).
@ -177,151 +185,185 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args
return err
}
o.Result = r
o.Mapper = r.Mapper().RESTMapper
o.f = f
// Set up writer
o.Out = out
o.ErrOut = errOut
o.Mapper, err = f.ToRESTMapper()
if err != nil {
return err
}
o.DynamicClient, err = f.DynamicClient()
if err != nil {
return err
}
return nil
}
func (o *DeleteOptions) Validate(cmd *cobra.Command) error {
if o.DeleteAll && len(o.Selector) > 0 {
if o.Output != "" && o.Output != "name" {
return cmdutil.UsageErrorf(cmd, "Unexpected -o output mode: %v. We only support '-o name'.", o.Output)
}
if o.DeleteAll && len(o.LabelSelector) > 0 {
return fmt.Errorf("cannot set --all and --selector at the same time")
}
if o.DeleteAll {
f := cmd.Flags().Lookup("ignore-not-found")
// The flag should never be missing
if f == nil {
return fmt.Errorf("missing --ignore-not-found flag")
}
// If the user didn't explicitly set the option, default to ignoring NotFound errors when used with --all
if !f.Changed {
o.IgnoreNotFound = true
}
if o.DeleteAll && len(o.FieldSelector) > 0 {
return fmt.Errorf("cannot set --all and --field-selector at the same time")
}
if o.DeleteNow {
if o.GracePeriod != -1 {
return fmt.Errorf("--now and --grace-period cannot be specified together")
}
o.GracePeriod = 1
if o.GracePeriod == 0 && !o.ForceDeletion && !o.WaitForDeletion {
// With the explicit --wait flag we need extra validation for backward compatibility
return fmt.Errorf("--grace-period=0 must have either --force specified, or --wait to be set to true")
}
if o.GracePeriod == 0 {
if o.ForceDeletion {
fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
} else {
// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
// into --grace-period=1 and wait until the object is successfully deleted. Users may provide --force
// to bypass this wait.
o.WaitForDeletion = true
o.GracePeriod = 1
}
switch {
case o.GracePeriod == 0 && o.ForceDeletion:
fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
case o.ForceDeletion:
fmt.Fprintf(o.ErrOut, "warning: --force is ignored because --grace-period is not 0.\n")
}
return nil
}
func (o *DeleteOptions) RunDelete() error {
shortOutput := o.Output == "name"
// By default use a reaper to delete all related resources.
if o.Cascade {
return ReapResult(o.Result, o.f, o.Out, true, o.IgnoreNotFound, o.Timeout, o.GracePeriod, o.WaitForDeletion, shortOutput, false)
}
return DeleteResult(o.Result, o.Out, o.IgnoreNotFound, o.GracePeriod, shortOutput)
return o.DeleteResult(o.Result)
}
func ReapResult(r *resource.Result, f cmdutil.Factory, out io.Writer, isDefaultDelete, ignoreNotFound bool, timeout time.Duration, gracePeriod int, waitForDeletion, shortOutput bool, quiet bool) error {
func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
found := 0
if ignoreNotFound {
r = r.IgnoreErrors(errors.IsNotFound)
}
err := r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
found++
reaper, err := f.Reaper(info.Mapping)
if err != nil {
// If there is no reaper for this resources and the user didn't explicitly ask for stop.
if kubectl.IsNoSuchReaperError(err) && isDefaultDelete {
// No client side reaper found. Let the server do cascading deletion.
return cascadingDeleteResource(info, out, shortOutput)
}
return cmdutil.AddSourceToErr("reaping", info.Source, err)
}
var options *metav1.DeleteOptions
if gracePeriod >= 0 {
options = metav1.NewDeleteOptions(int64(gracePeriod))
}
if err := reaper.Stop(info.Namespace, info.Name, timeout, options); err != nil {
return cmdutil.AddSourceToErr("stopping", info.Source, err)
}
if waitForDeletion {
if err := waitForObjectDeletion(info, timeout); err != nil {
return cmdutil.AddSourceToErr("stopping", info.Source, err)
}
}
if !quiet {
printDeletion(info, out, shortOutput)
}
return nil
})
if err != nil {
return err
}
if found == 0 {
fmt.Fprintf(out, "No resources found\n")
}
return nil
}
func DeleteResult(r *resource.Result, out io.Writer, ignoreNotFound bool, gracePeriod int, shortOutput bool) error {
found := 0
if ignoreNotFound {
if o.IgnoreNotFound {
r = r.IgnoreErrors(errors.IsNotFound)
}
deletedInfos := []*resource.Info{}
err := r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
deletedInfos = append(deletedInfos, info)
found++
// if we're here, it means that cascade=false (not the default), so we should orphan as requested
orphan := true
options := &metav1.DeleteOptions{}
if gracePeriod >= 0 {
options = metav1.NewDeleteOptions(int64(gracePeriod))
if o.GracePeriod >= 0 {
options = metav1.NewDeleteOptions(int64(o.GracePeriod))
}
options.OrphanDependents = &orphan
return deleteResource(info, out, shortOutput, options)
policy := metav1.DeletePropagationBackground
if !o.Cascade {
policy = metav1.DeletePropagationOrphan
}
options.PropagationPolicy = &policy
return o.deleteResource(info, options)
})
if err != nil {
return err
}
if found == 0 {
fmt.Fprintf(out, "No resources found\n")
fmt.Fprintf(o.Out, "No resources found\n")
return nil
}
return nil
if !o.WaitForDeletion {
return nil
}
// if we don't have a dynamic client, we don't want to wait. Eventually when delete is cleaned up, this will likely
// drop out.
if o.DynamicClient == nil {
return nil
}
effectiveTimeout := o.Timeout
if effectiveTimeout == 0 {
// if we requested to wait forever, set it to a week.
effectiveTimeout = 168 * time.Hour
}
waitOptions := kubectlwait.WaitOptions{
ResourceFinder: genericclioptions.ResourceFinderForResult(resource.InfoListVisitor(deletedInfos)),
DynamicClient: o.DynamicClient,
Timeout: effectiveTimeout,
Printer: printers.NewDiscardingPrinter(),
ConditionFn: kubectlwait.IsDeleted,
IOStreams: o.IOStreams,
}
err = waitOptions.RunWait()
if errors.IsForbidden(err) {
// if we're forbidden from waiting, we shouldn't fail.
glog.V(1).Info(err)
return nil
}
return err
}
func cascadingDeleteResource(info *resource.Info, out io.Writer, shortOutput bool) error {
falseVar := false
deleteOptions := &metav1.DeleteOptions{OrphanDependents: &falseVar}
return deleteResource(info, out, shortOutput, deleteOptions)
}
func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) error {
// TODO: Remove this in or after 1.12 release.
// Server version >= 1.11 no longer needs this hack.
mapping := info.ResourceMapping()
if mapping.Resource.GroupResource() == (schema.GroupResource{Group: "extensions", Resource: "daemonsets"}) ||
mapping.Resource.GroupResource() == (schema.GroupResource{Group: "apps", Resource: "daemonsets"}) {
if err := updateDaemonSet(info.Namespace, info.Name, o.DynamicClient); err != nil {
return err
}
}
func deleteResource(info *resource.Info, out io.Writer, shortOutput bool, deleteOptions *metav1.DeleteOptions) error {
if err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions); err != nil {
return cmdutil.AddSourceToErr("deleting", info.Source, err)
}
printDeletion(info, out, shortOutput)
o.PrintObj(info)
return nil
}
// deletion printing is special because they don't have an object to print. This logic mirrors PrintSuccess
func printDeletion(info *resource.Info, out io.Writer, shortOutput bool) {
// TODO: Remove this in or after 1.12 release.
// Server version >= 1.11 no longer needs this hack.
func updateDaemonSet(namespace, name string, dynamicClient dynamic.Interface) error {
dsClient := dynamicClient.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "daemonsets"}).Namespace(namespace)
obj, err := dsClient.Get(name, metav1.GetOptions{})
if err != nil {
return err
}
ds := &appsv1.DaemonSet{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, ds); err != nil {
return err
}
// We set the nodeSelector to a random label. This label is nearly guaranteed
// to not be set on any node so the DameonSetController will start deleting
// daemon pods. Once it's done deleting the daemon pods, it's safe to delete
// the DaemonSet.
ds.Spec.Template.Spec.NodeSelector = map[string]string{
string(uuid.NewUUID()): string(uuid.NewUUID()),
}
// force update to avoid version conflict
ds.ResourceVersion = ""
out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ds)
if err != nil {
return err
}
if _, err = dsClient.Update(&unstructured.Unstructured{Object: out}); err != nil {
return err
}
// Wait for the daemon set controller to kill all the daemon pods.
if err := wait.Poll(1*time.Second, 5*time.Minute, func() (bool, error) {
updatedObj, err := dsClient.Get(name, metav1.GetOptions{})
if err != nil {
return false, nil
}
updatedDS := &appsv1.DaemonSet{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(updatedObj.Object, ds); err != nil {
return false, nil
}
return updatedDS.Status.CurrentNumberScheduled+updatedDS.Status.NumberMisscheduled == 0, nil
}); err != nil {
return err
}
return nil
}
// deletion printing is special because we do not have an object to print.
// This mirrors name printer behavior
func (o *DeleteOptions) PrintObj(info *resource.Info) {
operation := "deleted"
groupKind := info.Mapping.GroupVersionKind
kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
@ -329,33 +371,16 @@ func printDeletion(info *resource.Info, out io.Writer, shortOutput bool) {
kindString = strings.ToLower(groupKind.Kind)
}
if shortOutput {
if o.GracePeriod == 0 {
operation = "force deleted"
}
if o.Output == "name" {
// -o name: prints resource/name
fmt.Fprintf(out, "%s/%s\n", kindString, info.Name)
fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
return
}
// understandable output by default
fmt.Fprintf(out, "%s \"%s\" %s\n", kindString, info.Name, operation)
}
// objectDeletionWaitInterval is the interval to wait between checks for deletion.
var objectDeletionWaitInterval = time.Second
// waitForObjectDeletion refreshes the object, waiting until it is deleted, a timeout is reached, or
// an error is encountered. It checks once a second.
func waitForObjectDeletion(info *resource.Info, timeout time.Duration) error {
copied := *info
info = &copied
// TODO: refactor Reaper so that we can pass the "wait" option into it, and then check for UID change.
return wait.PollImmediate(objectDeletionWaitInterval, timeout, func() (bool, error) {
switch err := info.Get(); {
case err == nil:
return false, nil
case errors.IsNotFound(err):
return true, nil
default:
return false, err
}
})
fmt.Fprintf(o.Out, "%s \"%s\" %s\n", kindString, info.Name, operation)
}

View File

@ -0,0 +1,193 @@
/*
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 cmd
import (
"time"
"github.com/spf13/cobra"
"k8s.io/client-go/dynamic"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
// PrintFlags composes common printer flag structs
// used for commands requiring deletion logic.
type DeleteFlags struct {
FileNameFlags *genericclioptions.FileNameFlags
LabelSelector *string
FieldSelector *string
All *bool
Cascade *bool
Force *bool
GracePeriod *int
IgnoreNotFound *bool
Now *bool
Timeout *time.Duration
Wait *bool
Output *string
}
func (f *DeleteFlags) ToOptions(dynamicClient dynamic.Interface, streams genericclioptions.IOStreams) *DeleteOptions {
options := &DeleteOptions{
DynamicClient: dynamicClient,
IOStreams: streams,
}
// add filename options
if f.FileNameFlags != nil {
options.FilenameOptions = f.FileNameFlags.ToOptions()
}
if f.LabelSelector != nil {
options.LabelSelector = *f.LabelSelector
}
if f.FieldSelector != nil {
options.FieldSelector = *f.FieldSelector
}
// add output format
if f.Output != nil {
options.Output = *f.Output
}
if f.All != nil {
options.DeleteAll = *f.All
}
if f.Cascade != nil {
options.Cascade = *f.Cascade
}
if f.Force != nil {
options.ForceDeletion = *f.Force
}
if f.GracePeriod != nil {
options.GracePeriod = *f.GracePeriod
}
if f.IgnoreNotFound != nil {
options.IgnoreNotFound = *f.IgnoreNotFound
}
if f.Now != nil {
options.DeleteNow = *f.Now
}
if f.Timeout != nil {
options.Timeout = *f.Timeout
}
if f.Wait != nil {
options.WaitForDeletion = *f.Wait
}
return options
}
func (f *DeleteFlags) AddFlags(cmd *cobra.Command) {
f.FileNameFlags.AddFlags(cmd.Flags())
if f.LabelSelector != nil {
cmd.Flags().StringVarP(f.LabelSelector, "selector", "l", *f.LabelSelector, "Selector (label query) to filter on, not including uninitialized ones.")
}
if f.FieldSelector != nil {
cmd.Flags().StringVarP(f.FieldSelector, "field-selector", "", *f.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
}
if f.All != nil {
cmd.Flags().BoolVar(f.All, "all", *f.All, "Delete all resources, including uninitialized ones, in the namespace of the specified resource types.")
}
if f.Force != nil {
cmd.Flags().BoolVar(f.Force, "force", *f.Force, "Only used when grace-period=0. If true, immediately remove resources from API and bypass graceful deletion. Note that immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
}
if f.Cascade != nil {
cmd.Flags().BoolVar(f.Cascade, "cascade", *f.Cascade, "If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.")
}
if f.Now != nil {
cmd.Flags().BoolVar(f.Now, "now", *f.Now, "If true, resources are signaled for immediate shutdown (same as --grace-period=1).")
}
if f.GracePeriod != nil {
cmd.Flags().IntVar(f.GracePeriod, "grace-period", *f.GracePeriod, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative. Set to 1 for immediate shutdown. Can only be set to 0 when --force is true (force deletion).")
}
if f.Timeout != nil {
cmd.Flags().DurationVar(f.Timeout, "timeout", *f.Timeout, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object")
}
if f.IgnoreNotFound != nil {
cmd.Flags().BoolVar(f.IgnoreNotFound, "ignore-not-found", *f.IgnoreNotFound, "Treat \"resource not found\" as a successful delete. Defaults to \"true\" when --all is specified.")
}
if f.Wait != nil {
cmd.Flags().BoolVar(f.Wait, "wait", *f.Wait, "If true, wait for resources to be gone before returning. This waits for finalizers.")
}
if f.Output != nil {
cmd.Flags().StringVarP(f.Output, "output", "o", *f.Output, "Output mode. Use \"-o name\" for shorter output (resource/name).")
}
}
// NewDeleteCommandFlags provides default flags and values for use with the "delete" command
func NewDeleteCommandFlags(usage string) *DeleteFlags {
cascade := true
gracePeriod := -1
// setup command defaults
all := false
force := false
ignoreNotFound := false
now := false
output := ""
labelSelector := ""
fieldSelector := ""
timeout := time.Duration(0)
wait := true
filenames := []string{}
recursive := false
return &DeleteFlags{
FileNameFlags: &genericclioptions.FileNameFlags{Usage: usage, Filenames: &filenames, Recursive: &recursive},
LabelSelector: &labelSelector,
FieldSelector: &fieldSelector,
Cascade: &cascade,
GracePeriod: &gracePeriod,
All: &all,
Force: &force,
IgnoreNotFound: &ignoreNotFound,
Now: &now,
Timeout: &timeout,
Wait: &wait,
Output: &output,
}
}
// NewDeleteFlags provides default flags and values for use in commands outside of "delete"
func NewDeleteFlags(usage string) *DeleteFlags {
cascade := true
gracePeriod := -1
force := false
timeout := time.Duration(0)
wait := false
filenames := []string{}
recursive := false
return &DeleteFlags{
FileNameFlags: &genericclioptions.FileNameFlags{Usage: usage, Filenames: &filenames, Recursive: &recursive},
Cascade: &cascade,
GracePeriod: &gracePeriod,
// add non-defaults
Force: &force,
Timeout: &timeout,
Wait: &wait,
}
}

View File

@ -17,47 +17,45 @@ limitations under the License.
package cmd
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strings"
"testing"
"time"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
var unstructuredSerializer = dynamic.ContentConfig().NegotiatedSerializer
var unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer
var fakecmd = &cobra.Command{
Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
DisableFlagsInUseLine: true,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
},
func fakecmd() *cobra.Command {
cmd := &cobra.Command{
Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
DisableFlagsInUseLine: true,
Run: func(cmd *cobra.Command, args []string) {},
}
return cmd
}
func TestDeleteObjectByTuple(t *testing.T) {
initTestErrorHandler(t)
_, _, rc := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@ -79,10 +77,9 @@ func TestDeleteObjectByTuple(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(tf, buf, errBuf)
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, streams)
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
@ -92,8 +89,8 @@ func TestDeleteObjectByTuple(t *testing.T) {
}
// Test cascading delete of object without client-side reaper doesn't make GET requests
buf, errBuf = bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd = NewCmdDelete(tf, buf, errBuf)
streams, _, buf, _ = genericclioptions.NewTestIOStreams()
cmd = NewCmdDelete(tf, streams)
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{"secrets/mysecret"})
@ -102,17 +99,17 @@ func TestDeleteObjectByTuple(t *testing.T) {
}
}
func hasExpectedOrphanDependents(body io.ReadCloser, expectedOrphanDependents *bool) bool {
if body == nil || expectedOrphanDependents == nil {
return body == nil && expectedOrphanDependents == nil
func hasExpectedPropagationPolicy(body io.ReadCloser, policy *metav1.DeletionPropagation) bool {
if body == nil || policy == nil {
return body == nil && policy == nil
}
var parsedBody metav1.DeleteOptions
rawBody, _ := ioutil.ReadAll(body)
json.Unmarshal(rawBody, &parsedBody)
if parsedBody.OrphanDependents == nil {
if parsedBody.PropagationPolicy == nil {
return false
}
return *expectedOrphanDependents == *parsedBody.OrphanDependents
return *policy == *parsedBody.PropagationPolicy
}
// Tests that DeleteOptions.OrphanDependents is appropriately set while deleting objects.
@ -120,16 +117,18 @@ func TestOrphanDependentsInDeleteObject(t *testing.T) {
initTestErrorHandler(t)
_, _, rc := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
var expectedOrphanDependents *bool
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
var policy *metav1.DeletionPropagation
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m, b := req.URL.Path, req.Method, req.Body; {
case p == "/namespaces/test/secrets/mysecret" && m == "DELETE" && hasExpectedOrphanDependents(b, expectedOrphanDependents):
case p == "/namespaces/test/secrets/mysecret" && m == "DELETE" && hasExpectedPropagationPolicy(b, policy):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
@ -137,13 +136,12 @@ func TestOrphanDependentsInDeleteObject(t *testing.T) {
}
}),
}
tf.Namespace = "test"
// DeleteOptions.OrphanDependents should be false, when cascade is true (default).
falseVar := false
expectedOrphanDependents = &falseVar
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(tf, buf, errBuf)
// DeleteOptions.PropagationPolicy should be Background, when cascade is true (default).
backgroundPolicy := metav1.DeletePropagationBackground
policy = &backgroundPolicy
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, streams)
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{"secrets/mysecret"})
@ -152,10 +150,10 @@ func TestOrphanDependentsInDeleteObject(t *testing.T) {
}
// Test that delete options should be set to orphan when cascade is false.
trueVar := true
expectedOrphanDependents = &trueVar
buf, errBuf = bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd = NewCmdDelete(tf, buf, errBuf)
orphanPolicy := metav1.DeletePropagationOrphan
policy = &orphanPolicy
streams, _, buf, _ = genericclioptions.NewTestIOStreams()
cmd = NewCmdDelete(tf, streams)
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
@ -170,8 +168,10 @@ func TestDeleteNamedObject(t *testing.T) {
initTestErrorHandler(t)
_, _, rc := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@ -193,10 +193,9 @@ func TestDeleteNamedObject(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(tf, buf, errBuf)
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, streams)
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
@ -206,8 +205,8 @@ func TestDeleteNamedObject(t *testing.T) {
}
// Test cascading delete of object without client-side reaper doesn't make GET requests
buf, errBuf = bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd = NewCmdDelete(tf, buf, errBuf)
streams, _, buf, _ = genericclioptions.NewTestIOStreams()
cmd = NewCmdDelete(tf, streams)
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
@ -221,8 +220,10 @@ func TestDeleteObject(t *testing.T) {
initTestErrorHandler(t)
_, _, rc := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@ -236,11 +237,10 @@ func TestDeleteObject(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(tf, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, streams)
cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
@ -251,37 +251,15 @@ func TestDeleteObject(t *testing.T) {
}
}
type fakeReaper struct {
namespace, name string
timeout time.Duration
deleteOptions *metav1.DeleteOptions
err error
}
func (r *fakeReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *metav1.DeleteOptions) error {
r.namespace, r.name = namespace, name
r.timeout = timeout
r.deleteOptions = gracePeriod
return r.err
}
type fakeReaperFactory struct {
cmdutil.Factory
reaper kubectl.Reaper
}
func (f *fakeReaperFactory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, error) {
return f.reaper, nil
}
func TestDeleteObjectGraceZero(t *testing.T) {
initTestErrorHandler(t)
pods, _, _ := testData()
objectDeletionWaitInterval = time.Millisecond
count := 0
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@ -306,12 +284,9 @@ func TestDeleteObjectGraceZero(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
reaper := &fakeReaper{}
fake := &fakeReaperFactory{Factory: tf, reaper: reaper}
cmd := NewCmdDelete(fake, buf, errBuf)
streams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, streams)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("grace-period", "0")
cmd.Run(cmd, []string{"pods/nginx"})
@ -320,17 +295,16 @@ func TestDeleteObjectGraceZero(t *testing.T) {
if buf.String() != "pod/nginx\n" {
t.Errorf("unexpected output: %s\n---\n%s", buf.String(), errBuf.String())
}
if reaper.deleteOptions == nil || reaper.deleteOptions.GracePeriodSeconds == nil || *reaper.deleteOptions.GracePeriodSeconds != 1 {
t.Errorf("unexpected reaper options: %#v", reaper)
}
if count != 4 {
if count != 0 {
t.Errorf("unexpected calls to GET: %d", count)
}
}
func TestDeleteObjectNotFound(t *testing.T) {
initTestErrorHandler(t)
tf := cmdtesting.NewTestFactory()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -343,18 +317,17 @@ func TestDeleteObjectNotFound(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
options := &DeleteOptions{
FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"},
Filenames: []string{"../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml"},
},
GracePeriod: -1,
Cascade: false,
Output: "name",
IOStreams: genericclioptions.NewTestIOStreamsDiscard(),
}
err := options.Complete(tf, buf, errBuf, []string{}, fakecmd)
err := options.Complete(tf, []string{}, fakecmd())
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -366,7 +339,9 @@ func TestDeleteObjectNotFound(t *testing.T) {
func TestDeleteObjectIgnoreNotFound(t *testing.T) {
initTestErrorHandler(t)
tf := cmdtesting.NewTestFactory()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -379,11 +354,10 @@ func TestDeleteObjectIgnoreNotFound(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd := NewCmdDelete(tf, streams)
cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("ignore-not-found", "true")
cmd.Flags().Set("output", "name")
@ -401,8 +375,10 @@ func TestDeleteAllNotFound(t *testing.T) {
svc.Items = append(svc.Items, api.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@ -420,8 +396,6 @@ func TestDeleteAllNotFound(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
// Make sure we can explicitly choose to fail on NotFound errors, even with --all
options := &DeleteOptions{
@ -431,8 +405,9 @@ func TestDeleteAllNotFound(t *testing.T) {
DeleteAll: true,
IgnoreNotFound: false,
Output: "name",
IOStreams: genericclioptions.NewTestIOStreamsDiscard(),
}
err := options.Complete(tf, buf, errBuf, []string{"services"}, fakecmd)
err := options.Complete(tf, []string{"services"}, fakecmd())
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -446,8 +421,10 @@ func TestDeleteAllIgnoreNotFound(t *testing.T) {
initTestErrorHandler(t)
_, svc, _ := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
// Add an item to the list which will result in a 404 on delete
svc.Items = append(svc.Items, api.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
@ -469,10 +446,9 @@ func TestDeleteAllIgnoreNotFound(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, buf, errBuf)
cmd := NewCmdDelete(tf, streams)
cmd.Flags().Set("all", "true")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
@ -487,8 +463,10 @@ func TestDeleteMultipleObject(t *testing.T) {
initTestErrorHandler(t)
_, svc, rc := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@ -504,12 +482,11 @@ func TestDeleteMultipleObject(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml")
cmd := NewCmdDelete(tf, streams)
cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/frontend-service.yaml")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
@ -523,8 +500,10 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
initTestErrorHandler(t)
_, svc, _ := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@ -540,18 +519,18 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
options := &DeleteOptions{
FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml", "../../../examples/guestbook/frontend-service.yaml"},
Filenames: []string{"../../../test/e2e/testing-manifests/guestbook/legacy/redis-master-controller.yaml", "../../../test/e2e/testing-manifests/guestbook/frontend-service.yaml"},
},
GracePeriod: -1,
Cascade: false,
Output: "name",
IOStreams: streams,
}
err := options.Complete(tf, buf, errBuf, []string{}, fakecmd)
err := options.Complete(tf, []string{}, fakecmd())
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -568,8 +547,10 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) {
initTestErrorHandler(t)
_, svc, rc := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@ -590,10 +571,9 @@ func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, buf, errBuf)
cmd := NewCmdDelete(tf, streams)
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
@ -607,8 +587,10 @@ func TestDeleteDirectory(t *testing.T) {
initTestErrorHandler(t)
_, _, rc := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@ -622,11 +604,10 @@ func TestDeleteDirectory(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy")
cmd := NewCmdDelete(tf, streams)
cmd.Flags().Set("filename", "../../../test/e2e/testing-manifests/guestbook/legacy")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
@ -640,8 +621,10 @@ func TestDeleteMultipleSelector(t *testing.T) {
initTestErrorHandler(t)
pods, svc, _ := testData()
tf := cmdtesting.NewTestFactory()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
@ -667,10 +650,9 @@ func TestDeleteMultipleSelector(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, buf, errBuf)
cmd := NewCmdDelete(tf, streams)
cmd.Flags().Set("selector", "a=b")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
@ -706,26 +688,29 @@ func TestResourceErrors(t *testing.T) {
}
for k, testCase := range testCases {
tf := cmdtesting.NewTestFactory()
tf.Namespace = "test"
tf.ClientConfigVal = defaultClientConfig()
t.Run(k, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
tf.ClientConfigVal = defaultClientConfig()
options := &DeleteOptions{
FilenameOptions: resource.FilenameOptions{},
GracePeriod: -1,
Cascade: false,
Output: "name",
}
err := options.Complete(tf, buf, errBuf, testCase.args, fakecmd)
if !testCase.errFn(err) {
t.Errorf("%s: unexpected error: %v", k, err)
continue
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
options := &DeleteOptions{
FilenameOptions: resource.FilenameOptions{},
GracePeriod: -1,
Cascade: false,
Output: "name",
IOStreams: streams,
}
err := options.Complete(tf, testCase.args, fakecmd())
if !testCase.errFn(err) {
t.Errorf("%s: unexpected error: %v", k, err)
return
}
if buf.Len() > 0 {
t.Errorf("buffer should be empty: %s", string(buf.Bytes()))
}
if buf.Len() > 0 {
t.Errorf("buffer should be empty: %s", string(buf.Bytes()))
}
})
}
}

View File

@ -18,21 +18,20 @@ package cmd
import (
"fmt"
"io"
"strings"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
)
var (
@ -69,67 +68,103 @@ var (
kubectl describe pods frontend`))
)
func NewCmdDescribe(f cmdutil.Factory, out, cmdErr io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}
describerSettings := &printers.DescriberSettings{}
type DescribeOptions struct {
CmdParent string
Selector string
Namespace string
// TODO: this should come from the factory, and may need to be loaded from the server, and so is probably
// going to have to be removed
validArgs := printersinternal.DescribableResources()
argAliases := kubectl.ResourceAliases(validArgs)
Describer func(*meta.RESTMapping) (printers.Describer, error)
NewBuilder func() *resource.Builder
BuilderArgs []string
EnforceNamespace bool
AllNamespaces bool
IncludeUninitialized bool
DescriberSettings *printers.DescriberSettings
FilenameOptions *resource.FilenameOptions
genericclioptions.IOStreams
}
func NewCmdDescribe(parent string, f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := &DescribeOptions{
FilenameOptions: &resource.FilenameOptions{},
DescriberSettings: &printers.DescriberSettings{
ShowEvents: true,
},
CmdParent: parent,
IOStreams: streams,
}
cmd := &cobra.Command{
Use: "describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME)",
DisableFlagsInUseLine: true,
Short: i18n.T("Show details of a specific resource or group of resources"),
Long: describeLong + "\n\n" + cmdutil.ValidResourceTypeList(f),
Long: describeLong + "\n\n" + cmdutil.SuggestApiResources(parent),
Example: describeExample,
Run: func(cmd *cobra.Command, args []string) {
err := RunDescribe(f, out, cmdErr, cmd, args, options, describerSettings)
cmdutil.CheckErr(err)
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Run())
},
ValidArgs: validArgs,
ArgAliases: argAliases,
}
usage := "containing the resource to describe"
cmdutil.AddFilenameOptionFlags(cmd, options, usage)
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().Bool("all-namespaces", false, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
cmd.Flags().BoolVar(&describerSettings.ShowEvents, "show-events", true, "If true, display events related to the described object.")
cmdutil.AddInclude3rdPartyFlags(cmd)
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, usage)
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
cmd.Flags().BoolVar(&o.DescriberSettings.ShowEvents, "show-events", o.DescriberSettings.ShowEvents, "If true, display events related to the described object.")
cmdutil.AddIncludeUninitializedFlag(cmd)
return cmd
}
func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions, describerSettings *printers.DescriberSettings) error {
selector := cmdutil.GetFlagString(cmd, "selector")
allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces")
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
func (o *DescribeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
if allNamespaces {
enforceNamespace = false
if o.AllNamespaces {
o.EnforceNamespace = false
}
if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(options.Filenames) {
fmt.Fprint(cmdErr, "You must specify the type of resource to describe. ", cmdutil.ValidResourceTypeList(f))
return cmdutil.UsageErrorf(cmd, "Required resource not specified.")
if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames) {
return fmt.Errorf("You must specify the type of resource to describe. %s\n", cmdutil.SuggestApiResources(o.CmdParent))
}
o.BuilderArgs = args
o.Describer = func(mapping *meta.RESTMapping) (printers.Describer, error) {
return cmdutil.DescriberFn(f, mapping)
}
o.NewBuilder = f.NewBuilder
// include the uninitialized objects by default
// unless user explicitly set --include-uninitialized=false
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, true)
r := f.NewBuilder().
o.IncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, true)
return nil
}
func (o *DescribeOptions) Validate(args []string) error {
return nil
}
func (o *DescribeOptions) Run() error {
r := o.NewBuilder().
Unstructured().
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
FilenameParam(enforceNamespace, options).
LabelSelectorParam(selector).
IncludeUninitialized(includeUninitialized).
ResourceTypeOrNameArgs(true, args...).
NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
FilenameParam(o.EnforceNamespace, o.FilenameOptions).
LabelSelectorParam(o.Selector).
IncludeUninitialized(o.IncludeUninitialized).
ResourceTypeOrNameArgs(true, o.BuilderArgs...).
Flatten().
Do()
err = r.Err()
err := r.Err()
if err != nil {
return err
}
@ -137,8 +172,8 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a
allErrs := []error{}
infos, err := r.Infos()
if err != nil {
if apierrors.IsNotFound(err) && len(args) == 2 {
return DescribeMatchingResources(f, cmdNamespace, args[0], args[1], describerSettings, out, err)
if apierrors.IsNotFound(err) && len(o.BuilderArgs) == 2 {
return o.DescribeMatchingResources(err, o.BuilderArgs[0], o.BuilderArgs[1])
}
allErrs = append(allErrs, err)
}
@ -147,7 +182,7 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a
first := true
for _, info := range infos {
mapping := info.ResourceMapping()
describer, err := f.Describer(mapping)
describer, err := o.Describer(mapping)
if err != nil {
if errs.Has(err.Error()) {
continue
@ -156,7 +191,7 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a
errs.Insert(err.Error())
continue
}
s, err := describer.Describe(info.Namespace, info.Name, *describerSettings)
s, err := describer.Describe(info.Namespace, info.Name, *o.DescriberSettings)
if err != nil {
if errs.Has(err.Error()) {
continue
@ -167,20 +202,20 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a
}
if first {
first = false
fmt.Fprint(out, s)
fmt.Fprint(o.Out, s)
} else {
fmt.Fprintf(out, "\n\n%s", s)
fmt.Fprintf(o.Out, "\n\n%s", s)
}
}
return utilerrors.NewAggregate(allErrs)
}
func DescribeMatchingResources(f cmdutil.Factory, namespace, rsrc, prefix string, describerSettings *printers.DescriberSettings, out io.Writer, originalError error) error {
r := f.NewBuilder().
func (o *DescribeOptions) DescribeMatchingResources(originalError error, resource, prefix string) error {
r := o.NewBuilder().
Unstructured().
NamespaceParam(namespace).DefaultNamespace().
ResourceTypeOrNameArgs(true, rsrc).
NamespaceParam(o.Namespace).DefaultNamespace().
ResourceTypeOrNameArgs(true, resource).
SingleResourceType().
Flatten().
Do()
@ -188,7 +223,7 @@ func DescribeMatchingResources(f cmdutil.Factory, namespace, rsrc, prefix string
if err != nil {
return err
}
describer, err := f.Describer(mapping)
describer, err := o.Describer(mapping)
if err != nil {
return err
}
@ -201,11 +236,11 @@ func DescribeMatchingResources(f cmdutil.Factory, namespace, rsrc, prefix string
info := infos[ix]
if strings.HasPrefix(info.Name, prefix) {
isFound = true
s, err := describer.Describe(info.Namespace, info.Name, *describerSettings)
s, err := describer.Describe(info.Namespace, info.Name, *o.DescriberSettings)
if err != nil {
return err
}
fmt.Fprintf(out, "%s\n", s)
fmt.Fprintf(o.Out, "%s\n", s)
}
}
if !isFound {

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