Switch to github.com/golang/dep for vendoring

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
Mrunal Patel 2017-01-31 16:45:59 -08:00
parent d6ab91be27
commit 8e5b17cf13
15431 changed files with 3971413 additions and 8881 deletions

206
vendor/k8s.io/kubernetes/pkg/kubectl/BUILD generated vendored Normal file
View file

@ -0,0 +1,206 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"apply.go",
"autoscale.go",
"bash_comp_utils.go",
"cluster.go",
"clusterrolebinding.go",
"configmap.go",
"custom_column_printer.go",
"deployment.go",
"describe.go",
"doc.go",
"explain.go",
"generate.go",
"history.go",
"interfaces.go",
"kubectl.go",
"namespace.go",
"pdb.go",
"proxy_server.go",
"quota.go",
"resource_filter.go",
"resource_printer.go",
"rolebinding.go",
"rollback.go",
"rolling_updater.go",
"rollout_status.go",
"run.go",
"scale.go",
"secret.go",
"secret_for_docker_registry.go",
"secret_for_tls.go",
"service.go",
"service_basic.go",
"serviceaccount.go",
"sorted_resource_name_list.go",
"sorting_printer.go",
"stop.go",
"versioned_client.go",
],
tags = ["automanaged"],
deps = [
"//federation/apis/federation:go_default_library",
"//federation/apis/federation/v1beta1:go_default_library",
"//federation/client/clientset_generated/federation_internalclientset:go_default_library",
"//pkg/api:go_default_library",
"//pkg/api/annotations:go_default_library",
"//pkg/api/events:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/api/util:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/extensions/v1beta1:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/apis/storage/util:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/core/v1:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/extensions/v1beta1: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/client/restclient:go_default_library",
"//pkg/client/retry:go_default_library",
"//pkg/client/unversioned:go_default_library",
"//pkg/controller/deployment/util:go_default_library",
"//pkg/credentialprovider:go_default_library",
"//pkg/fieldpath:go_default_library",
"//pkg/fields:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubelet/qos:go_default_library",
"//pkg/util:go_default_library",
"//pkg/util/integer:go_default_library",
"//pkg/util/intstr:go_default_library",
"//pkg/util/jsonpath:go_default_library",
"//pkg/util/node:go_default_library",
"//pkg/util/slice:go_default_library",
"//pkg/util/uuid:go_default_library",
"//vendor:github.com/emicklei/go-restful/swagger",
"//vendor:github.com/ghodss/yaml",
"//vendor:github.com/golang/glog",
"//vendor:github.com/spf13/cobra",
"//vendor:github.com/spf13/pflag",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
"//vendor:k8s.io/apimachinery/pkg/labels",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/util/validation",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/apimachinery/pkg/watch",
],
)
go_test(
name = "go_default_test",
srcs = [
"cluster_test.go",
"configmap_test.go",
"custom_column_printer_test.go",
"deployment_test.go",
"describe_test.go",
"generate_test.go",
"kubectl_test.go",
"namespace_test.go",
"proxy_server_test.go",
"quota_test.go",
"resource_printer_test.go",
"rolling_updater_test.go",
"rollout_status_test.go",
"run_test.go",
"scale_test.go",
"secret_for_docker_registry_test.go",
"secret_for_tls_test.go",
"secret_test.go",
"service_basic_test.go",
"service_test.go",
"serviceaccount_test.go",
"sorted_resource_name_list_test.go",
"sorting_printer_test.go",
"stop_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//federation/apis/federation:go_default_library",
"//federation/apis/federation/v1beta1:go_default_library",
"//federation/client/clientset_generated/federation_internalclientset/fake:go_default_library",
"//pkg/api:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/api/testing:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/extensions/v1beta1:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/client/clientset_generated/clientset/fake: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/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/client/restclient:go_default_library",
"//pkg/client/restclient/fake:go_default_library",
"//pkg/client/testing/core:go_default_library",
"//pkg/controller/deployment/util:go_default_library",
"//pkg/kubectl/testing:go_default_library",
"//pkg/util:go_default_library",
"//pkg/util/intstr:go_default_library",
"//pkg/util/testing:go_default_library",
"//vendor:github.com/ghodss/yaml",
"//vendor:github.com/spf13/cobra",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/yaml",
"//vendor:k8s.io/apimachinery/pkg/util/diff",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/watch",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubectl/cmd:all-srcs",
"//pkg/kubectl/metricsutil:all-srcs",
"//pkg/kubectl/resource:all-srcs",
"//pkg/kubectl/testing:all-srcs",
],
tags = ["automanaged"],
)

4
vendor/k8s.io/kubernetes/pkg/kubectl/OWNERS generated vendored Normal file
View file

@ -0,0 +1,4 @@
approvers:
- sig-cli-maintainers
reviewers:
- sig-cli

188
vendor/k8s.io/kubernetes/pkg/kubectl/apply.go generated vendored Normal file
View file

@ -0,0 +1,188 @@
/*
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 kubectl
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/annotations"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
// 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)
if err != nil {
return nil, err
}
if annots == nil {
return nil, nil
}
original, ok := annots[annotations.LastAppliedConfigAnnotation]
if !ok {
return nil, nil
}
return []byte(original), nil
}
// 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 {
if len(original) < 1 {
return nil
}
accessor := info.Mapping.MetadataAccessor
annots, err := accessor.Annotations(info.Object)
if err != nil {
return err
}
if annots == nil {
annots = map[string]string{}
}
annots[annotations.LastAppliedConfigAnnotation] = string(original)
if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annots); err != nil {
return err
}
return nil
}
// 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) {
// 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
if info.VersionedObject != nil {
// If an object was read from input, use that version.
accessor, err := meta.Accessor(info.VersionedObject)
if err != nil {
return nil, err
}
// Get the current annotations from the object.
annots := accessor.GetAnnotations()
if annots == nil {
annots = map[string]string{}
}
original := annots[annotations.LastAppliedConfigAnnotation]
delete(annots, annotations.LastAppliedConfigAnnotation)
accessor.SetAnnotations(annots)
// TODO: this needs to be abstracted - there should be no assumption that versioned object
// can be marshalled to JSON.
modified, err = runtime.Encode(codec, info.VersionedObject)
if err != nil {
return nil, err
}
if annotate {
annots[annotations.LastAppliedConfigAnnotation] = string(modified)
accessor.SetAnnotations(annots)
// TODO: this needs to be abstracted - there should be no assumption that versioned object
// can be marshalled to JSON.
modified, err = runtime.Encode(codec, info.VersionedObject)
if err != nil {
return nil, err
}
}
// Restore the object to its original condition.
annots[annotations.LastAppliedConfigAnnotation] = original
accessor.SetAnnotations(annots)
} else {
// 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)
if err != nil {
return nil, err
}
if annots == nil {
annots = map[string]string{}
}
original := annots[annotations.LastAppliedConfigAnnotation]
delete(annots, annotations.LastAppliedConfigAnnotation)
if err := accessor.SetAnnotations(info.Object, annots); err != nil {
return nil, err
}
modified, err = runtime.Encode(codec, info.Object)
if err != nil {
return nil, err
}
if annotate {
annots[annotations.LastAppliedConfigAnnotation] = string(modified)
if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annots); err != nil {
return nil, err
}
modified, err = runtime.Encode(codec, info.Object)
if err != nil {
return nil, err
}
}
// Restore the object to its original condition.
annots[annotations.LastAppliedConfigAnnotation] = original
if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annots); err != nil {
return nil, err
}
}
return modified, nil
}
// 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 {
return err
}
return CreateApplyAnnotation(info, 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)
if err != nil {
return err
}
return SetOriginalConfiguration(info, 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 {
if createAnnotation {
return CreateApplyAnnotation(info, codec)
}
return UpdateApplyAnnotation(info, codec)
}

135
vendor/k8s.io/kubernetes/pkg/kubectl/autoscale.go generated vendored Normal file
View file

@ -0,0 +1,135 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubectl
import (
"fmt"
"strconv"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/autoscaling"
)
type HorizontalPodAutoscalerV1Beta1 struct{}
func (HorizontalPodAutoscalerV1Beta1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"default-name", true},
{"name", false},
{"scaleRef-kind", false},
{"scaleRef-name", false},
{"scaleRef-apiVersion", false},
{"scaleRef-subresource", false},
{"min", false},
{"max", true},
{"cpu-percent", false},
}
}
func (HorizontalPodAutoscalerV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
return generateHPA(genericParams)
}
type HorizontalPodAutoscalerV1 struct{}
func (HorizontalPodAutoscalerV1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"default-name", true},
{"name", false},
{"scaleRef-kind", false},
{"scaleRef-name", false},
{"scaleRef-apiVersion", false},
{"min", false},
{"max", true},
{"cpu-percent", false},
}
}
func (HorizontalPodAutoscalerV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
return generateHPA(genericParams)
}
func generateHPA(genericParams map[string]interface{}) (runtime.Object, error) {
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
name, found := params["name"]
if !found || len(name) == 0 {
name, found = params["default-name"]
if !found || len(name) == 0 {
return nil, fmt.Errorf("'name' is a required parameter.")
}
}
minString, found := params["min"]
min := -1
var err error
if found {
if min, err = strconv.Atoi(minString); err != nil {
return nil, err
}
}
maxString, found := params["max"]
if !found {
return nil, fmt.Errorf("'max' is a required parameter.")
}
max, err := strconv.Atoi(maxString)
if err != nil {
return nil, err
}
if min > max {
return nil, fmt.Errorf("'max' must be greater than or equal to 'min'.")
}
cpuString, found := params["cpu-percent"]
cpu := -1
if found {
if cpu, err = strconv.Atoi(cpuString); err != nil {
return nil, err
}
}
scaler := autoscaling.HorizontalPodAutoscaler{
ObjectMeta: api.ObjectMeta{
Name: name,
},
Spec: autoscaling.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscaling.CrossVersionObjectReference{
Kind: params["scaleRef-kind"],
Name: params["scaleRef-name"],
APIVersion: params["scaleRef-apiVersion"],
},
MaxReplicas: int32(max),
},
}
if min > 0 {
v := int32(min)
scaler.Spec.MinReplicas = &v
}
if cpu >= 0 {
c := int32(cpu)
scaler.Spec.TargetCPUUtilizationPercentage = &c
}
return &scaler, nil
}

View file

@ -0,0 +1,36 @@
/*
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)
}

125
vendor/k8s.io/kubernetes/pkg/kubectl/cluster.go generated vendored Normal file
View file

@ -0,0 +1,125 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubectl
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime"
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
"k8s.io/kubernetes/pkg/api/v1"
)
// ClusterGeneratorV1Beta1 supports stable generation of a
// federation/cluster resource.
type ClusterGeneratorV1Beta1 struct {
// Name of the cluster context (required)
Name string
// ClientCIDR is the CIDR range in which the Kubernetes APIServer
// is available for the client (optional)
ClientCIDR string
// ServerAddress is the APIServer address of the Kubernetes cluster
// that is being registered (required)
ServerAddress string
// SecretName is the name of the secret that stores the credentials
// for the Kubernetes cluster that is being registered (optional)
SecretName string
}
// Ensure it supports the generator pattern that uses parameter
// injection.
var _ Generator = &ClusterGeneratorV1Beta1{}
// Ensure it supports the generator pattern that uses parameters
// specified during construction.
var _ StructuredGenerator = &ClusterGeneratorV1Beta1{}
// Generate returns a cluster resource using the specified parameters.
func (s ClusterGeneratorV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := ValidateParams(s.ParamNames(), genericParams)
if err != nil {
return nil, err
}
clustergen := &ClusterGeneratorV1Beta1{}
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
clustergen.Name = params["name"]
clustergen.ClientCIDR = params["client-cidr"]
clustergen.ServerAddress = params["server-address"]
clustergen.SecretName = params["secret"]
return clustergen.StructuredGenerate()
}
// ParamNames returns the set of supported input parameters when using
// the parameter injection generator pattern.
func (s ClusterGeneratorV1Beta1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"name", true},
{"client-cidr", false},
{"server-address", true},
{"secret", false},
}
}
// StructuredGenerate outputs a federation cluster resource object
// using the configured fields.
func (s ClusterGeneratorV1Beta1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
if s.ClientCIDR == "" {
s.ClientCIDR = "0.0.0.0/0"
}
if s.SecretName == "" {
s.SecretName = s.Name
}
cluster := &federationapi.Cluster{
ObjectMeta: v1.ObjectMeta{
Name: s.Name,
},
Spec: federationapi.ClusterSpec{
ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{
{
ClientCIDR: s.ClientCIDR,
ServerAddress: s.ServerAddress,
},
},
SecretRef: &v1.LocalObjectReference{
Name: s.SecretName,
},
},
}
return cluster, nil
}
// validate validates required fields are set to support structured
// generation.
func (s ClusterGeneratorV1Beta1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.ServerAddress) == 0 {
return fmt.Errorf("server address must be specified")
}
return nil
}

160
vendor/k8s.io/kubernetes/pkg/kubectl/cluster_test.go generated vendored Normal file
View file

@ -0,0 +1,160 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubectl
import (
"reflect"
"testing"
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
"k8s.io/kubernetes/pkg/api/v1"
)
func TestClusterGenerate(t *testing.T) {
tests := []struct {
params map[string]interface{}
expected *federationapi.Cluster
expectErr bool
}{
{
params: map[string]interface{}{
"name": "foo",
"client-cidr": "0.0.0.0/0",
"server-address": "10.20.30.40",
"secret": "foo-credentials",
},
expected: &federationapi.Cluster{
ObjectMeta: v1.ObjectMeta{
Name: "foo",
},
Spec: federationapi.ClusterSpec{
ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{
{
ClientCIDR: "0.0.0.0/0",
ServerAddress: "10.20.30.40",
},
},
SecretRef: &v1.LocalObjectReference{
Name: "foo-credentials",
},
},
},
expectErr: false,
},
{
params: map[string]interface{}{
"name": "foo",
"client-cidr": "10.20.30.40/16",
"server-address": "https://foo.example.com",
"secret": "foo-credentials",
},
expected: &federationapi.Cluster{
ObjectMeta: v1.ObjectMeta{
Name: "foo",
},
Spec: federationapi.ClusterSpec{
ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{
{
ClientCIDR: "10.20.30.40/16",
ServerAddress: "https://foo.example.com",
},
},
SecretRef: &v1.LocalObjectReference{
Name: "foo-credentials",
},
},
},
expectErr: false,
},
{
params: map[string]interface{}{
"name": "bar-cluster",
"client-cidr": "10.20.30.40/16",
"server-address": "http://10.20.30.40",
"secret": "credentials",
},
expected: &federationapi.Cluster{
ObjectMeta: v1.ObjectMeta{
Name: "bar-cluster",
},
Spec: federationapi.ClusterSpec{
ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{
{
ClientCIDR: "10.20.30.40/16",
ServerAddress: "http://10.20.30.40",
},
},
SecretRef: &v1.LocalObjectReference{
Name: "credentials",
},
},
},
expectErr: false,
},
{
params: map[string]interface{}{
"server-address": "https://10.20.30.40",
},
expected: nil,
expectErr: true,
},
{
params: map[string]interface{}{
"secret": "baz-credentials",
},
expected: nil,
expectErr: true,
},
{
params: map[string]interface{}{
"server-address": "http://foo.example.com",
"secret": "foo-credentials",
},
expected: nil,
expectErr: true,
},
{
params: map[string]interface{}{
"name": "foo",
"secret": "foo-credentials",
},
expected: nil,
expectErr: true,
},
{
params: map[string]interface{}{
"name": "foo",
"client-cidr": "10.20.30.40/16",
},
expected: nil,
expectErr: true,
},
}
generator := ClusterGeneratorV1Beta1{}
for i, test := range tests {
obj, err := generator.Generate(test.params)
if !test.expectErr && err != nil {
t.Errorf("[%d] unexpected error: %v", i, err)
}
if test.expectErr && err != nil {
continue
}
if !reflect.DeepEqual(obj.(*federationapi.Cluster), test.expected) {
t.Errorf("\n[%d] want:\n%#v\n[%d] got:\n%#v", i, test.expected, i, obj.(*federationapi.Cluster))
}
}
}

View file

@ -0,0 +1,157 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubectl
import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/apis/rbac"
)
// ClusterRoleBindingGeneratorV1 supports stable generation of a clusterRoleBinding.
type ClusterRoleBindingGeneratorV1 struct {
// Name of clusterRoleBinding (required)
Name string
// ClusterRole for the clusterRoleBinding (required)
ClusterRole string
// Users to derive the clusterRoleBinding from (optional)
Users []string
// Groups to derive the clusterRoleBinding from (optional)
Groups []string
// ServiceAccounts to derive the clusterRoleBinding from in namespace:name format(optional)
ServiceAccounts []string
}
// Ensure it supports the generator pattern that uses parameter injection.
var _ Generator = &ClusterRoleBindingGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction.
var _ StructuredGenerator = &ClusterRoleBindingGeneratorV1{}
// Generate returns a clusterRoleBinding using the specified parameters.
func (s ClusterRoleBindingGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := ValidateParams(s.ParamNames(), genericParams)
if err != nil {
return nil, err
}
delegate := &ClusterRoleBindingGeneratorV1{}
fromFileStrings, found := genericParams["user"]
if found {
fromFileArray, isArray := fromFileStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings)
}
delegate.Users = fromFileArray
delete(genericParams, "user")
}
fromLiteralStrings, found := genericParams["group"]
if found {
fromLiteralArray, isArray := fromLiteralStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings)
}
delegate.Groups = fromLiteralArray
delete(genericParams, "group")
}
fromSAStrings, found := genericParams["serviceaccount"]
if found {
fromLiteralArray, isArray := fromSAStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings)
}
delegate.ServiceAccounts = fromLiteralArray
delete(genericParams, "serviceaccounts")
}
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
delegate.Name = params["name"]
delegate.ClusterRole = params["clusterrole"]
return delegate.StructuredGenerate()
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern.
func (s ClusterRoleBindingGeneratorV1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"name", true},
{"clusterrole", false},
{"user", false},
{"group", false},
{"serviceaccount", false},
{"force", false},
}
}
// StructuredGenerate outputs a clusterRoleBinding object using the configured fields.
func (s ClusterRoleBindingGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
clusterRoleBinding := &rbac.ClusterRoleBinding{}
clusterRoleBinding.Name = s.Name
clusterRoleBinding.RoleRef = rbac.RoleRef{
APIGroup: rbac.GroupName,
Kind: "ClusterRole",
Name: s.ClusterRole,
}
for _, user := range s.Users {
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbac.Subject{
Kind: rbac.UserKind,
APIVersion: "rbac/v1alpha1",
Name: user,
})
}
for _, group := range s.Groups {
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbac.Subject{
Kind: rbac.GroupKind,
APIVersion: "rbac/v1alpha1",
Name: group,
})
}
for _, sa := range s.ServiceAccounts {
tokens := strings.Split(sa, ":")
if len(tokens) != 2 {
return nil, fmt.Errorf("serviceaccount must be <namespace>:<name>")
}
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbac.Subject{
Kind: rbac.ServiceAccountKind,
Namespace: tokens[0],
Name: tokens[1],
})
}
return clusterRoleBinding, nil
}
// validate validates required fields are set to support structured generation.
func (s ClusterRoleBindingGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.ClusterRole) == 0 {
return fmt.Errorf("clusterrole must be specified")
}
return nil
}

230
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/BUILD generated vendored Normal file
View file

@ -0,0 +1,230 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"annotate.go",
"apiversions.go",
"apply.go",
"attach.go",
"autoscale.go",
"certificates.go",
"clusterinfo.go",
"clusterinfo_dump.go",
"cmd.go",
"completion.go",
"convert.go",
"cp.go",
"create.go",
"create_clusterrolebinding.go",
"create_configmap.go",
"create_deployment.go",
"create_namespace.go",
"create_pdb.go",
"create_quota.go",
"create_rolebinding.go",
"create_secret.go",
"create_service.go",
"create_serviceaccount.go",
"delete.go",
"describe.go",
"drain.go",
"edit.go",
"exec.go",
"explain.go",
"expose.go",
"get.go",
"help.go",
"label.go",
"logs.go",
"options.go",
"patch.go",
"portforward.go",
"proxy.go",
"replace.go",
"rollingupdate.go",
"run.go",
"scale.go",
"stop.go",
"taint.go",
"top.go",
"top_node.go",
"top_pod.go",
"version.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/annotations:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/api/validation:go_default_library",
"//pkg/apis/batch/v1:go_default_library",
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/extensions/v1beta1:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/restclient:go_default_library",
"//pkg/client/typed/discovery:go_default_library",
"//pkg/client/unversioned:go_default_library",
"//pkg/client/unversioned/clientcmd:go_default_library",
"//pkg/client/unversioned/portforward:go_default_library",
"//pkg/client/unversioned/remotecommand:go_default_library",
"//pkg/fields:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/cmd/config:go_default_library",
"//pkg/kubectl/cmd/rollout: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/metricsutil:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubelet/server/remotecommand:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//pkg/util/crlf:go_default_library",
"//pkg/util/exec:go_default_library",
"//pkg/util/flag:go_default_library",
"//pkg/util/i18n:go_default_library",
"//pkg/util/interrupt:go_default_library",
"//pkg/util/intstr:go_default_library",
"//pkg/util/strategicpatch:go_default_library",
"//pkg/util/taints:go_default_library",
"//pkg/util/term:go_default_library",
"//pkg/version:go_default_library",
"//vendor:github.com/daviddengcn/go-colortext",
"//vendor:github.com/docker/distribution/reference",
"//vendor:github.com/docker/docker/pkg/term",
"//vendor:github.com/evanphx/json-patch",
"//vendor:github.com/golang/glog",
"//vendor:github.com/jonboulle/clockwork",
"//vendor:github.com/renstrom/dedent",
"//vendor:github.com/spf13/cobra",
"//vendor:github.com/spf13/pflag",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
"//vendor:k8s.io/apimachinery/pkg/labels",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/util/validation",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/apimachinery/pkg/util/yaml",
"//vendor:k8s.io/apimachinery/pkg/watch",
],
)
go_test(
name = "go_default_test",
srcs = [
"annotate_test.go",
"apply_test.go",
"attach_test.go",
"clusterinfo_dump_test.go",
"cmd_test.go",
"cp_test.go",
"create_configmap_test.go",
"create_deployment_test.go",
"create_namespace_test.go",
"create_quota_test.go",
"create_secret_test.go",
"create_service_test.go",
"create_serviceaccount_test.go",
"create_test.go",
"delete_test.go",
"describe_test.go",
"drain_test.go",
"exec_test.go",
"expose_test.go",
"get_test.go",
"label_test.go",
"logs_test.go",
"patch_test.go",
"portforward_test.go",
"replace_test.go",
"rollingupdate_test.go",
"run_test.go",
"taint_test.go",
"top_node_test.go",
"top_pod_test.go",
"top_test.go",
],
data = [
"//examples:config",
"//test/fixtures",
],
library = ":go_default_library",
tags = [
"automanaged",
],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/annotations:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/api/testing:go_default_library",
"//pkg/api/unversioned:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/client/restclient:go_default_library",
"//pkg/client/restclient/fake:go_default_library",
"//pkg/client/restclient/watch:go_default_library",
"//pkg/client/typed/dynamic:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/util/intstr:go_default_library",
"//pkg/util/strategicpatch:go_default_library",
"//pkg/util/strings:go_default_library",
"//pkg/util/term:go_default_library",
"//vendor:github.com/spf13/cobra",
"//vendor:github.com/stretchr/testify/assert",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/json",
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/streaming",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/apimachinery/pkg/watch",
"//vendor:k8s.io/heapster/metrics/apis/metrics/v1alpha1",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubectl/cmd/config:all-srcs",
"//pkg/kubectl/cmd/rollout:all-srcs",
"//pkg/kubectl/cmd/set:all-srcs",
"//pkg/kubectl/cmd/templates:all-srcs",
"//pkg/kubectl/cmd/testing:all-srcs",
"//pkg/kubectl/cmd/util:all-srcs",
],
tags = ["automanaged"],
)

346
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate.go generated vendored Normal file
View file

@ -0,0 +1,346 @@
/*
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 (
"bytes"
"encoding/json"
"fmt"
"io"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api"
"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/util/i18n"
"k8s.io/kubernetes/pkg/util/strategicpatch"
)
// AnnotateOptions have the data required to perform the annotate operation
type AnnotateOptions struct {
// Filename options
resource.FilenameOptions
// Common user flags
overwrite bool
local bool
dryrun bool
all bool
resourceVersion string
selector string
outputFormat string
recordChangeCause bool
// results of arg parsing
resources []string
newAnnotations map[string]string
removeAnnotations []string
// Common share fields
out io.Writer
}
var (
annotate_long = templates.LongDesc(`
Update the annotations on one or more resources.
* An annotation is a key/value pair that can hold larger (compared to a label), and possibly not human-readable, data.
* It is intended to store non-identifying auxiliary data, especially data manipulated by tools and system extensions.
* If --overwrite is true, then existing annotations can be overwritten, otherwise attempting to overwrite an annotation will result in an error.
* If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used.
` + valid_resources)
annotate_example = templates.Examples(`
# Update pod 'foo' with the annotation 'description' and the value 'my frontend'.
# If the same annotation is set multiple times, only the last value will be applied
kubectl annotate pods foo description='my frontend'
# Update a pod identified by type and name in "pod.json"
kubectl annotate -f pod.json description='my frontend'
# Update pod 'foo' with the annotation 'description' and the value 'my frontend running nginx', overwriting any existing value.
kubectl annotate --overwrite pods foo description='my frontend running nginx'
# Update all pods in the namespace
kubectl annotate pods --all description='my frontend running nginx'
# Update pod 'foo' only if the resource is unchanged from version 1.
kubectl annotate pods foo description='my frontend running nginx' --resource-version=1
# Update pod 'foo' by removing an annotation named 'description' if it exists.
# Does not require the --overwrite flag.
kubectl annotate pods foo description-`)
)
func NewCmdAnnotate(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &AnnotateOptions{}
// retrieve a list of handled resources from printer as valid args
validArgs, argAliases := []string{}, []string{}
p, err := f.Printer(nil, kubectl.PrintOptions{
ColumnLabels: []string{},
})
cmdutil.CheckErr(err)
if p != nil {
validArgs = p.HandledResources()
argAliases = kubectl.ResourceAliases(validArgs)
}
cmd := &cobra.Command{
Use: "annotate [--overwrite] (-f FILENAME | TYPE NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--resource-version=version]",
Short: i18n.T("Update the annotations on a resource"),
Long: annotate_long,
Example: annotate_example,
Run: func(cmd *cobra.Command, args []string) {
if err := options.Complete(f, out, cmd, args); err != nil {
cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
}
if err := options.Validate(); err != nil {
cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
}
cmdutil.CheckErr(options.RunAnnotate(f, cmd))
},
ValidArgs: validArgs,
ArgAliases: argAliases,
}
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().Bool("overwrite", false, "If true, allow annotations to be overwritten, otherwise reject annotation updates that overwrite existing annotations.")
cmd.Flags().Bool("local", false, "If true, annotation will NOT contact api-server but run locally.")
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.")
cmd.Flags().Bool("all", false, "select all resources in the namespace of the specified resource types")
cmd.Flags().String("resource-version", "", "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.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(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) (err error) {
o.out = out
o.local = cmdutil.GetFlagBool(cmd, "local")
o.overwrite = cmdutil.GetFlagBool(cmd, "overwrite")
o.all = cmdutil.GetFlagBool(cmd, "all")
o.resourceVersion = cmdutil.GetFlagString(cmd, "resource-version")
o.selector = cmdutil.GetFlagString(cmd, "selector")
o.outputFormat = cmdutil.GetFlagString(cmd, "output")
o.dryrun = cmdutil.GetDryRunFlag(cmd)
o.recordChangeCause = cmdutil.GetRecordFlag(cmd)
// retrieves resource and annotation args from args
// also checks args to verify that all resources are specified before annotations
resources, annotationArgs, err := cmdutil.GetResourcesAndPairs(args, "annotation")
if err != nil {
return err
}
o.resources = resources
o.newAnnotations, o.removeAnnotations, err = parseAnnotations(annotationArgs)
return err
}
// Validate checks to the AnnotateOptions to see if there is sufficient information run the command.
func (o AnnotateOptions) Validate() error {
if len(o.resources) < 1 && cmdutil.IsFilenameEmpty(o.Filenames) {
return fmt.Errorf("one or more resources must be specified as <resource> <name> or <resource>/<name>")
}
if len(o.newAnnotations) < 1 && len(o.removeAnnotations) < 1 {
return fmt.Errorf("at least one annotation update is required")
}
return validateAnnotations(o.removeAnnotations, o.newAnnotations)
}
// 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()
mapper, typer := f.Object()
b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
Flatten()
if !o.local {
b = b.SelectorParam(o.selector).
ResourceTypeOrNameArgs(o.all, o.resources...).
Latest()
}
r := b.Do()
if err := r.Err(); err != nil {
return err
}
var singleItemImpliedResource bool
r.IntoSingleItemImplied(&singleItemImpliedResource)
// only apply resource version locking on a single resource.
// we must perform this check after o.builder.Do() as
// []o.resources can not not accurately return the proper number
// of resources when they are not passed in "resource/name" format.
if !singleItemImpliedResource && len(o.resourceVersion) > 0 {
return fmt.Errorf("--resource-version may only be used with a single resource")
}
return r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
var outputObj runtime.Object
obj, err := cmdutil.MaybeConvertObject(info.Object, info.Mapping.GroupVersionKind.GroupVersion(), info.Mapping)
if err != nil {
return err
}
if o.dryrun || o.local {
if err := o.updateAnnotations(obj); err != nil {
return err
}
outputObj = obj
} else {
name, namespace := info.Name, info.Namespace
oldData, err := json.Marshal(obj)
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.updateAnnotations(obj); err != nil {
return err
}
newData, err := json.Marshal(obj)
if err != nil {
return err
}
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj)
createdPatch := err == nil
if err != nil {
glog.V(2).Infof("couldn't compute patch: %v", err)
}
mapping := info.ResourceMapping()
client, err := f.ClientForMapping(mapping)
if err != nil {
return err
}
helper := resource.NewHelper(client, mapping)
if createdPatch {
outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes)
} else {
outputObj, err = helper.Replace(namespace, name, false, obj)
}
if err != nil {
return err
}
}
if o.outputFormat != "" {
return f.PrintObject(cmd, mapper, outputObj, o.out)
}
cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, o.dryrun, "annotated")
return nil
})
}
// parseAnnotations retrieves new and remove annotations from annotation args
func parseAnnotations(annotationArgs []string) (map[string]string, []string, error) {
return cmdutil.ParsePairs(annotationArgs, "annotation", true)
}
// validateAnnotations checks the format of annotation args and checks removed annotations aren't in the new annotations map
func validateAnnotations(removeAnnotations []string, newAnnotations map[string]string) error {
var modifyRemoveBuf bytes.Buffer
for _, removeAnnotation := range removeAnnotations {
if _, found := newAnnotations[removeAnnotation]; found {
if modifyRemoveBuf.Len() > 0 {
modifyRemoveBuf.WriteString(", ")
}
modifyRemoveBuf.WriteString(fmt.Sprintf(removeAnnotation))
}
}
if modifyRemoveBuf.Len() > 0 {
return fmt.Errorf("can not both modify and remove the following annotation(s) in the same command: %s", modifyRemoveBuf.String())
}
return nil
}
// validateNoAnnotationOverwrites validates that when overwrite is false, to-be-updated annotations don't exist in the object annotation map (yet)
func validateNoAnnotationOverwrites(accessor meta.Object, annotations map[string]string) error {
var buf bytes.Buffer
for key := range annotations {
// change-cause annotation can always be overwritten
if key == kubectl.ChangeCauseAnnotation {
continue
}
if value, found := accessor.GetAnnotations()[key]; found {
if buf.Len() > 0 {
buf.WriteString("; ")
}
buf.WriteString(fmt.Sprintf("'%s' already has a value (%s)", key, value))
}
}
if buf.Len() > 0 {
return fmt.Errorf("--overwrite is false but found the following declared annotation(s): %s", buf.String())
}
return nil
}
// updateAnnotations updates annotations of obj
func (o AnnotateOptions) updateAnnotations(obj runtime.Object) error {
accessor, err := meta.Accessor(obj)
if err != nil {
return err
}
if !o.overwrite {
if err := validateNoAnnotationOverwrites(accessor, o.newAnnotations); err != nil {
return err
}
}
annotations := accessor.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
for key, value := range o.newAnnotations {
annotations[key] = value
}
for _, annotation := range o.removeAnnotations {
delete(annotations, annotation)
}
accessor.SetAnnotations(annotations)
if len(o.resourceVersion) != 0 {
accessor.SetResourceVersion(o.resourceVersion)
}
return nil
}

View file

@ -0,0 +1,604 @@
/*
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 (
"bytes"
"net/http"
"reflect"
"strings"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestValidateAnnotationOverwrites(t *testing.T) {
tests := []struct {
meta *api.ObjectMeta
annotations map[string]string
expectErr bool
scenario string
}{
{
meta: &api.ObjectMeta{
Annotations: map[string]string{
"a": "A",
"b": "B",
},
},
annotations: map[string]string{
"a": "a",
"c": "C",
},
scenario: "share first annotation",
expectErr: true,
},
{
meta: &api.ObjectMeta{
Annotations: map[string]string{
"a": "A",
"c": "C",
},
},
annotations: map[string]string{
"b": "B",
"c": "c",
},
scenario: "share second annotation",
expectErr: true,
},
{
meta: &api.ObjectMeta{
Annotations: map[string]string{
"a": "A",
"c": "C",
},
},
annotations: map[string]string{
"b": "B",
"d": "D",
},
scenario: "no overlap",
},
{
meta: &api.ObjectMeta{},
annotations: map[string]string{
"a": "A",
"b": "B",
},
scenario: "no annotations",
},
}
for _, test := range tests {
err := validateNoAnnotationOverwrites(test.meta, test.annotations)
if test.expectErr && err == nil {
t.Errorf("%s: unexpected non-error", test.scenario)
} else if !test.expectErr && err != nil {
t.Errorf("%s: unexpected error: %v", test.scenario, err)
}
}
}
func TestParseAnnotations(t *testing.T) {
testURL := "https://test.com/index.htm?id=123#u=user-name"
testJSON := `'{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"ReplicationController","namespace":"default","name":"my-nginx","uid":"c544ee78-2665-11e5-8051-42010af0c213","apiVersion":"v1","resourceVersion":"61368"}}'`
tests := []struct {
annotations []string
expected map[string]string
expectedRemove []string
scenario string
expectedErr string
expectErr bool
}{
{
annotations: []string{"a=b", "c=d"},
expected: map[string]string{"a": "b", "c": "d"},
expectedRemove: []string{},
scenario: "add two annotations",
expectErr: false,
},
{
annotations: []string{"url=" + testURL, api.CreatedByAnnotation + "=" + testJSON},
expected: map[string]string{"url": testURL, api.CreatedByAnnotation: testJSON},
expectedRemove: []string{},
scenario: "add annotations with special characters",
expectErr: false,
},
{
annotations: []string{},
expected: map[string]string{},
expectedRemove: []string{},
scenario: "add no annotations",
expectErr: false,
},
{
annotations: []string{"a=b", "c=d", "e-"},
expected: map[string]string{"a": "b", "c": "d"},
expectedRemove: []string{"e"},
scenario: "add two annotations, remove one",
expectErr: false,
},
{
annotations: []string{"ab", "c=d"},
expectedErr: "invalid annotation format: ab",
scenario: "incorrect annotation input (missing =value)",
expectErr: true,
},
{
annotations: []string{"a="},
expected: map[string]string{"a": ""},
expectedRemove: []string{},
scenario: "add valid annotation with empty value",
expectErr: false,
},
{
annotations: []string{"ab", "a="},
expectedErr: "invalid annotation format: ab",
scenario: "incorrect annotation input (missing =value)",
expectErr: true,
},
}
for _, test := range tests {
annotations, remove, err := parseAnnotations(test.annotations)
switch {
case test.expectErr && err == nil:
t.Errorf("%s: unexpected non-error, should return %v", test.scenario, test.expectedErr)
case test.expectErr && err.Error() != test.expectedErr:
t.Errorf("%s: unexpected error %v, expected %v", test.scenario, err, test.expectedErr)
case !test.expectErr && err != nil:
t.Errorf("%s: unexpected error %v", test.scenario, err)
case !test.expectErr && !reflect.DeepEqual(annotations, test.expected):
t.Errorf("%s: expected %v, got %v", test.scenario, test.expected, annotations)
case !test.expectErr && !reflect.DeepEqual(remove, test.expectedRemove):
t.Errorf("%s: expected %v, got %v", test.scenario, test.expectedRemove, remove)
}
}
}
func TestValidateAnnotations(t *testing.T) {
tests := []struct {
removeAnnotations []string
newAnnotations map[string]string
expectedErr string
scenario string
}{
{
expectedErr: "can not both modify and remove the following annotation(s) in the same command: a",
removeAnnotations: []string{"a"},
newAnnotations: map[string]string{"a": "b", "c": "d"},
scenario: "remove an added annotation",
},
{
expectedErr: "can not both modify and remove the following annotation(s) in the same command: a, c",
removeAnnotations: []string{"a", "c"},
newAnnotations: map[string]string{"a": "b", "c": "d"},
scenario: "remove added annotations",
},
}
for _, test := range tests {
if err := validateAnnotations(test.removeAnnotations, test.newAnnotations); err == nil {
t.Errorf("%s: unexpected non-error", test.scenario)
} else if err.Error() != test.expectedErr {
t.Errorf("%s: expected error %s, got %s", test.scenario, test.expectedErr, err.Error())
}
}
}
func TestUpdateAnnotations(t *testing.T) {
tests := []struct {
obj runtime.Object
overwrite bool
version string
annotations map[string]string
remove []string
expected runtime.Object
expectErr bool
}{
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{"a": "b"},
},
},
annotations: map[string]string{"a": "b"},
expectErr: true,
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{"a": "b"},
},
},
annotations: map[string]string{"a": "c"},
overwrite: true,
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{"a": "c"},
},
},
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{"a": "b"},
},
},
annotations: map[string]string{"c": "d"},
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{"a": "b", "c": "d"},
},
},
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{"a": "b"},
},
},
annotations: map[string]string{"c": "d"},
version: "2",
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{"a": "b", "c": "d"},
ResourceVersion: "2",
},
},
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{"a": "b"},
},
},
annotations: map[string]string{},
remove: []string{"a"},
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{},
},
},
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{"a": "b", "c": "d"},
},
},
annotations: map[string]string{"e": "f"},
remove: []string{"a"},
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{
"c": "d",
"e": "f",
},
},
},
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{"a": "b", "c": "d"},
},
},
annotations: map[string]string{"e": "f"},
remove: []string{"g"},
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{
"a": "b",
"c": "d",
"e": "f",
},
},
},
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{"a": "b", "c": "d"},
},
},
remove: []string{"e"},
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{
"a": "b",
"c": "d",
},
},
},
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{},
},
annotations: map[string]string{"a": "b"},
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Annotations: map[string]string{"a": "b"},
},
},
},
}
for _, test := range tests {
options := &AnnotateOptions{
overwrite: test.overwrite,
newAnnotations: test.annotations,
removeAnnotations: test.remove,
resourceVersion: test.version,
}
err := options.updateAnnotations(test.obj)
if test.expectErr {
if err == nil {
t.Errorf("unexpected non-error: %v", test)
}
continue
}
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v %v", err, test)
}
if !reflect.DeepEqual(test.obj, test.expected) {
t.Errorf("expected: %v, got %v", test.expected, test.obj)
}
}
}
func TestAnnotateErrors(t *testing.T) {
testCases := map[string]struct {
args []string
flags map[string]string
errFn func(error) bool
}{
"no args": {
args: []string{},
errFn: func(err error) bool { return strings.Contains(err.Error(), "one or more resources must be specified") },
},
"not enough annotations": {
args: []string{"pods"},
errFn: func(err error) bool {
return strings.Contains(err.Error(), "at least one annotation update is required")
},
},
"no resources remove annotations": {
args: []string{"pods-"},
errFn: func(err error) bool { return strings.Contains(err.Error(), "one or more resources must be specified") },
},
"no resources add annotations": {
args: []string{"pods=bar"},
errFn: func(err error) bool { return strings.Contains(err.Error(), "one or more resources must be specified") },
},
}
for k, testCase := range testCases {
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(f, buf)
cmd.SetOutput(buf)
for k, v := range testCase.flags {
cmd.Flags().Set(k, v)
}
options := &AnnotateOptions{}
err := options.Complete(f, buf, cmd, testCase.args)
if err == nil {
err = options.Validate()
}
if !testCase.errFn(err) {
t.Errorf("%s: unexpected error: %v", k, err)
continue
}
if tf.Printer.(*testPrinter).Objects != nil {
t.Errorf("unexpected print to default printer")
}
if buf.Len() > 0 {
t.Errorf("buffer should be empty: %s", string(buf.Bytes()))
}
}
}
func TestAnnotateObject(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method {
case "GET":
switch req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
case "PATCH":
switch req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
default:
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(f, buf)
cmd.SetOutput(buf)
options := &AnnotateOptions{}
args := []string{"pods/foo", "a=b", "c-"}
if err := options.Complete(f, buf, 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(f, cmd); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestAnnotateObjectFromFile(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method {
case "GET":
switch req.URL.Path {
case "/namespaces/test/replicationcontrollers/cassandra":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
case "PATCH":
switch req.URL.Path {
case "/namespaces/test/replicationcontrollers/cassandra":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
default:
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(f, buf)
cmd.SetOutput(buf)
options := &AnnotateOptions{}
options.Filenames = []string{"../../../examples/storage/cassandra/cassandra-controller.yaml"}
args := []string{"a=b", "c-"}
if err := options.Complete(f, buf, 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(f, cmd); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestAnnotateLocal(t *testing.T) {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(f, buf)
cmd.Flags().Set("local", "true")
options := &AnnotateOptions{}
options.Filenames = []string{"../../../examples/storage/cassandra/cassandra-controller.yaml"}
args := []string{"a=b"}
if err := options.Complete(f, buf, 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(f, cmd); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestAnnotateMultipleObjects(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method {
case "GET":
switch req.URL.Path {
case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
case "PATCH":
switch req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
case "/namespaces/test/pods/bar":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[1])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
default:
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(f, buf)
cmd.SetOutput(buf)
cmd.Flags().Set("all", "true")
options := &AnnotateOptions{}
args := []string{"pods", "a=b", "c-"}
if err := options.Complete(f, buf, 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(f, cmd); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}

View file

@ -0,0 +1,74 @@
/*
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"
"sort"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
var (
apiversions_example = templates.Examples(`
# Print the supported API versions
kubectl api-versions`)
)
func NewCmdApiVersions(f cmdutil.Factory, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "api-versions",
// apiversions is deprecated.
Aliases: []string{"apiversions"},
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: apiversions_example,
Run: func(cmd *cobra.Command, args []string) {
err := RunApiVersions(f, out)
cmdutil.CheckErr(err)
},
}
return cmd
}
func RunApiVersions(f cmdutil.Factory, w io.Writer) error {
if len(os.Args) > 1 && os.Args[1] == "apiversions" {
printDeprecationWarning("api-versions", "apiversions")
}
clientset, err := f.ClientSet()
if err != nil {
return err
}
groupList, err := clientset.Discovery().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)
}
return nil
}

601
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply.go generated vendored Normal file
View file

@ -0,0 +1,601 @@
/*
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"
"strings"
"time"
"github.com/jonboulle/clockwork"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/annotations"
"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/resource"
"k8s.io/kubernetes/pkg/util/strategicpatch"
)
type ApplyOptions struct {
FilenameOptions resource.FilenameOptions
Selector string
Force bool
Prune bool
Cascade bool
GracePeriod int
PruneResources []pruneResource
Timeout time.Duration
}
const (
// maxPatchRetry is the maximum number of conflicts retry for during a patch operation before returning failure
maxPatchRetry = 5
// backOffPeriod is the period to back off when apply patch resutls in error.
backOffPeriod = 1 * time.Second
// how many times we can retry before back off
triesBeforeBackOff = 1
warningNoLastAppliedConfigAnnotation = "Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply\n"
)
var (
apply_long = templates.LongDesc(`
Apply a configuration to a resource by filename or stdin.
This resource will be created if it doesn't exist yet.
To use 'apply', always create the resource initially with either 'apply' or 'create --save-config'.
JSON and YAML formats are accepted.
Alpha Disclaimer: the --prune functionality is not yet complete. Do not use unless you are aware of what the current state is. See https://issues.k8s.io/34274.`)
apply_example = templates.Examples(`
# Apply the configuration in pod.json to a pod.
kubectl apply -f ./pod.json
# Apply the JSON passed into stdin to a pod.
cat pod.json | kubectl apply -f -
# Note: --prune is still in Alpha
# Apply the configuration in manifest.yaml that matches label app=nginx and delete all the other resources that are not in the file and match label app=nginx.
kubectl apply --prune -f manifest.yaml -l app=nginx
# Apply the configuration in manifest.yaml and delete all the other configmaps that are not in the file.
kubectl apply --prune -f manifest.yaml --all --prune-whitelist=core/v1/ConfigMap`)
)
func NewCmdApply(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
var options ApplyOptions
cmd := &cobra.Command{
Use: "apply -f FILENAME",
Short: "Apply a configuration to a resource by filename or stdin",
Long: apply_long,
Example: apply_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(validateArgs(cmd, args))
cmdutil.CheckErr(validatePruneAll(options.Prune, cmdutil.GetFlagBool(cmd, "all"), options.Selector))
cmdutil.CheckErr(RunApply(f, cmd, out, errOut, &options))
},
}
usage := "that contains the configuration to apply"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
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 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).")
cmdutil.AddValidateFlags(cmd)
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.")
cmd.Flags().Bool("all", false, "[-all] to select all the specified resources.")
cmd.Flags().StringArray("prune-whitelist", []string{}, "Overwrite the default whitelist with <group/version/kind> for --prune")
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
func validateArgs(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return cmdutil.UsageError(cmd, "Unexpected args: %v", args)
}
return nil
}
func validatePruneAll(prune, all bool, selector string) error {
if prune && !all && selector == "" {
return fmt.Errorf("all resources selected for prune without explicitly passing --all. To prune all resources, pass the --all flag. If you did not mean to prune all resources, specify a label selector.")
}
return nil
}
func parsePruneResources(gvks []string) ([]pruneResource, error) {
pruneResources := []pruneResource{}
for _, groupVersionKind := range gvks {
gvk := strings.Split(groupVersionKind, "/")
if len(gvk) != 3 {
return nil, fmt.Errorf("invalid GroupVersionKind format: %v, please follow <group/version/kind>", groupVersionKind)
}
namespaced := true
if gvk[2] == "Namespace" ||
gvk[2] == "Node" ||
gvk[2] == "PersistentVolume" {
namespaced = false
}
if gvk[0] == "core" {
gvk[0] = ""
}
pruneResources = append(pruneResources, pruneResource{gvk[0], gvk[1], gvk[2], namespaced})
}
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"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
if err != nil {
return err
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
if options.Prune {
options.PruneResources, err = parsePruneResources(cmdutil.GetFlagStringArray(cmd, "prune-whitelist"))
if err != nil {
return err
}
}
mapper, typer := f.Object()
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &options.FilenameOptions).
SelectorParam(options.Selector).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
dryRun := cmdutil.GetFlagBool(cmd, "dry-run")
output := cmdutil.GetFlagString(cmd, "output")
shortOutput := output == "name"
encoder := f.JSONEncoder()
decoder := f.Decoder(false)
visitedUids := sets.NewString()
visitedNamespaces := sets.NewString()
count := 0
err = r.Visit(func(info *resource.Info, err error) error {
// In this method, info.Object contains the object retrieved from the server
// and info.VersionedObject contains the object decoded from the input source.
if err != nil {
return err
}
if info.Namespaced() {
visitedNamespaces.Insert(info.Namespace)
}
// 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)
if err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving modified configuration from:\n%v\nfor:", info), info.Source, err)
}
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)
}
// Create the resource if it doesn't exist
// First, update the annotation used by kubectl apply
if err := kubectl.CreateApplyAnnotation(info, encoder); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
if cmdutil.ShouldRecord(cmd, info) {
if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
}
if !dryRun {
// Then create the resource and skip the three-way merge
if err := createAndRefresh(info); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
if uid, err := info.Mapping.UID(info.Object); err != nil {
return err
} else {
visitedUids.Insert(string(uid))
}
}
count++
if len(output) > 0 && !shortOutput {
return cmdutil.PrintResourceInfoForCommand(cmd, info, f, out)
}
cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, dryRun, "created")
return nil
}
if !dryRun {
annotationMap, err := info.Mapping.MetadataAccessor.Annotations(info.Object)
if err != nil {
return err
}
if _, ok := annotationMap[annotations.LastAppliedConfigAnnotation]; !ok {
fmt.Fprintf(errOut, warningNoLastAppliedConfigAnnotation)
}
overwrite := cmdutil.GetFlagBool(cmd, "overwrite")
helper := resource.NewHelper(info.Client, info.Mapping)
patcher := &patcher{
encoder: encoder,
decoder: decoder,
mapping: info.Mapping,
helper: helper,
clientsetFunc: f.ClientSet,
overwrite: overwrite,
backOff: clockwork.NewRealClock(),
force: options.Force,
cascade: options.Cascade,
timeout: options.Timeout,
gracePeriod: options.GracePeriod,
}
patchBytes, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name)
if err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
}
if cmdutil.ShouldRecord(cmd, info) {
patch, err := cmdutil.ChangeResourcePatch(info, f.Command())
if err != nil {
return err
}
_, err = helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch)
if err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patch, info), info.Source, err)
}
}
if uid, err := info.Mapping.UID(info.Object); err != nil {
return err
} else {
visitedUids.Insert(string(uid))
}
}
count++
if len(output) > 0 && !shortOutput {
return cmdutil.PrintResourceInfoForCommand(cmd, info, f, out)
}
cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, dryRun, "configured")
return nil
})
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("no objects passed to apply")
}
if !options.Prune {
return nil
}
selector, err := labels.Parse(options.Selector)
if err != nil {
return err
}
p := pruner{
mapper: mapper,
clientFunc: f.ClientForMapping,
clientsetFunc: f.ClientSet,
selector: selector,
visitedUids: visitedUids,
cascade: options.Cascade,
dryRun: dryRun,
gracePeriod: options.GracePeriod,
out: out,
}
namespacedRESTMappings, nonNamespacedRESTMappings, err := getRESTMappings(&(options.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(n, m, shortOutput); err != nil {
return fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err)
}
}
}
for _, m := range nonNamespacedRESTMappings {
if err := p.prune(api.NamespaceNone, m, shortOutput); err != nil {
return fmt.Errorf("error pruning nonNamespaced object %v: %v", m.GroupVersionKind, err)
}
}
return nil
}
type pruneResource struct {
group string
version string
kind string
namespaced bool
}
func (pr pruneResource) String() string {
return fmt.Sprintf("%v/%v, Kind=%v, Namespaced=%v", pr.group, pr.version, pr.kind, pr.namespaced)
}
func getRESTMappings(pruneResources *[]pruneResource) (namespaced, nonNamespaced []*meta.RESTMapping, err error) {
if len(*pruneResources) == 0 {
// default whitelist
// TODO: need to handle the older api versions - e.g. v1beta1 jobs. Github issue: #35991
*pruneResources = []pruneResource{
{"", "v1", "ConfigMap", true},
{"", "v1", "Endpoints", true},
{"", "v1", "Namespace", false},
{"", "v1", "PersistentVolumeClaim", true},
{"", "v1", "PersistentVolume", false},
{"", "v1", "Pod", true},
{"", "v1", "ReplicationController", true},
{"", "v1", "Secret", true},
{"", "v1", "Service", true},
{"batch", "v1", "Job", true},
{"extensions", "v1beta1", "DaemonSet", true},
{"extensions", "v1beta1", "Deployment", true},
{"extensions", "v1beta1", "HorizontalPodAutoscaler", true},
{"extensions", "v1beta1", "Ingress", true},
{"extensions", "v1beta1", "ReplicaSet", true},
{"apps", "v1beta1", "StatefulSet", true},
}
}
registeredMapper := api.Registry.RESTMapper()
for _, resource := range *pruneResources {
addedMapping, err := registeredMapper.RESTMapping(schema.GroupKind{Group: resource.group, Kind: resource.kind}, resource.version)
if err != nil {
return nil, nil, fmt.Errorf("invalid resource %v: %v", resource, err)
}
if resource.namespaced {
namespaced = append(namespaced, addedMapping)
} else {
nonNamespaced = append(nonNamespaced, addedMapping)
}
}
return namespaced, nonNamespaced, nil
}
type pruner struct {
mapper meta.RESTMapper
clientFunc resource.ClientMapperFunc
clientsetFunc func() (*internalclientset.Clientset, error)
visitedUids sets.String
selector labels.Selector
cascade bool
dryRun bool
gracePeriod int
out io.Writer
}
func (p *pruner) prune(namespace string, mapping *meta.RESTMapping, shortOutput bool) error {
c, err := p.clientFunc(mapping)
if err != nil {
return err
}
objList, err := resource.NewHelper(c, mapping).List(namespace, mapping.GroupVersionKind.Version, p.selector, false)
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)
if err != nil {
return err
}
if _, ok := annots[annotations.LastAppliedConfigAnnotation]; !ok {
// don't prune resources not created with apply
continue
}
uid, err := mapping.UID(obj)
if err != nil {
return err
}
if p.visitedUids.Has(string(uid)) {
continue
}
name, err := mapping.Name(obj)
if err != nil {
return err
}
if !p.dryRun {
if err := p.delete(namespace, name, mapping, c); err != nil {
return err
}
}
cmdutil.PrintSuccess(p.mapper, shortOutput, p.out, mapping.Resource, name, p.dryRun, "pruned")
}
return nil
}
func (p *pruner) delete(namespace, name string, mapping *meta.RESTMapping, c resource.RESTClient) error {
return runDelete(namespace, name, mapping, c, nil, p.cascade, p.gracePeriod, p.clientsetFunc)
}
func runDelete(namespace, name string, mapping *meta.RESTMapping, c resource.RESTClient, helper *resource.Helper, cascade bool, gracePeriod int, clientsetFunc func() (*internalclientset.Clientset, 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 *api.DeleteOptions
if gracePeriod >= 0 {
options = api.NewDeleteOptions(int64(gracePeriod))
}
if err := r.Stop(namespace, name, 2*time.Minute, options); err != nil {
return err
}
return nil
}
func (p *patcher) delete(namespace, name string) error {
return runDelete(namespace, name, p.mapping, nil, p.helper, p.cascade, p.gracePeriod, p.clientsetFunc)
}
type patcher struct {
encoder runtime.Encoder
decoder runtime.Decoder
mapping *meta.RESTMapping
helper *resource.Helper
clientsetFunc func() (*internalclientset.Clientset, error)
overwrite bool
backOff clockwork.Clock
force bool
cascade bool
timeout time.Duration
gracePeriod int
}
func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string) ([]byte, error) {
// Serialize the current configuration of the object from the server.
current, err := runtime.Encode(p.encoder, obj)
if err != nil {
return 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)
if err != nil {
return nil, cmdutil.AddSourceToErr(fmt.Sprintf("retrieving original configuration from:\n%v\nfor:", obj), source, err)
}
// Create the versioned struct from the type defined in the restmapping
// (which is the API version we'll be submitting the patch to)
versionedObject, err := api.Scheme.New(p.mapping.GroupVersionKind)
if err != nil {
return nil, cmdutil.AddSourceToErr(fmt.Sprintf("getting instance of versioned object for %v:", p.mapping.GroupVersionKind), source, err)
}
// Compute a three way strategic merge patch to send to server.
patch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, versionedObject, p.overwrite)
if err != nil {
format := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
return nil, cmdutil.AddSourceToErr(fmt.Sprintf(format, original, modified, current), source, err)
}
_, err = p.helper.Patch(namespace, name, api.StrategicMergePatchType, patch)
return patch, err
}
func (p *patcher) patch(current runtime.Object, modified []byte, source, namespace, name string) ([]byte, error) {
var getErr error
patchBytes, err := p.patchSimple(current, modified, source, namespace, name)
for i := 1; i <= maxPatchRetry && errors.IsConflict(err); i++ {
if i > triesBeforeBackOff {
p.backOff.Sleep(backOffPeriod)
}
current, getErr = p.helper.Get(namespace, name, false)
if getErr != nil {
return nil, getErr
}
patchBytes, err = p.patchSimple(current, modified, source, namespace, name)
}
if err != nil && p.force {
patchBytes, err = p.deleteAndCreate(modified, namespace, name)
}
return patchBytes, err
}
func (p *patcher) deleteAndCreate(modified []byte, namespace, name string) ([]byte, error) {
err := p.delete(namespace, name)
if err != nil {
return modified, err
}
err = wait.PollImmediate(kubectl.Interval, 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 {
return modified, err
}
versionedObject, _, err := p.decoder.Decode(modified, nil, nil)
if err != nil {
return modified, err
}
_, err = p.helper.Create(namespace, true, versionedObject)
return modified, err
}

440
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_test.go generated vendored Normal file
View file

@ -0,0 +1,440 @@
/*
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 (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"testing"
"github.com/spf13/cobra"
kubeerr "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/annotations"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
func TestApplyExtraArgsFail(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
f, _, _, _ := cmdtesting.NewAPIFactory()
c := NewCmdApply(f, buf, errBuf)
if validateApplyArgs(c, []string{"rc"}) == nil {
t.Fatalf("unexpected non-error")
}
}
func validateApplyArgs(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return cmdutil.UsageError(cmd, "Unexpected args: %v", args)
}
return nil
}
const (
filenameRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.yaml"
filenameSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/service.yaml"
filenameRCSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-service.yaml"
)
func readBytesFromFile(t *testing.T, filename string) []byte {
file, err := os.Open(filename)
if err != nil {
t.Fatal(err)
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
t.Fatal(err)
}
return data
}
func readReplicationController(t *testing.T, filenameRC string) (string, []byte) {
rcObj := readReplicationControllerFromFile(t, filenameRC)
metaAccessor, err := meta.Accessor(rcObj)
if err != nil {
t.Fatal(err)
}
rcBytes, err := runtime.Encode(testapi.Default.Codec(), rcObj)
if err != nil {
t.Fatal(err)
}
return metaAccessor.GetName(), rcBytes
}
func readReplicationControllerFromFile(t *testing.T, filename string) *api.ReplicationController {
data := readBytesFromFile(t, filename)
rc := api.ReplicationController{}
if err := runtime.DecodeInto(testapi.Default.Codec(), data, &rc); err != nil {
t.Fatal(err)
}
return &rc
}
func readServiceFromFile(t *testing.T, filename string) *api.Service {
data := readBytesFromFile(t, filename)
svc := api.Service{}
if err := runtime.DecodeInto(testapi.Default.Codec(), data, &svc); err != nil {
t.Fatal(err)
}
return &svc
}
func annotateRuntimeObject(t *testing.T, originalObj, currentObj runtime.Object, kind string) (string, []byte) {
originalAccessor, err := meta.Accessor(originalObj)
if err != nil {
t.Fatal(err)
}
originalLabels := originalAccessor.GetLabels()
originalLabels["DELETE_ME"] = "DELETE_ME"
originalAccessor.SetLabels(originalLabels)
original, err := runtime.Encode(testapi.Default.Codec(), originalObj)
if err != nil {
t.Fatal(err)
}
currentAccessor, err := meta.Accessor(currentObj)
if err != nil {
t.Fatal(err)
}
currentAnnotations := currentAccessor.GetAnnotations()
if currentAnnotations == nil {
currentAnnotations = make(map[string]string)
}
currentAnnotations[annotations.LastAppliedConfigAnnotation] = string(original)
currentAccessor.SetAnnotations(currentAnnotations)
current, err := runtime.Encode(testapi.Default.Codec(), currentObj)
if err != nil {
t.Fatal(err)
}
return currentAccessor.GetName(), current
}
func readAndAnnotateReplicationController(t *testing.T, filename string) (string, []byte) {
rc1 := readReplicationControllerFromFile(t, filename)
rc2 := readReplicationControllerFromFile(t, filename)
return annotateRuntimeObject(t, rc1, rc2, "ReplicationController")
}
func readAndAnnotateService(t *testing.T, filename string) (string, []byte) {
svc1 := readServiceFromFile(t, filename)
svc2 := readServiceFromFile(t, filename)
return annotateRuntimeObject(t, svc1, svc2, "Service")
}
func validatePatchApplication(t *testing.T, req *http.Request) {
patch, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
patchMap := map[string]interface{}{}
if err := json.Unmarshal(patch, &patchMap); err != nil {
t.Fatal(err)
}
annotationsMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"})
if _, ok := annotationsMap[annotations.LastAppliedConfigAnnotation]; !ok {
t.Fatalf("patch does not contain annotation:\n%s\n", patch)
}
labelMap := walkMapPath(t, patchMap, []string{"metadata", "labels"})
if deleteMe, ok := labelMap["DELETE_ME"]; !ok || deleteMe != nil {
t.Fatalf("patch does not remove deleted key: DELETE_ME:\n%s\n", patch)
}
}
func walkMapPath(t *testing.T, start map[string]interface{}, path []string) map[string]interface{} {
finish := start
for i := 0; i < len(path); i++ {
var ok bool
finish, ok = finish[path[i]].(map[string]interface{})
if !ok {
t.Fatalf("key:%s of path:%v not found in map:%v", path[i], path, start)
}
}
return finish
}
func TestApplyObjectWithoutAnnotation(t *testing.T) {
initTestErrorHandler(t)
nameRC, rcBytes := readReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == pathRC && m == "GET":
bodyRC := ioutil.NopCloser(bytes.NewReader(rcBytes))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case p == pathRC && m == "PATCH":
bodyRC := ioutil.NopCloser(bytes.NewReader(rcBytes))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply(f, buf, errBuf)
cmd.Flags().Set("filename", filenameRC)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
// uses the name from the file, not the response
expectRC := "replicationcontroller/" + nameRC + "\n"
expectWarning := warningNoLastAppliedConfigAnnotation
if errBuf.String() != expectWarning {
t.Fatalf("unexpected non-warning: %s\nexpected: %s", errBuf.String(), expectWarning)
}
if buf.String() != expectRC {
t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC)
}
}
func TestApplyObject(t *testing.T) {
initTestErrorHandler(t)
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == pathRC && m == "GET":
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case p == pathRC && m == "PATCH":
validatePatchApplication(t, req)
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply(f, buf, errBuf)
cmd.Flags().Set("filename", filenameRC)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
// uses the name from the file, not the response
expectRC := "replicationcontroller/" + nameRC + "\n"
if buf.String() != expectRC {
t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC)
}
}
func TestApplyRetry(t *testing.T) {
initTestErrorHandler(t)
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
firstPatch := true
retry := false
getCount := 0
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == pathRC && m == "GET":
getCount++
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case p == pathRC && m == "PATCH":
if firstPatch {
firstPatch = false
statusErr := kubeerr.NewConflict(schema.GroupResource{Group: "", Resource: "rc"}, "test-rc", fmt.Errorf("the object has been modified. Please apply at first."))
bodyBytes, _ := json.Marshal(statusErr)
bodyErr := ioutil.NopCloser(bytes.NewReader(bodyBytes))
return &http.Response{StatusCode: http.StatusConflict, Header: defaultHeader(), Body: bodyErr}, nil
}
retry = true
validatePatchApplication(t, req)
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply(f, buf, errBuf)
cmd.Flags().Set("filename", filenameRC)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
if !retry || getCount != 2 {
t.Fatalf("apply didn't retry when get conflict error")
}
// uses the name from the file, not the response
expectRC := "replicationcontroller/" + nameRC + "\n"
if buf.String() != expectRC {
t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC)
}
}
func TestApplyNonExistObject(t *testing.T) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers"
pathNameRC := pathRC + "/" + nameRC
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/api/v1/namespaces/test" && m == "GET":
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader(nil))}, nil
case p == pathNameRC && m == "GET":
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader(nil))}, nil
case p == pathRC && m == "POST":
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: bodyRC}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply(f, buf, errBuf)
cmd.Flags().Set("filename", filenameRC)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
// uses the name from the file, not the response
expectRC := "replicationcontroller/" + nameRC + "\n"
if buf.String() != expectRC {
t.Errorf("unexpected output: %s\nexpected: %s", buf.String(), expectRC)
}
}
func TestApplyMultipleObjectsAsList(t *testing.T) {
testApplyMultipleObjects(t, true)
}
func TestApplyMultipleObjectsAsFiles(t *testing.T) {
testApplyMultipleObjects(t, false)
}
func testApplyMultipleObjects(t *testing.T, asList bool) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
nameSVC, currentSVC := readAndAnnotateService(t, filenameSVC)
pathSVC := "/namespaces/test/services/" + nameSVC
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == pathRC && m == "GET":
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case p == pathRC && m == "PATCH":
validatePatchApplication(t, req)
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case p == pathSVC && m == "GET":
bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil
case p == pathSVC && m == "PATCH":
validatePatchApplication(t, req)
bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply(f, buf, errBuf)
if asList {
cmd.Flags().Set("filename", filenameRCSVC)
} else {
cmd.Flags().Set("filename", filenameRC)
cmd.Flags().Set("filename", filenameSVC)
}
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
// Names should come from the REST response, NOT the files
expectRC := "replicationcontroller/" + nameRC + "\n"
expectSVC := "service/" + nameSVC + "\n"
// Test both possible orders since output is non-deterministic.
expectOne := expectRC + expectSVC
expectTwo := expectSVC + expectRC
if buf.String() != expectOne && buf.String() != expectTwo {
t.Fatalf("unexpected output: %s\nexpected: %s OR %s", buf.String(), expectOne, expectTwo)
}
}

288
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach.go generated vendored Normal file
View file

@ -0,0 +1,288 @@
/*
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"
"net/url"
"github.com/golang/glog"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/pkg/api"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
"k8s.io/kubernetes/pkg/util/term"
)
var (
attach_example = templates.Examples(`
# Get output from running pod 123456-7890, using the first container by default
kubectl attach 123456-7890
# Get output from ruby-container from pod 123456-7890
kubectl attach 123456-7890 -c ruby-container
# Switch to raw terminal mode, sends stdin to 'bash' in ruby-container from pod 123456-7890
# and sends stdout/stderr from 'bash' back to the client
kubectl attach 123456-7890 -c ruby-container -i -t`)
)
func NewCmdAttach(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command {
options := &AttachOptions{
StreamOptions: StreamOptions{
In: cmdIn,
Out: cmdOut,
Err: cmdErr,
},
Attach: &DefaultRemoteAttach{},
}
cmd := &cobra.Command{
Use: "attach POD -c CONTAINER",
Short: "Attach to a running container",
Long: "Attach to a process that is already running inside an existing container.",
Example: attach_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.Run())
},
}
// TODO support UID
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")
return cmd
}
// RemoteAttach defines the interface accepted by the Attach command - provided for test stubbing
type RemoteAttach interface {
Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.TerminalSizeQueue) error
}
// DefaultRemoteAttach is the standard implementation of attaching
type DefaultRemoteAttach struct{}
func (*DefaultRemoteAttach) Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.TerminalSizeQueue) error {
exec, err := remotecommand.NewExecutor(config, method, url)
if err != nil {
return err
}
return exec.Stream(remotecommand.StreamOptions{
SupportedProtocols: remotecommandserver.SupportedStreamingProtocols,
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
Tty: tty,
TerminalSizeQueue: terminalSizeQueue,
})
}
// AttachOptions declare the arguments accepted by the Exec command
type AttachOptions struct {
StreamOptions
CommandName string
Pod *api.Pod
Attach RemoteAttach
PodClient coreclient.PodsGetter
Config *restclient.Config
}
// Complete verifies command line arguments and loads data from the command environment
func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []string) error {
if len(argsIn) == 0 {
return cmdutil.UsageError(cmd, "POD is required for attach")
}
if len(argsIn) > 1 {
return cmdutil.UsageError(cmd, fmt.Sprintf("expected a single argument: POD, saw %d: %s", len(argsIn), argsIn))
}
p.PodName = argsIn[0]
namespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}
p.Namespace = namespace
config, err := f.ClientConfig()
if err != nil {
return err
}
p.Config = config
clientset, err := f.ClientSet()
if err != nil {
return err
}
p.PodClient = clientset.Core()
if p.CommandName == "" {
p.CommandName = cmd.CommandPath()
}
return nil
}
// Validate checks that the provided attach options are specified.
func (p *AttachOptions) Validate() error {
allErrs := []error{}
if len(p.PodName) == 0 {
allErrs = append(allErrs, fmt.Errorf("pod name must be specified"))
}
if p.Out == nil || p.Err == nil {
allErrs = append(allErrs, fmt.Errorf("both output and error output must be provided"))
}
if p.Attach == nil || p.PodClient == nil || p.Config == nil {
allErrs = append(allErrs, fmt.Errorf("client, client config, and attach must be provided"))
}
return utilerrors.NewAggregate(allErrs)
}
// Run executes a validated remote execution against a pod.
func (p *AttachOptions) Run() error {
if p.Pod == nil {
pod, err := p.PodClient.Pods(p.Namespace).Get(p.PodName, metav1.GetOptions{})
if err != nil {
return err
}
if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed {
return fmt.Errorf("cannot attach a container in a completed pod; current phase is %s", pod.Status.Phase)
}
p.Pod = pod
// TODO: convert this to a clean "wait" behavior
}
pod := p.Pod
// check for TTY
containerToAttach, err := p.containerToAttachTo(pod)
if err != nil {
return fmt.Errorf("cannot attach to the container: %v", err)
}
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)
}
} 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
// an error "Unrecognized input header"
p.TTY = true
}
// ensure we can recover the terminal while attached
t := p.setupTTY()
// save p.Err so we can print the command prompt message below
stderr := p.Err
var sizeQueue term.TerminalSizeQueue
if t.Raw {
if size := t.GetSize(); size != nil {
// fake resizing +1 and then back to normal so that attach-detach-reattach will result in the
// screen being redrawn
sizePlusOne := *size
sizePlusOne.Width++
sizePlusOne.Height++
// this call spawns a goroutine to monitor/update the terminal size
sizeQueue = t.MonitorSize(&sizePlusOne, size)
}
// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
// true
p.Err = nil
}
fn := func() error {
if !p.Quiet && stderr != nil {
fmt.Fprintln(stderr, "If you don't see a command prompt, try pressing enter.")
}
restClient, err := restclient.RESTClientFor(p.Config)
if err != nil {
return err
}
// TODO: consider abstracting into a client invocation or client helper
req := restClient.Post().
Resource("pods").
Name(pod.Name).
Namespace(pod.Namespace).
SubResource("attach")
req.VersionedParams(&api.PodAttachOptions{
Container: containerToAttach.Name,
Stdin: p.Stdin,
Stdout: p.Out != nil,
Stderr: p.Err != nil,
TTY: t.Raw,
}, api.ParameterCodec)
return p.Attach.Attach("POST", req.URL(), p.Config, p.In, p.Out, p.Err, t.Raw, sizeQueue)
}
if err := t.Safe(fn); err != nil {
return err
}
if p.Stdin && t.Raw && pod.Spec.RestartPolicy == api.RestartPolicyAlways {
fmt.Fprintf(p.Out, "Session ended, resume using '%s %s -c %s -i -t' command when the pod is running\n", p.CommandName, pod.Name, containerToAttach.Name)
}
return nil
}
// containerToAttach returns a reference to the container to attach to, given
// by name or the first container if name is empty.
func (p *AttachOptions) containerToAttachTo(pod *api.Pod) (*api.Container, error) {
if len(p.ContainerName) > 0 {
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == p.ContainerName {
return &pod.Spec.Containers[i], nil
}
}
for i := range pod.Spec.InitContainers {
if pod.Spec.InitContainers[i].Name == p.ContainerName {
return &pod.Spec.InitContainers[i], nil
}
}
return nil, fmt.Errorf("container not found (%s)", p.ContainerName)
}
glog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name)
return &pod.Spec.Containers[0], nil
}
// GetContainerName returns the name of the container to attach to, with a fallback.
func (p *AttachOptions) GetContainerName(pod *api.Pod) (string, error) {
c, err := p.containerToAttachTo(pod)
if err != nil {
return "", err
}
return c.Name, nil
}

316
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach_test.go generated vendored Normal file
View file

@ -0,0 +1,316 @@
/*
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 (
"bytes"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"testing"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/util/term"
)
type fakeRemoteAttach struct {
method string
url *url.URL
err error
}
func (f *fakeRemoteAttach) Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.TerminalSizeQueue) error {
f.method = method
f.url = url
return f.err
}
func TestPodAndContainerAttach(t *testing.T) {
tests := []struct {
args []string
p *AttachOptions
name string
expectError bool
expectedPod string
expectedContainer string
}{
{
p: &AttachOptions{},
expectError: true,
name: "empty",
},
{
p: &AttachOptions{},
args: []string{"foo", "bar"},
expectError: true,
name: "too many args",
},
{
p: &AttachOptions{},
args: []string{"foo"},
expectedPod: "foo",
name: "no container, no flags",
},
{
p: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
args: []string{"foo"},
expectedPod: "foo",
expectedContainer: "bar",
name: "container in flag",
},
{
p: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "initfoo"}},
args: []string{"foo"},
expectedPod: "foo",
expectedContainer: "initfoo",
name: "init container in flag",
},
{
p: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
args: []string{"foo", "-c", "wrong"},
expectError: true,
name: "non-existing container in flag",
},
}
for _, test := range tests {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { return nil, nil }),
}
tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
cmd := &cobra.Command{}
options := test.p
err := options.Complete(f, cmd, test.args)
if test.expectError && err == nil {
t.Errorf("unexpected non-error (%s)", test.name)
}
if !test.expectError && err != nil {
t.Errorf("unexpected error: %v (%s)", err, test.name)
}
if err != nil {
continue
}
if options.PodName != test.expectedPod {
t.Errorf("expected: %s, got: %s (%s)", test.expectedPod, options.PodName, test.name)
}
if options.ContainerName != test.expectedContainer {
t.Errorf("expected: %s, got: %s (%s)", test.expectedContainer, options.ContainerName, test.name)
}
}
}
func TestAttach(t *testing.T) {
version := api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version
tests := []struct {
name, version, podPath, attachPath, container string
pod *api.Pod
remoteAttachErr bool
exepctedErr string
}{
{
name: "pod attach",
version: version,
podPath: "/api/" + version + "/namespaces/test/pods/foo",
attachPath: "/api/" + version + "/namespaces/test/pods/foo/attach",
pod: attachPod(),
container: "bar",
},
{
name: "pod attach error",
version: version,
podPath: "/api/" + version + "/namespaces/test/pods/foo",
attachPath: "/api/" + version + "/namespaces/test/pods/foo/attach",
pod: attachPod(),
remoteAttachErr: true,
container: "bar",
exepctedErr: "attach error",
},
{
name: "container not found error",
version: version,
podPath: "/api/" + version + "/namespaces/test/pods/foo",
attachPath: "/api/" + version + "/namespaces/test/pods/foo/attach",
pod: attachPod(),
container: "foo",
exepctedErr: "cannot attach to the container: container not found (foo)",
},
}
for _, test := range tests {
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
// Ensures no GET is performed when deleting by name
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.ClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.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,
}
cmd := &cobra.Command{}
if err := params.Complete(f, 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())
}
}
}
func TestAttachWarnings(t *testing.T) {
version := api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version
tests := []struct {
name, container, version, podPath, expectedErr, expectedOut string
pod *api.Pod
stdin, tty bool
}{
{
name: "fallback tty if not supported",
version: version,
podPath: "/api/" + version + "/namespaces/test/pods/foo",
pod: attachPod(),
stdin: true,
tty: true,
expectedErr: "Unable to use a TTY - container bar did not allocate one",
},
}
for _, test := range tests {
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
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.ClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.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,
}
cmd := &cobra.Command{}
if err := params.Complete(f, 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())
continue
}
}
}
}
}
func attachPod() *api.Pod {
return &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{
{
Name: "bar",
},
},
InitContainers: []api.Container{
{
Name: "initfoo",
},
},
},
Status: api.PodStatus{
Phase: api.PodRunning,
},
}
}

203
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/autoscale.go generated vendored Normal file
View file

@ -0,0 +1,203 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"io"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"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"
"github.com/spf13/cobra"
)
var (
autoscaleLong = templates.LongDesc(`
Creates an autoscaler that automatically chooses and sets the number of pods that run in a kubernetes cluster.
Looks up a Deployment, ReplicaSet, or ReplicationController by name and creates an autoscaler that uses the given resource as a reference.
An autoscaler can automatically increase or decrease number of pods deployed within the system as needed.`)
autoscaleExample = templates.Examples(`
# Auto scale a deployment "foo", with the number of pods between 2 and 10, target CPU utilization specified so a default autoscaling policy will be used:
kubectl autoscale deployment foo --min=2 --max=10
# Auto scale a replication controller "foo", with the number of pods between 1 and 5, target CPU utilization at 80%:
kubectl autoscale rc foo --max=5 --cpu-percent=80`)
)
func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}
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] [flags]",
Short: "Auto-scale a Deployment, ReplicaSet, or ReplicationController",
Long: autoscaleLong,
Example: autoscaleExample,
Run: func(cmd *cobra.Command, args []string) {
err := RunAutoscale(f, out, cmd, args, options)
cmdutil.CheckErr(err)
},
ValidArgs: validArgs,
ArgAliases: argAliases,
}
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().String("generator", "horizontalpodautoscaler/v1", "The name of the API generator to use. Currently there is only 1 generator.")
cmd.Flags().Int("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().Int("max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
cmd.MarkFlagRequired("max")
cmd.Flags().Int("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", "", "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.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()
if err != nil {
return err
}
// validate flags
if err := validateFlags(cmd); err != nil {
return err
}
mapper, typer := f.Object()
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
ResourceTypeOrNameArgs(false, args...).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
// Get the generator, setup and validate all required parameters
generatorName := cmdutil.GetFlagString(cmd, "generator")
generators := f.Generators("autoscale")
generator, found := generators[generatorName]
if !found {
return cmdutil.UsageError(cmd, fmt.Sprintf("generator %q not found.", generatorName))
}
names := generator.ParamNames()
count := 0
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 {
return err
}
name := info.Name
params := kubectl.MakeParams(cmd, names)
params["default-name"] = name
params["scaleRef-kind"] = mapping.GroupVersionKind.Kind
params["scaleRef-name"] = name
params["scaleRef-apiVersion"] = mapping.GroupVersionKind.GroupVersion().String()
if err = kubectl.ValidateParams(names, params); err != nil {
return err
}
// Check for invalid flags used against the present generator.
if err := kubectl.EnsureFlagsValid(cmd, generators, generatorName); err != nil {
return err
}
// Generate new object
object, err := generator.Generate(params)
if err != nil {
return err
}
resourceMapper := &resource.Mapper{
ObjectTyper: typer,
RESTMapper: mapper,
ClientMapper: resource.ClientMapperFunc(f.ClientForMapping),
Decoder: f.Decoder(true),
}
hpa, err := resourceMapper.InfoForObject(object, nil)
if err != nil {
return err
}
if cmdutil.ShouldRecord(cmd, hpa) {
if err := cmdutil.RecordChangeCause(hpa.Object, f.Command()); err != nil {
return err
}
object = hpa.Object
}
if cmdutil.GetDryRunFlag(cmd) {
return f.PrintObject(cmd, mapper, object, out)
}
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), hpa, f.JSONEncoder()); err != nil {
return err
}
object, err = resource.NewHelper(hpa.Client, hpa.Mapping).Create(namespace, false, object)
if err != nil {
return err
}
count++
if len(cmdutil.GetFlagString(cmd, "output")) > 0 {
return f.PrintObject(cmd, mapper, object, out)
}
cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, cmdutil.GetDryRunFlag(cmd), "autoscaled")
return nil
})
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("no objects passed to autoscale")
}
return nil
}
func validateFlags(cmd *cobra.Command) error {
errs := []error{}
max, min := cmdutil.GetFlagInt(cmd, "max"), cmdutil.GetFlagInt(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

@ -0,0 +1,197 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"io"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/apis/certificates"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"github.com/spf13/cobra"
)
func NewCmdCertificate(f cmdutil.Factory, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "certificate SUBCOMMAND",
Short: "Modify certificate resources.",
Long: "Modify certificate resources.",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
cmd.AddCommand(NewCmdCertificateApprove(f, out))
cmd.AddCommand(NewCmdCertificateDeny(f, out))
return cmd
}
type CertificateOptions struct {
resource.FilenameOptions
csrNames []string
outputStyle string
}
func (options *CertificateOptions) Complete(cmd *cobra.Command, args []string) error {
options.csrNames = args
options.outputStyle = cmdutil.GetFlagString(cmd, "output")
return nil
}
func (options *CertificateOptions) Validate() error {
if len(options.csrNames) < 1 && cmdutil.IsFilenameEmpty(options.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{}
cmd := &cobra.Command{
Use: "approve (-f FILENAME | NAME)",
Short: "Approve a certificate signing request",
Long: templates.LongDesc(`
Approve a certificate signing request.
kubectl certificate approve allows a cluster admin to approve a certificate
signing request (CSR). This action tells a certificate signing controller to
issue a certificate to the requestor with the attributes requested in the CSR.
SECURITY NOTICE: Depending on the requested attributes, the issued certificate
can potentially grant a requester access to cluster resources or to authenticate
as a requested identity. Before approving a CSR, ensure you understand what the
signed certificate can do.
`),
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(cmd, args))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.RunCertificateApprove(f, out))
},
}
cmdutil.AddOutputFlagsForMutation(cmd)
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) {
var alreadyApproved bool
for _, c := range csr.Status.Conditions {
if c.Type == certificates.CertificateApproved {
alreadyApproved = true
}
}
if alreadyApproved {
return csr, "approved"
}
csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateApproved,
Reason: "KubectlApprove",
Message: "This CSR was approved by kubectl certificate approve.",
LastUpdateTime: metav1.Now(),
})
return csr, "approved"
})
}
func NewCmdCertificateDeny(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := CertificateOptions{}
cmd := &cobra.Command{
Use: "deny (-f FILENAME | NAME)",
Short: "Deny a certificate signing request",
Long: templates.LongDesc(`
Deny a certificate signing request.
kubectl certificate deny allows a cluster admin to deny a certificate
signing request (CSR). This action tells a certificate signing controller to
not to issue a certificate to the requestor.
`),
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(cmd, args))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.RunCertificateDeny(f, out))
},
}
cmdutil.AddOutputFlagsForMutation(cmd)
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) {
var alreadyDenied bool
for _, c := range csr.Status.Conditions {
if c.Type == certificates.CertificateDenied {
alreadyDenied = true
}
}
if alreadyDenied {
return csr, "denied"
}
csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateDenied,
Reason: "KubectlDeny",
Message: "This CSR was approved by kubectl certificate deny.",
LastUpdateTime: metav1.Now(),
})
return csr, "denied"
})
}
func (options *CertificateOptions) modifyCertificateCondition(f cmdutil.Factory, out io.Writer, modify func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, string)) error {
var found int
mapper, typer := f.Object()
c, err := f.ClientSet()
if err != nil {
return err
}
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
FilenameParam(false, &options.FilenameOptions).
ResourceNames("certificatesigningrequest", options.csrNames...).
RequireObject(true).
Flatten().
Latest().
Do()
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
}
found++
cmdutil.PrintSuccess(mapper, options.outputStyle == "name", out, info.Mapping.Resource, info.Name, false, verb)
return nil
})
if found == 0 {
fmt.Fprintf(out, "No resources found\n")
}
return err
}

132
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/clusterinfo.go generated vendored Normal file
View file

@ -0,0 +1,132 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"io"
"os"
"strconv"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"github.com/daviddengcn/go-colortext"
"github.com/spf13/cobra"
)
var (
longDescr = templates.LongDesc(`
Display addresses of the master and services with label kubernetes.io/cluster-service=true
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.`)
clusterinfo_example = templates.Examples(`
# Print the address of the master and cluster services
kubectl cluster-info`)
)
func NewCmdClusterInfo(f cmdutil.Factory, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "cluster-info",
// clusterinfo is deprecated.
Aliases: []string{"clusterinfo"},
Short: "Display cluster info",
Long: longDescr,
Example: clusterinfo_example,
Run: func(cmd *cobra.Command, args []string) {
err := RunClusterInfo(f, out, cmd)
cmdutil.CheckErr(err)
},
}
cmdutil.AddInclude3rdPartyFlags(cmd)
cmd.AddCommand(NewCmdClusterInfoDump(f, out))
return cmd
}
func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error {
if len(os.Args) > 1 && os.Args[1] == "clusterinfo" {
printDeprecationWarning("cluster-info", "clusterinfo")
}
client, err := f.ClientConfig()
if err != nil {
return err
}
printService(out, "Kubernetes master", client.Host)
mapper, typer := f.Object()
cmdNamespace := cmdutil.GetFlagString(cmd, "namespace")
if cmdNamespace == "" {
cmdNamespace = api.NamespaceSystem
}
// TODO use generalized labels once they are implemented (#341)
b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
NamespaceParam(cmdNamespace).DefaultNamespace().
SelectorParam("kubernetes.io/cluster-service=true").
ResourceTypeOrNameArgs(false, []string{"services"}...).
Latest()
b.Do().Visit(func(r *resource.Info, err error) error {
if err != nil {
return err
}
services := r.Object.(*api.ServiceList).Items
for _, service := range services {
var link string
if len(service.Status.LoadBalancer.Ingress) > 0 {
ingress := service.Status.LoadBalancer.Ingress[0]
ip := ingress.IP
if ip == "" {
ip = ingress.Hostname
}
for _, port := range service.Spec.Ports {
link += "http://" + ip + ":" + strconv.Itoa(int(port.Port)) + " "
}
} else {
if len(client.GroupVersion.Group) == 0 {
link = client.Host + "/api/" + client.GroupVersion.Version + "/proxy/namespaces/" + service.ObjectMeta.Namespace + "/services/" + service.ObjectMeta.Name
} else {
link = client.Host + "/api/" + client.GroupVersion.Group + "/" + client.GroupVersion.Version + "/proxy/namespaces/" + service.ObjectMeta.Namespace + "/services/" + service.ObjectMeta.Name
}
}
name := service.ObjectMeta.Labels["kubernetes.io/name"]
if len(name) == 0 {
name = service.ObjectMeta.Name
}
printService(out, name, link)
}
return nil
})
out.Write([]byte("\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\n"))
return nil
// TODO consider printing more information about cluster
}
func printService(out io.Writer, name, link string) {
ct.ChangeColor(ct.Green, false, ct.None, false)
fmt.Fprint(out, name)
ct.ResetColor()
fmt.Fprintf(out, " is running at ")
ct.ChangeColor(ct.Yellow, false, ct.None, false)
fmt.Fprint(out, link)
ct.ResetColor()
fmt.Fprintln(out, "")
}

View file

@ -0,0 +1,215 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"io"
"os"
"path"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
// NewCmdCreateSecret groups subcommands to create various types of secrets
func NewCmdClusterInfoDump(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "dump",
Short: "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, args, cmdOut))
},
}
cmd.Flags().String("output-directory", "", "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.")
return cmd
}
var (
dumpLong = templates.LongDesc(`
Dumps cluster info out suitable for debugging and diagnosing cluster problems. By default, dumps everything to
stdout. You can optionally specify a directory with --output-directory. If you specify a directory, kubernetes will
build a set of files in that directory. By default only dumps things in the 'kube-system' namespace, but you can
switch to a different namespace with the --namespaces flag, or specify --all-namespaces to dump all namespaces.
The command also dumps the logs of all of the pods in the cluster, these logs are dumped into different directories
based on namespace and pod name.`)
dumpExample = templates.Examples(`
# Dump current cluster state to stdout
kubectl cluster-info dump
# Dump current cluster state to /path/to/cluster-state
kubectl cluster-info dump --output-directory=/path/to/cluster-state
# Dump all namespaces to stdout
kubectl cluster-info dump --all-namespaces
# Dump a set of namespaces to /path/to/cluster-state
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")
if len(dir) == 0 || dir == "-" {
return defaultWriter
}
fullFile := path.Join(dir, filename)
parent := path.Dir(fullFile)
cmdutil.CheckErr(os.MkdirAll(parent, 0755))
file, err := os.Create(path.Join(dir, filename))
cmdutil.CheckErr(err)
return file
}
func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, args []string, out io.Writer) error {
clientset, err := f.ClientSet()
if err != nil {
return err
}
printer, _, err := kubectl.GetPrinter("json", "", false, true)
if err != nil {
return err
}
nodes, err := clientset.Core().Nodes().List(api.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(nodes, setupOutputWriter(cmd, out, "nodes.json")); err != nil {
return err
}
var namespaces []string
if cmdutil.GetFlagBool(cmd, "all-namespaces") {
namespaceList, err := clientset.Core().Namespaces().List(api.ListOptions{})
if err != nil {
return err
}
for ix := range namespaceList.Items {
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
}
namespaces = []string{
api.NamespaceSystem,
cmdNamespace,
}
}
}
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(api.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(events, setupOutputWriter(cmd, out, path.Join(namespace, "events.json"))); err != nil {
return err
}
rcs, err := clientset.Core().ReplicationControllers(namespace).List(api.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(rcs, setupOutputWriter(cmd, out, path.Join(namespace, "replication-controllers.json"))); err != nil {
return err
}
svcs, err := clientset.Core().Services(namespace).List(api.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(svcs, setupOutputWriter(cmd, out, path.Join(namespace, "services.json"))); err != nil {
return err
}
sets, err := clientset.Extensions().DaemonSets(namespace).List(api.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(sets, setupOutputWriter(cmd, out, path.Join(namespace, "daemonsets.json"))); err != nil {
return err
}
deps, err := clientset.Extensions().Deployments(namespace).List(api.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(deps, setupOutputWriter(cmd, out, path.Join(namespace, "deployments.json"))); err != nil {
return err
}
rps, err := clientset.Extensions().ReplicaSets(namespace).List(api.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(rps, setupOutputWriter(cmd, out, path.Join(namespace, "replicasets.json"))); err != nil {
return err
}
pods, err := clientset.Core().Pods(namespace).List(api.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(pods, setupOutputWriter(cmd, out, path.Join(namespace, "pods.json"))); err != nil {
return err
}
for ix := range pods.Items {
pod := &pods.Items[ix]
writer := setupOutputWriter(cmd, out, path.Join(namespace, pod.Name, "logs.txt"))
writer.Write([]byte(fmt.Sprintf("==== START logs for %s/%s ====\n", pod.Namespace, pod.Name)))
request, err := f.LogsForObject(pod, &api.PodLogOptions{})
if err != nil {
return err
}
data, err := request.DoRaw()
if err != nil {
return err
}
writer.Write(data)
writer.Write([]byte(fmt.Sprintf("==== END logs for %s/%s ====\n", pod.Namespace, pod.Name)))
}
}
dir := cmdutil.GetFlagString(cmd, "output-directory")
if len(dir) == 0 {
dir = "."
}
if dir != "-" {
fmt.Fprintf(out, "Cluster info dumped to %s", dir)
}
return nil
}

View file

@ -0,0 +1,70 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"io/ioutil"
"os"
"path"
"testing"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestSetupOutputWriterNoOp(t *testing.T) {
tests := []string{"", "-"}
for _, test := range tests {
out := &bytes.Buffer{}
f, _, _, _ := cmdtesting.NewAPIFactory()
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)
}
}
}
func TestSetupOutputWriterFile(t *testing.T) {
file := "output.json"
dir, err := ioutil.TempDir(os.TempDir(), "out")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
fullPath := path.Join(dir, file)
defer os.RemoveAll(dir)
out := &bytes.Buffer{}
f, _, _, _ := cmdtesting.NewAPIFactory()
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)
}
output := "some data here"
writer.Write([]byte(output))
data, err := ioutil.ReadFile(fullPath)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if string(data) != output {
t.Errorf("expected: %v, saw: %v", output, data)
}
}

347
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd.go generated vendored Normal file
View file

@ -0,0 +1,347 @@
/*
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"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config"
"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/util/flag"
"k8s.io/kubernetes/pkg/util/i18n"
"github.com/golang/glog"
"github.com/spf13/cobra"
)
const (
bash_completion_func = `# call kubectl get $1,
__kubectl_override_flag_list=(kubeconfig cluster user context namespace server)
__kubectl_override_flags()
{
local ${__kubectl_override_flag_list[*]} two_word_of of
for w in "${words[@]}"; do
if [ -n "${two_word_of}" ]; then
eval "${two_word_of}=\"--${two_word_of}=\${w}\""
two_word_of=
continue
fi
for of in "${__kubectl_override_flag_list[@]}"; do
case "${w}" in
--${of}=*)
eval "${of}=\"${w}\""
;;
--${of})
two_word_of="${of}"
;;
esac
done
if [ "${w}" == "--all-namespaces" ]; then
namespace="--all-namespaces"
fi
done
for of in "${__kubectl_override_flag_list[@]}"; do
if eval "test -n \"\$${of}\""; then
eval "echo \${${of}}"
fi
done
}
__kubectl_get_namespaces()
{
local template kubectl_out
template="{{ range .items }}{{ .metadata.name }} {{ end }}"
if kubectl_out=$(kubectl get -o template --template="${template}" namespace 2>/dev/null); then
COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
fi
}
__kubectl_parse_get()
{
local template
template="{{ 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" ) )
fi
}
__kubectl_get_resource()
{
if [[ ${#nouns[@]} -eq 0 ]]; then
return 1
fi
__kubectl_parse_get "${nouns[${#nouns[@]} -1]}"
}
__kubectl_get_resource_pod()
{
__kubectl_parse_get "pod"
}
__kubectl_get_resource_rc()
{
__kubectl_parse_get "rc"
}
__kubectl_get_resource_node()
{
__kubectl_parse_get "node"
}
# $1 is the name of the pod we want to get the list of containers inside
__kubectl_get_containers()
{
local template
template="{{ range .spec.containers }}{{ .name }} {{ end }}"
__debug "${FUNCNAME} nouns are ${nouns[*]}"
local len="${#nouns[@]}"
if [[ ${len} -ne 1 ]]; then
return
fi
local last=${nouns[${len} -1]}
local kubectl_out
if kubectl_out=$(kubectl get $(__kubectl_override_flags) -o template --template="${template}" pods "${last}" 2>/dev/null); then
COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
fi
}
# Require both a pod and a container to be specified
__kubectl_require_pod_and_container()
{
if [[ ${#nouns[@]} -eq 0 ]]; then
__kubectl_parse_get pods
return 0
fi;
__kubectl_get_containers
return 0
}
__custom_func() {
case ${last_command} in
kubectl_get | kubectl_describe | kubectl_delete | kubectl_label | kubectl_stop | kubectl_edit | kubectl_patch |\
kubectl_annotate | kubectl_expose | kubectl_scale | kubectl_autoscale | kubectl_taint | kubectl_rollout_*)
__kubectl_get_resource
return
;;
kubectl_logs | kubectl_attach)
__kubectl_require_pod_and_container
return
;;
kubectl_exec | kubectl_port-forward | kubectl_top_pod)
__kubectl_get_resource_pod
return
;;
kubectl_rolling-update)
__kubectl_get_resource_rc
return
;;
kubectl_cordon | kubectl_uncordon | kubectl_drain | kubectl_top_node)
__kubectl_get_resource_node
return
;;
*)
;;
esac
}
`
// If you add a resource to this list, please also take a look at pkg/kubectl/kubectl.go
// and add a short forms entry in expandResourceShortcut() when appropriate.
// TODO: This should be populated using the discovery information from apiserver.
valid_resources = `Valid resource types include:
* all
* clusters (valid only for federation apiservers)
* clusterrolebindings
* clusterroles
* componentstatuses (aka 'cs')
* configmaps (aka 'cm')
* daemonsets (aka 'ds')
* deployments (aka 'deploy')
* endpoints (aka 'ep')
* events (aka 'ev')
* horizontalpodautoscalers (aka 'hpa')
* ingresses (aka 'ing')
* jobs
* limitranges (aka 'limits')
* namespaces (aka 'ns')
* networkpolicies
* nodes (aka 'no')
* persistentvolumeclaims (aka 'pvc')
* persistentvolumes (aka 'pv')
* pods (aka 'po')
* poddisruptionbudgets (aka 'pdb')
* podsecuritypolicies (aka 'psp')
* podtemplates
* replicasets (aka 'rs')
* replicationcontrollers (aka 'rc')
* resourcequotas (aka 'quota')
* rolebindings
* roles
* secrets
* serviceaccounts (aka 'sa')
* services (aka 'svc')
* statefulsets
* storageclasses
* thirdpartyresources
`
)
// NewKubectlCommand creates the `kubectl` command and its nested children.
func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command {
// Parent command to which all subcommands are added.
cmds := &cobra.Command{
Use: "kubectl",
Short: "kubectl controls the Kubernetes cluster manager",
Long: templates.LongDesc(`
kubectl controls the Kubernetes cluster manager.
Find more information at https://github.com/kubernetes/kubernetes.`),
Run: runHelp,
BashCompletionFunction: bash_completion_func,
}
f.BindFlags(cmds.PersistentFlags())
f.BindExternalFlags(cmds.PersistentFlags())
// Sending in 'nil' for the getLanguageFn() results in using
// the LANG environment variable.
//
// TODO: Consider adding a flag or file preference for setting
// the language, instead of just loading from the LANG env. variable.
i18n.LoadTranslations("kubectl", nil)
// From this point and forward we get warnings on flags that contain "_" separators
cmds.SetGlobalNormalizationFunc(flag.WarnWordSepNormalizeFunc)
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, out, err),
},
},
{
Message: "Basic Commands (Intermediate):",
Commands: []*cobra.Command{
NewCmdGet(f, out, err),
NewCmdExplain(f, out, err),
NewCmdEdit(f, out, err),
NewCmdDelete(f, out, err),
},
},
{
Message: "Deploy Commands:",
Commands: []*cobra.Command{
rollout.NewCmdRollout(f, out, err),
NewCmdRollingUpdate(f, out),
NewCmdScale(f, out),
NewCmdAutoscale(f, out),
},
},
{
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),
},
},
{
Message: "Troubleshooting and Debugging Commands:",
Commands: []*cobra.Command{
NewCmdDescribe(f, out, err),
NewCmdLogs(f, out),
NewCmdAttach(f, in, out, err),
NewCmdExec(f, in, out, err),
NewCmdPortForward(f, out, err),
NewCmdProxy(f, out),
NewCmdCp(f, in, out, err),
},
},
{
Message: "Advanced Commands:",
Commands: []*cobra.Command{
NewCmdApply(f, out, err),
NewCmdPatch(f, out),
NewCmdReplace(f, out),
NewCmdConvert(f, out),
},
},
{
Message: "Settings Commands:",
Commands: []*cobra.Command{
NewCmdLabel(f, out),
NewCmdAnnotate(f, out),
NewCmdCompletion(f, out),
},
},
}
groups.Add(cmds)
filters := []string{
"options",
Deprecated("kubectl", "delete", cmds, NewCmdStop(f, out)),
}
templates.ActsAsRootCommand(cmds, filters, groups...)
if cmds.Flag("namespace") != nil {
if cmds.Flag("namespace").Annotations == nil {
cmds.Flag("namespace").Annotations = map[string][]string{}
}
cmds.Flag("namespace").Annotations[cobra.BashCompCustom] = append(
cmds.Flag("namespace").Annotations[cobra.BashCompCustom],
"__kubectl_get_namespaces",
)
}
cmds.AddCommand(cmdconfig.NewCmdConfig(clientcmd.NewDefaultPathOptions(), out, err))
cmds.AddCommand(NewCmdVersion(f, out))
cmds.AddCommand(NewCmdApiVersions(f, out))
cmds.AddCommand(NewCmdOptions(out))
return cmds
}
func runHelp(cmd *cobra.Command, args []string) {
cmd.Help()
}
func printDeprecationWarning(command, alias string) {
glog.Warningf("%s is DEPRECATED and will be removed in a future version. Use %s instead.", alias, command)
}
func Deprecated(baseName, to string, parent, cmd *cobra.Command) string {
cmd.Long = fmt.Sprintf("Deprecated: This command is deprecated, all its functionalities are covered by \"%s %s\"", baseName, to)
cmd.Short = fmt.Sprintf("Deprecated: %s", to)
parent.AddCommand(cmd)
return cmd.Name()
}

636
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd_test.go generated vendored Normal file
View file

@ -0,0 +1,636 @@
/*
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 (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"reflect"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake"
"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/util/strings"
)
func initTestErrorHandler(t *testing.T) {
cmdutil.BehaviorOnFatal(func(str string, code int) {
t.Errorf("Error running command (exit code %d): %s", code, str)
})
}
func defaultHeader() http.Header {
header := http.Header{}
header.Set("Content-Type", runtime.ContentTypeJSON)
return header
}
func defaultClientConfig() *restclient.Config {
return &restclient.Config{
APIPath: "/api",
ContentConfig: restclient.ContentConfig{
NegotiatedSerializer: api.Codecs,
ContentType: runtime.ContentTypeJSON,
GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion,
},
}
}
func defaultClientConfigForVersion(version *schema.GroupVersion) *restclient.Config {
return &restclient.Config{
APIPath: "/api",
ContentConfig: restclient.ContentConfig{
NegotiatedSerializer: api.Codecs,
ContentType: runtime.ContentTypeJSON,
GroupVersion: version,
},
}
}
type testPrinter struct {
Objects []runtime.Object
Err error
}
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
}
type testDescriber struct {
Name, Namespace string
Settings kubectl.DescriberSettings
Output string
Err error
}
func (t *testDescriber) Describe(namespace, name string, describerSettings kubectl.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))))
}
func policyObjBody(obj runtime.Object) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Policy.Codec(), obj))))
}
func bytesBody(bodyBytes []byte) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader(bodyBytes))
}
func stringBody(body string) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(body)))
}
// TODO(jlowdermilk): refactor the Factory so we can test client versions properly,
// with different client/server version skew scenarios.
// Verify that resource.RESTClients constructed from a factory respect mapping.APIVersion
//func TestClientVersions(t *testing.T) {
// f := cmdutil.NewFactory(nil)
//
// version := testapi.Default.Version()
// mapping := &meta.RESTMapping{
// APIVersion: version,
// }
// c, err := f.ClientForMapping(mapping)
// if err != nil {
// t.Errorf("unexpected error: %v", err)
// }
// client := c.(*client.RESTClient)
// if client.APIVersion() != version {
// t.Errorf("unexpected Client APIVersion: %s %v", client.APIVersion, client)
// }
//}
func Example_printReplicationControllerWithNamespace() {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = kubectl.NewHumanReadablePrinter(kubectl.PrintOptions{
WithNamespace: true,
ColumnLabels: []string{},
})
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
ctrl := &api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: "beep",
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: api.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "foo",
Image: "someimage",
},
},
},
},
},
Status: api.ReplicationControllerStatus{
Replicas: 1,
ReadyReplicas: 1,
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// NAMESPACE NAME DESIRED CURRENT READY AGE
// beep foo 1 1 1 10y
}
func Example_printMultiContainersReplicationControllerWithWide() {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = kubectl.NewHumanReadablePrinter(kubectl.PrintOptions{
Wide: true,
ColumnLabels: []string{},
})
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
ctrl := &api.ReplicationController{
ObjectMeta: api.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: api.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,
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// NAME DESIRED CURRENT READY AGE CONTAINER(S) IMAGE(S) SELECTOR
// foo 1 1 0 10y foo,foo2 someimage,someimage2 foo=bar
}
func Example_printReplicationController() {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = kubectl.NewHumanReadablePrinter(kubectl.PrintOptions{
ColumnLabels: []string{},
})
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
ctrl := &api.ReplicationController{
ObjectMeta: api.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: api.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,
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, 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() {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = kubectl.NewHumanReadablePrinter(kubectl.PrintOptions{
Wide: true,
ColumnLabels: []string{},
})
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
nodeName := "kubernetes-node-abcd"
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
pod := &api.Pod{
ObjectMeta: api.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",
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, 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() {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = kubectl.NewHumanReadablePrinter(kubectl.PrintOptions{
ShowLabels: true,
ColumnLabels: []string{},
})
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
nodeName := "kubernetes-node-abcd"
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
pod := &api.Pod{
ObjectMeta: api.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},
},
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, 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{
Items: []api.Pod{
{
ObjectMeta: api.ObjectMeta{
Name: "test1",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodPending,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
{
ObjectMeta: api.ObjectMeta{
Name: "test2",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodRunning,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
{
ObjectMeta: api.ObjectMeta{
Name: "test3",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodSucceeded,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
{
ObjectMeta: api.ObjectMeta{
Name: "test4",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodFailed,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
{
ObjectMeta: api.ObjectMeta{
Name: "test5",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodUnknown,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
}},
}
}
func Example_printPodHideTerminated() {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = kubectl.NewHumanReadablePrinter(kubectl.PrintOptions{
ColumnLabels: []string{},
})
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
podList := newAllPhasePodList()
// filter pods
filterFuncs := f.DefaultResourceFilterFunc()
filterOpts := f.DefaultResourceFilterOptions(cmd, false)
_, filteredPodList, errs := cmdutil.FilterResourceList(podList, filterFuncs, filterOpts)
if errs != nil {
fmt.Printf("Unexpected filter error: %v\n", errs)
}
for _, pod := range filteredPodList {
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, 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
// test5 1/2 Unknown 6 10y
}
func Example_printPodShowAll() {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = kubectl.NewHumanReadablePrinter(kubectl.PrintOptions{
ShowAll: true,
ColumnLabels: []string{},
})
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
podList := newAllPhasePodList()
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, 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_printServiceWithNamespacesAndLabels() {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Printer = kubectl.NewHumanReadablePrinter(kubectl.PrintOptions{
WithNamespace: true,
ColumnLabels: []string{"l1"},
})
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
svc := &api.ServiceList{
Items: []api.Service{
{
ObjectMeta: api.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",
},
Status: api.ServiceStatus{},
},
{
ObjectMeta: api.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",
},
Status: api.ServiceStatus{},
}},
}
ld := strings.NewLineDelimiter(os.Stdout, "|")
defer ld.Flush()
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, svc, ld)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// |NAMESPACE NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE L1|
// |ns1 svc1 10.1.1.1 <unknown> 53/UDP,53/TCP 10y value|
// |ns2 svc2 10.1.1.2 <unknown> 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)
if root.Parent() != nil {
t.Fatal("We expect the root command to be returned")
}
if root.GlobalNormalizationFunc() == nil {
t.Fatal("We expect that root command has a global normalization function")
}
if reflect.ValueOf(root.GlobalNormalizationFunc()).Pointer() != reflect.ValueOf(root.Flags().GetNormalizeFunc()).Pointer() {
t.Fatal("root command seems to have a wrong normalization function")
}
sub := root
for sub.HasSubCommands() {
sub = sub.Commands()[0]
}
// In case of failure of this test check this PR: spf13/cobra#110
if reflect.ValueOf(sub.Flags().GetNormalizeFunc()).Pointer() != reflect.ValueOf(root.Flags().GetNormalizeFunc()).Pointer() {
t.Fatal("child and root commands should have the same normalization functions")
}
}
func genResponseWithJsonEncodedBody(bodyStruct interface{}) (*http.Response, error) {
jsonBytes, err := json.Marshal(bodyStruct)
if err != nil {
return nil, err
}
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bytesBody(jsonBytes)}, nil
}

301
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/completion.go generated vendored Normal file
View file

@ -0,0 +1,301 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
const boilerPlate = `
# Copyright 2016 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
`
var (
completion_long = templates.LongDesc(`
Output shell completion code for the specified shell (bash or zsh).
The shell code must be evalutated to provide interactive
completion of kubectl commands. This can be done by sourcing it from
the .bash_profile.
Note: this requires the bash-completion framework, which is not installed
by default on Mac. This can be installed by using homebrew:
$ brew install bash-completion
Once installed, bash_completion must be evaluated. This can be done by adding the
following line to the .bash_profile
$ source $(brew --prefix)/etc/bash_completion
Note for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2`)
completion_example = templates.Examples(`
# Install bash completion on a Mac using homebrew
brew install bash-completion
printf "\n# Bash completion support\nsource $(brew --prefix)/etc/bash_completion\n" >> $HOME/.bash_profile
source $HOME/.bash_profile
# Load the kubectl completion code for bash into the current shell
source <(kubectl completion bash)
# Write bash completion code to a file and source if from .bash_profile
kubectl completion bash > ~/.kube/completion.bash.inc
printf "\n# Kubectl shell completion\nsource '$HOME/.kube/completion.bash.inc'\n" >> $HOME/.bash_profile
source $HOME/.bash_profile
# Load the kubectl completion code for zsh[1] into the current shell
source <(kubectl completion zsh)`)
)
var (
completion_shells = map[string]func(out io.Writer, cmd *cobra.Command) error{
"bash": runCompletionBash,
"zsh": runCompletionZsh,
}
)
func NewCmdCompletion(f cmdutil.Factory, out io.Writer) *cobra.Command {
shells := []string{}
for s := range completion_shells {
shells = append(shells, s)
}
cmd := &cobra.Command{
Use: "completion SHELL",
Short: "Output shell completion code for the specified shell (bash or zsh)",
Long: completion_long,
Example: completion_example,
Run: func(cmd *cobra.Command, args []string) {
err := RunCompletion(out, cmd, args)
cmdutil.CheckErr(err)
},
ValidArgs: shells,
}
return cmd
}
func RunCompletion(out io.Writer, cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return cmdutil.UsageError(cmd, "Shell not specified.")
}
if len(args) > 1 {
return cmdutil.UsageError(cmd, "Too many arguments. Expected only the shell type.")
}
run, found := completion_shells[args[0]]
if !found {
return cmdutil.UsageError(cmd, "Unsupported shell type %q.", args[0])
}
return run(out, cmd.Parent())
}
func runCompletionBash(out io.Writer, kubectl *cobra.Command) error {
_, err := out.Write([]byte(boilerPlate))
if err != nil {
return err
}
return kubectl.GenBashCompletion(out)
}
func runCompletionZsh(out io.Writer, kubectl *cobra.Command) error {
_, err := out.Write([]byte(boilerPlate))
if err != nil {
return err
}
zsh_initialization := `
__kubectl_bash_source() {
alias shopt=':'
alias _expand=_bash_expand
alias _complete=_bash_comp
emulate -L sh
setopt kshglob noshglob braceexpand
source "$@"
}
__kubectl_type() {
# -t is not supported by zsh
if [ "$1" == "-t" ]; then
shift
# fake Bash 4 to disable "complete -o nospace". Instead
# "compopt +-o nospace" is used in the code to toggle trailing
# spaces. We don't support that, but leave trailing spaces on
# all the time
if [ "$1" = "__kubectl_compopt" ]; then
echo builtin
return 0
fi
fi
type "$@"
}
__kubectl_compgen() {
local completions w
completions=( $(compgen "$@") ) || return $?
# filter by given word as prefix
while [[ "$1" = -* && "$1" != -- ]]; do
shift
shift
done
if [[ "$1" == -- ]]; then
shift
fi
for w in "${completions[@]}"; do
if [[ "${w}" = "$1"* ]]; then
echo "${w}"
fi
done
}
__kubectl_compopt() {
true # don't do anything. Not supported by bashcompinit in zsh
}
__kubectl_declare() {
if [ "$1" == "-F" ]; then
whence -w "$@"
else
builtin declare "$@"
fi
}
__kubectl_ltrim_colon_completions()
{
if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
# Remove colon-word prefix from COMPREPLY items
local colon_word=${1%${1##*:}}
local i=${#COMPREPLY[*]}
while [[ $((--i)) -ge 0 ]]; do
COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
done
fi
}
__kubectl_get_comp_words_by_ref() {
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[${COMP_CWORD}-1]}"
words=("${COMP_WORDS[@]}")
cword=("${COMP_CWORD[@]}")
}
__kubectl_filedir() {
local RET OLD_IFS w qw
__debug "_filedir $@ cur=$cur"
if [[ "$1" = \~* ]]; then
# somehow does not work. Maybe, zsh does not call this at all
eval echo "$1"
return 0
fi
OLD_IFS="$IFS"
IFS=$'\n'
if [ "$1" = "-d" ]; then
shift
RET=( $(compgen -d) )
else
RET=( $(compgen -f) )
fi
IFS="$OLD_IFS"
IFS="," __debug "RET=${RET[@]} len=${#RET[@]}"
for w in ${RET[@]}; do
if [[ ! "${w}" = "${cur}"* ]]; then
continue
fi
if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then
qw="$(__kubectl_quote "${w}")"
if [ -d "${w}" ]; then
COMPREPLY+=("${qw}/")
else
COMPREPLY+=("${qw}")
fi
fi
done
}
__kubectl_quote() {
if [[ $1 == \'* || $1 == \"* ]]; then
# Leave out first character
printf %q "${1:1}"
else
printf %q "$1"
fi
}
autoload -U +X bashcompinit && bashcompinit
# use word boundary patterns for BSD or GNU sed
LWORD='[[:<:]]'
RWORD='[[:>:]]'
if sed --help 2>&1 | grep -q GNU; then
LWORD='\<'
RWORD='\>'
fi
__kubectl_convert_bash_to_zsh() {
sed \
-e 's/declare -F/whence -w/' \
-e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \
-e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \
-e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \
-e "s/${LWORD}_filedir${RWORD}/__kubectl_filedir/g" \
-e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__kubectl_get_comp_words_by_ref/g" \
-e "s/${LWORD}__ltrim_colon_completions${RWORD}/__kubectl_ltrim_colon_completions/g" \
-e "s/${LWORD}compgen${RWORD}/__kubectl_compgen/g" \
-e "s/${LWORD}compopt${RWORD}/__kubectl_compopt/g" \
-e "s/${LWORD}declare${RWORD}/__kubectl_declare/g" \
-e "s/\\\$(type${RWORD}/\$(__kubectl_type/g" \
<<'BASH_COMPLETION_EOF'
`
out.Write([]byte(zsh_initialization))
buf := new(bytes.Buffer)
kubectl.GenBashCompletion(buf)
out.Write(buf.Bytes())
zsh_tail := `
BASH_COMPLETION_EOF
}
__kubectl_bash_source <(__kubectl_convert_bash_to_zsh)
`
out.Write([]byte(zsh_tail))
return nil
}

79
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/BUILD generated vendored Normal file
View file

@ -0,0 +1,79 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"config.go",
"create_authinfo.go",
"create_cluster.go",
"create_context.go",
"current_context.go",
"delete_cluster.go",
"delete_context.go",
"get_clusters.go",
"get_contexts.go",
"navigation_step_parser.go",
"set.go",
"unset.go",
"use_context.go",
"view.go",
],
tags = ["automanaged"],
deps = [
"//pkg/client/unversioned/clientcmd:go_default_library",
"//pkg/client/unversioned/clientcmd/api:go_default_library",
"//pkg/client/unversioned/clientcmd/api/latest:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/util/flag:go_default_library",
"//vendor:github.com/spf13/cobra",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
],
)
go_test(
name = "go_default_test",
srcs = [
"config_test.go",
"create_authinfo_test.go",
"current_context_test.go",
"delete_cluster_test.go",
"delete_context_test.go",
"get_clusters_test.go",
"get_contexts_test.go",
"navigation_step_parser_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/client/unversioned/clientcmd:go_default_library",
"//pkg/client/unversioned/clientcmd/api:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/util/flag:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/util/diff",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,81 @@
/*
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 config
import (
"io"
"path"
"strconv"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
// NewCmdConfig creates a command object for the "config" action, and adds all child commands to it.
func NewCmdConfig(pathOptions *clientcmd.PathOptions, out, errOut io.Writer) *cobra.Command {
if len(pathOptions.ExplicitFileFlag) == 0 {
pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag
}
cmd := &cobra.Command{
Use: "config SUBCOMMAND",
Short: "Modify kubeconfig files",
Long: templates.LongDesc(`
Modify kubeconfig files using subcommands like "kubectl config set current-context my-context"
The loading order follows these rules:
1. If the --` + pathOptions.ExplicitFileFlag + ` flag is set, then only that file is loaded. The flag may only be set once and no merging takes place.
2. If $` + pathOptions.EnvVar + ` environment variable is set, then it is used a list of paths (normal path delimitting rules for your system). These paths are merged. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list.
3. Otherwise, ` + path.Join("${HOME}", pathOptions.GlobalFileSubpath) + ` is used and no merging takes place.`),
Run: cmdutil.DefaultSubCommandRun(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(out, 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, pathOptions))
return cmd
}
func toBool(propertyValue string) (bool, error) {
boolValue := false
if len(propertyValue) != 0 {
var err error
boolValue, err = strconv.ParseBool(propertyValue)
if err != nil {
return false, err
}
}
return boolValue, nil
}

View file

@ -0,0 +1,950 @@
/*
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 config
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path"
"reflect"
"strings"
"testing"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
func newRedFederalCowHammerConfig() clientcmdapi.Config {
return clientcmdapi.Config{
AuthInfos: map[string]*clientcmdapi.AuthInfo{
"red-user": {Token: "red-token"}},
Clusters: map[string]*clientcmdapi.Cluster{
"cow-cluster": {Server: "http://cow.org:8080"}},
Contexts: map[string]*clientcmdapi.Context{
"federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster"}},
CurrentContext: "federal-context",
}
}
func Example_view() {
expectedConfig := newRedFederalCowHammerConfig()
test := configCommandTest{
args: []string{"view"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
output := test.run(nil)
fmt.Printf("%v", output)
// Output:
// apiVersion: v1
// clusters:
// - cluster:
// server: http://cow.org:8080
// name: cow-cluster
// contexts:
// - context:
// cluster: cow-cluster
// user: red-user
// name: federal-context
// current-context: federal-context
// kind: Config
// preferences: {}
// users:
// - name: red-user
// user:
// token: red-token
}
func TestCurrentContext(t *testing.T) {
startingConfig := newRedFederalCowHammerConfig()
test := configCommandTest{
args: []string{"current-context"},
startingConfig: startingConfig,
expectedConfig: startingConfig,
expectedOutputs: []string{startingConfig.CurrentContext},
}
test.run(t)
}
func TestSetCurrentContext(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
startingConfig := newRedFederalCowHammerConfig()
newContextName := "the-new-context"
startingConfig.Contexts[newContextName] = clientcmdapi.NewContext()
expectedConfig.Contexts[newContextName] = clientcmdapi.NewContext()
expectedConfig.CurrentContext = newContextName
test := configCommandTest{
args: []string{"use-context", "the-new-context"},
startingConfig: startingConfig,
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestSetNonExistentContext(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
test := configCommandTest{
args: []string{"use-context", "non-existent-config"},
startingConfig: expectedConfig,
expectedConfig: expectedConfig,
}
func() {
defer func() {
// Restore cmdutil behavior.
cmdutil.DefaultBehaviorOnFatal()
}()
// Check exit code.
cmdutil.BehaviorOnFatal(func(e string, code int) {
if code != 1 {
t.Errorf("The exit code is %d, expected 1", code)
}
expectedOutputs := []string{`no context exists with the name: "non-existent-config"`}
test.checkOutput(e, expectedOutputs, t)
})
test.run(t)
}()
}
func TestSetIntoExistingStruct(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.AuthInfos["red-user"].Password = "new-path-value"
test := configCommandTest{
args: []string{"set", "users.red-user.password", "new-path-value"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestSetWithPathPrefixIntoExistingStruct(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.Clusters["cow-cluster"].Server = "http://cow.org:8080/foo/baz"
test := configCommandTest{
args: []string{"set", "clusters.cow-cluster.server", "http://cow.org:8080/foo/baz"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
dc := clientcmd.NewDefaultClientConfig(expectedConfig, &clientcmd.ConfigOverrides{})
dcc, err := dc.ClientConfig()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expectedHost := "http://cow.org:8080/foo/baz"
if expectedHost != dcc.Host {
t.Fatalf("expected client.Config.Host = %q instead of %q", expectedHost, dcc.Host)
}
}
func TestUnsetStruct(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
delete(expectedConfig.AuthInfos, "red-user")
test := configCommandTest{
args: []string{"unset", "users.red-user"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestUnsetField(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.AuthInfos["red-user"] = clientcmdapi.NewAuthInfo()
test := configCommandTest{
args: []string{"unset", "users.red-user.token"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestSetIntoNewStruct(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
cluster := clientcmdapi.NewCluster()
cluster.Server = "new-server-value"
expectedConfig.Clusters["big-cluster"] = cluster
test := configCommandTest{
args: []string{"set", "clusters.big-cluster.server", "new-server-value"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestSetBoolean(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
cluster := clientcmdapi.NewCluster()
cluster.InsecureSkipTLSVerify = true
expectedConfig.Clusters["big-cluster"] = cluster
test := configCommandTest{
args: []string{"set", "clusters.big-cluster.insecure-skip-tls-verify", "true"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestSetIntoNewConfig(t *testing.T) {
expectedConfig := *clientcmdapi.NewConfig()
context := clientcmdapi.NewContext()
context.AuthInfo = "fake-user"
expectedConfig.Contexts["new-context"] = context
test := configCommandTest{
args: []string{"set", "contexts.new-context.user", "fake-user"},
startingConfig: *clientcmdapi.NewConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestNewEmptyAuth(t *testing.T) {
expectedConfig := *clientcmdapi.NewConfig()
expectedConfig.AuthInfos["the-user-name"] = clientcmdapi.NewAuthInfo()
test := configCommandTest{
args: []string{"set-credentials", "the-user-name"},
startingConfig: *clientcmdapi.NewConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestAdditionalAuth(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
authInfo := clientcmdapi.NewAuthInfo()
authInfo.Token = "token"
expectedConfig.AuthInfos["another-user"] = authInfo
test := configCommandTest{
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagBearerToken + "=token"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestEmbedClientCert(t *testing.T) {
fakeCertFile, _ := ioutil.TempFile("", "")
defer os.Remove(fakeCertFile.Name())
fakeData := []byte("fake-data")
ioutil.WriteFile(fakeCertFile.Name(), fakeData, 0600)
expectedConfig := newRedFederalCowHammerConfig()
authInfo := clientcmdapi.NewAuthInfo()
authInfo.ClientCertificateData = fakeData
expectedConfig.AuthInfos["another-user"] = authInfo
test := configCommandTest{
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=" + fakeCertFile.Name(), "--" + clientcmd.FlagEmbedCerts + "=true"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestEmbedClientKey(t *testing.T) {
fakeKeyFile, _ := ioutil.TempFile("", "")
defer os.Remove(fakeKeyFile.Name())
fakeData := []byte("fake-data")
ioutil.WriteFile(fakeKeyFile.Name(), fakeData, 0600)
expectedConfig := newRedFederalCowHammerConfig()
authInfo := clientcmdapi.NewAuthInfo()
authInfo.ClientKeyData = fakeData
expectedConfig.AuthInfos["another-user"] = authInfo
test := configCommandTest{
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagKeyFile + "=" + fakeKeyFile.Name(), "--" + clientcmd.FlagEmbedCerts + "=true"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestEmbedNoKeyOrCertDisallowed(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
test := configCommandTest{
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagEmbedCerts + "=true"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
func() {
defer func() {
// Restore cmdutil behavior.
cmdutil.DefaultBehaviorOnFatal()
}()
// Check exit code.
cmdutil.BehaviorOnFatal(func(e string, code int) {
if code != 1 {
t.Errorf("The exit code is %d, expected 1", code)
}
expectedOutputs := []string{"--client-certificate", "--client-key", "embed"}
test.checkOutput(e, expectedOutputs, t)
})
test.run(t)
}()
}
func TestEmptyTokenAndCertAllowed(t *testing.T) {
fakeCertFile, _ := ioutil.TempFile("", "cert-file")
defer os.Remove(fakeCertFile.Name())
expectedConfig := newRedFederalCowHammerConfig()
authInfo := clientcmdapi.NewAuthInfo()
authInfo.ClientCertificate = path.Base(fakeCertFile.Name())
expectedConfig.AuthInfos["another-user"] = authInfo
test := configCommandTest{
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=" + fakeCertFile.Name(), "--" + clientcmd.FlagBearerToken + "="},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestTokenAndCertAllowed(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
authInfo := clientcmdapi.NewAuthInfo()
authInfo.Token = "token"
authInfo.ClientCertificate = "/cert-file"
expectedConfig.AuthInfos["another-user"] = authInfo
test := configCommandTest{
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=/cert-file", "--" + clientcmd.FlagBearerToken + "=token"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestTokenAndBasicDisallowed(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
test := configCommandTest{
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagUsername + "=myuser", "--" + clientcmd.FlagBearerToken + "=token"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
func() {
defer func() {
// Restore cmdutil behavior.
cmdutil.DefaultBehaviorOnFatal()
}()
// Check exit code.
cmdutil.BehaviorOnFatal(func(e string, code int) {
if code != 1 {
t.Errorf("The exit code is %d, expected 1", code)
}
expectedOutputs := []string{"--token", "--username"}
test.checkOutput(e, expectedOutputs, t)
})
test.run(t)
}()
}
func TestBasicClearsToken(t *testing.T) {
authInfoWithToken := clientcmdapi.NewAuthInfo()
authInfoWithToken.Token = "token"
authInfoWithBasic := clientcmdapi.NewAuthInfo()
authInfoWithBasic.Username = "myuser"
authInfoWithBasic.Password = "mypass"
startingConfig := newRedFederalCowHammerConfig()
startingConfig.AuthInfos["another-user"] = authInfoWithToken
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.AuthInfos["another-user"] = authInfoWithBasic
test := configCommandTest{
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagUsername + "=myuser", "--" + clientcmd.FlagPassword + "=mypass"},
startingConfig: startingConfig,
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestTokenClearsBasic(t *testing.T) {
authInfoWithBasic := clientcmdapi.NewAuthInfo()
authInfoWithBasic.Username = "myuser"
authInfoWithBasic.Password = "mypass"
authInfoWithToken := clientcmdapi.NewAuthInfo()
authInfoWithToken.Token = "token"
startingConfig := newRedFederalCowHammerConfig()
startingConfig.AuthInfos["another-user"] = authInfoWithBasic
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.AuthInfos["another-user"] = authInfoWithToken
test := configCommandTest{
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagBearerToken + "=token"},
startingConfig: startingConfig,
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestTokenLeavesCert(t *testing.T) {
authInfoWithCerts := clientcmdapi.NewAuthInfo()
authInfoWithCerts.ClientCertificate = "cert"
authInfoWithCerts.ClientCertificateData = []byte("certdata")
authInfoWithCerts.ClientKey = "key"
authInfoWithCerts.ClientKeyData = []byte("keydata")
authInfoWithTokenAndCerts := clientcmdapi.NewAuthInfo()
authInfoWithTokenAndCerts.Token = "token"
authInfoWithTokenAndCerts.ClientCertificate = "cert"
authInfoWithTokenAndCerts.ClientCertificateData = []byte("certdata")
authInfoWithTokenAndCerts.ClientKey = "key"
authInfoWithTokenAndCerts.ClientKeyData = []byte("keydata")
startingConfig := newRedFederalCowHammerConfig()
startingConfig.AuthInfos["another-user"] = authInfoWithCerts
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.AuthInfos["another-user"] = authInfoWithTokenAndCerts
test := configCommandTest{
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagBearerToken + "=token"},
startingConfig: startingConfig,
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestCertLeavesToken(t *testing.T) {
authInfoWithToken := clientcmdapi.NewAuthInfo()
authInfoWithToken.Token = "token"
authInfoWithTokenAndCerts := clientcmdapi.NewAuthInfo()
authInfoWithTokenAndCerts.Token = "token"
authInfoWithTokenAndCerts.ClientCertificate = "/cert"
authInfoWithTokenAndCerts.ClientKey = "/key"
startingConfig := newRedFederalCowHammerConfig()
startingConfig.AuthInfos["another-user"] = authInfoWithToken
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.AuthInfos["another-user"] = authInfoWithTokenAndCerts
test := configCommandTest{
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=/cert", "--" + clientcmd.FlagKeyFile + "=/key"},
startingConfig: startingConfig,
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestSetBytesBad(t *testing.T) {
startingConfig := newRedFederalCowHammerConfig()
startingConfig.Clusters["another-cluster"] = clientcmdapi.NewCluster()
test := configCommandTest{
args: []string{"set", "clusters.another-cluster.certificate-authority-data", "cadata"},
startingConfig: startingConfig,
expectedConfig: startingConfig,
}
func() {
defer func() {
// Restore cmdutil behavior.
cmdutil.DefaultBehaviorOnFatal()
}()
// Check exit code.
cmdutil.BehaviorOnFatal(func(e string, code int) {
if code != 1 {
t.Errorf("The exit code is %d, expected 1", code)
}
})
test.run(t)
}()
}
func TestSetBytes(t *testing.T) {
clusterInfoWithCAData := clientcmdapi.NewCluster()
clusterInfoWithCAData.CertificateAuthorityData = []byte("cadata")
startingConfig := newRedFederalCowHammerConfig()
startingConfig.Clusters["another-cluster"] = clientcmdapi.NewCluster()
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.Clusters["another-cluster"] = clusterInfoWithCAData
test := configCommandTest{
args: []string{"set", "clusters.another-cluster.certificate-authority-data", "cadata", "--set-raw-bytes"},
startingConfig: startingConfig,
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestSetBase64Bytes(t *testing.T) {
clusterInfoWithCAData := clientcmdapi.NewCluster()
clusterInfoWithCAData.CertificateAuthorityData = []byte("cadata")
startingConfig := newRedFederalCowHammerConfig()
startingConfig.Clusters["another-cluster"] = clientcmdapi.NewCluster()
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.Clusters["another-cluster"] = clusterInfoWithCAData
test := configCommandTest{
args: []string{"set", "clusters.another-cluster.certificate-authority-data", "Y2FkYXRh"},
startingConfig: startingConfig,
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestUnsetBytes(t *testing.T) {
clusterInfoWithCAData := clientcmdapi.NewCluster()
clusterInfoWithCAData.CertificateAuthorityData = []byte("cadata")
startingConfig := newRedFederalCowHammerConfig()
startingConfig.Clusters["another-cluster"] = clusterInfoWithCAData
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.Clusters["another-cluster"] = clientcmdapi.NewCluster()
test := configCommandTest{
args: []string{"unset", "clusters.another-cluster.certificate-authority-data"},
startingConfig: startingConfig,
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestCAClearsInsecure(t *testing.T) {
fakeCAFile, _ := ioutil.TempFile("", "ca-file")
defer os.Remove(fakeCAFile.Name())
clusterInfoWithInsecure := clientcmdapi.NewCluster()
clusterInfoWithInsecure.InsecureSkipTLSVerify = true
clusterInfoWithCA := clientcmdapi.NewCluster()
clusterInfoWithCA.CertificateAuthority = path.Base(fakeCAFile.Name())
startingConfig := newRedFederalCowHammerConfig()
startingConfig.Clusters["another-cluster"] = clusterInfoWithInsecure
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.Clusters["another-cluster"] = clusterInfoWithCA
test := configCommandTest{
args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=" + fakeCAFile.Name()},
startingConfig: startingConfig,
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestCAClearsCAData(t *testing.T) {
clusterInfoWithCAData := clientcmdapi.NewCluster()
clusterInfoWithCAData.CertificateAuthorityData = []byte("cadata")
clusterInfoWithCA := clientcmdapi.NewCluster()
clusterInfoWithCA.CertificateAuthority = "/cafile"
startingConfig := newRedFederalCowHammerConfig()
startingConfig.Clusters["another-cluster"] = clusterInfoWithCAData
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.Clusters["another-cluster"] = clusterInfoWithCA
test := configCommandTest{
args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=/cafile", "--" + clientcmd.FlagInsecure + "=false"},
startingConfig: startingConfig,
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestInsecureClearsCA(t *testing.T) {
clusterInfoWithInsecure := clientcmdapi.NewCluster()
clusterInfoWithInsecure.InsecureSkipTLSVerify = true
clusterInfoWithCA := clientcmdapi.NewCluster()
clusterInfoWithCA.CertificateAuthority = "cafile"
clusterInfoWithCA.CertificateAuthorityData = []byte("cadata")
startingConfig := newRedFederalCowHammerConfig()
startingConfig.Clusters["another-cluster"] = clusterInfoWithCA
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.Clusters["another-cluster"] = clusterInfoWithInsecure
test := configCommandTest{
args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagInsecure + "=true"},
startingConfig: startingConfig,
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestCADataClearsCA(t *testing.T) {
fakeCAFile, _ := ioutil.TempFile("", "")
defer os.Remove(fakeCAFile.Name())
fakeData := []byte("cadata")
ioutil.WriteFile(fakeCAFile.Name(), fakeData, 0600)
clusterInfoWithCAData := clientcmdapi.NewCluster()
clusterInfoWithCAData.CertificateAuthorityData = fakeData
clusterInfoWithCA := clientcmdapi.NewCluster()
clusterInfoWithCA.CertificateAuthority = "cafile"
startingConfig := newRedFederalCowHammerConfig()
startingConfig.Clusters["another-cluster"] = clusterInfoWithCA
expectedConfig := newRedFederalCowHammerConfig()
expectedConfig.Clusters["another-cluster"] = clusterInfoWithCAData
test := configCommandTest{
args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=" + fakeCAFile.Name(), "--" + clientcmd.FlagEmbedCerts + "=true"},
startingConfig: startingConfig,
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestEmbedNoCADisallowed(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
test := configCommandTest{
args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagEmbedCerts + "=true"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
func() {
defer func() {
// Restore cmdutil behavior.
cmdutil.DefaultBehaviorOnFatal()
}()
// Check exit code.
cmdutil.BehaviorOnFatal(func(e string, code int) {
if code != 1 {
t.Errorf("The exit code is %d, expected 1", code)
}
expectedOutputs := []string{"--certificate-authority", "embed"}
test.checkOutput(e, expectedOutputs, t)
})
test.run(t)
}()
}
func TestCAAndInsecureDisallowed(t *testing.T) {
test := configCommandTest{
args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=cafile", "--" + clientcmd.FlagInsecure + "=true"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: newRedFederalCowHammerConfig(),
}
func() {
defer func() {
// Restore cmdutil behavior.
cmdutil.DefaultBehaviorOnFatal()
}()
// Check exit code.
cmdutil.BehaviorOnFatal(func(e string, code int) {
if code != 1 {
t.Errorf("The exit code is %d, expected 1", code)
}
expectedOutputs := []string{"certificate", "insecure"}
test.checkOutput(e, expectedOutputs, t)
})
test.run(t)
}()
}
func TestMergeExistingAuth(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
authInfo := expectedConfig.AuthInfos["red-user"]
authInfo.ClientKey = "/key"
expectedConfig.AuthInfos["red-user"] = authInfo
test := configCommandTest{
args: []string{"set-credentials", "red-user", "--" + clientcmd.FlagKeyFile + "=/key"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestNewEmptyCluster(t *testing.T) {
expectedConfig := *clientcmdapi.NewConfig()
expectedConfig.Clusters["new-cluster"] = clientcmdapi.NewCluster()
test := configCommandTest{
args: []string{"set-cluster", "new-cluster"},
startingConfig: *clientcmdapi.NewConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestAdditionalCluster(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
cluster := clientcmdapi.NewCluster()
cluster.CertificateAuthority = "/ca-location"
cluster.InsecureSkipTLSVerify = false
cluster.Server = "serverlocation"
expectedConfig.Clusters["different-cluster"] = cluster
test := configCommandTest{
args: []string{"set-cluster", "different-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation", "--" + clientcmd.FlagInsecure + "=false", "--" + clientcmd.FlagCAFile + "=/ca-location"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestOverwriteExistingCluster(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
cluster := clientcmdapi.NewCluster()
cluster.Server = "serverlocation"
expectedConfig.Clusters["cow-cluster"] = cluster
test := configCommandTest{
args: []string{"set-cluster", "cow-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestNewEmptyContext(t *testing.T) {
expectedConfig := *clientcmdapi.NewConfig()
expectedConfig.Contexts["new-context"] = clientcmdapi.NewContext()
test := configCommandTest{
args: []string{"set-context", "new-context"},
startingConfig: *clientcmdapi.NewConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestAdditionalContext(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
context := clientcmdapi.NewContext()
context.Cluster = "some-cluster"
context.AuthInfo = "some-user"
context.Namespace = "different-namespace"
expectedConfig.Contexts["different-context"] = context
test := configCommandTest{
args: []string{"set-context", "different-context", "--" + clientcmd.FlagClusterName + "=some-cluster", "--" + clientcmd.FlagAuthInfoName + "=some-user", "--" + clientcmd.FlagNamespace + "=different-namespace"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestMergeExistingContext(t *testing.T) {
expectedConfig := newRedFederalCowHammerConfig()
context := expectedConfig.Contexts["federal-context"]
context.Namespace = "hammer"
expectedConfig.Contexts["federal-context"] = context
test := configCommandTest{
args: []string{"set-context", "federal-context", "--" + clientcmd.FlagNamespace + "=hammer"},
startingConfig: newRedFederalCowHammerConfig(),
expectedConfig: expectedConfig,
}
test.run(t)
}
func TestToBool(t *testing.T) {
type test struct {
in string
out bool
err string
}
tests := []test{
{"", false, ""},
{"true", true, ""},
{"on", false, `strconv.ParseBool: parsing "on": invalid syntax`},
}
for _, curr := range tests {
b, err := toBool(curr.in)
if (len(curr.err) != 0) && err == nil {
t.Errorf("Expected error: %v, but got nil", curr.err)
}
if (len(curr.err) == 0) && err != nil {
t.Errorf("Unexpected error: %v", err)
}
if (err != nil) && (err.Error() != curr.err) {
t.Errorf("Expected %v, got %v", curr.err, err)
}
if b != curr.out {
t.Errorf("Expected %v, got %v", curr.out, b)
}
}
}
func testConfigCommand(args []string, startingConfig clientcmdapi.Config, t *testing.T) (string, clientcmdapi.Config) {
fakeKubeFile, _ := ioutil.TempFile("", "")
defer os.Remove(fakeKubeFile.Name())
err := clientcmd.WriteToFile(startingConfig, fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
argsToUse := make([]string, 0, 2+len(args))
argsToUse = append(argsToUse, "--kubeconfig="+fakeKubeFile.Name())
argsToUse = append(argsToUse, args...)
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfig(clientcmd.NewDefaultPathOptions(), buf, buf)
cmd.SetArgs(argsToUse)
cmd.Execute()
// outBytes, _ := ioutil.ReadFile(fakeKubeFile.Name())
config := clientcmd.GetConfigFromFileOrDie(fakeKubeFile.Name())
return buf.String(), *config
}
type configCommandTest struct {
args []string
startingConfig clientcmdapi.Config
expectedConfig clientcmdapi.Config
expectedOutputs []string
}
func (test configCommandTest) checkOutput(out string, expectedOutputs []string, t *testing.T) {
for _, expectedOutput := range expectedOutputs {
if !strings.Contains(out, expectedOutput) {
t.Errorf("expected '%s' in output, got '%s'", expectedOutput, out)
}
}
}
func (test configCommandTest) run(t *testing.T) string {
out, actualConfig := testConfigCommand(test.args, test.startingConfig, t)
testSetNilMapsToEmpties(reflect.ValueOf(&test.expectedConfig))
testSetNilMapsToEmpties(reflect.ValueOf(&actualConfig))
testClearLocationOfOrigin(&actualConfig)
if !api.Semantic.DeepEqual(test.expectedConfig, actualConfig) {
t.Errorf("diff: %v", diff.ObjectDiff(test.expectedConfig, actualConfig))
t.Errorf("expected: %#v\n actual: %#v", test.expectedConfig, actualConfig)
}
test.checkOutput(out, test.expectedOutputs, t)
return out
}
func testClearLocationOfOrigin(config *clientcmdapi.Config) {
for key, obj := range config.AuthInfos {
obj.LocationOfOrigin = ""
config.AuthInfos[key] = obj
}
for key, obj := range config.Clusters {
obj.LocationOfOrigin = ""
config.Clusters[key] = obj
}
for key, obj := range config.Contexts {
obj.LocationOfOrigin = ""
config.Contexts[key] = obj
}
}
func testSetNilMapsToEmpties(curr reflect.Value) {
actualCurrValue := curr
if curr.Kind() == reflect.Ptr {
actualCurrValue = curr.Elem()
}
switch actualCurrValue.Kind() {
case reflect.Map:
for _, mapKey := range actualCurrValue.MapKeys() {
currMapValue := actualCurrValue.MapIndex(mapKey)
testSetNilMapsToEmpties(currMapValue)
}
case reflect.Struct:
for fieldIndex := 0; fieldIndex < actualCurrValue.NumField(); fieldIndex++ {
currFieldValue := actualCurrValue.Field(fieldIndex)
if currFieldValue.Kind() == reflect.Map && currFieldValue.IsNil() {
newValue := reflect.MakeMap(currFieldValue.Type())
currFieldValue.Set(newValue)
} else {
testSetNilMapsToEmpties(currFieldValue.Addr())
}
}
}
}

View file

@ -0,0 +1,299 @@
/*
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 config
import (
"errors"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/util/flag"
)
type createAuthInfoOptions struct {
configAccess clientcmd.ConfigAccess
name string
authPath flag.StringFlag
clientCertificate flag.StringFlag
clientKey flag.StringFlag
token flag.StringFlag
username flag.StringFlag
password flag.StringFlag
embedCertData flag.Tristate
authProvider flag.StringFlag
authProviderArgs map[string]string
authProviderArgsToRemove []string
}
const (
flagAuthProvider = "auth-provider"
flagAuthProviderArg = "auth-provider-arg"
)
var (
create_authinfo_long = fmt.Sprintf(templates.LongDesc(`
Sets a user entry in kubeconfig
Specifying a name that already exists will merge new fields on top of existing values.
Client-certificate flags:
--%v=certfile --%v=keyfile
Bearer token flags:
--%v=bearer_token
Basic auth flags:
--%v=basic_user --%v=basic_password
Bearer token and basic auth are mutually exclusive.`), clientcmd.FlagCertFile, clientcmd.FlagKeyFile, clientcmd.FlagBearerToken, clientcmd.FlagUsername, clientcmd.FlagPassword)
create_authinfo_example = templates.Examples(`
# Set only the "client-key" field on the "cluster-admin"
# entry, without touching other values:
kubectl config set-credentials cluster-admin --client-key=~/.kube/admin.key
# Set basic auth for the "cluster-admin" entry
kubectl config set-credentials cluster-admin --username=admin --password=uXFGweU9l35qcif
# Embed client certificate data in the "cluster-admin" entry
kubectl config set-credentials cluster-admin --client-certificate=~/.kube/admin.crt --embed-certs=true
# Enable the Google Compute Platform auth provider for the "cluster-admin" entry
kubectl config set-credentials cluster-admin --auth-provider=gcp
# Enable the OpenID Connect auth provider for the "cluster-admin" entry with additional args
kubectl config set-credentials cluster-admin --auth-provider=oidc --auth-provider-arg=client-id=foo --auth-provider-arg=client-secret=bar
# Remove the "client-secret" config value for the OpenID Connect auth provider for the "cluster-admin" entry
kubectl config set-credentials cluster-admin --auth-provider=oidc --auth-provider-arg=client-secret-`)
)
func NewCmdConfigSetAuthInfo(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &createAuthInfoOptions{configAccess: configAccess}
return newCmdConfigSetAuthInfo(out, options)
}
func newCmdConfigSetAuthInfo(out io.Writer, options *createAuthInfoOptions) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("set-credentials NAME [--%v=path/to/certfile] [--%v=path/to/keyfile] [--%v=bearer_token] [--%v=basic_user] [--%v=basic_password] [--%v=provider_name] [--%v=key=value]", clientcmd.FlagCertFile, clientcmd.FlagKeyFile, clientcmd.FlagBearerToken, clientcmd.FlagUsername, clientcmd.FlagPassword, flagAuthProvider, flagAuthProviderArg),
Short: "Sets a user entry in kubeconfig",
Long: create_authinfo_long,
Example: create_authinfo_example,
Run: func(cmd *cobra.Command, args []string) {
if !options.complete(cmd, out) {
cmd.Help()
return
}
cmdutil.CheckErr(options.run())
fmt.Fprintf(out, "User %q set.\n", options.name)
},
}
cmd.Flags().Var(&options.clientCertificate, clientcmd.FlagCertFile, "path to "+clientcmd.FlagCertFile+" file for the user entry in kubeconfig")
cmd.MarkFlagFilename(clientcmd.FlagCertFile)
cmd.Flags().Var(&options.clientKey, clientcmd.FlagKeyFile, "path to "+clientcmd.FlagKeyFile+" file for the user entry in kubeconfig")
cmd.MarkFlagFilename(clientcmd.FlagKeyFile)
cmd.Flags().Var(&options.token, clientcmd.FlagBearerToken, clientcmd.FlagBearerToken+" for the user entry in kubeconfig")
cmd.Flags().Var(&options.username, clientcmd.FlagUsername, clientcmd.FlagUsername+" for the user entry in kubeconfig")
cmd.Flags().Var(&options.password, clientcmd.FlagPassword, clientcmd.FlagPassword+" for the user entry in kubeconfig")
cmd.Flags().Var(&options.authProvider, flagAuthProvider, "auth provider for the user entry in kubeconfig")
cmd.Flags().StringSlice(flagAuthProviderArg, nil, "'key=value' arugments for the auth provider")
f := cmd.Flags().VarPF(&options.embedCertData, clientcmd.FlagEmbedCerts, "", "embed client cert/key for the user entry in kubeconfig")
f.NoOptDefVal = "true"
return cmd
}
func (o createAuthInfoOptions) run() error {
err := o.validate()
if err != nil {
return err
}
config, err := o.configAccess.GetStartingConfig()
if err != nil {
return err
}
startingStanza, exists := config.AuthInfos[o.name]
if !exists {
startingStanza = clientcmdapi.NewAuthInfo()
}
authInfo := o.modifyAuthInfo(*startingStanza)
config.AuthInfos[o.name] = &authInfo
if err := clientcmd.ModifyConfig(o.configAccess, *config, true); err != nil {
return err
}
return nil
}
// authInfo builds an AuthInfo object from the options
func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.AuthInfo) clientcmdapi.AuthInfo {
modifiedAuthInfo := existingAuthInfo
var setToken, setBasic bool
if o.clientCertificate.Provided() {
certPath := o.clientCertificate.Value()
if o.embedCertData.Value() {
modifiedAuthInfo.ClientCertificateData, _ = ioutil.ReadFile(certPath)
modifiedAuthInfo.ClientCertificate = ""
} else {
certPath, _ = filepath.Abs(certPath)
modifiedAuthInfo.ClientCertificate = certPath
if len(modifiedAuthInfo.ClientCertificate) > 0 {
modifiedAuthInfo.ClientCertificateData = nil
}
}
}
if o.clientKey.Provided() {
keyPath := o.clientKey.Value()
if o.embedCertData.Value() {
modifiedAuthInfo.ClientKeyData, _ = ioutil.ReadFile(keyPath)
modifiedAuthInfo.ClientKey = ""
} else {
keyPath, _ = filepath.Abs(keyPath)
modifiedAuthInfo.ClientKey = keyPath
if len(modifiedAuthInfo.ClientKey) > 0 {
modifiedAuthInfo.ClientKeyData = nil
}
}
}
if o.token.Provided() {
modifiedAuthInfo.Token = o.token.Value()
setToken = len(modifiedAuthInfo.Token) > 0
}
if o.username.Provided() {
modifiedAuthInfo.Username = o.username.Value()
setBasic = setBasic || len(modifiedAuthInfo.Username) > 0
}
if o.password.Provided() {
modifiedAuthInfo.Password = o.password.Value()
setBasic = setBasic || len(modifiedAuthInfo.Password) > 0
}
if o.authProvider.Provided() {
newName := o.authProvider.Value()
// Only overwrite if the existing auth-provider is nil, or different than the newly specified one.
if modifiedAuthInfo.AuthProvider == nil || modifiedAuthInfo.AuthProvider.Name != newName {
modifiedAuthInfo.AuthProvider = &clientcmdapi.AuthProviderConfig{
Name: newName,
}
}
}
if modifiedAuthInfo.AuthProvider != nil {
if modifiedAuthInfo.AuthProvider.Config == nil {
modifiedAuthInfo.AuthProvider.Config = make(map[string]string)
}
for _, toRemove := range o.authProviderArgsToRemove {
delete(modifiedAuthInfo.AuthProvider.Config, toRemove)
}
for key, value := range o.authProviderArgs {
modifiedAuthInfo.AuthProvider.Config[key] = value
}
}
// If any auth info was set, make sure any other existing auth types are cleared
if setToken || setBasic {
if !setToken {
modifiedAuthInfo.Token = ""
}
if !setBasic {
modifiedAuthInfo.Username = ""
modifiedAuthInfo.Password = ""
}
}
return modifiedAuthInfo
}
func (o *createAuthInfoOptions) complete(cmd *cobra.Command, out io.Writer) bool {
args := cmd.Flags().Args()
if len(args) != 1 {
return false
}
authProviderArgs, err := cmd.Flags().GetStringSlice(flagAuthProviderArg)
if err != nil {
fmt.Fprintf(out, "Error: %s\n", err)
return false
}
if len(authProviderArgs) > 0 {
newPairs, removePairs, err := cmdutil.ParsePairs(authProviderArgs, flagAuthProviderArg, true)
if err != nil {
fmt.Fprintf(out, "Error: %s\n", err)
return false
}
o.authProviderArgs = newPairs
o.authProviderArgsToRemove = removePairs
}
o.name = args[0]
return true
}
func (o createAuthInfoOptions) validate() error {
if len(o.name) == 0 {
return errors.New("you must specify a non-empty user name")
}
methods := []string{}
if len(o.token.Value()) > 0 {
methods = append(methods, fmt.Sprintf("--%v", clientcmd.FlagBearerToken))
}
if len(o.username.Value()) > 0 || len(o.password.Value()) > 0 {
methods = append(methods, fmt.Sprintf("--%v/--%v", clientcmd.FlagUsername, clientcmd.FlagPassword))
}
if len(methods) > 1 {
return fmt.Errorf("you cannot specify more than one authentication method at the same time: %v", strings.Join(methods, ", "))
}
if o.embedCertData.Value() {
certPath := o.clientCertificate.Value()
keyPath := o.clientKey.Value()
if certPath == "" && keyPath == "" {
return fmt.Errorf("you must specify a --%s or --%s to embed", clientcmd.FlagCertFile, clientcmd.FlagKeyFile)
}
if certPath != "" {
if _, err := ioutil.ReadFile(certPath); err != nil {
return fmt.Errorf("error reading %s data from %s: %v", clientcmd.FlagCertFile, certPath, err)
}
}
if keyPath != "" {
if _, err := ioutil.ReadFile(keyPath); err != nil {
return fmt.Errorf("error reading %s data from %s: %v", clientcmd.FlagKeyFile, keyPath, err)
}
}
}
return nil
}

View file

@ -0,0 +1,190 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"bytes"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/util/flag"
)
func stringFlagFor(s string) flag.StringFlag {
var f flag.StringFlag
f.Set(s)
return f
}
func TestCreateAuthInfoOptions(t *testing.T) {
tests := []struct {
flags []string
wantParseErr bool
wantCompleteErr bool
wantValidateErr bool
wantOptions *createAuthInfoOptions
}{
{
flags: []string{
"me",
},
wantOptions: &createAuthInfoOptions{
name: "me",
},
},
{
flags: []string{
"me",
"--token=foo",
},
wantOptions: &createAuthInfoOptions{
name: "me",
token: stringFlagFor("foo"),
},
},
{
flags: []string{
"me",
"--username=jane",
"--password=bar",
},
wantOptions: &createAuthInfoOptions{
name: "me",
username: stringFlagFor("jane"),
password: stringFlagFor("bar"),
},
},
{
// Cannot provide both token and basic auth.
flags: []string{
"me",
"--token=foo",
"--username=jane",
"--password=bar",
},
wantValidateErr: true,
},
{
flags: []string{
"--auth-provider=oidc",
"--auth-provider-arg=client-id=foo",
"--auth-provider-arg=client-secret=bar",
"me",
},
wantOptions: &createAuthInfoOptions{
name: "me",
authProvider: stringFlagFor("oidc"),
authProviderArgs: map[string]string{
"client-id": "foo",
"client-secret": "bar",
},
authProviderArgsToRemove: []string{},
},
},
{
flags: []string{
"--auth-provider=oidc",
"--auth-provider-arg=client-id-",
"--auth-provider-arg=client-secret-",
"me",
},
wantOptions: &createAuthInfoOptions{
name: "me",
authProvider: stringFlagFor("oidc"),
authProviderArgs: map[string]string{},
authProviderArgsToRemove: []string{
"client-id",
"client-secret",
},
},
},
{
flags: []string{
"--auth-provider-arg=client-id-", // auth provider name not required
"--auth-provider-arg=client-secret-",
"me",
},
wantOptions: &createAuthInfoOptions{
name: "me",
authProviderArgs: map[string]string{},
authProviderArgsToRemove: []string{
"client-id",
"client-secret",
},
},
},
{
flags: []string{
"--auth-provider=oidc",
"--auth-provider-arg=client-id", // values must be of form 'key=value' or 'key-'
"me",
},
wantCompleteErr: true,
},
{
flags: []string{
// No name for authinfo provided.
},
wantCompleteErr: true,
},
}
for i, test := range tests {
buff := new(bytes.Buffer)
opts := new(createAuthInfoOptions)
cmd := newCmdConfigSetAuthInfo(buff, opts)
if err := cmd.ParseFlags(test.flags); err != nil {
if !test.wantParseErr {
t.Errorf("case %d: parsing error for flags %q: %v: %s", i, test.flags, err, buff)
}
continue
}
if test.wantParseErr {
t.Errorf("case %d: expected parsing error for flags %q: %s", i, test.flags, buff)
continue
}
if !opts.complete(cmd, buff) {
if !test.wantCompleteErr {
t.Errorf("case %d: complete() error for flags %q: %s", i, test.flags, buff)
}
continue
}
if test.wantCompleteErr {
t.Errorf("case %d: complete() expected errors for flags %q: %s", i, test.flags, buff)
continue
}
if err := opts.validate(); err != nil {
if !test.wantValidateErr {
t.Errorf("case %d: flags %q: validate failed: %v", i, test.flags, err)
}
continue
}
if test.wantValidateErr {
t.Errorf("case %d: flags %q: expected validate to fail", i, test.flags)
continue
}
if !reflect.DeepEqual(opts, test.wantOptions) {
t.Errorf("case %d: flags %q: mis-matched options,\nwanted=%#v\ngot= %#v", i, test.flags, test.wantOptions, opts)
}
}
}

View file

@ -0,0 +1,183 @@
/*
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 config
import (
"errors"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/util/flag"
)
type createClusterOptions struct {
configAccess clientcmd.ConfigAccess
name string
server flag.StringFlag
apiVersion flag.StringFlag
insecureSkipTLSVerify flag.Tristate
certificateAuthority flag.StringFlag
embedCAData flag.Tristate
}
var (
create_cluster_long = templates.LongDesc(`
Sets a cluster entry in kubeconfig.
Specifying a name that already exists will merge new fields on top of existing values for those fields.`)
create_cluster_example = templates.Examples(`
# Set only the server field on the e2e cluster entry without touching other values.
kubectl config set-cluster e2e --server=https://1.2.3.4
# Embed certificate authority data for the e2e cluster entry
kubectl config set-cluster e2e --certificate-authority=~/.kube/e2e/kubernetes.ca.crt
# Disable cert checking for the dev cluster entry
kubectl config set-cluster e2e --insecure-skip-tls-verify=true`)
)
func NewCmdConfigSetCluster(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &createClusterOptions{configAccess: configAccess}
cmd := &cobra.Command{
Use: fmt.Sprintf("set-cluster NAME [--%v=server] [--%v=path/to/certificate/authority] [--%v=true]", clientcmd.FlagAPIServer, clientcmd.FlagCAFile, clientcmd.FlagInsecure),
Short: "Sets a cluster entry in kubeconfig",
Long: create_cluster_long,
Example: create_cluster_example,
Run: func(cmd *cobra.Command, args []string) {
if !options.complete(cmd) {
return
}
cmdutil.CheckErr(options.run())
fmt.Fprintf(out, "Cluster %q set.\n", options.name)
},
}
options.insecureSkipTLSVerify.Default(false)
cmd.Flags().Var(&options.server, clientcmd.FlagAPIServer, clientcmd.FlagAPIServer+" for the cluster entry in kubeconfig")
cmd.Flags().Var(&options.apiVersion, clientcmd.FlagAPIVersion, clientcmd.FlagAPIVersion+" for the cluster entry in kubeconfig")
f := cmd.Flags().VarPF(&options.insecureSkipTLSVerify, clientcmd.FlagInsecure, "", clientcmd.FlagInsecure+" for the cluster entry in kubeconfig")
f.NoOptDefVal = "true"
cmd.Flags().Var(&options.certificateAuthority, clientcmd.FlagCAFile, "path to "+clientcmd.FlagCAFile+" file for the cluster entry in kubeconfig")
cmd.MarkFlagFilename(clientcmd.FlagCAFile)
f = cmd.Flags().VarPF(&options.embedCAData, clientcmd.FlagEmbedCerts, "", clientcmd.FlagEmbedCerts+" for the cluster entry in kubeconfig")
f.NoOptDefVal = "true"
return cmd
}
func (o createClusterOptions) run() error {
err := o.validate()
if err != nil {
return err
}
config, err := o.configAccess.GetStartingConfig()
if err != nil {
return err
}
startingStanza, exists := config.Clusters[o.name]
if !exists {
startingStanza = clientcmdapi.NewCluster()
}
cluster := o.modifyCluster(*startingStanza)
config.Clusters[o.name] = &cluster
if err := clientcmd.ModifyConfig(o.configAccess, *config, true); err != nil {
return err
}
return nil
}
// cluster builds a Cluster object from the options
func (o *createClusterOptions) modifyCluster(existingCluster clientcmdapi.Cluster) clientcmdapi.Cluster {
modifiedCluster := existingCluster
if o.server.Provided() {
modifiedCluster.Server = o.server.Value()
}
if o.insecureSkipTLSVerify.Provided() {
modifiedCluster.InsecureSkipTLSVerify = o.insecureSkipTLSVerify.Value()
// Specifying insecure mode clears any certificate authority
if modifiedCluster.InsecureSkipTLSVerify {
modifiedCluster.CertificateAuthority = ""
modifiedCluster.CertificateAuthorityData = nil
}
}
if o.certificateAuthority.Provided() {
caPath := o.certificateAuthority.Value()
if o.embedCAData.Value() {
modifiedCluster.CertificateAuthorityData, _ = ioutil.ReadFile(caPath)
modifiedCluster.InsecureSkipTLSVerify = false
modifiedCluster.CertificateAuthority = ""
} else {
caPath, _ = filepath.Abs(caPath)
modifiedCluster.CertificateAuthority = caPath
// Specifying a certificate authority file clears certificate authority data and insecure mode
if caPath != "" {
modifiedCluster.InsecureSkipTLSVerify = false
modifiedCluster.CertificateAuthorityData = nil
}
}
}
return modifiedCluster
}
func (o *createClusterOptions) complete(cmd *cobra.Command) bool {
args := cmd.Flags().Args()
if len(args) != 1 {
cmd.Help()
return false
}
o.name = args[0]
return true
}
func (o createClusterOptions) validate() error {
if len(o.name) == 0 {
return errors.New("you must specify a non-empty cluster name")
}
if o.insecureSkipTLSVerify.Value() && o.certificateAuthority.Value() != "" {
return errors.New("you cannot specify a certificate authority and insecure mode at the same time")
}
if o.embedCAData.Value() {
caPath := o.certificateAuthority.Value()
if caPath == "" {
return fmt.Errorf("you must specify a --%s to embed", clientcmd.FlagCAFile)
}
if _, err := ioutil.ReadFile(caPath); err != nil {
return fmt.Errorf("could not read %s data from %s: %v", clientcmd.FlagCAFile, caPath, err)
}
}
return nil
}

View file

@ -0,0 +1,135 @@
/*
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 config
import (
"errors"
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/util/flag"
)
type createContextOptions struct {
configAccess clientcmd.ConfigAccess
name string
cluster flag.StringFlag
authInfo flag.StringFlag
namespace flag.StringFlag
}
var (
create_context_long = templates.LongDesc(`
Sets a context entry in kubeconfig
Specifying a name that already exists will merge new fields on top of existing values for those fields.`)
create_context_example = templates.Examples(`
# Set the user field on the gce context entry without touching other values
kubectl config set-context gce --user=cluster-admin`)
)
func NewCmdConfigSetContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &createContextOptions{configAccess: configAccess}
cmd := &cobra.Command{
Use: fmt.Sprintf("set-context NAME [--%v=cluster_nickname] [--%v=user_nickname] [--%v=namespace]", clientcmd.FlagClusterName, clientcmd.FlagAuthInfoName, clientcmd.FlagNamespace),
Short: "Sets a context entry in kubeconfig",
Long: create_context_long,
Example: create_context_example,
Run: func(cmd *cobra.Command, args []string) {
if !options.complete(cmd) {
return
}
cmdutil.CheckErr(options.run())
fmt.Fprintf(out, "Context %q set.\n", options.name)
},
}
cmd.Flags().Var(&options.cluster, clientcmd.FlagClusterName, clientcmd.FlagClusterName+" for the context entry in kubeconfig")
cmd.Flags().Var(&options.authInfo, clientcmd.FlagAuthInfoName, clientcmd.FlagAuthInfoName+" for the context entry in kubeconfig")
cmd.Flags().Var(&options.namespace, clientcmd.FlagNamespace, clientcmd.FlagNamespace+" for the context entry in kubeconfig")
return cmd
}
func (o createContextOptions) run() error {
err := o.validate()
if err != nil {
return err
}
config, err := o.configAccess.GetStartingConfig()
if err != nil {
return err
}
startingStanza, exists := config.Contexts[o.name]
if !exists {
startingStanza = clientcmdapi.NewContext()
}
context := o.modifyContext(*startingStanza)
config.Contexts[o.name] = &context
if err := clientcmd.ModifyConfig(o.configAccess, *config, true); err != nil {
return err
}
return nil
}
func (o *createContextOptions) modifyContext(existingContext clientcmdapi.Context) clientcmdapi.Context {
modifiedContext := existingContext
if o.cluster.Provided() {
modifiedContext.Cluster = o.cluster.Value()
}
if o.authInfo.Provided() {
modifiedContext.AuthInfo = o.authInfo.Value()
}
if o.namespace.Provided() {
modifiedContext.Namespace = o.namespace.Value()
}
return modifiedContext
}
func (o *createContextOptions) complete(cmd *cobra.Command) bool {
args := cmd.Flags().Args()
if len(args) != 1 {
cmd.Help()
return false
}
o.name = args[0]
return true
}
func (o createContextOptions) validate() error {
if len(o.name) == 0 {
return errors.New("you must specify a non-empty context name")
}
return nil
}

View file

@ -0,0 +1,73 @@
/*
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 config
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
type CurrentContextOptions struct {
ConfigAccess clientcmd.ConfigAccess
}
var (
current_context_long = templates.LongDesc(`
Displays the current-context`)
current_context_example = templates.Examples(`
# Display the current-context
kubectl config current-context`)
)
func NewCmdConfigCurrentContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &CurrentContextOptions{ConfigAccess: configAccess}
cmd := &cobra.Command{
Use: "current-context",
Short: "Displays the current-context",
Long: current_context_long,
Example: current_context_example,
Run: func(cmd *cobra.Command, args []string) {
err := RunCurrentContext(out, args, options)
cmdutil.CheckErr(err)
},
}
return cmd
}
func RunCurrentContext(out io.Writer, args []string, options *CurrentContextOptions) error {
config, err := options.ConfigAccess.GetStartingConfig()
if err != nil {
return err
}
if config.CurrentContext == "" {
err = fmt.Errorf("current-context is not set\n")
return err
}
fmt.Fprintf(out, "%s\n", config.CurrentContext)
return nil
}

View file

@ -0,0 +1,90 @@
/*
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 config
import (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
type currentContextTest struct {
startingConfig clientcmdapi.Config
expectedError string
}
func newFederalContextConfig() clientcmdapi.Config {
return clientcmdapi.Config{
CurrentContext: "federal-context",
}
}
func TestCurrentContextWithSetContext(t *testing.T) {
test := currentContextTest{
startingConfig: newFederalContextConfig(),
expectedError: "",
}
test.run(t)
}
func TestCurrentContextWithUnsetContext(t *testing.T) {
test := currentContextTest{
startingConfig: *clientcmdapi.NewConfig(),
expectedError: "current-context is not set",
}
test.run(t)
}
func (test currentContextTest) run(t *testing.T) {
fakeKubeFile, _ := ioutil.TempFile("", "")
defer os.Remove(fakeKubeFile.Name())
err := clientcmd.WriteToFile(test.startingConfig, fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = fakeKubeFile.Name()
pathOptions.EnvVar = ""
options := CurrentContextOptions{
ConfigAccess: pathOptions,
}
buf := bytes.NewBuffer([]byte{})
err = RunCurrentContext(buf, []string{}, &options)
if len(test.expectedError) != 0 {
if err == nil {
t.Errorf("Did not get %v", test.expectedError)
} else {
if !strings.Contains(err.Error(), test.expectedError) {
t.Errorf("Expected %v, but got %v", test.expectedError, err)
}
}
return
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}

View file

@ -0,0 +1,82 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
var (
delete_cluster_example = templates.Examples(`
# Delete the minikube cluster
kubectl config delete-cluster minikube`)
)
func NewCmdConfigDeleteCluster(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
cmd := &cobra.Command{
Use: "delete-cluster NAME",
Short: "Delete the specified cluster from the kubeconfig",
Long: "Delete the specified cluster from the kubeconfig",
Example: delete_cluster_example,
Run: func(cmd *cobra.Command, args []string) {
err := runDeleteCluster(out, configAccess, cmd)
cmdutil.CheckErr(err)
},
}
return cmd
}
func runDeleteCluster(out io.Writer, configAccess clientcmd.ConfigAccess, cmd *cobra.Command) error {
config, err := configAccess.GetStartingConfig()
if err != nil {
return err
}
args := cmd.Flags().Args()
if len(args) != 1 {
cmd.Help()
return nil
}
configFile := configAccess.GetDefaultFilename()
if configAccess.IsExplicitFile() {
configFile = configAccess.GetExplicitFile()
}
name := args[0]
_, ok := config.Clusters[name]
if !ok {
return fmt.Errorf("cannot delete cluster %s, not in %s", name, configFile)
}
delete(config.Clusters, name)
if err := clientcmd.ModifyConfig(configAccess, *config, true); err != nil {
return err
}
fmt.Fprintf(out, "deleted cluster %s from %s\n", name, configFile)
return nil
}

View file

@ -0,0 +1,94 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
type deleteClusterTest struct {
config clientcmdapi.Config
clusterToDelete string
expectedClusters []string
expectedOut string
}
func TestDeleteCluster(t *testing.T) {
conf := clientcmdapi.Config{
Clusters: map[string]*clientcmdapi.Cluster{
"minikube": {Server: "https://192.168.0.99"},
"otherkube": {Server: "https://192.168.0.100"},
},
}
test := deleteClusterTest{
config: conf,
clusterToDelete: "minikube",
expectedClusters: []string{"otherkube"},
expectedOut: "deleted cluster minikube from %s\n",
}
test.run(t)
}
func (test deleteClusterTest) run(t *testing.T) {
fakeKubeFile, _ := ioutil.TempFile("", "")
defer os.Remove(fakeKubeFile.Name())
err := clientcmd.WriteToFile(test.config, fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = fakeKubeFile.Name()
pathOptions.EnvVar = ""
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfigDeleteCluster(buf, pathOptions)
cmd.SetArgs([]string{test.clusterToDelete})
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpected error executing command: %v", err)
}
expectedOutWithFile := fmt.Sprintf(test.expectedOut, fakeKubeFile.Name())
if expectedOutWithFile != buf.String() {
t.Errorf("expected output %s, but got %s", expectedOutWithFile, buf.String())
return
}
// Verify cluster was removed from kubeconfig file
config, err := clientcmd.LoadFromFile(fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error loading kubeconfig file: %v", err)
}
clusters := make([]string, 0, len(config.Clusters))
for k := range config.Clusters {
clusters = append(clusters, k)
}
if !reflect.DeepEqual(test.expectedClusters, clusters) {
t.Errorf("expected clusters %v, but found %v in kubeconfig", test.expectedClusters, clusters)
}
}

View file

@ -0,0 +1,82 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
var (
delete_context_example = templates.Examples(`
# Delete the context for the minikube cluster
kubectl config delete-context minikube`)
)
func NewCmdConfigDeleteContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
cmd := &cobra.Command{
Use: "delete-context NAME",
Short: "Delete the specified context from the kubeconfig",
Long: "Delete the specified context from the kubeconfig",
Example: delete_context_example,
Run: func(cmd *cobra.Command, args []string) {
err := runDeleteContext(out, configAccess, cmd)
cmdutil.CheckErr(err)
},
}
return cmd
}
func runDeleteContext(out io.Writer, configAccess clientcmd.ConfigAccess, cmd *cobra.Command) error {
config, err := configAccess.GetStartingConfig()
if err != nil {
return err
}
args := cmd.Flags().Args()
if len(args) != 1 {
cmd.Help()
return nil
}
configFile := configAccess.GetDefaultFilename()
if configAccess.IsExplicitFile() {
configFile = configAccess.GetExplicitFile()
}
name := args[0]
_, ok := config.Contexts[name]
if !ok {
return fmt.Errorf("cannot delete context %s, not in %s", name, configFile)
}
delete(config.Contexts, name)
if err := clientcmd.ModifyConfig(configAccess, *config, true); err != nil {
return err
}
fmt.Fprintf(out, "deleted context %s from %s\n", name, configFile)
return nil
}

View file

@ -0,0 +1,94 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
type deleteContextTest struct {
config clientcmdapi.Config
contextToDelete string
expectedContexts []string
expectedOut string
}
func TestDeleteContext(t *testing.T) {
conf := clientcmdapi.Config{
Contexts: map[string]*clientcmdapi.Context{
"minikube": {Cluster: "minikube"},
"otherkube": {Cluster: "otherkube"},
},
}
test := deleteContextTest{
config: conf,
contextToDelete: "minikube",
expectedContexts: []string{"otherkube"},
expectedOut: "deleted context minikube from %s\n",
}
test.run(t)
}
func (test deleteContextTest) run(t *testing.T) {
fakeKubeFile, _ := ioutil.TempFile("", "")
defer os.Remove(fakeKubeFile.Name())
err := clientcmd.WriteToFile(test.config, fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = fakeKubeFile.Name()
pathOptions.EnvVar = ""
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfigDeleteContext(buf, pathOptions)
cmd.SetArgs([]string{test.contextToDelete})
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpected error executing command: %v", err)
}
expectedOutWithFile := fmt.Sprintf(test.expectedOut, fakeKubeFile.Name())
if expectedOutWithFile != buf.String() {
t.Errorf("expected output %s, but got %s", expectedOutWithFile, buf.String())
return
}
// Verify context was removed from kubeconfig file
config, err := clientcmd.LoadFromFile(fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error loading kubeconfig file: %v", err)
}
contexts := make([]string, 0, len(config.Contexts))
for k := range config.Contexts {
contexts = append(contexts, k)
}
if !reflect.DeepEqual(test.expectedContexts, contexts) {
t.Errorf("expected contexts %v, but found %v in kubeconfig", test.expectedContexts, contexts)
}
}

View file

@ -0,0 +1,64 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
var (
get_clusters_example = templates.Examples(`
# List the clusters kubectl knows about
kubectl config get-clusters`)
)
// NewCmdConfigGetClusters creates a command object for the "get-clusters" action, which
// lists all clusters defined in the kubeconfig.
func NewCmdConfigGetClusters(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
cmd := &cobra.Command{
Use: "get-clusters",
Short: "Display clusters defined in the kubeconfig",
Long: "Display clusters defined in the kubeconfig.",
Example: get_clusters_example,
Run: func(cmd *cobra.Command, args []string) {
err := runGetClusters(out, configAccess)
cmdutil.CheckErr(err)
},
}
return cmd
}
func runGetClusters(out io.Writer, configAccess clientcmd.ConfigAccess) error {
config, err := configAccess.GetStartingConfig()
if err != nil {
return err
}
fmt.Fprintf(out, "NAME\n")
for name := range config.Clusters {
fmt.Fprintf(out, "%s\n", name)
}
return nil
}

View file

@ -0,0 +1,81 @@
/*
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 config
import (
"bytes"
"io/ioutil"
"os"
"testing"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
type getClustersTest struct {
config clientcmdapi.Config
expected string
}
func TestGetClusters(t *testing.T) {
conf := clientcmdapi.Config{
Clusters: map[string]*clientcmdapi.Cluster{
"minikube": {Server: "https://192.168.0.99"},
},
}
test := getClustersTest{
config: conf,
expected: `NAME
minikube
`,
}
test.run(t)
}
func TestGetClustersEmpty(t *testing.T) {
test := getClustersTest{
config: clientcmdapi.Config{},
expected: "NAME\n",
}
test.run(t)
}
func (test getClustersTest) run(t *testing.T) {
fakeKubeFile, _ := ioutil.TempFile("", "")
defer os.Remove(fakeKubeFile.Name())
err := clientcmd.WriteToFile(test.config, fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = fakeKubeFile.Name()
pathOptions.EnvVar = ""
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfigGetClusters(buf, pathOptions)
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpected error executing command: %v", err)
}
if len(test.expected) != 0 {
if buf.String() != test.expected {
t.Errorf("expected %v, but got %v", test.expected, buf.String())
}
return
}
}

View file

@ -0,0 +1,169 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"fmt"
"io"
"strings"
"text/tabwriter"
"github.com/spf13/cobra"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
// GetContextsOptions contains the assignable options from the args.
type GetContextsOptions struct {
configAccess clientcmd.ConfigAccess
nameOnly bool
showHeaders bool
contextNames []string
out io.Writer
}
var (
getContextsLong = templates.LongDesc(`Displays one or many contexts from the kubeconfig file.`)
getContextsExample = templates.Examples(`
# List all the contexts in your kubeconfig file
kubectl config get-contexts
# Describe one context in your kubeconfig file.
kubectl config get-contexts my-context`)
)
// 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}
cmd := &cobra.Command{
Use: "get-contexts [(-o|--output=)name)]",
Short: "Describe one or many contexts",
Long: getContextsLong,
Example: getContextsExample,
Run: func(cmd *cobra.Command, args []string) {
validOutputTypes := sets.NewString("", "json", "yaml", "wide", "name", "custom-columns", "custom-columns-file", "go-template", "go-template-file", "jsonpath", "jsonpath-file")
supportedOutputTypes := sets.NewString("", "name")
outputFormat := cmdutil.GetFlagString(cmd, "output")
if !validOutputTypes.Has(outputFormat) {
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)
cmd.Flags().Set("output", "")
}
cmdutil.CheckErr(options.Complete(cmd, args, out))
cmdutil.CheckErr(options.RunGetContexts())
},
}
cmdutil.AddOutputFlags(cmd)
cmdutil.AddNoHeadersFlags(cmd)
return cmd
}
// Complete assigns GetContextsOptions from the args.
func (o *GetContextsOptions) Complete(cmd *cobra.Command, args []string, out io.Writer) error {
o.contextNames = args
o.out = out
o.nameOnly = false
if cmdutil.GetFlagString(cmd, "output") == "name" {
o.nameOnly = true
}
o.showHeaders = true
if cmdutil.GetFlagBool(cmd, "no-headers") || o.nameOnly {
o.showHeaders = false
}
return nil
}
// RunGetContexts implements all the necessary functionality for context retrieval.
func (o GetContextsOptions) RunGetContexts() error {
config, err := o.configAccess.GetStartingConfig()
if err != nil {
return err
}
out, found := o.out.(*tabwriter.Writer)
if !found {
out = kubectl.GetNewTabWriter(o.out)
defer out.Flush()
}
// Build a list of context names to print, and warn if any requested contexts are not found.
// Do this before printing the headers so it doesn't look ugly.
allErrs := []error{}
toPrint := []string{}
if len(o.contextNames) == 0 {
for name := range config.Contexts {
toPrint = append(toPrint, name)
}
} else {
for _, name := range o.contextNames {
_, ok := config.Contexts[name]
if ok {
toPrint = append(toPrint, name)
} else {
allErrs = append(allErrs, fmt.Errorf("context %v not found", name))
}
}
}
if o.showHeaders {
err = printContextHeaders(out, o.nameOnly)
if err != nil {
allErrs = append(allErrs, err)
}
}
for _, name := range toPrint {
err = printContext(name, config.Contexts[name], out, o.nameOnly, config.CurrentContext == name)
if err != nil {
allErrs = append(allErrs, err)
}
}
return utilerrors.NewAggregate(allErrs)
}
func printContextHeaders(out io.Writer, nameOnly bool) error {
columnNames := []string{"CURRENT", "NAME", "CLUSTER", "AUTHINFO", "NAMESPACE"}
if nameOnly {
columnNames = columnNames[:1]
}
_, err := fmt.Fprintf(out, "%s\n", strings.Join(columnNames, "\t"))
return err
}
func printContext(name string, context *clientcmdapi.Context, w io.Writer, nameOnly, current bool) error {
if nameOnly {
_, err := fmt.Fprintf(w, "%s\n", name)
return err
}
prefix := " "
if current {
prefix = "*"
}
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", prefix, name, context.Cluster, context.AuthInfo, context.Namespace)
return err
}

View file

@ -0,0 +1,158 @@
/*
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 config
import (
"bytes"
"io/ioutil"
"os"
"testing"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
type getContextsTest struct {
startingConfig clientcmdapi.Config
names []string
noHeader bool
nameOnly bool
expectedOut string
}
func TestGetContextsAll(t *testing.T) {
tconf := clientcmdapi.Config{
CurrentContext: "shaker-context",
Contexts: map[string]*clientcmdapi.Context{
"shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}}
test := getContextsTest{
startingConfig: tconf,
names: []string{},
noHeader: false,
nameOnly: false,
expectedOut: `CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* shaker-context big-cluster blue-user saw-ns
`,
}
test.run(t)
}
func TestGetContextsAllNoHeader(t *testing.T) {
tconf := clientcmdapi.Config{
CurrentContext: "shaker-context",
Contexts: map[string]*clientcmdapi.Context{
"shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}}
test := getContextsTest{
startingConfig: tconf,
names: []string{},
noHeader: true,
nameOnly: false,
expectedOut: "* shaker-context big-cluster blue-user saw-ns\n",
}
test.run(t)
}
func TestGetContextsAllName(t *testing.T) {
tconf := clientcmdapi.Config{
Contexts: map[string]*clientcmdapi.Context{
"shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}}
test := getContextsTest{
startingConfig: tconf,
names: []string{},
noHeader: false,
nameOnly: true,
expectedOut: "shaker-context\n",
}
test.run(t)
}
func TestGetContextsAllNameNoHeader(t *testing.T) {
tconf := clientcmdapi.Config{
CurrentContext: "shaker-context",
Contexts: map[string]*clientcmdapi.Context{
"shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}}
test := getContextsTest{
startingConfig: tconf,
names: []string{},
noHeader: true,
nameOnly: true,
expectedOut: "shaker-context\n",
}
test.run(t)
}
func TestGetContextsAllNone(t *testing.T) {
test := getContextsTest{
startingConfig: *clientcmdapi.NewConfig(),
names: []string{},
noHeader: true,
nameOnly: false,
expectedOut: "",
}
test.run(t)
}
func TestGetContextsSelectOneOfTwo(t *testing.T) {
tconf := clientcmdapi.Config{
CurrentContext: "shaker-context",
Contexts: map[string]*clientcmdapi.Context{
"shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"},
"not-this": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}}
test := getContextsTest{
startingConfig: tconf,
names: []string{"shaker-context"},
noHeader: true,
nameOnly: true,
expectedOut: "shaker-context\n",
}
test.run(t)
}
func (test getContextsTest) run(t *testing.T) {
fakeKubeFile, _ := ioutil.TempFile("", "")
defer os.Remove(fakeKubeFile.Name())
err := clientcmd.WriteToFile(test.startingConfig, fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = fakeKubeFile.Name()
pathOptions.EnvVar = ""
buf := bytes.NewBuffer([]byte{})
options := GetContextsOptions{
configAccess: pathOptions,
}
cmd := NewCmdConfigGetContexts(buf, options.configAccess)
if test.nameOnly {
cmd.Flags().Set("output", "name")
}
if test.noHeader {
cmd.Flags().Set("no-headers", "true")
}
cmd.Run(cmd, test.names)
if len(test.expectedOut) != 0 {
if buf.String() != test.expectedOut {
t.Errorf("Expected %v, but got %v", test.expectedOut, buf.String())
}
return
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}

View file

@ -0,0 +1,152 @@
/*
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 config
import (
"fmt"
"reflect"
"strings"
"k8s.io/apimachinery/pkg/util/sets"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
type navigationSteps struct {
steps []navigationStep
currentStepIndex int
}
type navigationStep struct {
stepValue string
stepType reflect.Type
}
func newNavigationSteps(path string) (*navigationSteps, error) {
steps := []navigationStep{}
individualParts := strings.Split(path, ".")
currType := reflect.TypeOf(clientcmdapi.Config{})
currPartIndex := 0
for currPartIndex < len(individualParts) {
switch currType.Kind() {
case reflect.Map:
// if we're in a map, we need to locate a name. That name may contain dots, so we need to know what tokens are legal for the map's value type
// for example, we could have a set request like: `set clusters.10.10.12.56.insecure-skip-tls-verify true`. We enter this case with
// steps representing 10, 10, 12, 56, insecure-skip-tls-verify. The name is "10.10.12.56", so we want to collect all those parts together and
// store them as a single step. In order to do that, we need to determine what set of tokens is a legal step AFTER the name of the map key
// This set of reflective code pulls the type of the map values, uses that type to look up the set of legal tags. Those legal tags are used to
// walk the list of remaining parts until we find a match to a legal tag or the end of the string. That name is used to burn all the used parts.
mapValueType := currType.Elem().Elem()
mapValueOptions, err := getPotentialTypeValues(mapValueType)
if err != nil {
return nil, err
}
nextPart := findNameStep(individualParts[currPartIndex:], sets.StringKeySet(mapValueOptions))
steps = append(steps, navigationStep{nextPart, mapValueType})
currPartIndex += len(strings.Split(nextPart, "."))
currType = mapValueType
case reflect.Struct:
nextPart := individualParts[currPartIndex]
options, err := getPotentialTypeValues(currType)
if err != nil {
return nil, err
}
fieldType, exists := options[nextPart]
if !exists {
return nil, fmt.Errorf("unable to parse %v after %v at %v", path, steps, currType)
}
steps = append(steps, navigationStep{nextPart, fieldType})
currPartIndex += len(strings.Split(nextPart, "."))
currType = fieldType
}
}
return &navigationSteps{steps, 0}, nil
}
func (s *navigationSteps) pop() navigationStep {
if s.moreStepsRemaining() {
s.currentStepIndex++
return s.steps[s.currentStepIndex-1]
}
return navigationStep{}
}
func (s *navigationSteps) peek() navigationStep {
if s.moreStepsRemaining() {
return s.steps[s.currentStepIndex]
}
return navigationStep{}
}
func (s *navigationSteps) moreStepsRemaining() bool {
return len(s.steps) > s.currentStepIndex
}
// findNameStep takes the list of parts and a set of valid tags that can be used after the name. It then walks the list of parts
// until it find a valid "next" tag or until it reaches the end of the parts and then builds the name back up out of the individual parts
func findNameStep(parts []string, typeOptions sets.String) string {
if len(parts) == 0 {
return ""
}
numberOfPartsInStep := findKnownValue(parts[1:], typeOptions) + 1
// if we didn't find a known value, then the entire thing must be a name
if numberOfPartsInStep == 0 {
numberOfPartsInStep = len(parts)
}
nextParts := parts[0:numberOfPartsInStep]
return strings.Join(nextParts, ".")
}
// getPotentialTypeValues takes a type and looks up the tags used to represent its fields when serialized.
func getPotentialTypeValues(typeValue reflect.Type) (map[string]reflect.Type, error) {
if typeValue.Kind() == reflect.Ptr {
typeValue = typeValue.Elem()
}
if typeValue.Kind() != reflect.Struct {
return nil, fmt.Errorf("%v is not of type struct", typeValue)
}
ret := make(map[string]reflect.Type)
for fieldIndex := 0; fieldIndex < typeValue.NumField(); fieldIndex++ {
fieldType := typeValue.Field(fieldIndex)
yamlTag := fieldType.Tag.Get("json")
yamlTagName := strings.Split(yamlTag, ",")[0]
ret[yamlTagName] = fieldType.Type
}
return ret, nil
}
func findKnownValue(parts []string, valueOptions sets.String) int {
for i := range parts {
if valueOptions.Has(parts[i]) {
return i
}
}
return -1
}

View file

@ -0,0 +1,96 @@
/*
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 config
import (
"reflect"
"strings"
"testing"
"k8s.io/apimachinery/pkg/util/diff"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
type stepParserTest struct {
path string
expectedNavigationSteps navigationSteps
expectedError string
}
func TestParseWithDots(t *testing.T) {
test := stepParserTest{
path: "clusters.my.dot.delimited.name.server",
expectedNavigationSteps: navigationSteps{
steps: []navigationStep{
{"clusters", reflect.TypeOf(make(map[string]*clientcmdapi.Cluster))},
{"my.dot.delimited.name", reflect.TypeOf(clientcmdapi.Cluster{})},
{"server", reflect.TypeOf("")},
},
},
}
test.run(t)
}
func TestParseWithDotsEndingWithName(t *testing.T) {
test := stepParserTest{
path: "contexts.10.12.12.12",
expectedNavigationSteps: navigationSteps{
steps: []navigationStep{
{"contexts", reflect.TypeOf(make(map[string]*clientcmdapi.Context))},
{"10.12.12.12", reflect.TypeOf(clientcmdapi.Context{})},
},
},
}
test.run(t)
}
func TestParseWithBadValue(t *testing.T) {
test := stepParserTest{
path: "user.bad",
expectedNavigationSteps: navigationSteps{
steps: []navigationStep{},
},
expectedError: "unable to parse user.bad after [] at api.Config",
}
test.run(t)
}
func (test stepParserTest) run(t *testing.T) {
actualSteps, err := newNavigationSteps(test.path)
if len(test.expectedError) != 0 {
if err == nil {
t.Errorf("Did not get %v", test.expectedError)
} else {
if !strings.Contains(err.Error(), test.expectedError) {
t.Errorf("Expected %v, but got %v", test.expectedError, err)
}
}
return
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !reflect.DeepEqual(test.expectedNavigationSteps, *actualSteps) {
t.Errorf("diff: %v", diff.ObjectDiff(test.expectedNavigationSteps, *actualSteps))
t.Errorf("expected: %#v\n actual: %#v", test.expectedNavigationSteps, *actualSteps)
}
}

247
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/set.go generated vendored Normal file
View file

@ -0,0 +1,247 @@
/*
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 config
import (
"encoding/base64"
"errors"
"fmt"
"io"
"reflect"
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/util/flag"
)
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
propertyValue string
setRawBytes flag.Tristate
}
var set_long = templates.LongDesc(`
Sets an individual value in a kubeconfig file
PROPERTY_NAME is a dot delimited name where each token represents either an attribute name or a map key. Map keys may not contain dots.
PROPERTY_VALUE is the new value you wish to set. Binary fields such as 'certificate-authority-data' expect a base64 encoded string unless the --set-raw-bytes flag is used.`)
func NewCmdConfigSet(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &setOptions{configAccess: configAccess}
cmd := &cobra.Command{
Use: "set PROPERTY_NAME PROPERTY_VALUE",
Short: "Sets an individual value in a kubeconfig file",
Long: set_long,
Run: func(cmd *cobra.Command, args []string) {
if !options.complete(cmd) {
return
}
cmdutil.CheckErr(options.run())
fmt.Fprintf(out, "Property %q set.\n", options.propertyName)
},
}
f := cmd.Flags().VarPF(&options.setRawBytes, "set-raw-bytes", "", "When writing a []byte PROPERTY_VALUE, write the given string directly without base64 decoding.")
f.NoOptDefVal = "true"
return cmd
}
func (o setOptions) run() error {
err := o.validate()
if err != nil {
return err
}
config, err := o.configAccess.GetStartingConfig()
if err != nil {
return err
}
steps, err := newNavigationSteps(o.propertyName)
if err != nil {
return err
}
setRawBytes := false
if o.setRawBytes.Provided() {
setRawBytes = o.setRawBytes.Value()
}
err = modifyConfig(reflect.ValueOf(config), steps, o.propertyValue, false, setRawBytes)
if err != nil {
return err
}
if err := clientcmd.ModifyConfig(o.configAccess, *config, false); err != nil {
return err
}
return nil
}
func (o *setOptions) complete(cmd *cobra.Command) bool {
endingArgs := cmd.Flags().Args()
if len(endingArgs) != 2 {
cmd.Help()
return false
}
o.propertyValue = endingArgs[1]
o.propertyName = endingArgs[0]
return true
}
func (o setOptions) validate() error {
if len(o.propertyValue) == 0 {
return errors.New("you cannot use set to unset a property")
}
if len(o.propertyName) == 0 {
return errors.New("you must specify a property")
}
return nil
}
func modifyConfig(curr reflect.Value, steps *navigationSteps, propertyValue string, unset bool, setRawBytes bool) error {
currStep := steps.pop()
actualCurrValue := curr
if curr.Kind() == reflect.Ptr {
actualCurrValue = curr.Elem()
}
switch actualCurrValue.Kind() {
case reflect.Map:
if !steps.moreStepsRemaining() && !unset {
return fmt.Errorf("can't set a map to a value: %v", actualCurrValue)
}
mapKey := reflect.ValueOf(currStep.stepValue)
mapValueType := curr.Type().Elem().Elem()
if !steps.moreStepsRemaining() && unset {
actualCurrValue.SetMapIndex(mapKey, reflect.Value{})
return nil
}
currMapValue := actualCurrValue.MapIndex(mapKey)
needToSetNewMapValue := currMapValue.Kind() == reflect.Invalid
if needToSetNewMapValue {
currMapValue = reflect.New(mapValueType.Elem()).Elem().Addr()
actualCurrValue.SetMapIndex(mapKey, currMapValue)
}
err := modifyConfig(currMapValue, steps, propertyValue, unset, setRawBytes)
if err != nil {
return err
}
return nil
case reflect.String:
if steps.moreStepsRemaining() {
return fmt.Errorf("can't have more steps after a string. %v", steps)
}
actualCurrValue.SetString(propertyValue)
return nil
case reflect.Slice:
if steps.moreStepsRemaining() {
return fmt.Errorf("can't have more steps after bytes. %v", steps)
}
innerKind := actualCurrValue.Type().Elem().Kind()
if innerKind != reflect.Uint8 {
return fmt.Errorf("unrecognized slice type. %v", innerKind)
}
if unset {
actualCurrValue.Set(reflect.Zero(actualCurrValue.Type()))
return nil
}
if setRawBytes {
actualCurrValue.SetBytes([]byte(propertyValue))
} else {
val, err := base64.StdEncoding.DecodeString(propertyValue)
if err != nil {
return fmt.Errorf("error decoding input value: %v", err)
}
actualCurrValue.SetBytes(val)
}
return nil
case reflect.Bool:
if steps.moreStepsRemaining() {
return fmt.Errorf("can't have more steps after a bool. %v", steps)
}
boolValue, err := toBool(propertyValue)
if err != nil {
return err
}
actualCurrValue.SetBool(boolValue)
return nil
case reflect.Struct:
for fieldIndex := 0; fieldIndex < actualCurrValue.NumField(); fieldIndex++ {
currFieldValue := actualCurrValue.Field(fieldIndex)
currFieldType := actualCurrValue.Type().Field(fieldIndex)
currYamlTag := currFieldType.Tag.Get("json")
currFieldTypeYamlName := strings.Split(currYamlTag, ",")[0]
if currFieldTypeYamlName == currStep.stepValue {
thisMapHasNoValue := (currFieldValue.Kind() == reflect.Map && currFieldValue.IsNil())
if thisMapHasNoValue {
newValue := reflect.MakeMap(currFieldValue.Type())
currFieldValue.Set(newValue)
if !steps.moreStepsRemaining() && unset {
return nil
}
}
if !steps.moreStepsRemaining() && unset {
// if we're supposed to unset the value or if the value is a map that doesn't exist, create a new value and overwrite
newValue := reflect.New(currFieldValue.Type()).Elem()
currFieldValue.Set(newValue)
return nil
}
return modifyConfig(currFieldValue.Addr(), steps, propertyValue, unset, setRawBytes)
}
}
return fmt.Errorf("unable to locate path %#v under %v", currStep, actualCurrValue)
}
panic(fmt.Errorf("unrecognized type: %v", actualCurrValue))
}

View file

@ -0,0 +1,106 @@
/*
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 config
import (
"errors"
"fmt"
"io"
"reflect"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
type unsetOptions struct {
configAccess clientcmd.ConfigAccess
propertyName string
}
var unset_long = templates.LongDesc(`
Unsets an individual value in a kubeconfig file
PROPERTY_NAME is a dot delimited name where each token represents either an attribute name or a map key. Map keys may not contain dots.`)
func NewCmdConfigUnset(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &unsetOptions{configAccess: configAccess}
cmd := &cobra.Command{
Use: "unset PROPERTY_NAME",
Short: "Unsets an individual value in a kubeconfig file",
Long: unset_long,
Run: func(cmd *cobra.Command, args []string) {
if !options.complete(cmd) {
return
}
cmdutil.CheckErr(options.run())
fmt.Fprintf(out, "Property %q unset.\n", options.propertyName)
},
}
return cmd
}
func (o unsetOptions) run() error {
err := o.validate()
if err != nil {
return err
}
config, err := o.configAccess.GetStartingConfig()
if err != nil {
return err
}
steps, err := newNavigationSteps(o.propertyName)
if err != nil {
return err
}
err = modifyConfig(reflect.ValueOf(config), steps, "", true, true)
if err != nil {
return err
}
if err := clientcmd.ModifyConfig(o.configAccess, *config, false); err != nil {
return err
}
return nil
}
func (o *unsetOptions) complete(cmd *cobra.Command) bool {
endingArgs := cmd.Flags().Args()
if len(endingArgs) != 1 {
cmd.Help()
return false
}
o.propertyName = endingArgs[0]
return true
}
func (o unsetOptions) validate() error {
if len(o.propertyName) == 0 {
return errors.New("you must specify a property")
}
return nil
}

View file

@ -0,0 +1,107 @@
/*
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 config
import (
"errors"
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
var (
use_context_example = templates.Examples(`
# Use the context for the minikube cluster
kubectl config use-context minikube`)
)
type useContextOptions struct {
configAccess clientcmd.ConfigAccess
contextName string
}
func NewCmdConfigUseContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
options := &useContextOptions{configAccess: configAccess}
cmd := &cobra.Command{
Use: "use-context CONTEXT_NAME",
Short: "Sets the current-context in a kubeconfig file",
Long: `Sets the current-context in a kubeconfig file`,
Example: use_context_example,
Run: func(cmd *cobra.Command, args []string) {
if !options.complete(cmd) {
return
}
cmdutil.CheckErr(options.run())
fmt.Fprintf(out, "Switched to context %q.\n", options.contextName)
},
}
return cmd
}
func (o useContextOptions) run() error {
config, err := o.configAccess.GetStartingConfig()
if err != nil {
return err
}
err = o.validate(config)
if err != nil {
return err
}
config.CurrentContext = o.contextName
if err := clientcmd.ModifyConfig(o.configAccess, *config, true); err != nil {
return err
}
return nil
}
func (o *useContextOptions) complete(cmd *cobra.Command) bool {
endingArgs := cmd.Flags().Args()
if len(endingArgs) != 1 {
cmd.Help()
return false
}
o.contextName = endingArgs[0]
return true
}
func (o useContextOptions) validate(config *clientcmdapi.Config) error {
if len(o.contextName) == 0 {
return errors.New("you must specify a current-context")
}
for name := range config.Contexts {
if name == o.contextName {
return nil
}
}
return fmt.Errorf("no context exists with the name: %q.", o.contextName)
}

166
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/view.go generated vendored Normal file
View file

@ -0,0 +1,166 @@
/*
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 config
import (
"errors"
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/util/flag"
)
type ViewOptions struct {
ConfigAccess clientcmd.ConfigAccess
Merge flag.Tristate
Flatten bool
Minify bool
RawByteData bool
}
var (
view_long = templates.LongDesc(`
Display merged kubeconfig settings or a specified kubeconfig file.
You can use --output jsonpath={...} to extract specific values using a jsonpath expression.`)
view_example = templates.Examples(`
# Show Merged kubeconfig settings.
kubectl config view
# Get the password for the e2e user
kubectl config view -o jsonpath='{.users[?(@.name == "e2e")].user.password}'`)
)
func NewCmdConfigView(out io.Writer, ConfigAccess clientcmd.ConfigAccess) *cobra.Command {
options := &ViewOptions{ConfigAccess: ConfigAccess}
// Default to yaml
defaultOutputFormat := "yaml"
cmd := &cobra.Command{
Use: "view",
Short: "Display merged kubeconfig settings or a specified kubeconfig file",
Long: view_long,
Example: view_example,
Run: func(cmd *cobra.Command, args []string) {
options.Complete()
outputFormat := cmdutil.GetFlagString(cmd, "output")
if outputFormat == "wide" {
fmt.Printf("--output wide is not available in kubectl config view; reset to default output format (%s)\n\n", defaultOutputFormat)
cmd.Flags().Set("output", defaultOutputFormat)
}
if outputFormat == "" {
fmt.Printf("Reset to default output format (%s) as --output is empty\n", defaultOutputFormat)
cmd.Flags().Set("output", defaultOutputFormat)
}
printer, _, err := cmdutil.PrinterForCommand(cmd)
cmdutil.CheckErr(err)
version, err := cmdutil.OutputVersion(cmd, &latest.ExternalVersion)
cmdutil.CheckErr(err)
printer = kubectl.NewVersionedPrinter(printer, latest.Scheme, version)
cmdutil.CheckErr(options.Run(out, printer))
},
}
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().Set("output", defaultOutputFormat)
options.Merge.Default(true)
f := cmd.Flags().VarPF(&options.Merge, "merge", "", "merge the full hierarchy of kubeconfig files")
f.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")
return cmd
}
func (o ViewOptions) Run(out io.Writer, printer kubectl.ResourcePrinter) error {
config, err := o.loadConfig()
if err != nil {
return err
}
if o.Minify {
if err := clientcmdapi.MinifyConfig(config); err != nil {
return err
}
}
if o.Flatten {
if err := clientcmdapi.FlattenConfig(config); err != nil {
return err
}
} else if !o.RawByteData {
clientcmdapi.ShortenConfig(config)
}
err = printer.PrintObj(config, 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
}
func (o ViewOptions) loadConfig() (*clientcmdapi.Config, error) {
err := o.Validate()
if err != nil {
return nil, err
}
config, err := o.getStartingConfig()
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 {
case !o.Merge.Value():
return clientcmd.LoadFromFile(o.ConfigAccess.GetExplicitFile())
default:
return o.ConfigAccess.GetStartingConfig()
}
}

192
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/convert.go generated vendored Normal file
View file

@ -0,0 +1,192 @@
/*
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"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/api"
"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"
"github.com/spf13/cobra"
)
var (
convert_long = templates.LongDesc(`
Convert config files between different API versions. Both YAML
and JSON formats are accepted.
The command takes filename, directory, or URL as input, and convert it into format
of version specified by --output-version flag. If target version is not specified or
not supported, convert to latest version.
The default output will be printed to stdout in YAML format. One can use -o option
to change to output destination.`)
convert_example = templates.Examples(`
# Convert 'pod.yaml' to latest version and print to stdout.
kubectl convert -f pod.yaml
# Convert the live state of the resource specified by 'pod.yaml' to the latest version
# and print to stdout in json format.
kubectl convert -f pod.yaml --local -o json
# Convert all files under current directory to latest version and create them all.
kubectl convert -f . | kubectl create -f -`)
)
// NewCmdConvert creates a command object for the generic "convert" action, which
// translates the config file into a given version.
func NewCmdConvert(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &ConvertOptions{}
cmd := &cobra.Command{
Use: "convert -f FILENAME",
Short: "Convert config files between different API versions",
Long: convert_long,
Example: convert_example,
Run: func(cmd *cobra.Command, args []string) {
err := options.Complete(f, out, cmd, args)
cmdutil.CheckErr(err)
err = options.RunConvert()
cmdutil.CheckErr(err)
},
}
usage := "to need to get converted."
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.MarkFlagRequired("filename")
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().BoolVar(&options.local, "local", true, "If true, convert will NOT try to contact api-server but run locally.")
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
// ConvertOptions have the data required to perform the convert operation
type ConvertOptions struct {
resource.FilenameOptions
builder *resource.Builder
local bool
encoder runtime.Encoder
out io.Writer
printer kubectl.ResourcePrinter
outputVersion schema.GroupVersion
}
// Complete collects information required to run Convert command from command line.
func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) (err error) {
o.outputVersion, err = cmdutil.OutputVersion(cmd, &api.Registry.EnabledVersionsForGroup(api.GroupName)[0])
if err != nil {
return err
}
if !api.Registry.IsEnabledVersion(o.outputVersion) {
cmdutil.UsageError(cmd, "'%s' is not a registered version.", o.outputVersion)
}
// build the builder
mapper, typer := f.Object()
clientMapper := resource.ClientMapperFunc(f.ClientForMapping)
if o.local {
fmt.Fprintln(os.Stderr, "running in local mode...")
o.builder = resource.NewBuilder(mapper, typer, resource.DisabledClientForMapping{ClientMapper: clientMapper}, f.Decoder(true))
} else {
o.builder = resource.NewBuilder(mapper, typer, clientMapper, f.Decoder(true))
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
if err != nil {
return err
}
o.builder = o.builder.Schema(schema)
}
cmdNamespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}
o.builder = o.builder.NamespaceParam(cmdNamespace).
ContinueOnError().
FilenameParam(false, &o.FilenameOptions).
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"
}
}
o.encoder = f.JSONEncoder()
o.printer, _, err = kubectl.GetPrinter(outputFormat, templateFile, false, cmdutil.GetFlagBool(cmd, "allow-missing-template-keys"))
if err != nil {
return err
}
return nil
}
// RunConvert implements the generic Convert command
func (o *ConvertOptions) RunConvert() error {
r := o.builder.Do()
err := r.Err()
if err != nil {
return err
}
singleItemImplied := false
infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos()
if err != nil {
return err
}
if len(infos) == 0 {
return fmt.Errorf("no objects passed to convert")
}
objects, err := resource.AsVersionedObject(infos, !singleItemImplied, o.outputVersion, o.encoder)
if err != nil {
return err
}
if meta.IsListType(objects) {
_, items, err := cmdutil.FilterResourceList(objects, nil, nil)
if err != nil {
return err
}
filteredObj, err := cmdutil.ObjectListToVersionedObject(items, o.outputVersion)
if err != nil {
return err
}
return o.printer.PrintObj(filteredObj, o.out)
}
return o.printer.PrintObj(objects, o.out)
}

303
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cp.go generated vendored Normal file
View file

@ -0,0 +1,303 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"archive/tar"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"strings"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"github.com/renstrom/dedent"
"github.com/spf13/cobra"
)
var (
cp_example = templates.Examples(`
# !!!Important Note!!!
# Requires that the 'tar' binary is present in your container
# image. If 'tar' is not present, 'kubectl cp' will fail.
# Copy /tmp/foo_dir local directory to /tmp/bar_dir in a remote pod in the default namespace
kubectl cp /tmp/foo_dir <some-pod>:/tmp/bar_dir
# Copy /tmp/foo local file to /tmp/bar in a remote pod in a specific container
kubectl cp /tmp/foo <some-pod>:/tmp/bar -c <specific-container>
# Copy /tmp/foo local file to /tmp/bar in a remote pod in namespace <some-namespace>
kubectl cp /tmp/foo <some-namespace>/<some-pod>:/tmp/bar
# Copy /tmp/foo from a remote pod to /tmp/bar locally
kubectl cp <some-namespace>/<some-pod>:/tmp/foo /tmp/bar`)
cpUsageStr = dedent.Dedent(`
expected 'cp <file-spec-src> <file-spec-dest> [-c container]'.
<file-spec> is:
[namespace/]pod-name:/file/path for a remote file
/file/path for a local file`)
)
// NewCmdCp creates a new Copy command.
func NewCmdCp(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "cp <file-spec-src> <file-spec-dest>",
Short: "Copy files and directories to and from containers.",
Long: "Copy files and directories to and from containers.",
Example: cp_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(runCopy(f, cmd, cmdOut, cmdErr, args))
},
}
cmd.Flags().StringP("container", "c", "", "Container name. If omitted, the first container in the pod will be chosen")
return cmd
}
type fileSpec struct {
PodNamespace string
PodName string
File string
}
func extractFileSpec(arg string) (fileSpec, error) {
pieces := strings.Split(arg, ":")
if len(pieces) == 1 {
return fileSpec{File: arg}, nil
}
if len(pieces) != 2 {
return fileSpec{}, fmt.Errorf("Unexpected fileSpec: %s, expected [[namespace/]pod:]file/path", arg)
}
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
}
return fileSpec{}, fmt.Errorf("Unexpected file spec: %s, expected [[namespace/]pod:]file/path", arg)
}
func runCopy(f cmdutil.Factory, cmd *cobra.Command, out, cmderr io.Writer, args []string) error {
if len(args) != 2 {
return cmdutil.UsageError(cmd, cpUsageStr)
}
srcSpec, err := extractFileSpec(args[0])
if err != nil {
return err
}
destSpec, err := extractFileSpec(args[1])
if err != nil {
return err
}
if len(srcSpec.PodName) != 0 {
return copyFromPod(f, cmd, out, cmderr, srcSpec, destSpec)
}
if len(destSpec.PodName) != 0 {
return copyToPod(f, cmd, out, cmderr, srcSpec, destSpec)
}
return cmdutil.UsageError(cmd, "One of src or dest must be a remote file specification")
}
func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer, src, dest fileSpec) error {
reader, writer := io.Pipe()
go func() {
defer writer.Close()
err := makeTar(src.File, writer)
cmdutil.CheckErr(err)
}()
// TODO: Improve error messages by first testing if 'tar' is present in the container?
cmdArr := []string{"tar", "xf", "-"}
destDir := path.Dir(dest.File)
if len(destDir) > 0 {
cmdArr = append(cmdArr, "-C", destDir)
}
options := &ExecOptions{
StreamOptions: StreamOptions{
In: reader,
Out: stdout,
Err: stderr,
Stdin: true,
Namespace: dest.PodNamespace,
PodName: dest.PodName,
},
Command: cmdArr,
Executor: &DefaultRemoteExecutor{},
}
return execute(f, cmd, options)
}
func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, out, cmderr io.Writer, src, dest fileSpec) error {
reader, outStream := io.Pipe()
options := &ExecOptions{
StreamOptions: StreamOptions{
In: nil,
Out: outStream,
Err: cmderr,
Namespace: src.PodNamespace,
PodName: src.PodName,
},
// TODO: Improve error messages by first testing if 'tar' is present in the container?
Command: []string{"tar", "cf", "-", src.File},
Executor: &DefaultRemoteExecutor{},
}
go func() {
defer outStream.Close()
execute(f, cmd, options)
}()
prefix := getPrefix(src.File)
return untarAll(reader, dest.File, prefix)
}
func makeTar(filepath string, writer io.Writer) error {
// TODO: use compression here?
tarWriter := tar.NewWriter(writer)
defer tarWriter.Close()
return recursiveTar(path.Dir(filepath), path.Base(filepath), tarWriter)
}
func recursiveTar(base, file string, tw *tar.Writer) error {
filepath := path.Join(base, file)
stat, err := os.Stat(filepath)
if err != nil {
return err
}
if stat.IsDir() {
files, err := ioutil.ReadDir(filepath)
if err != nil {
return err
}
for _, f := range files {
if err := recursiveTar(base, path.Join(file, f.Name()), tw); err != nil {
return err
}
}
return nil
}
hdr, err := tar.FileInfoHeader(stat, filepath)
if err != nil {
return err
}
hdr.Name = file
if err := tw.WriteHeader(hdr); err != nil {
return err
}
f, err := os.Open(filepath)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(tw, f)
return err
}
func untarAll(reader io.Reader, destFile, prefix string) error {
// TODO: use compression here?
tarReader := tar.NewReader(reader)
for {
header, err := tarReader.Next()
if err != nil {
if err != io.EOF {
return err
}
break
}
outFileName := path.Join(destFile, header.Name[len(prefix):])
baseName := path.Dir(outFileName)
if err := os.MkdirAll(baseName, 0755); err != nil {
return err
}
if header.FileInfo().IsDir() {
os.MkdirAll(outFileName, 0755)
continue
}
outFile, err := os.Create(outFileName)
if err != nil {
return err
}
defer outFile.Close()
io.Copy(outFile, tarReader)
}
return nil
}
func getPrefix(file string) string {
if file[0] == '/' {
// tar strips the leading '/' if it's there, so we will too
return file[1:]
}
return file
}
func execute(f cmdutil.Factory, cmd *cobra.Command, options *ExecOptions) error {
if len(options.Namespace) == 0 {
namespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}
options.Namespace = namespace
}
container := cmdutil.GetFlagString(cmd, "container")
if len(container) > 0 {
options.ContainerName = 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()
if err := options.Validate(); err != nil {
return err
}
if err := options.Run(); err != nil {
return err
}
return nil
}

180
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cp_test.go generated vendored Normal file
View file

@ -0,0 +1,180 @@
/*
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 (
"bytes"
"io"
"io/ioutil"
"os"
"path"
"testing"
)
func TestExtractFileSpec(t *testing.T) {
tests := []struct {
spec string
expectedPod string
expectedNamespace string
expectedFile string
expectErr bool
}{
{
spec: "namespace/pod:/some/file",
expectedPod: "pod",
expectedNamespace: "namespace",
expectedFile: "/some/file",
},
{
spec: "pod:/some/file",
expectedPod: "pod",
expectedFile: "/some/file",
},
{
spec: "/some/file",
expectedFile: "/some/file",
},
{
spec: "some:bad:spec",
expectErr: true,
},
}
for _, test := range tests {
spec, err := extractFileSpec(test.spec)
if test.expectErr && err == nil {
t.Errorf("unexpected non-error")
continue
}
if err != nil && !test.expectErr {
t.Errorf("unexpected error: %v", err)
continue
}
if spec.PodName != test.expectedPod {
t.Errorf("expected: %s, saw: %s", test.expectedPod, spec.PodName)
}
if spec.PodNamespace != test.expectedNamespace {
t.Errorf("expected: %s, saw: %s", test.expectedNamespace, spec.PodNamespace)
}
if spec.File != test.expectedFile {
t.Errorf("expected: %s, saw: %s", test.expectedFile, spec.File)
}
}
}
func TestGetPrefix(t *testing.T) {
tests := []struct {
input string
expected string
}{
{
input: "/foo/bar",
expected: "foo/bar",
},
{
input: "foo/bar",
expected: "foo/bar",
},
}
for _, test := range tests {
out := getPrefix(test.input)
if out != test.expected {
t.Errorf("expected: %s, saw: %s", test.expected, out)
}
}
}
func TestTarUntar(t *testing.T) {
dir, err := ioutil.TempDir(os.TempDir(), "input")
dir2, err2 := ioutil.TempDir(os.TempDir(), "output")
if err != nil || err2 != nil {
t.Errorf("unexpected error: %v | %v", err, err2)
t.FailNow()
}
defer func() {
if err := os.RemoveAll(dir); err != nil {
t.Errorf("Unexpected error cleaning up: %v", err)
}
if err := os.RemoveAll(dir2); err != nil {
t.Errorf("Unexpected error cleaning up: %v", err)
}
}()
files := []struct {
name string
data string
}{
{
name: "foo",
data: "foobarbaz",
},
{
name: "dir/blah",
data: "bazblahfoo",
},
{
name: "some/other/directory",
data: "with more data here",
},
{
name: "blah",
data: "same file name different data",
},
}
for _, file := range files {
filepath := path.Join(dir, file.name)
if err := os.MkdirAll(path.Dir(filepath), 0755); err != nil {
t.Errorf("unexpected error: %v", err)
t.FailNow()
}
f, err := os.Create(filepath)
if err != nil {
t.Errorf("unexpected error: %v", err)
t.FailNow()
}
defer f.Close()
if _, err := io.Copy(f, bytes.NewBuffer([]byte(file.data))); err != nil {
t.Errorf("unexpected error: %v", err)
t.FailNow()
}
}
writer := &bytes.Buffer{}
if err := makeTar(dir, writer); err != nil {
t.Errorf("unexpected error: %v", err)
}
reader := bytes.NewBuffer(writer.Bytes())
if err := untarAll(reader, dir2, ""); err != nil {
t.Errorf("unexpected error: %v", err)
t.FailNow()
}
for _, file := range files {
filepath := path.Join(dir, file.name)
f, err := os.Open(filepath)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
defer f.Close()
buff := &bytes.Buffer{}
io.Copy(buff, f)
if file.data != string(buff.Bytes()) {
t.Errorf("expected: %s, saw: %s", file.data, string(buff.Bytes()))
}
}
}

262
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create.go generated vendored Normal file
View file

@ -0,0 +1,262 @@
/*
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"
gruntime "runtime"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"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/resource"
)
var (
create_long = templates.LongDesc(`
Create a resource by filename or stdin.
JSON and YAML formats are accepted.`)
create_example = templates.Examples(`
# 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 := &resource.FilenameOptions{}
cmd := &cobra.Command{
Use: "create -f FILENAME",
Short: "Create a resource by filename or stdin",
Long: create_long,
Example: create_example,
Run: func(cmd *cobra.Command, args []string) {
if cmdutil.IsFilenameEmpty(options.Filenames) {
defaultRunFunc := cmdutil.DefaultSubCommandRun(errOut)
defaultRunFunc(cmd, args)
return
}
cmdutil.CheckErr(ValidateArgs(cmd, args))
cmdutil.CheckErr(RunCreate(f, cmd, out, errOut, options))
},
}
usage := "to use to create the resource"
cmdutil.AddFilenameOptionFlags(cmd, options, usage)
cmd.MarkFlagRequired("filename")
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().Bool("edit", false, "Edit the API resource before creating")
cmd.Flags().Bool("windows-line-endings", gruntime.GOOS == "windows", "Only relevant if --edit=true. Use Windows line-endings (default Unix line-endings)")
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
// 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))
cmd.AddCommand(NewCmdCreateClusterRoleBinding(f, out))
cmd.AddCommand(NewCmdCreateRoleBinding(f, out))
cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, out))
return cmd
}
func ValidateArgs(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return cmdutil.UsageError(cmd, "Unexpected args: %v", args)
}
return nil
}
func RunCreate(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, options *resource.FilenameOptions) error {
if cmdutil.GetFlagBool(cmd, "edit") {
return RunEditOnCreate(f, out, errOut, cmd, options)
}
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
if err != nil {
return err
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
dryRun := cmdutil.GetFlagBool(cmd, "dry-run")
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, f.JSONEncoder()); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
if cmdutil.ShouldRecord(cmd, info) {
if err := cmdutil.RecordChangeCause(info.Object, f.Command()); 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.PrintResourceInfoForCommand(cmd, info, f, out)
}
if !shortOutput {
f.PrintObjectSpecificMessage(info.Object, out)
}
cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, dryRun, "created")
return nil
})
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("no objects passed to create")
}
return nil
}
func RunEditOnCreate(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, options *resource.FilenameOptions) error {
return runEdit(f, out, errOut, cmd, []string{}, options, EditBeforeCreateMode)
}
// 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) == 0 {
return "", cmdutil.UsageError(cmd, "NAME is required")
}
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
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, _, 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.UpdateApplyAnnotation(info, f.JSONEncoder()); err != nil {
return err
}
if !options.DryRun {
obj, err = resource.NewHelper(client, mapping).Create(namespace, false, info.Object)
if err != nil {
return err
}
}
if useShortOutput := options.OutputFormat == "name"; useShortOutput || len(options.OutputFormat) == 0 {
cmdutil.PrintSuccess(mapper, useShortOutput, out, mapping.Resource, options.Name, options.DryRun, "created")
return nil
}
return f.PrintObject(cmd, mapper, obj, out)
}

View file

@ -0,0 +1,87 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"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"
)
var (
clusterRoleBindingLong = templates.LongDesc(`
Create a ClusterRoleBinding for a particular ClusterRole.`)
clusterRoleBindingExample = templates.Examples(`
# Create a ClusterRoleBinding for user1, user2, and group1 using the cluster-admin ClusterRole
kubectl create clusterrolebinding cluster-admin --clusterrole=cluster-admin --user=user1 --user=user2 --group=group1`)
)
// ClusterRoleBinding is a command to ease creating ClusterRoleBindings.
func NewCmdCreateClusterRoleBinding(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "clusterrolebinding NAME --clusterrole=NAME [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run]",
Short: "Create a ClusterRoleBinding for a particular ClusterRole",
Long: clusterRoleBindingLong,
Example: clusterRoleBindingExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateClusterRoleBinding(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ClusterRoleBindingV1GeneratorName)
cmd.Flags().String("clusterrole", "", "ClusterRole this ClusterRoleBinding should reference")
cmd.Flags().StringSlice("user", []string{}, "usernames to bind to the role")
cmd.Flags().StringSlice("group", []string{}, "groups to bind to the role")
cmd.Flags().StringSlice("serviceaccount", []string{}, "service accounts to bind to the role")
return cmd
}
// CreateClusterRoleBinding is the implementation of the create configmap command.
func CreateClusterRoleBinding(f cmdutil.Factory, cmdOut io.Writer, 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:
generator = &kubectl.ClusterRoleBindingGeneratorV1{
Name: name,
ClusterRole: cmdutil.GetFlagString(cmd, "clusterrole"),
Users: cmdutil.GetFlagStringSlice(cmd, "user"),
Groups: cmdutil.GetFlagStringSlice(cmd, "group"),
ServiceAccounts: cmdutil.GetFlagStringSlice(cmd, "serviceaccount"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}

View file

@ -0,0 +1,99 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"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"
)
var (
configMapLong = templates.LongDesc(`
Create a configmap based on a file, directory, or specified literal value.
A single configmap may package one or more key/value pairs.
When creating a configmap based on a file, the key will default to the basename of the file, and the value will
default to the file content. If the basename is an invalid key, you may specify an alternate key.
When creating a configmap based on a directory, each file whose basename is a valid key in the directory will be
packaged into the configmap. Any directory entries except regular files are ignored (e.g. subdirectories,
symlinks, devices, pipes, etc).`)
configMapExample = templates.Examples(`
# Create a new configmap named my-config with keys for each file in folder bar
kubectl create configmap my-config --from-file=path/to/bar
# Create a new configmap named my-config with specified keys instead of names on disk
kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt
# Create a new configmap named my-config with key1=config1 and key2=config2
kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2`)
)
// ConfigMap is a command to ease creating ConfigMaps.
func NewCmdCreateConfigMap(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run]",
Aliases: []string{"cm"},
Short: "Create a configmap from a local file, directory or literal value",
Long: configMapLong,
Example: configMapExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateConfigMap(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ConfigMapV1GeneratorName)
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 configmap key.")
cmd.Flags().StringArray("from-literal", []string{}, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
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 {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.ConfigMapV1GeneratorName:
generator = &kubectl.ConfigMapGeneratorV1{
Name: name,
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
LiteralSources: cmdutil.GetFlagStringArray(cmd, "from-literal"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}

View file

@ -0,0 +1,55 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"net/http"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestCreateConfigMap(t *testing.T) {
configMap := &api.ConfigMap{}
configMap.Name = "my-configmap"
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/configmaps" && m == "POST":
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, configMap)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateConfigMap(f, buf)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{configMap.Name})
expectedOutput := "configmap/" + configMap.Name + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
}
}

View file

@ -0,0 +1,80 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"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"
)
var (
deploymentLong = templates.LongDesc(`
Create a deployment with the specified name.`)
deploymentExample = templates.Examples(`
# Create a new deployment named my-dep that runs the busybox image.
kubectl create deployment my-dep --image=busybox`)
)
// NewCmdCreateDeployment is a macro command to create a new deployment
func NewCmdCreateDeployment(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "deployment NAME --image=image [--dry-run]",
Aliases: []string{"deploy"},
Short: "Create a deployment with the specified name.",
Long: deploymentLong,
Example: deploymentExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateDeployment(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.DeploymentBasicV1Beta1GeneratorName)
cmd.Flags().StringSlice("image", []string{}, "Image name to run.")
cmd.MarkFlagRequired("image")
return cmd
}
// CreateDeployment implements the behavior to run the create deployment command
func CreateDeployment(f cmdutil.Factory, cmdOut io.Writer, 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.DeploymentBasicV1Beta1GeneratorName:
generator = &kubectl.DeploymentBasicGeneratorV1{Name: name, Images: cmdutil.GetFlagStringSlice(cmd, "image")}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}

View file

@ -0,0 +1,56 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestCreateDeployment(t *testing.T) {
depName := "jonny-dep"
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateDeployment(f, buf)
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/" + depName + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
}
}
func TestCreateDeploymentNoImage(t *testing.T) {
depName := "jonny-dep"
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateDeployment(f, buf)
cmd.Flags().Set("dry-run", "true")
cmd.Flags().Set("output", "name")
err := CreateDeployment(f, buf, cmd, []string{depName})
assert.Error(t, err, "at least one image must be specified")
}

View file

@ -0,0 +1,79 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"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"
)
var (
namespaceLong = templates.LongDesc(`
Create a namespace with the specified name.`)
namespaceExample = templates.Examples(`
# Create a new namespace named my-namespace
kubectl create namespace my-namespace`)
)
// NewCmdCreateNamespace is a macro command to create a new namespace
func NewCmdCreateNamespace(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "namespace NAME [--dry-run]",
Aliases: []string{"ns"},
Short: "Create a namespace with the specified name",
Long: namespaceLong,
Example: namespaceExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateNamespace(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
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 {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.NamespaceV1GeneratorName:
generator = &kubectl.NamespaceGeneratorV1{Name: name}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}

View file

@ -0,0 +1,54 @@
/*
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 (
"bytes"
"net/http"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestCreateNamespace(t *testing.T) {
namespaceObject := &api.Namespace{}
namespaceObject.Name = "my-namespace"
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces" && m == "POST":
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, namespaceObject)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateNamespace(f, buf)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{namespaceObject.Name})
expectedOutput := "namespace/" + namespaceObject.Name + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
}
}

90
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_pdb.go generated vendored Normal file
View file

@ -0,0 +1,90 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"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"
)
var (
pdbLong = templates.LongDesc(`
Create a pod disruption budget with the specified name, selector, and desired minimum available pods`)
pdbExample = templates.Examples(`
# Create a pod disruption budget named my-pdb that will select all pods with the app=rails label
# and require at least one of them being available at any point in time.
kubectl create poddisruptionbudget my-pdb --selector=app=rails --min-available=1
# Create a pod disruption budget named my-pdb that will select all pods with the app=nginx label
# and require at least half of the pods selected to be available at any point in time.
kubectl create pdb my-pdb --selector=app=nginx --min-available=50%`)
)
// NewCmdCreatePodDisruptionBudget is a macro command to create a new pod disruption budget.
func NewCmdCreatePodDisruptionBudget(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "poddisruptionbudget NAME --selector=SELECTOR --min-available=N [--dry-run]",
Aliases: []string{"pdb"},
Short: "Create a pod disruption budget with the specified name.",
Long: pdbLong,
Example: pdbExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreatePodDisruptionBudget(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.PodDisruptionBudgetV1GeneratorName)
cmd.Flags().String("min-available", "1", "The minimum number or percentage of available pods this budget requires.")
cmd.Flags().String("selector", "", "A label selector to use for this budget. Only equality-based selector requirements are supported.")
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 {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.PodDisruptionBudgetV1GeneratorName:
generator = &kubectl.PodDisruptionBudgetV1Generator{
Name: name,
MinAvailable: cmdutil.GetFlagString(cmd, "min-available"),
Selector: cmdutil.GetFlagString(cmd, "selector"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetFlagBool(cmd, "dry-run"),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}

View file

@ -0,0 +1,88 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"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"
)
var (
quotaLong = templates.LongDesc(`
Create a resourcequota with the specified name, hard limits and optional scopes`)
quotaExample = templates.Examples(`
# Create a new resourcequota named my-quota
kubectl create quota my-quota --hard=cpu=1,memory=1G,pods=2,services=3,replicationcontrollers=2,resourcequotas=1,secrets=5,persistentvolumeclaims=10
# Create a new resourcequota named best-effort
kubectl create quota best-effort --hard=pods=100 --scopes=BestEffort`)
)
// NewCmdCreateQuota is a macro command to create a new quota
func NewCmdCreateQuota(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "quota NAME [--hard=key1=value1,key2=value2] [--scopes=Scope1,Scope2] [--dry-run=bool]",
Aliases: []string{"resourcequota"},
Short: "Create a quota with the specified name.",
Long: quotaLong,
Example: quotaExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateQuota(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ResourceQuotaV1GeneratorName)
cmd.Flags().String("hard", "", "A comma-delimited set of resource=quantity pairs that define a hard limit.")
cmd.Flags().String("scopes", "", "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 {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.ResourceQuotaV1GeneratorName:
generator = &kubectl.ResourceQuotaGeneratorV1{
Name: name,
Hard: cmdutil.GetFlagString(cmd, "hard"),
Scopes: cmdutil.GetFlagString(cmd, "scopes"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetFlagBool(cmd, "dry-run"),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}

View file

@ -0,0 +1,80 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"net/http"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestCreateQuota(t *testing.T) {
resourceQuotaObject := &api.ResourceQuota{}
resourceQuotaObject.Name = "my-quota"
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/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 map[string]string
expectedOutput string
}{
"single resource": {
flags: map[string]string{"hard": "cpu=1", "output": "name"},
expectedOutput: "resourcequota/" + resourceQuotaObject.Name + "\n",
},
"single resource with a scope": {
flags: map[string]string{"hard": "cpu=1", "output": "name", "scopes": "BestEffort"},
expectedOutput: "resourcequota/" + resourceQuotaObject.Name + "\n",
},
"multiple resources": {
flags: map[string]string{"hard": "cpu=1,pods=42", "output": "name", "scopes": "BestEffort"},
expectedOutput: "resourcequota/" + resourceQuotaObject.Name + "\n",
},
"single resource with multiple scopes": {
flags: map[string]string{"hard": "cpu=1", "output": "name", "scopes": "BestEffort,NotTerminating"},
expectedOutput: "resourcequota/" + resourceQuotaObject.Name + "\n",
},
}
for name, test := range tests {
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateQuota(f, buf)
cmd.Flags().Set("hard", "cpu=1")
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

@ -0,0 +1,88 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"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"
)
var (
roleBindingLong = templates.LongDesc(`
Create a RoleBinding for a particular Role or ClusterRole.`)
roleBindingExample = templates.Examples(`
# Create a RoleBinding for user1, user2, and group1 using the admin ClusterRole
kubectl create rolebinding admin --clusterrole=admin --user=user1 --user=user2 --group=group1`)
)
// RoleBinding is a command to ease creating RoleBindings.
func NewCmdCreateRoleBinding(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "rolebinding NAME --clusterrole=NAME|--role=NAME [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run]",
Short: "Create a RoleBinding for a particular Role or ClusterRole",
Long: roleBindingLong,
Example: roleBindingExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateRoleBinding(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.RoleBindingV1GeneratorName)
cmd.Flags().String("clusterrole", "", "ClusterRole this RoleBinding should reference")
cmd.Flags().String("role", "", "Role this RoleBinding should reference")
cmd.Flags().StringSlice("user", []string{}, "usernames to bind to the role")
cmd.Flags().StringSlice("group", []string{}, "groups to bind to the role")
cmd.Flags().StringSlice("serviceaccount", []string{}, "service accounts to bind to the role")
return cmd
}
func CreateRoleBinding(f cmdutil.Factory, cmdOut io.Writer, 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:
generator = &kubectl.RoleBindingGeneratorV1{
Name: name,
ClusterRole: cmdutil.GetFlagString(cmd, "clusterrole"),
Role: cmdutil.GetFlagString(cmd, "role"),
Users: cmdutil.GetFlagStringSlice(cmd, "user"),
Groups: cmdutil.GetFlagStringSlice(cmd, "group"),
ServiceAccounts: cmdutil.GetFlagStringSlice(cmd, "serviceaccount"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}

View file

@ -0,0 +1,260 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"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"
)
// NewCmdCreateSecret groups subcommands to create various types of secrets
func NewCmdCreateSecret(f cmdutil.Factory, cmdOut, errOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "secret",
Short: "Create a secret using specified subcommand",
Long: "Create a secret using specified subcommand.",
Run: cmdutil.DefaultSubCommandRun(errOut),
}
cmd.AddCommand(NewCmdCreateSecretDockerRegistry(f, cmdOut))
cmd.AddCommand(NewCmdCreateSecretTLS(f, cmdOut))
cmd.AddCommand(NewCmdCreateSecretGeneric(f, cmdOut))
return cmd
}
var (
secretLong = templates.LongDesc(`
Create a secret based on a file, directory, or specified literal value.
A single secret may package one or more key/value pairs.
When creating a secret based on a file, the key will default to the basename of the file, and the value will
default to the file content. If the basename is an invalid key, you may specify an alternate key.
When creating a secret based on a directory, each file whose basename is a valid key in the directory will be
packaged into the secret. Any directory entries except regular files are ignored (e.g. subdirectories,
symlinks, devices, pipes, etc).`)
secretExample = templates.Examples(`
# Create a new secret named my-secret with keys for each file in folder bar
kubectl create secret generic my-secret --from-file=path/to/bar
# Create a new secret named my-secret with specified keys instead of names on disk
kubectl create secret generic my-secret --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub
# Create a new secret named my-secret with key1=supersecret and key2=topsecret
kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret`)
)
// NewCmdCreateSecretGeneric is a command to create generic secrets from files, directories, or literal values
func NewCmdCreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "generic NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run]",
Short: "Create a secret from a local file, directory or literal value",
Long: secretLong,
Example: secretExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateSecretGeneric(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
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)")
cmd.Flags().String("type", "", "The type of secret to create")
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 {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.SecretV1GeneratorName:
generator = &kubectl.SecretGeneratorV1{
Name: name,
Type: cmdutil.GetFlagString(cmd, "type"),
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
LiteralSources: cmdutil.GetFlagStringArray(cmd, "from-literal"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}
var (
secretForDockerRegistryLong = templates.LongDesc(`
Create a new secret for use with Docker registries.
Dockercfg secrets are used to authenticate against Docker registries.
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
authenticate to the registry.
When creating applications, you may have a Docker registry that requires authentication. In order for the
nodes to pull images on your behalf, they have to have the credentials. You can provide this information
by creating a dockercfg secret and attaching it to your service account.`)
secretForDockerRegistryExample = templates.Examples(`
# If you don't already have a .dockercfg file, you can create a dockercfg secret directly by using:
kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL`)
)
// NewCmdCreateSecretDockerRegistry is a macro command for creating secrets to work with Docker registries
func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
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]",
Short: "Create a secret for use with a Docker registry",
Long: secretForDockerRegistryLong,
Example: secretForDockerRegistryExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateSecretDockerRegistry(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.SecretForDockerRegistryV1GeneratorName)
cmd.Flags().String("docker-username", "", "Username for Docker registry authentication")
cmd.MarkFlagRequired("docker-username")
cmd.Flags().String("docker-password", "", "Password for Docker registry authentication")
cmd.MarkFlagRequired("docker-password")
cmd.Flags().String("docker-email", "", "Email for Docker registry")
cmd.MarkFlagRequired("docker-email")
cmd.Flags().String("docker-server", "https://index.docker.io/v1/", "Server location for Docker registry")
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 {
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.UsageError(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"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}
var (
secretForTLSLong = templates.LongDesc(`
Create a TLS secret from the given public/private key pair.
The public/private key pair must exist before hand. The public key certificate must be .PEM encoded and match the given private key.`)
secretForTLSExample = templates.Examples(`
# Create a new TLS secret named tls-secret with the given key pair:
kubectl create secret tls tls-secret --cert=path/to/tls.cert --key=path/to/tls.key`)
)
// NewCmdCreateSecretTLS is a macro command for creating secrets to work with Docker registries
func NewCmdCreateSecretTLS(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "tls NAME --cert=path/to/cert/file --key=path/to/key/file [--dry-run]",
Short: "Create a TLS secret",
Long: secretForTLSLong,
Example: secretForTLSExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateSecretTLS(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.SecretForTLSV1GeneratorName)
cmd.Flags().String("cert", "", "Path to PEM encoded public key certificate.")
cmd.Flags().String("key", "", "Path to private key associated with given certificate.")
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 {
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 {
return cmdutil.UsageError(cmd, "flag %s is required", requiredFlag)
}
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.SecretForTLSV1GeneratorName:
generator = &kubectl.SecretForTLSGeneratorV1{
Name: name,
Key: cmdutil.GetFlagString(cmd, "key"),
Cert: cmdutil.GetFlagString(cmd, "cert"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetFlagBool(cmd, "dry-run"),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}

View file

@ -0,0 +1,93 @@
/*
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 (
"bytes"
"net/http"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestCreateSecretGeneric(t *testing.T) {
secretObject := &api.Secret{
Data: map[string][]byte{
"password": []byte("includes,comma"),
"username": []byte("test_user"),
},
}
secretObject.Name = "my-secret"
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/secrets" && m == "POST":
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, secretObject)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateSecretGeneric(f, buf)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("from-literal", "password=includes,comma")
cmd.Flags().Set("from-literal", "username=test_user")
cmd.Run(cmd, []string{secretObject.Name})
expectedOutput := "secret/" + secretObject.Name + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
}
}
func TestCreateSecretDockerRegistry(t *testing.T) {
secretObject := &api.Secret{}
secretObject.Name = "my-secret"
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/secrets" && m == "POST":
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, secretObject)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateSecretDockerRegistry(f, buf)
cmd.Flags().Set("docker-username", "test-user")
cmd.Flags().Set("docker-password", "test-pass")
cmd.Flags().Set("docker-email", "test-email")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{secretObject.Name})
expectedOutput := "secret/" + secretObject.Name + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", buf.String(), expectedOutput)
}
}

View file

@ -0,0 +1,282 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
// NewCmdCreateService is a macro command to create a new service
func NewCmdCreateService(f cmdutil.Factory, cmdOut, errOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "service",
Aliases: []string{"svc"},
Short: "Create a service using specified subcommand.",
Long: "Create a service using specified subcommand.",
Run: cmdutil.DefaultSubCommandRun(errOut),
}
cmd.AddCommand(NewCmdCreateServiceClusterIP(f, cmdOut))
cmd.AddCommand(NewCmdCreateServiceNodePort(f, cmdOut))
cmd.AddCommand(NewCmdCreateServiceLoadBalancer(f, cmdOut))
cmd.AddCommand(NewCmdCreateServiceExternalName(f, cmdOut))
return cmd
}
var (
serviceClusterIPLong = templates.LongDesc(`
Create a clusterIP service with the specified name.`)
serviceClusterIPExample = templates.Examples(`
# Create a new clusterIP service named my-cs
kubectl create service clusterip my-cs --tcp=5678:8080
# Create a new clusterIP service named my-cs (in headless mode)
kubectl create service clusterip my-cs --clusterip="None"`)
)
func addPortFlags(cmd *cobra.Command) {
cmd.Flags().StringSlice("tcp", []string{}, "Port pairs can be specified as '<port>:<targetPort>'.")
}
// NewCmdCreateServiceClusterIP is a command to create a clusterIP service
func NewCmdCreateServiceClusterIP(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "clusterip NAME [--tcp=<port>:<targetPort>] [--dry-run]",
Short: "Create a clusterIP service.",
Long: serviceClusterIPLong,
Example: serviceClusterIPExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateServiceClusterIP(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceClusterIPGeneratorV1Name)
addPortFlags(cmd)
cmd.Flags().String("clusterip", "", "Assign your own ClusterIP or set to 'None' for a 'headless' service (no loadbalancing).")
return cmd
}
// CreateServiceClusterIP implements the behavior to run the create service clusterIP command
func CreateServiceClusterIP(f cmdutil.Factory, cmdOut io.Writer, 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:
generator = &kubectl.ServiceCommonGeneratorV1{
Name: name,
TCP: cmdutil.GetFlagStringSlice(cmd, "tcp"),
Type: api.ServiceTypeClusterIP,
ClusterIP: cmdutil.GetFlagString(cmd, "clusterip"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}
var (
serviceNodePortLong = templates.LongDesc(`
Create a nodeport service with the specified name.`)
serviceNodePortExample = templates.Examples(`
# Create a new nodeport service named my-ns
kubectl create service nodeport my-ns --tcp=5678:8080`)
)
// NewCmdCreateServiceNodePort is a macro command for creating a NodePort service
func NewCmdCreateServiceNodePort(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "nodeport NAME [--tcp=port:targetPort] [--dry-run]",
Short: "Create a NodePort service.",
Long: serviceNodePortLong,
Example: serviceNodePortExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateServiceNodePort(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
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 {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.ServiceNodePortGeneratorV1Name:
generator = &kubectl.ServiceCommonGeneratorV1{
Name: name,
TCP: cmdutil.GetFlagStringSlice(cmd, "tcp"),
Type: api.ServiceTypeNodePort,
ClusterIP: "",
NodePort: cmdutil.GetFlagInt(cmd, "node-port"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}
var (
serviceLoadBalancerLong = templates.LongDesc(`
Create a LoadBalancer service with the specified name.`)
serviceLoadBalancerExample = templates.Examples(`
# Create a new LoadBalancer service named my-lbs
kubectl create service loadbalancer my-lbs --tcp=5678:8080`)
)
// NewCmdCreateServiceLoadBalancer is a macro command for creating a LoadBalancer service
func NewCmdCreateServiceLoadBalancer(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "loadbalancer NAME [--tcp=port:targetPort] [--dry-run]",
Short: "Create a LoadBalancer service.",
Long: serviceLoadBalancerLong,
Example: serviceLoadBalancerExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateServiceLoadBalancer(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceLoadBalancerGeneratorV1Name)
addPortFlags(cmd)
return cmd
}
// CreateServiceLoadBalancer is the implementation of the service loadbalancer command
func CreateServiceLoadBalancer(f cmdutil.Factory, cmdOut io.Writer, 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:
generator = &kubectl.ServiceCommonGeneratorV1{
Name: name,
TCP: cmdutil.GetFlagStringSlice(cmd, "tcp"),
Type: api.ServiceTypeLoadBalancer,
ClusterIP: "",
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetFlagBool(cmd, "dry-run"),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}
var (
serviceExternalNameLong = templates.LongDesc(`
Create an ExternalName service with the specified name.
ExternalName service references to an external DNS address instead of
only pods, which will allow application authors to reference services
that exist off platform, on other clusters, or locally.`)
serviceExternalNameExample = templates.Examples(`
# Create a new ExternalName service named my-ns
kubectl create service externalname my-ns --external-name bar.com`)
)
// NewCmdCreateServiceExternalName is a macro command for creating a ExternalName service
func NewCmdCreateServiceExternalName(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "externalname NAME --external-name external.name [--dry-run]",
Short: "Create an ExternalName service.",
Long: serviceExternalNameLong,
Example: serviceExternalNameExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateExternalNameService(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceExternalNameGeneratorV1Name)
addPortFlags(cmd)
cmd.Flags().String("external-name", "", "external name of service")
cmd.MarkFlagRequired("external-name")
return cmd
}
// CreateExternalNameService is the implementation of the service externalname command
func CreateExternalNameService(f cmdutil.Factory, cmdOut io.Writer, 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:
generator = &kubectl.ServiceCommonGeneratorV1{
Name: name,
Type: api.ServiceTypeExternalName,
ExternalName: cmdutil.GetFlagString(cmd, "external-name"),
ClusterIP: "",
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}

View file

@ -0,0 +1,56 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"net/http"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestCreateService(t *testing.T) {
service := &api.Service{}
service.Name = "my-service"
f, tf, codec, negSer := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: negSer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/services" && m == "POST":
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, service)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateServiceClusterIP(f, buf)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("tcp", "8080:8000")
cmd.Run(cmd, []string{service.Name})
expectedOutput := "service/" + service.Name + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
}
}

View file

@ -0,0 +1,79 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"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"
)
var (
serviceAccountLong = templates.LongDesc(`
Create a service account with the specified name.`)
serviceAccountExample = templates.Examples(`
# Create a new service account named my-service-account
kubectl create serviceaccount my-service-account`)
)
// NewCmdCreateServiceAccount is a macro command to create a new service account
func NewCmdCreateServiceAccount(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "serviceaccount NAME [--dry-run]",
Aliases: []string{"sa"},
Short: "Create a service account with the specified name",
Long: serviceAccountLong,
Example: serviceAccountExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateServiceAccount(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
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 {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.ServiceAccountV1GeneratorName:
generator = &kubectl.ServiceAccountGeneratorV1{Name: name}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetDryRunFlag(cmd),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}

View file

@ -0,0 +1,55 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"net/http"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestCreateServiceAccount(t *testing.T) {
serviceAccountObject := &api.ServiceAccount{}
serviceAccountObject.Name = "my-service-account"
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/serviceaccounts" && m == "POST":
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, serviceAccountObject)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateServiceAccount(f, buf)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{serviceAccountObject.Name})
expectedOutput := "serviceaccount/" + serviceAccountObject.Name + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
}
}

145
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_test.go generated vendored Normal file
View file

@ -0,0 +1,145 @@
/*
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 (
"bytes"
"net/http"
"testing"
"k8s.io/kubernetes/pkg/client/restclient/fake"
"k8s.io/kubernetes/pkg/client/typed/dynamic"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestExtraArgsFail(t *testing.T) {
initTestErrorHandler(t)
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
f, _, _, _ := cmdtesting.NewAPIFactory()
c := NewCmdCreate(f, buf, errBuf)
if ValidateArgs(c, []string{"rc"}) == nil {
t.Errorf("unexpected non-error")
}
}
func TestCreateObject(t *testing.T) {
initTestErrorHandler(t)
_, _, rc := testData()
rc.Items[0].Name = "redis-master-controller"
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreate(f, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
// uses the name from the file, not the response
if buf.String() != "replicationcontroller/redis-master-controller\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestCreateMultipleObject(t *testing.T) {
initTestErrorHandler(t)
_, svc, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/services" && m == http.MethodPost:
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreate(f, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
// Names should come from the REST response, NOT the files
if buf.String() != "replicationcontroller/rc1\nservice/baz\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestCreateDirectory(t *testing.T) {
initTestErrorHandler(t)
_, _, rc := testData()
rc.Items[0].Name = "name"
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreate(f, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontroller/name\nreplicationcontroller/name\nreplicationcontroller/name\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}

335
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete.go generated vendored Normal file
View file

@ -0,0 +1,335 @@
/*
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"
"time"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/api"
"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"
)
var (
delete_long = templates.LongDesc(`
Delete resources by filenames, stdin, resources and names, or by resources and label selector.
JSON and YAML formats are accepted. Only one type of the arguments may be specified: filenames,
resources and names, or resources and label selector.
Some resources, such as pods, support graceful deletion. These resources define a default period
before they are forcibly terminated (the grace period) but you may override that value with
the --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often
represent entities in the cluster, deletion may not be acknowledged immediately. If the node
hosting a pod is down or cannot reach the API server, termination may take significantly longer
than the grace period. To force delete a resource, you must pass a grace period of 0 and specify
the --force flag.
IMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been
terminated, which can leave those processes running until the node detects the deletion and
completes graceful deletion. If your processes use shared storage or talk to a remote API and
depend on the name of the pod to identify themselves, force deleting those pods may result in
multiple processes running on different machines using the same identification which may lead
to data corruption or inconsistency. Only force delete pods when you are sure the pod is
terminated, or if your application can tolerate multiple copies of the same pod running at once.
Also, if you force delete pods the scheduler may place new pods on those nodes before the node
has released those resources and causing those pods to be evicted immediately.
Note that the delete command does NOT do resource version checks, so if someone
submits an update to a resource right when you submit a delete, their update
will be lost along with the rest of the resource.`)
delete_example = templates.Examples(`
# Delete a pod using the type and name specified in pod.json.
kubectl delete -f ./pod.json
# Delete a pod based on the type and name in the JSON passed into stdin.
cat pod.json | kubectl delete -f -
# Delete pods and services with same names "baz" and "foo"
kubectl delete pod,service baz foo
# Delete pods and services with label name=myLabel.
kubectl delete pods,services -l name=myLabel
# Delete a pod with minimal delay
kubectl delete pod foo --now
# Force delete a pod on a dead node
kubectl delete pod foo --grace-period=0 --force
# Delete a pod with UID 1234-56-7890-234234-456456.
kubectl delete pod 1234-56-7890-234234-456456
# Delete all pods
kubectl delete pods --all`)
)
type DeleteOptions struct {
resource.FilenameOptions
Selector string
DeleteAll bool
IgnoreNotFound bool
Cascade bool
DeleteNow bool
ForceDeletion bool
WaitForDeletion bool
GracePeriod int
Timeout time.Duration
Include3rdParty bool
Output string
Mapper meta.RESTMapper
Result *resource.Result
f cmdutil.Factory
Out io.Writer
ErrOut io.Writer
}
func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := &DeleteOptions{}
// retrieve a list of handled resources from printer as valid args
validArgs, argAliases := []string{}, []string{}
p, err := f.Printer(nil, kubectl.PrintOptions{
ColumnLabels: []string{},
})
cmdutil.CheckErr(err)
if p != nil {
validArgs = p.HandledResources()
argAliases = kubectl.ResourceAliases(validArgs)
}
cmd := &cobra.Command{
Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
Short: "Delete resources by filenames, stdin, resources and names, or by resources and label selector",
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); err != nil {
cmdutil.CheckErr(err)
}
if err := options.Validate(f, cmd); err != nil {
cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
}
if err := options.RunDelete(); err != nil {
cmdutil.CheckErr(err)
}
},
SuggestFor: []string{"rm"},
ValidArgs: validArgs,
ArgAliases: argAliases,
}
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.")
cmd.Flags().BoolVar(&options.DeleteAll, "all", false, "[-all] to select all the specified resources.")
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)
return cmd
}
func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args []string) error {
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
// Set up client based support.
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
o.Mapper = mapper
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
SelectorParam(o.Selector).
SelectAllParam(o.DeleteAll).
ResourceTypeOrNameArgs(false, args...).RequireObject(false).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
o.Result = r
o.f = f
// Set up writer
o.Out = out
o.ErrOut = errOut
return nil
}
func (o *DeleteOptions) Validate(f cmdutil.Factory, cmd *cobra.Command) error {
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.DeleteNow {
if o.GracePeriod != -1 {
return fmt.Errorf("--now and --grace-period cannot be specified together")
}
o.GracePeriod = 1
}
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
}
}
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, o.Mapper, false)
}
return DeleteResult(o.Result, o.Out, o.IgnoreNotFound, shortOutput, o.Mapper)
}
func ReapResult(r *resource.Result, f cmdutil.Factory, out io.Writer, isDefaultDelete, ignoreNotFound bool, timeout time.Duration, gracePeriod int, waitForDeletion, shortOutput bool, mapper meta.RESTMapper, quiet bool) 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 {
return deleteResource(info, out, shortOutput, mapper)
}
return cmdutil.AddSourceToErr("reaping", info.Source, err)
}
var options *api.DeleteOptions
if gracePeriod >= 0 {
options = api.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 {
cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "deleted")
}
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, shortOutput bool, mapper meta.RESTMapper) 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++
return deleteResource(info, out, shortOutput, mapper)
})
if err != nil {
return err
}
if found == 0 {
fmt.Fprintf(out, "No resources found\n")
}
return nil
}
func deleteResource(info *resource.Info, out io.Writer, shortOutput bool, mapper meta.RESTMapper) error {
if err := resource.NewHelper(info.Client, info.Mapping).Delete(info.Namespace, info.Name); err != nil {
return cmdutil.AddSourceToErr("deleting", info.Source, err)
}
cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "deleted")
return nil
}
// objectDeletionWaitInterval is the interval to wait between checks for deletion. Exposed for testing.
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
}
})
}

603
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete_test.go generated vendored Normal file
View file

@ -0,0 +1,603 @@
/*
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 (
"bytes"
"net/http"
"strings"
"testing"
"time"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake"
"k8s.io/kubernetes/pkg/client/typed/dynamic"
"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"
)
var unstructuredSerializer = dynamic.ContentConfig().NegotiatedSerializer
func TestDeleteObjectByTuple(t *testing.T) {
_, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
// Ensures no GET is performed when deleting by name
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf)
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{"replicationcontrollers/redis-master-controller"})
if buf.String() != "replicationcontroller/redis-master-controller\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDeleteNamedObject(t *testing.T) {
_, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
// Ensures no GET is performed when deleting by name
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf)
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{"replicationcontrollers", "redis-master-controller"})
if buf.String() != "replicationcontroller/redis-master-controller\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDeleteObject(t *testing.T) {
_, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
// uses the name from the file, not the response
if buf.String() != "replicationcontroller/redis-master\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
type fakeReaper struct {
namespace, name string
timeout time.Duration
deleteOptions *api.DeleteOptions
err error
}
func (r *fakeReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.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) {
pods, _, _ := testData()
objectDeletionWaitInterval = time.Millisecond
count := 0
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Logf("got request %s %s", req.Method, req.URL.Path)
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/pods/nginx" && m == "GET":
count++
switch count {
case 1, 2, 3:
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &metav1.Status{})}, nil
}
case p == "/api/v1/namespaces/test" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil
case p == "/namespaces/test/pods/nginx" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
reaper := &fakeReaper{}
fake := &fakeReaperFactory{Factory: f, reaper: reaper}
cmd := NewCmdDelete(fake, buf, errBuf)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("grace-period", "0")
cmd.Run(cmd, []string{"pod/nginx"})
// uses the name from the file, not the response
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 {
t.Errorf("unexpected calls to GET: %d", count)
}
}
func TestDeleteObjectNotFound(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: stringBody("")}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
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"},
},
GracePeriod: -1,
Cascade: false,
Output: "name",
}
err := options.Complete(f, buf, errBuf, []string{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = options.RunDelete()
if err == nil || !errors.IsNotFound(err) {
t.Errorf("unexpected error: expected NotFound, got %v", err)
}
}
func TestDeleteObjectIgnoreNotFound(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: stringBody("")}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("ignore-not-found", "true")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
if buf.String() != "" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDeleteAllNotFound(t *testing.T) {
_, svc, _ := testData()
// Add an item to the list which will result in a 404 on delete
svc.Items = append(svc.Items, api.Service{ObjectMeta: api.ObjectMeta{Name: "foo"}})
notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/services" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
case p == "/namespaces/test/services/foo" && m == "DELETE":
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, notFoundError)}, nil
case p == "/namespaces/test/services/baz" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
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{
FilenameOptions: resource.FilenameOptions{},
GracePeriod: -1,
Cascade: false,
DeleteAll: true,
IgnoreNotFound: false,
Output: "name",
}
err := options.Complete(f, buf, errBuf, []string{"services"})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = options.RunDelete()
if err == nil || !errors.IsNotFound(err) {
t.Errorf("unexpected error: expected NotFound, got %v", err)
}
}
func TestDeleteAllIgnoreNotFound(t *testing.T) {
_, svc, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
// Add an item to the list which will result in a 404 on delete
svc.Items = append(svc.Items, api.Service{ObjectMeta: api.ObjectMeta{Name: "foo"}})
notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/services" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
case p == "/namespaces/test/services/foo" && m == "DELETE":
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, notFoundError)}, nil
case p == "/namespaces/test/services/baz" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf)
cmd.Flags().Set("all", "true")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{"services"})
if buf.String() != "service/baz\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDeleteMultipleObject(t *testing.T) {
_, svc, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
case p == "/namespaces/test/services/frontend" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontroller/redis-master\nservice/frontend\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
_, svc, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: stringBody("")}, nil
case p == "/namespaces/test/services/frontend" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
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", "../../../examples/guestbook/frontend-service.yaml"},
},
GracePeriod: -1,
Cascade: false,
Output: "name",
}
err := options.Complete(f, buf, errBuf, []string{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = options.RunDelete()
if err == nil || !errors.IsNotFound(err) {
t.Errorf("unexpected error: expected NotFound, got %v", err)
}
if buf.String() != "service/frontend\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) {
_, svc, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/baz" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
case p == "/namespaces/test/replicationcontrollers/foo" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
case p == "/namespaces/test/services/baz" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
case p == "/namespaces/test/services/foo" && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
default:
// Ensures no GET is performed when deleting by name
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf)
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{"replicationcontrollers,services", "baz", "foo"})
if buf.String() != "replicationcontroller/baz\nreplicationcontroller/foo\nservice/baz\nservice/foo\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDeleteDirectory(t *testing.T) {
_, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontroller/frontend\nreplicationcontroller/redis-master\nreplicationcontroller/redis-slave\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDeleteMultipleSelector(t *testing.T) {
pods, svc, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/pods" && m == "GET":
if req.URL.Query().Get(metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String())) != "a=b" {
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
}
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
case p == "/namespaces/test/services" && m == "GET":
if req.URL.Query().Get(metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String())) != "a=b" {
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
}
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
case strings.HasPrefix(p, "/namespaces/test/pods/") && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
case strings.HasPrefix(p, "/namespaces/test/services/") && m == "DELETE":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf)
cmd.Flags().Set("selector", "a=b")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{"pods,services"})
if buf.String() != "pod/foo\npod/bar\nservice/baz\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestResourceErrors(t *testing.T) {
testCases := map[string]struct {
args []string
errFn func(error) bool
}{
"no args": {
args: []string{},
errFn: func(err error) bool { return strings.Contains(err.Error(), "You must provide one or more resources") },
},
"resources but no selectors": {
args: []string{"pods"},
errFn: func(err error) bool {
return strings.Contains(err.Error(), "resource(s) were provided, but no name, label selector, or --all flag specified")
},
},
"multiple resources but no selectors": {
args: []string{"pods,deployments"},
errFn: func(err error) bool {
return strings.Contains(err.Error(), "resource(s) were provided, but no name, label selector, or --all flag specified")
},
},
}
for k, testCase := range testCases {
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
options := &DeleteOptions{
FilenameOptions: resource.FilenameOptions{},
GracePeriod: -1,
Cascade: false,
Output: "name",
}
err := options.Complete(f, buf, errBuf, testCase.args)
if !testCase.errFn(err) {
t.Errorf("%s: unexpected error: %v", k, err)
continue
}
if tf.Printer.(*testPrinter).Objects != nil {
t.Errorf("unexpected print to default printer")
}
if buf.Len() > 0 {
t.Errorf("buffer should be empty: %s", string(buf.Bytes()))
}
}
}

205
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe.go generated vendored Normal file
View file

@ -0,0 +1,205 @@
/*
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"
"strings"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
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"
)
var (
describe_long = templates.LongDesc(`
Show details of a specific resource or group of resources.
This command joins many API calls together to form a detailed description of a
given resource or group of resources.
$ kubectl describe TYPE NAME_PREFIX
will first check for an exact match on TYPE and NAME_PREFIX. If no such resource
exists, it will output details for every resource that has a name prefixed with NAME_PREFIX.
` + valid_resources)
describe_example = templates.Examples(`
# Describe a node
kubectl describe nodes kubernetes-node-emt8.c.myproject.internal
# Describe a pod
kubectl describe pods/nginx
# Describe a pod identified by type and name in "pod.json"
kubectl describe -f pod.json
# Describe all pods
kubectl describe pods
# Describe pods by label name=myLabel
kubectl describe po -l name=myLabel
# Describe all pods managed by the 'frontend' replication controller (rc-created pods
# get the name of the rc as a prefix in the pod the name).
kubectl describe pods frontend`)
)
func NewCmdDescribe(f cmdutil.Factory, out, cmdErr io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}
describerSettings := &kubectl.DescriberSettings{}
validArgs := kubectl.DescribableResources()
argAliases := kubectl.ResourceAliases(validArgs)
cmd := &cobra.Command{
Use: "describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME)",
Short: "Show details of a specific resource or group of resources",
Long: describe_long,
Example: describe_example,
Run: func(cmd *cobra.Command, args []string) {
err := RunDescribe(f, out, cmdErr, cmd, args, options, describerSettings)
cmdutil.CheckErr(err)
},
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 '!='.")
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)
return cmd
}
func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions, describerSettings *kubectl.DescriberSettings) error {
selector := cmdutil.GetFlagString(cmd, "selector")
allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces")
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
if allNamespaces {
enforceNamespace = false
}
if len(args) == 0 && cmdutil.IsFilenameEmpty(options.Filenames) {
fmt.Fprint(cmdErr, "You must specify the type of resource to describe. ", valid_resources)
return cmdutil.UsageError(cmd, "Required resource not specified.")
}
mapper, typer := f.Object()
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
FilenameParam(enforceNamespace, options).
SelectorParam(selector).
ResourceTypeOrNameArgs(true, args...).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
allErrs := []error{}
infos, err := r.Infos()
if err != nil {
if apierrors.IsNotFound(err) && len(args) == 2 {
return DescribeMatchingResources(mapper, typer, f, cmdNamespace, args[0], args[1], describerSettings, out, err)
}
allErrs = append(allErrs, err)
}
errs := sets.NewString()
first := true
for _, info := range infos {
mapping := info.ResourceMapping()
describer, err := f.Describer(mapping)
if err != nil {
if errs.Has(err.Error()) {
continue
}
allErrs = append(allErrs, err)
errs.Insert(err.Error())
continue
}
s, err := describer.Describe(info.Namespace, info.Name, *describerSettings)
if err != nil {
if errs.Has(err.Error()) {
continue
}
allErrs = append(allErrs, err)
errs.Insert(err.Error())
continue
}
if first {
first = false
fmt.Fprint(out, s)
} else {
fmt.Fprintf(out, "\n\n%s", s)
}
}
return utilerrors.NewAggregate(allErrs)
}
func DescribeMatchingResources(mapper meta.RESTMapper, typer runtime.ObjectTyper, f cmdutil.Factory, namespace, rsrc, prefix string, describerSettings *kubectl.DescriberSettings, out io.Writer, originalError error) error {
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
NamespaceParam(namespace).DefaultNamespace().
ResourceTypeOrNameArgs(true, rsrc).
SingleResourceType().
Flatten().
Do()
mapping, err := r.ResourceMapping()
if err != nil {
return err
}
describer, err := f.Describer(mapping)
if err != nil {
return err
}
infos, err := r.Infos()
if err != nil {
return err
}
isFound := false
for ix := range infos {
info := infos[ix]
if strings.HasPrefix(info.Name, prefix) {
isFound = true
s, err := describer.Describe(info.Namespace, info.Name, *describerSettings)
if err != nil {
return err
}
fmt.Fprintf(out, "%s\n", s)
}
}
if !isFound {
return originalError
}
return nil
}

View file

@ -0,0 +1,146 @@
/*
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 (
"bytes"
"fmt"
"net/http"
"testing"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
func TestDescribeUnknownSchemaObject(t *testing.T) {
d := &testDescriber{Output: "test output"}
f, tf, codec, ns := cmdtesting.NewTestFactory()
tf.Describer = d
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))},
}
tf.Namespace = "non-default"
buf := bytes.NewBuffer([]byte{})
buferr := bytes.NewBuffer([]byte{})
cmd := NewCmdDescribe(f, buf, buferr)
cmd.Run(cmd, []string{"type", "foo"})
if d.Name != "foo" || d.Namespace != "non-default" {
t.Errorf("unexpected describer: %#v", d)
}
if buf.String() != fmt.Sprintf("%s", d.Output) {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDescribeObject(t *testing.T) {
_, _, rc := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
d := &testDescriber{Output: "test output"}
tf.Describer = d
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "GET":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
buferr := bytes.NewBuffer([]byte{})
cmd := NewCmdDescribe(f, buf, buferr)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Run(cmd, []string{})
if d.Name != "redis-master" || d.Namespace != "test" {
t.Errorf("unexpected describer: %#v", d)
}
if buf.String() != fmt.Sprintf("%s", d.Output) {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDescribeListObjects(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
d := &testDescriber{Output: "test output"}
tf.Describer = d
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
buferr := bytes.NewBuffer([]byte{})
cmd := NewCmdDescribe(f, buf, buferr)
cmd.Run(cmd, []string{"pods"})
if buf.String() != fmt.Sprintf("%s\n\n%s", d.Output, d.Output) {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDescribeObjectShowEvents(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
d := &testDescriber{Output: "test output"}
tf.Describer = d
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
buferr := bytes.NewBuffer([]byte{})
cmd := NewCmdDescribe(f, buf, buferr)
cmd.Flags().Set("show-events", "true")
cmd.Run(cmd, []string{"pods"})
if d.Settings.ShowEvents != true {
t.Errorf("ShowEvents = true expected, got ShowEvents = %v", d.Settings.ShowEvents)
}
}
func TestDescribeObjectSkipEvents(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
d := &testDescriber{Output: "test output"}
tf.Describer = d
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
buferr := bytes.NewBuffer([]byte{})
cmd := NewCmdDescribe(f, buf, buferr)
cmd.Flags().Set("show-events", "false")
cmd.Run(cmd, []string{"pods"})
if d.Settings.ShowEvents != false {
t.Errorf("ShowEvents = false expected, got ShowEvents = %v", d.Settings.ShowEvents)
}
}

658
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/drain.go generated vendored Normal file
View file

@ -0,0 +1,658 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"errors"
"fmt"
"io"
"math"
"reflect"
"strings"
"time"
"github.com/jonboulle/clockwork"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/fields"
"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/kubelet/types"
)
type DrainOptions struct {
client *internalclientset.Clientset
restClient *restclient.RESTClient
factory cmdutil.Factory
Force bool
GracePeriodSeconds int
IgnoreDaemonsets bool
Timeout time.Duration
backOff clockwork.Clock
DeleteLocalData bool
mapper meta.RESTMapper
nodeInfo *resource.Info
out io.Writer
errOut io.Writer
typer runtime.ObjectTyper
}
// Takes a pod and returns a bool indicating whether or not to operate on the
// pod, an optional warning message, and an optional fatal error.
type podFilter func(api.Pod) (include bool, w *warning, f *fatal)
type warning struct {
string
}
type fatal struct {
string
}
const (
EvictionKind = "Eviction"
EvictionSubresource = "pods/eviction"
kDaemonsetFatal = "DaemonSet-managed pods (use --ignore-daemonsets to ignore)"
kDaemonsetWarning = "Ignoring DaemonSet-managed pods"
kLocalStorageFatal = "pods with local storage (use --delete-local-data to override)"
kLocalStorageWarning = "Deleting pods with local storage"
kUnmanagedFatal = "pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet (use --force to override)"
kUnmanagedWarning = "Deleting pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet"
kMaxNodeUpdateRetry = 10
)
var (
cordon_long = templates.LongDesc(`
Mark node as unschedulable.`)
cordon_example = templates.Examples(`
# Mark node "foo" as unschedulable.
kubectl cordon foo`)
)
func NewCmdCordon(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &DrainOptions{factory: f, out: out}
cmd := &cobra.Command{
Use: "cordon NODE",
Short: "Mark node as unschedulable",
Long: cordon_long,
Example: cordon_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.SetupDrain(cmd, args))
cmdutil.CheckErr(options.RunCordonOrUncordon(true))
},
}
return cmd
}
var (
uncordon_long = templates.LongDesc(`
Mark node as schedulable.`)
uncordon_example = templates.Examples(`
# Mark node "foo" as schedulable.
$ kubectl uncordon foo`)
)
func NewCmdUncordon(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &DrainOptions{factory: f, out: out}
cmd := &cobra.Command{
Use: "uncordon NODE",
Short: "Mark node as schedulable",
Long: uncordon_long,
Example: uncordon_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.SetupDrain(cmd, args))
cmdutil.CheckErr(options.RunCordonOrUncordon(false))
},
}
return cmd
}
var (
drain_long = templates.LongDesc(`
Drain node in preparation for maintenance.
The given node will be marked unschedulable to prevent new pods from arriving.
'drain' evicts the pods if the APIServer supports eviciton
(http://kubernetes.io/docs/admin/disruptions/). Otherwise, it will use normal DELETE
to delete the pods.
The 'drain' evicts or deletes all pods except mirror pods (which cannot be deleted through
the API server). If there are DaemonSet-managed pods, drain will not proceed
without --ignore-daemonsets, and regardless it will not delete any
DaemonSet-managed pods, because those pods would be immediately replaced by the
DaemonSet controller, which ignores unschedulable markings. If there are any
pods that are neither mirror pods nor managed by ReplicationController,
ReplicaSet, DaemonSet, StatefulSet or Job, then drain will not delete any pods unless you
use --force.
'drain' waits for graceful termination. You should not operate on the machine until
the command completes.
When you are ready to put the node back into service, use kubectl uncordon, which
will make the node schedulable again.
![Workflow](http://kubernetes.io/images/docs/kubectl_drain.svg)`)
drain_example = templates.Examples(`
# Drain node "foo", even if there are pods not managed by a ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet on it.
$ kubectl drain foo --force
# As above, but abort if there are pods not managed by a ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet, and use a grace period of 15 minutes.
$ kubectl drain foo --grace-period=900`)
)
func NewCmdDrain(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := &DrainOptions{factory: f, out: out, errOut: errOut, backOff: clockwork.NewRealClock()}
cmd := &cobra.Command{
Use: "drain NODE",
Short: "Drain node in preparation for maintenance",
Long: drain_long,
Example: drain_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.SetupDrain(cmd, args))
cmdutil.CheckErr(options.RunDrain())
},
}
cmd.Flags().BoolVar(&options.Force, "force", false, "Continue even if there are pods not managed by a ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet.")
cmd.Flags().BoolVar(&options.IgnoreDaemonsets, "ignore-daemonsets", false, "Ignore DaemonSet-managed pods.")
cmd.Flags().BoolVar(&options.DeleteLocalData, "delete-local-data", false, "Continue even if there are pods using emptyDir (local data that will be deleted when the node is drained).")
cmd.Flags().IntVar(&options.GracePeriodSeconds, "grace-period", -1, "Period of time in seconds given to each pod to terminate gracefully. If negative, the default value specified in the pod will be used.")
cmd.Flags().DurationVar(&options.Timeout, "timeout", 0, "The length of time to wait before giving up, zero means infinite")
return cmd
}
// SetupDrain populates some fields from the factory, grabs command line
// arguments and looks up the node using Builder
func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error {
var err error
if len(args) != 1 {
return cmdutil.UsageError(cmd, fmt.Sprintf("USAGE: %s [flags]", cmd.Use))
}
if o.client, err = o.factory.ClientSet(); err != nil {
return err
}
o.restClient, err = o.factory.RESTClient()
if err != nil {
return err
}
o.mapper, o.typer = o.factory.Object()
cmdNamespace, _, err := o.factory.DefaultNamespace()
if err != nil {
return err
}
r := o.factory.NewBuilder().
NamespaceParam(cmdNamespace).DefaultNamespace().
ResourceNames("node", args[0]).
Do()
if err = r.Err(); err != nil {
return err
}
return r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
o.nodeInfo = info
return nil
})
}
// RunDrain runs the 'drain' command
func (o *DrainOptions) RunDrain() error {
if err := o.RunCordonOrUncordon(true); err != nil {
return err
}
err := o.deleteOrEvictPodsSimple()
if err == nil {
cmdutil.PrintSuccess(o.mapper, false, o.out, "node", o.nodeInfo.Name, false, "drained")
}
return err
}
func (o *DrainOptions) deleteOrEvictPodsSimple() error {
pods, err := o.getPodsForDeletion()
if err != nil {
return err
}
err = o.deleteOrEvictPods(pods)
if err != nil {
pendingPods, newErr := o.getPodsForDeletion()
if newErr != nil {
return newErr
}
fmt.Fprintf(o.errOut, "There are pending pods when an error occurred: %v\n", err)
for _, pendingPod := range pendingPods {
fmt.Fprintf(o.errOut, "%s/%s\n", "pod", pendingPod.Name)
}
}
return err
}
func (o *DrainOptions) getController(sr *api.SerializedReference) (interface{}, error) {
switch sr.Reference.Kind {
case "ReplicationController":
return o.client.Core().ReplicationControllers(sr.Reference.Namespace).Get(sr.Reference.Name, metav1.GetOptions{})
case "DaemonSet":
return o.client.Extensions().DaemonSets(sr.Reference.Namespace).Get(sr.Reference.Name, metav1.GetOptions{})
case "Job":
return o.client.Batch().Jobs(sr.Reference.Namespace).Get(sr.Reference.Name, metav1.GetOptions{})
case "ReplicaSet":
return o.client.Extensions().ReplicaSets(sr.Reference.Namespace).Get(sr.Reference.Name, metav1.GetOptions{})
case "StatefulSet":
return o.client.Apps().StatefulSets(sr.Reference.Namespace).Get(sr.Reference.Name, metav1.GetOptions{})
}
return nil, fmt.Errorf("Unknown controller kind %q", sr.Reference.Kind)
}
func (o *DrainOptions) getPodCreator(pod api.Pod) (*api.SerializedReference, error) {
creatorRef, found := pod.ObjectMeta.Annotations[api.CreatedByAnnotation]
if !found {
return nil, nil
}
// Now verify that the specified creator actually exists.
sr := &api.SerializedReference{}
if err := runtime.DecodeInto(o.factory.Decoder(true), []byte(creatorRef), sr); err != nil {
return nil, err
}
// We assume the only reason for an error is because the controller is
// gone/missing, not for any other cause. TODO(mml): something more
// sophisticated than this
_, err := o.getController(sr)
if err != nil {
return nil, err
}
return sr, nil
}
func (o *DrainOptions) unreplicatedFilter(pod api.Pod) (bool, *warning, *fatal) {
// any finished pod can be removed
if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed {
return true, nil, nil
}
sr, err := o.getPodCreator(pod)
if err != nil {
return false, nil, &fatal{err.Error()}
}
if sr != nil {
return true, nil, nil
}
if !o.Force {
return false, nil, &fatal{kUnmanagedFatal}
}
return true, &warning{kUnmanagedWarning}, nil
}
func (o *DrainOptions) daemonsetFilter(pod api.Pod) (bool, *warning, *fatal) {
// Note that we return false in all cases where the pod is DaemonSet managed,
// regardless of flags. We never delete them, the only question is whether
// their presence constitutes an error.
sr, err := o.getPodCreator(pod)
if err != nil {
return false, nil, &fatal{err.Error()}
}
if sr == nil || sr.Reference.Kind != "DaemonSet" {
return true, nil, nil
}
if _, err := o.client.Extensions().DaemonSets(sr.Reference.Namespace).Get(sr.Reference.Name, metav1.GetOptions{}); err != nil {
return false, nil, &fatal{err.Error()}
}
if !o.IgnoreDaemonsets {
return false, nil, &fatal{kDaemonsetFatal}
}
return false, &warning{kDaemonsetWarning}, nil
}
func mirrorPodFilter(pod api.Pod) (bool, *warning, *fatal) {
if _, found := pod.ObjectMeta.Annotations[types.ConfigMirrorAnnotationKey]; found {
return false, nil, nil
}
return true, nil, nil
}
func hasLocalStorage(pod api.Pod) bool {
for _, volume := range pod.Spec.Volumes {
if volume.EmptyDir != nil {
return true
}
}
return false
}
func (o *DrainOptions) localStorageFilter(pod api.Pod) (bool, *warning, *fatal) {
if !hasLocalStorage(pod) {
return true, nil, nil
}
if !o.DeleteLocalData {
return false, nil, &fatal{kLocalStorageFatal}
}
return true, &warning{kLocalStorageWarning}, nil
}
// Map of status message to a list of pod names having that status.
type podStatuses map[string][]string
func (ps podStatuses) Message() string {
msgs := []string{}
for key, pods := range ps {
msgs = append(msgs, fmt.Sprintf("%s: %s", key, strings.Join(pods, ", ")))
}
return strings.Join(msgs, "; ")
}
// getPodsForDeletion returns all the pods we're going to delete. If there are
// any pods preventing us from deleting, we return that list in an error.
func (o *DrainOptions) getPodsForDeletion() (pods []api.Pod, err error) {
podList, err := o.client.Core().Pods(api.NamespaceAll).List(api.ListOptions{
FieldSelector: fields.SelectorFromSet(fields.Set{"spec.nodeName": o.nodeInfo.Name})})
if err != nil {
return pods, err
}
ws := podStatuses{}
fs := podStatuses{}
for _, pod := range podList.Items {
podOk := true
for _, filt := range []podFilter{mirrorPodFilter, o.localStorageFilter, o.unreplicatedFilter, o.daemonsetFilter} {
filterOk, w, f := filt(pod)
podOk = podOk && filterOk
if w != nil {
ws[w.string] = append(ws[w.string], pod.Name)
}
if f != nil {
fs[f.string] = append(fs[f.string], pod.Name)
}
}
if podOk {
pods = append(pods, pod)
}
}
if len(fs) > 0 {
return []api.Pod{}, errors.New(fs.Message())
}
if len(ws) > 0 {
fmt.Fprintf(o.errOut, "WARNING: %s\n", ws.Message())
}
return pods, nil
}
func (o *DrainOptions) deletePod(pod api.Pod) error {
deleteOptions := &api.DeleteOptions{}
if o.GracePeriodSeconds >= 0 {
gracePeriodSeconds := int64(o.GracePeriodSeconds)
deleteOptions.GracePeriodSeconds = &gracePeriodSeconds
}
return o.client.Core().Pods(pod.Namespace).Delete(pod.Name, deleteOptions)
}
func (o *DrainOptions) evictPod(pod api.Pod, policyGroupVersion string) error {
deleteOptions := &api.DeleteOptions{}
if o.GracePeriodSeconds >= 0 {
gracePeriodSeconds := int64(o.GracePeriodSeconds)
deleteOptions.GracePeriodSeconds = &gracePeriodSeconds
}
eviction := &policy.Eviction{
TypeMeta: metav1.TypeMeta{
APIVersion: policyGroupVersion,
Kind: EvictionKind,
},
ObjectMeta: api.ObjectMeta{
Name: pod.Name,
Namespace: pod.Namespace,
},
DeleteOptions: deleteOptions,
}
// Remember to change change the URL manipulation func when Evction's version change
return o.client.Policy().Evictions(eviction.Namespace).Evict(eviction)
}
// deleteOrEvictPods deletes or evicts the pods on the api server
func (o *DrainOptions) deleteOrEvictPods(pods []api.Pod) error {
if len(pods) == 0 {
return nil
}
policyGroupVersion, err := SupportEviction(o.client)
if err != nil {
return err
}
getPodFn := func(namespace, name string) (*api.Pod, error) {
return o.client.Core().Pods(namespace).Get(name, metav1.GetOptions{})
}
if len(policyGroupVersion) > 0 {
return o.evictPods(pods, policyGroupVersion, getPodFn)
} else {
return o.deletePods(pods, getPodFn)
}
}
func (o *DrainOptions) evictPods(pods []api.Pod, policyGroupVersion string, getPodFn func(namespace, name string) (*api.Pod, error)) error {
doneCh := make(chan bool, len(pods))
errCh := make(chan error, 1)
for _, pod := range pods {
go func(pod api.Pod, doneCh chan bool, errCh chan error) {
var err error
for {
err = o.evictPod(pod, policyGroupVersion)
if err == nil {
break
} else if apierrors.IsTooManyRequests(err) {
time.Sleep(5 * time.Second)
} else {
errCh <- fmt.Errorf("error when evicting pod %q: %v", pod.Name, err)
return
}
}
podArray := []api.Pod{pod}
_, err = o.waitForDelete(podArray, kubectl.Interval, time.Duration(math.MaxInt64), true, getPodFn)
if err == nil {
doneCh <- true
} else {
errCh <- fmt.Errorf("error when waiting for pod %q terminating: %v", pod.Name, err)
}
}(pod, doneCh, errCh)
}
doneCount := 0
// 0 timeout means infinite, we use MaxInt64 to represent it.
var globalTimeout time.Duration
if o.Timeout == 0 {
globalTimeout = time.Duration(math.MaxInt64)
} else {
globalTimeout = o.Timeout
}
for {
select {
case err := <-errCh:
return err
case <-doneCh:
doneCount++
if doneCount == len(pods) {
return nil
}
case <-time.After(globalTimeout):
return fmt.Errorf("Drain did not complete within %v", globalTimeout)
}
}
}
func (o *DrainOptions) deletePods(pods []api.Pod, getPodFn func(namespace, name string) (*api.Pod, error)) error {
// 0 timeout means infinite, we use MaxInt64 to represent it.
var globalTimeout time.Duration
if o.Timeout == 0 {
globalTimeout = time.Duration(math.MaxInt64)
} else {
globalTimeout = o.Timeout
}
for _, pod := range pods {
err := o.deletePod(pod)
if err != nil {
return err
}
}
_, err := o.waitForDelete(pods, kubectl.Interval, globalTimeout, false, getPodFn)
return err
}
func (o *DrainOptions) waitForDelete(pods []api.Pod, interval, timeout time.Duration, usingEviction bool, getPodFn func(string, string) (*api.Pod, error)) ([]api.Pod, error) {
var verbStr string
if usingEviction {
verbStr = "evicted"
} else {
verbStr = "deleted"
}
err := wait.PollImmediate(interval, timeout, func() (bool, error) {
pendingPods := []api.Pod{}
for i, pod := range pods {
p, err := getPodFn(pod.Namespace, pod.Name)
if apierrors.IsNotFound(err) || (p != nil && p.ObjectMeta.UID != pod.ObjectMeta.UID) {
cmdutil.PrintSuccess(o.mapper, false, o.out, "pod", pod.Name, false, verbStr)
continue
} else if err != nil {
return false, err
} else {
pendingPods = append(pendingPods, pods[i])
}
}
pods = pendingPods
if len(pendingPods) > 0 {
return false, nil
}
return true, nil
})
return pods, err
}
// SupportEviction uses Discovery API to find out if the server support eviction subresource
// If support, it will return its groupVersion; Otherwise, it will return ""
func SupportEviction(clientset *internalclientset.Clientset) (string, error) {
discoveryClient := clientset.Discovery()
groupList, err := discoveryClient.ServerGroups()
if err != nil {
return "", err
}
foundPolicyGroup := false
var policyGroupVersion string
for _, group := range groupList.Groups {
if group.Name == "policy" {
foundPolicyGroup = true
policyGroupVersion = group.PreferredVersion.GroupVersion
break
}
}
if !foundPolicyGroup {
return "", nil
}
resourceList, err := discoveryClient.ServerResourcesForGroupVersion("v1")
if err != nil {
return "", err
}
for _, resource := range resourceList.APIResources {
if resource.Name == EvictionSubresource && resource.Kind == EvictionKind {
return policyGroupVersion, nil
}
}
return "", nil
}
// RunCordonOrUncordon runs either Cordon or Uncordon. The desired value for
// "Unschedulable" is passed as the first arg.
func (o *DrainOptions) RunCordonOrUncordon(desired bool) error {
cmdNamespace, _, err := o.factory.DefaultNamespace()
if err != nil {
return err
}
if o.nodeInfo.Mapping.GroupVersionKind.Kind == "Node" {
unsched := reflect.ValueOf(o.nodeInfo.Object).Elem().FieldByName("Spec").FieldByName("Unschedulable")
if unsched.Bool() == desired {
cmdutil.PrintSuccess(o.mapper, false, o.out, o.nodeInfo.Mapping.Resource, o.nodeInfo.Name, false, already(desired))
} else {
helper := resource.NewHelper(o.restClient, o.nodeInfo.Mapping)
unsched.SetBool(desired)
var err error
for i := 0; i < kMaxNodeUpdateRetry; i++ {
// We don't care about what previous versions may exist, we always want
// to overwrite, and Replace always sets current ResourceVersion if version is "".
helper.Versioner.SetResourceVersion(o.nodeInfo.Object, "")
_, err = helper.Replace(cmdNamespace, o.nodeInfo.Name, true, o.nodeInfo.Object)
if err != nil {
if !apierrors.IsConflict(err) {
return err
}
} else {
break
}
// It's a race, no need to sleep
}
if err != nil {
return err
}
cmdutil.PrintSuccess(o.mapper, false, o.out, o.nodeInfo.Mapping.Resource, o.nodeInfo.Name, false, changed(desired))
}
} else {
cmdutil.PrintSuccess(o.mapper, false, o.out, o.nodeInfo.Mapping.Resource, o.nodeInfo.Name, false, "skipped")
}
return nil
}
// already() and changed() return suitable strings for {un,}cordoning
func already(desired bool) string {
if desired {
return "already cordoned"
}
return "already uncordoned"
}
func changed(desired bool) string {
if desired {
return "cordoned"
}
return "uncordoned"
}

758
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/drain_test.go generated vendored Normal file
View file

@ -0,0 +1,758 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
const (
EvictionMethod = "Eviction"
DeleteMethod = "Delete"
)
var node *api.Node
var cordoned_node *api.Node
func TestMain(m *testing.M) {
// Create a node.
node = &api.Node{
ObjectMeta: api.ObjectMeta{
Name: "node",
CreationTimestamp: metav1.Time{Time: time.Now()},
},
Spec: api.NodeSpec{
ExternalID: "node",
},
Status: api.NodeStatus{},
}
clone, _ := api.Scheme.DeepCopy(node)
// A copy of the same node, but cordoned.
cordoned_node = clone.(*api.Node)
cordoned_node.Spec.Unschedulable = true
os.Exit(m.Run())
}
func TestCordon(t *testing.T) {
tests := []struct {
description string
node *api.Node
expected *api.Node
cmd func(cmdutil.Factory, io.Writer) *cobra.Command
arg string
expectFatal bool
}{
{
description: "node/node syntax",
node: cordoned_node,
expected: node,
cmd: NewCmdUncordon,
arg: "node/node",
expectFatal: false,
},
{
description: "uncordon for real",
node: cordoned_node,
expected: node,
cmd: NewCmdUncordon,
arg: "node",
expectFatal: false,
},
{
description: "uncordon does nothing",
node: node,
expected: node,
cmd: NewCmdUncordon,
arg: "node",
expectFatal: false,
},
{
description: "cordon does nothing",
node: cordoned_node,
expected: cordoned_node,
cmd: NewCmdCordon,
arg: "node",
expectFatal: false,
},
{
description: "cordon for real",
node: node,
expected: cordoned_node,
cmd: NewCmdCordon,
arg: "node",
expectFatal: false,
},
{
description: "cordon missing node",
node: node,
expected: node,
cmd: NewCmdCordon,
arg: "bar",
expectFatal: true,
},
{
description: "uncordon missing node",
node: node,
expected: node,
cmd: NewCmdUncordon,
arg: "bar",
expectFatal: true,
},
}
for _, test := range tests {
f, tf, codec, ns := cmdtesting.NewAPIFactory()
new_node := &api.Node{}
updated := false
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
m := &MyReq{req}
switch {
case m.isFor("GET", "/nodes/node"):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, test.node)}, nil
case m.isFor("GET", "/nodes/bar"):
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: stringBody("nope")}, nil
case m.isFor("PUT", "/nodes/node"):
data, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Fatalf("%s: unexpected error: %v", test.description, err)
}
defer req.Body.Close()
if err := runtime.DecodeInto(codec, data, new_node); err != nil {
t.Fatalf("%s: unexpected error: %v", test.description, err)
}
if !reflect.DeepEqual(test.expected.Spec, new_node.Spec) {
t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, test.expected.Spec, new_node.Spec)
}
updated = true
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, new_node)}, nil
default:
t.Fatalf("%s: unexpected request: %v %#v\n%#v", test.description, req.Method, req.URL, req)
return nil, nil
}
}),
}
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
cmd := test.cmd(f, buf)
saw_fatal := false
func() {
defer func() {
// Recover from the panic below.
_ = recover()
// Restore cmdutil behavior
cmdutil.DefaultBehaviorOnFatal()
}()
cmdutil.BehaviorOnFatal(func(e string, code int) {
saw_fatal = true
panic(e)
})
cmd.SetArgs([]string{test.arg})
cmd.Execute()
}()
if test.expectFatal {
if !saw_fatal {
t.Fatalf("%s: unexpected non-error", test.description)
}
if updated {
t.Fatalf("%s: unexpcted update", test.description)
}
}
if !test.expectFatal && saw_fatal {
t.Fatalf("%s: unexpected error", test.description)
}
if !reflect.DeepEqual(test.expected.Spec, test.node.Spec) && !updated {
t.Fatalf("%s: node never updated", test.description)
}
}
}
func TestDrain(t *testing.T) {
labels := make(map[string]string)
labels["my_key"] = "my_value"
rc := api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: "rc",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
Labels: labels,
SelfLink: testapi.Default.SelfLink("replicationcontrollers", "rc"),
},
Spec: api.ReplicationControllerSpec{
Selector: labels,
},
}
rc_anno := make(map[string]string)
rc_anno[api.CreatedByAnnotation] = refJson(t, &rc)
rc_pod := api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "bar",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
Labels: labels,
Annotations: rc_anno,
},
Spec: api.PodSpec{
NodeName: "node",
},
}
ds := extensions.DaemonSet{
ObjectMeta: api.ObjectMeta{
Name: "ds",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
SelfLink: "/apis/extensions/v1beta1/namespaces/default/daemonsets/ds",
},
Spec: extensions.DaemonSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: labels},
},
}
ds_anno := make(map[string]string)
ds_anno[api.CreatedByAnnotation] = refJson(t, &ds)
ds_pod := api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "bar",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
Labels: labels,
Annotations: ds_anno,
},
Spec: api.PodSpec{
NodeName: "node",
},
}
job := batch.Job{
ObjectMeta: api.ObjectMeta{
Name: "job",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
SelfLink: "/apis/batch/v1/namespaces/default/jobs/job",
},
Spec: batch.JobSpec{
Selector: &metav1.LabelSelector{MatchLabels: labels},
},
}
job_pod := api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "bar",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
Labels: labels,
Annotations: map[string]string{api.CreatedByAnnotation: refJson(t, &job)},
},
}
rs := extensions.ReplicaSet{
ObjectMeta: api.ObjectMeta{
Name: "rs",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
Labels: labels,
SelfLink: testapi.Default.SelfLink("replicasets", "rs"),
},
Spec: extensions.ReplicaSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: labels},
},
}
rs_anno := make(map[string]string)
rs_anno[api.CreatedByAnnotation] = refJson(t, &rs)
rs_pod := api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "bar",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
Labels: labels,
Annotations: rs_anno,
},
Spec: api.PodSpec{
NodeName: "node",
},
}
naked_pod := api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "bar",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
Labels: labels,
},
Spec: api.PodSpec{
NodeName: "node",
},
}
emptydir_pod := api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "bar",
Namespace: "default",
CreationTimestamp: metav1.Time{Time: time.Now()},
Labels: labels,
},
Spec: api.PodSpec{
NodeName: "node",
Volumes: []api.Volume{
{
Name: "scratch",
VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{Medium: ""}},
},
},
},
}
tests := []struct {
description string
node *api.Node
expected *api.Node
pods []api.Pod
rcs []api.ReplicationController
replicaSets []extensions.ReplicaSet
args []string
expectFatal bool
expectDelete bool
}{
{
description: "RC-managed pod",
node: node,
expected: cordoned_node,
pods: []api.Pod{rc_pod},
rcs: []api.ReplicationController{rc},
args: []string{"node"},
expectFatal: false,
expectDelete: true,
},
{
description: "DS-managed pod",
node: node,
expected: cordoned_node,
pods: []api.Pod{ds_pod},
rcs: []api.ReplicationController{rc},
args: []string{"node"},
expectFatal: true,
expectDelete: false,
},
{
description: "DS-managed pod with --ignore-daemonsets",
node: node,
expected: cordoned_node,
pods: []api.Pod{ds_pod},
rcs: []api.ReplicationController{rc},
args: []string{"node", "--ignore-daemonsets"},
expectFatal: false,
expectDelete: false,
},
{
description: "Job-managed pod",
node: node,
expected: cordoned_node,
pods: []api.Pod{job_pod},
rcs: []api.ReplicationController{rc},
args: []string{"node"},
expectFatal: false,
expectDelete: true,
},
{
description: "RS-managed pod",
node: node,
expected: cordoned_node,
pods: []api.Pod{rs_pod},
replicaSets: []extensions.ReplicaSet{rs},
args: []string{"node"},
expectFatal: false,
expectDelete: true,
},
{
description: "naked pod",
node: node,
expected: cordoned_node,
pods: []api.Pod{naked_pod},
rcs: []api.ReplicationController{},
args: []string{"node"},
expectFatal: true,
expectDelete: false,
},
{
description: "naked pod with --force",
node: node,
expected: cordoned_node,
pods: []api.Pod{naked_pod},
rcs: []api.ReplicationController{},
args: []string{"node", "--force"},
expectFatal: false,
expectDelete: true,
},
{
description: "pod with EmptyDir",
node: node,
expected: cordoned_node,
pods: []api.Pod{emptydir_pod},
args: []string{"node", "--force"},
expectFatal: true,
expectDelete: false,
},
{
description: "pod with EmptyDir and --delete-local-data",
node: node,
expected: cordoned_node,
pods: []api.Pod{emptydir_pod},
args: []string{"node", "--force", "--delete-local-data=true"},
expectFatal: false,
expectDelete: true,
},
{
description: "empty node",
node: node,
expected: cordoned_node,
pods: []api.Pod{},
rcs: []api.ReplicationController{rc},
args: []string{"node"},
expectFatal: false,
expectDelete: false,
},
}
testEviction := false
for i := 0; i < 2; i++ {
testEviction = !testEviction
var currMethod string
if testEviction {
currMethod = EvictionMethod
} else {
currMethod = DeleteMethod
}
for _, test := range tests {
new_node := &api.Node{}
deleted := false
evicted := false
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
m := &MyReq{req}
switch {
case req.Method == "GET" && req.URL.Path == "/api":
apiVersions := metav1.APIVersions{
Versions: []string{"v1"},
}
return genResponseWithJsonEncodedBody(apiVersions)
case req.Method == "GET" && req.URL.Path == "/apis":
groupList := metav1.APIGroupList{
Groups: []metav1.APIGroup{
{
Name: "policy",
PreferredVersion: metav1.GroupVersionForDiscovery{
GroupVersion: "policy/v1beta1",
},
},
},
}
return genResponseWithJsonEncodedBody(groupList)
case req.Method == "GET" && req.URL.Path == "/api/v1":
resourceList := metav1.APIResourceList{
GroupVersion: "v1",
}
if testEviction {
resourceList.APIResources = []metav1.APIResource{
{
Name: EvictionSubresource,
Kind: EvictionKind,
},
}
}
return genResponseWithJsonEncodedBody(resourceList)
case m.isFor("GET", "/nodes/node"):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, test.node)}, nil
case m.isFor("GET", "/namespaces/default/replicationcontrollers/rc"):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &test.rcs[0])}, nil
case m.isFor("GET", "/namespaces/default/daemonsets/ds"):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(testapi.Extensions.Codec(), &ds)}, nil
case m.isFor("GET", "/namespaces/default/jobs/job"):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(testapi.Batch.Codec(), &job)}, nil
case m.isFor("GET", "/namespaces/default/replicasets/rs"):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(testapi.Extensions.Codec(), &test.replicaSets[0])}, nil
case m.isFor("GET", "/namespaces/default/pods/bar"):
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &api.Pod{})}, nil
case m.isFor("GET", "/pods"):
values, err := url.ParseQuery(req.URL.RawQuery)
if err != nil {
t.Fatalf("%s: unexpected error: %v", test.description, err)
}
get_params := make(url.Values)
get_params["fieldSelector"] = []string{"spec.nodeName=node"}
if !reflect.DeepEqual(get_params, values) {
t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, get_params, values)
}
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.PodList{Items: test.pods})}, nil
case m.isFor("GET", "/replicationcontrollers"):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.ReplicationControllerList{Items: test.rcs})}, nil
case m.isFor("PUT", "/nodes/node"):
data, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Fatalf("%s: unexpected error: %v", test.description, err)
}
defer req.Body.Close()
if err := runtime.DecodeInto(codec, data, new_node); err != nil {
t.Fatalf("%s: unexpected error: %v", test.description, err)
}
if !reflect.DeepEqual(test.expected.Spec, new_node.Spec) {
t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, test.expected.Spec, new_node.Spec)
}
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, new_node)}, nil
case m.isFor("DELETE", "/namespaces/default/pods/bar"):
deleted = true
return &http.Response{StatusCode: 204, Header: defaultHeader(), Body: objBody(codec, &test.pods[0])}, nil
case m.isFor("POST", "/namespaces/default/pods/bar/eviction"):
evicted = true
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: policyObjBody(&policy.Eviction{})}, nil
default:
t.Fatalf("%s: unexpected request: %v %#v\n%#v", test.description, req.Method, req.URL, req)
return nil, nil
}
}),
}
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdDrain(f, buf, errBuf)
saw_fatal := false
func() {
defer func() {
// Recover from the panic below.
_ = recover()
// Restore cmdutil behavior
cmdutil.DefaultBehaviorOnFatal()
}()
cmdutil.BehaviorOnFatal(func(e string, code int) { saw_fatal = true; panic(e) })
cmd.SetArgs(test.args)
cmd.Execute()
}()
if test.expectFatal {
if !saw_fatal {
t.Fatalf("%s: unexpected non-error when using %s", test.description, currMethod)
}
}
if test.expectDelete {
// Test Delete
if !testEviction && !deleted {
t.Fatalf("%s: pod never deleted", test.description)
}
// Test Eviction
if testEviction && !evicted {
t.Fatalf("%s: pod never evicted", test.description)
}
}
if !test.expectDelete {
if deleted {
t.Fatalf("%s: unexpected delete when using %s", test.description, currMethod)
}
}
}
}
}
func TestDeletePods(t *testing.T) {
ifHasBeenCalled := map[string]bool{}
tests := []struct {
description string
interval time.Duration
timeout time.Duration
expectPendingPods bool
expectError bool
expectedError *error
getPodFn func(namespace, name string) (*api.Pod, error)
}{
{
description: "Wait for deleting to complete",
interval: 100 * time.Millisecond,
timeout: 10 * time.Second,
expectPendingPods: false,
expectError: false,
expectedError: nil,
getPodFn: func(namespace, name string) (*api.Pod, error) {
oldPodMap, _ := createPods(false)
newPodMap, _ := createPods(true)
if oldPod, found := oldPodMap[name]; found {
if _, ok := ifHasBeenCalled[name]; !ok {
ifHasBeenCalled[name] = true
return &oldPod, nil
}
if oldPod.ObjectMeta.Generation < 4 {
newPod := newPodMap[name]
return &newPod, nil
}
return nil, apierrors.NewNotFound(schema.GroupResource{Resource: "pods"}, name)
}
return nil, apierrors.NewNotFound(schema.GroupResource{Resource: "pods"}, name)
},
},
{
description: "Deleting could timeout",
interval: 200 * time.Millisecond,
timeout: 3 * time.Second,
expectPendingPods: true,
expectError: true,
expectedError: &wait.ErrWaitTimeout,
getPodFn: func(namespace, name string) (*api.Pod, error) {
oldPodMap, _ := createPods(false)
if oldPod, found := oldPodMap[name]; found {
return &oldPod, nil
}
return nil, fmt.Errorf("%q: not found", name)
},
},
{
description: "Client error could be passed out",
interval: 200 * time.Millisecond,
timeout: 5 * time.Second,
expectPendingPods: true,
expectError: true,
expectedError: nil,
getPodFn: func(namespace, name string) (*api.Pod, error) {
return nil, errors.New("This is a random error for testing")
},
},
}
for _, test := range tests {
f, _, _, _ := cmdtesting.NewAPIFactory()
o := DrainOptions{}
o.mapper, _ = f.Object()
o.out = os.Stdout
_, pods := createPods(false)
pendingPods, err := o.waitForDelete(pods, test.interval, test.timeout, false, test.getPodFn)
if test.expectError {
if err == nil {
t.Fatalf("%s: unexpected non-error", test.description)
} else if test.expectedError != nil {
if *test.expectedError != err {
t.Fatalf("%s: the error does not match expected error", test.description)
}
}
}
if !test.expectError && err != nil {
t.Fatalf("%s: unexpected error", test.description)
}
if test.expectPendingPods && len(pendingPods) == 0 {
t.Fatalf("%s: unexpected empty pods", test.description)
}
if !test.expectPendingPods && len(pendingPods) > 0 {
t.Fatalf("%s: unexpected pending pods", test.description)
}
}
}
func createPods(ifCreateNewPods bool) (map[string]api.Pod, []api.Pod) {
podMap := make(map[string]api.Pod)
podSlice := []api.Pod{}
for i := 0; i < 8; i++ {
var uid types.UID
if ifCreateNewPods {
uid = types.UID(i)
} else {
uid = types.UID(strconv.Itoa(i) + strconv.Itoa(i))
}
pod := api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "pod" + strconv.Itoa(i),
Namespace: "default",
UID: uid,
Generation: int64(i),
},
}
podMap[pod.Name] = pod
podSlice = append(podSlice, pod)
}
return podMap, podSlice
}
type MyReq struct {
Request *http.Request
}
func (m *MyReq) isFor(method string, path string) bool {
req := m.Request
return method == req.Method && (req.URL.Path == path ||
req.URL.Path == strings.Join([]string{"/api/v1", path}, "") ||
req.URL.Path == strings.Join([]string{"/apis/extensions/v1beta1", path}, "") ||
req.URL.Path == strings.Join([]string{"/apis/batch/v1", path}, ""))
}
func refJson(t *testing.T, o runtime.Object) string {
ref, err := api.GetReference(o)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, _, codec, _ := cmdtesting.NewAPIFactory()
json, err := runtime.Encode(codec, &api.SerializedReference{Reference: *ref})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return string(json)
}

691
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/edit.go generated vendored Normal file
View file

@ -0,0 +1,691 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
gruntime "runtime"
"strings"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/kubernetes/pkg/api"
"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/util/crlf"
"k8s.io/kubernetes/pkg/util/strategicpatch"
"github.com/golang/glog"
"github.com/spf13/cobra"
)
var (
editLong = templates.LongDesc(`
Edit a resource from the default editor.
The edit command allows you to directly edit any API resource you can retrieve via the
command line tools. It will open the editor defined by your KUBE_EDITOR, or EDITOR
environment variables, or fall back to 'vi' for Linux or 'notepad' for Windows.
You can edit multiple objects, although changes are applied one at a time. The command
accepts filenames as well as command line arguments, although the files you point to must
be previously saved versions of resources.
The files to edit will be output in the default API version, or a version specified
by --output-version. The default format is YAML - if you would like to edit in JSON
pass -o json. The flag --windows-line-endings can be used to force Windows line endings,
otherwise the default for your operating system will be used.
In the event an error occurs while updating, a temporary file will be created on disk
that contains your unapplied changes. The most common error when updating a resource
is another editor changing the resource on the server. When this occurs, you will have
to apply your changes to the newer version of the resource, or update your temporary
saved copy to include the latest resource version.`)
editExample = templates.Examples(`
# Edit the service named 'docker-registry':
kubectl edit svc/docker-registry
# Use an alternative editor
KUBE_EDITOR="nano" kubectl edit svc/docker-registry
# Edit the service 'docker-registry' in JSON using the v1 API format:
kubectl edit svc/docker-registry --output-version=v1 -o json`)
)
func NewCmdEdit(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}
// retrieve a list of handled resources from printer as valid args
validArgs, argAliases := []string{}, []string{}
p, err := f.Printer(nil, kubectl.PrintOptions{
ColumnLabels: []string{},
})
cmdutil.CheckErr(err)
if p != nil {
validArgs = p.HandledResources()
argAliases = kubectl.ResourceAliases(validArgs)
}
cmd := &cobra.Command{
Use: "edit (RESOURCE/NAME | -f FILENAME)",
Short: "Edit a resource on the server",
Long: editLong,
Example: fmt.Sprintf(editExample),
Run: func(cmd *cobra.Command, args []string) {
err := RunEdit(f, out, errOut, cmd, args, options)
cmdutil.CheckErr(err)
},
ValidArgs: validArgs,
ArgAliases: argAliases,
}
usage := "to use to edit the resource"
cmdutil.AddFilenameOptionFlags(cmd, options, usage)
cmdutil.AddValidateFlags(cmd)
cmd.Flags().StringP("output", "o", "yaml", "Output format. One of: yaml|json.")
cmd.Flags().String("output-version", "", "Output the formatted object with the given group version (for ex: 'extensions/v1beta1').")
cmd.Flags().Bool("windows-line-endings", gruntime.GOOS == "windows", "Use Windows line-endings (default Unix line-endings)")
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
func RunEdit(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error {
return runEdit(f, out, errOut, cmd, args, options, NormalEditMode)
}
func runEdit(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions, editMode EditMode) error {
o, err := getPrinter(cmd)
if err != nil {
return err
}
mapper, resourceMapper, r, cmdNamespace, err := getMapperAndResult(f, args, options, editMode)
if err != nil {
return err
}
clientConfig, err := f.ClientConfig()
if err != nil {
return err
}
encoder := f.JSONEncoder()
defaultVersion, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion)
if err != nil {
return err
}
normalEditInfos, err := r.Infos()
if err != nil {
return err
}
var (
windowsLineEndings = cmdutil.GetFlagBool(cmd, "windows-line-endings")
edit = editor.NewDefaultEditor(f.EditorEnvs())
)
editFn := func(info *resource.Info, err error) error {
var (
results = editResults{}
original = []byte{}
edited = []byte{}
file string
)
containsError := false
var infos []*resource.Info
for {
switch editMode {
case NormalEditMode:
infos = normalEditInfos
case EditBeforeCreateMode:
infos = []*resource.Info{info}
default:
err = fmt.Errorf("Not supported edit mode %q", editMode)
}
originalObj, err := resource.AsVersionedObject(infos, false, defaultVersion, encoder)
if err != nil {
return err
}
objToEdit := originalObj
// generate the file to edit
buf := &bytes.Buffer{}
var w io.Writer = buf
if windowsLineEndings {
w = crlf.NewCRLFWriter(w)
}
if o.addHeader {
results.header.writeTo(w)
}
if !containsError {
if err := o.printer.PrintObj(objToEdit, w); err != nil {
return preservedFile(err, results.file, errOut)
}
original = buf.Bytes()
} else {
// In case of an error, preserve the edited file.
// Remove the comments (header) from it since we already
// have included the latest header in the buffer above.
buf.Write(manualStrip(edited))
}
// launch the editor
editedDiff := edited
edited, file, err = edit.LaunchTempFile(fmt.Sprintf("%s-edit-", filepath.Base(os.Args[0])), o.ext, buf)
if err != nil {
return preservedFile(err, results.file, errOut)
}
if editMode == NormalEditMode || containsError {
if bytes.Equal(stripComments(editedDiff), stripComments(edited)) {
// Ugly hack right here. We will hit this either (1) when we try to
// save the same changes we tried to save in the previous iteration
// which means our changes are invalid or (2) when we exit the second
// time. The second case is more usual so we can probably live with it.
// TODO: A less hacky fix would be welcome :)
return preservedFile(fmt.Errorf("%s", "Edit cancelled, no valid changes were saved."), file, errOut)
}
}
// cleanup any file from the previous pass
if len(results.file) > 0 {
os.Remove(results.file)
}
glog.V(4).Infof("User edited:\n%s", string(edited))
// Apply validation
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
if err != nil {
return preservedFile(err, file, errOut)
}
err = schema.ValidateBytes(stripComments(edited))
if err != nil {
results = editResults{
file: file,
}
containsError = true
fmt.Fprintln(out, results.addError(errors.NewInvalid(api.Kind(""), "", field.ErrorList{field.Invalid(nil, "The edited file failed validation", fmt.Sprintf("%v", err))}), infos[0]))
continue
}
// Compare content without comments
if bytes.Equal(stripComments(original), stripComments(edited)) {
os.Remove(file)
fmt.Fprintln(errOut, "Edit cancelled, no changes made.")
return nil
}
lines, err := hasLines(bytes.NewBuffer(edited))
if err != nil {
return preservedFile(err, file, errOut)
}
if !lines {
os.Remove(file)
fmt.Fprintln(errOut, "Edit cancelled, saved file was empty.")
return nil
}
results = editResults{
file: file,
}
// parse the edited file
updates, err := resourceMapper.InfoForData(edited, "edited-file")
if err != nil {
// syntax error
containsError = true
results.header.reasons = append(results.header.reasons, editReason{head: fmt.Sprintf("The edited file had a syntax error: %v", err)})
continue
}
// not a syntax error as it turns out...
containsError = false
namespaceVisitor := resource.NewFlattenListVisitor(updates, resourceMapper)
// need to make sure the original namespace wasn't changed while editing
if err = namespaceVisitor.Visit(resource.RequireNamespace(cmdNamespace)); err != nil {
return preservedFile(err, file, errOut)
}
// iterate through all items to apply annotations
mutatedObjects, err := visitAnnotation(cmd, f, updates, resourceMapper, encoder)
if err != nil {
return preservedFile(err, file, errOut)
}
// if we mutated a list in the visitor, persist the changes on the overall object
if meta.IsListType(updates.Object) {
meta.SetList(updates.Object, mutatedObjects)
}
switch editMode {
case NormalEditMode:
err = visitToPatch(originalObj, updates, mapper, resourceMapper, encoder, out, errOut, defaultVersion, &results, file)
case EditBeforeCreateMode:
err = visitToCreate(updates, mapper, resourceMapper, out, errOut, defaultVersion, &results, file)
default:
err = fmt.Errorf("Not supported edit mode %q", editMode)
}
if err != nil {
return preservedFile(err, results.file, errOut)
}
// Handle all possible errors
//
// 1. retryable: propose kubectl replace -f
// 2. notfound: indicate the location of the saved configuration of the deleted resource
// 3. invalid: retry those on the spot by looping ie. reloading the editor
if results.retryable > 0 {
fmt.Fprintf(errOut, "You can run `%s replace -f %s` to try this update again.\n", filepath.Base(os.Args[0]), file)
return cmdutil.ErrExit
}
if results.notfound > 0 {
fmt.Fprintf(errOut, "The edits you made on deleted resources have been saved to %q\n", file)
return cmdutil.ErrExit
}
if len(results.edit) == 0 {
if results.notfound == 0 {
os.Remove(file)
} else {
fmt.Fprintf(out, "The edits you made on deleted resources have been saved to %q\n", file)
}
return nil
}
if len(results.header.reasons) > 0 {
containsError = true
}
}
}
switch editMode {
// If doing normal edit we cannot use Visit because we need to edit a list for convenience. Ref: #20519
case NormalEditMode:
return editFn(nil, nil)
// If doing an edit before created, we don't want a list and instead want the normal behavior as kubectl create.
case EditBeforeCreateMode:
return r.Visit(editFn)
default:
return fmt.Errorf("Not supported edit mode %q", editMode)
}
}
func getPrinter(cmd *cobra.Command) (*editPrinterOptions, error) {
switch format := cmdutil.GetFlagString(cmd, "output"); format {
case "json":
return &editPrinterOptions{
printer: &kubectl.JSONPrinter{},
ext: ".json",
addHeader: false,
}, nil
// If flag -o is not specified, use yaml as default
case "yaml", "":
return &editPrinterOptions{
printer: &kubectl.YAMLPrinter{},
ext: ".yaml",
addHeader: true,
}, nil
default:
return nil, cmdutil.UsageError(cmd, "The flag 'output' must be one of yaml|json")
}
}
func getMapperAndResult(f cmdutil.Factory, args []string, options *resource.FilenameOptions, editMode EditMode) (meta.RESTMapper, *resource.Mapper, *resource.Result, string, error) {
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return nil, nil, nil, "", err
}
var mapper meta.RESTMapper
var typer runtime.ObjectTyper
switch editMode {
case NormalEditMode:
mapper, typer = f.Object()
case EditBeforeCreateMode:
mapper, typer, err = f.UnstructuredObject()
default:
return nil, nil, nil, "", fmt.Errorf("Not supported edit mode %q", editMode)
}
if err != nil {
return nil, nil, nil, "", err
}
resourceMapper := &resource.Mapper{
ObjectTyper: typer,
RESTMapper: mapper,
ClientMapper: resource.ClientMapperFunc(f.ClientForMapping),
// NB: we use `f.Decoder(false)` to get a plain deserializer for
// the resourceMapper, since it's used to read in edits and
// we don't want to convert into the internal version when
// reading in edits (this would cause us to potentially try to
// compare two different GroupVersions).
Decoder: f.Decoder(false),
}
var b *resource.Builder
switch editMode {
case NormalEditMode:
b = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ResourceTypeOrNameArgs(true, args...).
Latest()
case EditBeforeCreateMode:
b = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme)
default:
return nil, nil, nil, "", fmt.Errorf("Not supported edit mode %q", editMode)
}
r := b.NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
ContinueOnError().
Flatten().
Do()
err = r.Err()
if err != nil {
return nil, nil, nil, "", err
}
return mapper, resourceMapper, r, cmdNamespace, err
}
func visitToPatch(
originalObj runtime.Object,
updates *resource.Info,
mapper meta.RESTMapper,
resourceMapper *resource.Mapper,
encoder runtime.Encoder,
out, errOut io.Writer,
defaultVersion schema.GroupVersion,
results *editResults,
file string,
) error {
patchVisitor := resource.NewFlattenListVisitor(updates, resourceMapper)
err := patchVisitor.Visit(func(info *resource.Info, incomingErr error) error {
currOriginalObj := originalObj
// if we're editing a list, then navigate the list to find the item that we're currently trying to edit
if meta.IsListType(originalObj) {
currOriginalObj = nil
editObjUID, err := meta.NewAccessor().UID(info.Object)
if err != nil {
return err
}
listItems, err := meta.ExtractList(originalObj)
if err != nil {
return err
}
// iterate through the list to find the item with the matching UID
for i := range listItems {
originalObjUID, err := meta.NewAccessor().UID(listItems[i])
if err != nil {
return err
}
if editObjUID == originalObjUID {
currOriginalObj = listItems[i]
break
}
}
if currOriginalObj == nil {
return fmt.Errorf("no original object found for %#v", info.Object)
}
}
originalSerialization, err := runtime.Encode(encoder, currOriginalObj)
if err != nil {
return err
}
editedSerialization, err := runtime.Encode(encoder, info.Object)
if err != nil {
return err
}
// compute the patch on a per-item basis
// use strategic merge to create a patch
originalJS, err := yaml.ToJSON(originalSerialization)
if err != nil {
return err
}
editedJS, err := yaml.ToJSON(editedSerialization)
if err != nil {
return err
}
if reflect.DeepEqual(originalJS, editedJS) {
// no edit, so just skip it.
cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, false, "skipped")
return nil
}
preconditions := []strategicpatch.PreconditionFunc{strategicpatch.RequireKeyUnchanged("apiVersion"),
strategicpatch.RequireKeyUnchanged("kind"), strategicpatch.RequireMetadataKeyUnchanged("name")}
patch, err := strategicpatch.CreateTwoWayMergePatch(originalJS, editedJS, currOriginalObj, preconditions...)
if err != nil {
glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err)
if strategicpatch.IsPreconditionFailed(err) {
return fmt.Errorf("%s", "At least one of apiVersion, kind and name was changed")
}
return err
}
results.version = defaultVersion
patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch)
if err != nil {
fmt.Fprintln(out, results.addError(err, info))
return nil
}
info.Refresh(patched, true)
cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, false, "edited")
return nil
})
return err
}
func visitToCreate(updates *resource.Info, mapper meta.RESTMapper, resourceMapper *resource.Mapper, out, errOut io.Writer, defaultVersion schema.GroupVersion, results *editResults, file string) error {
createVisitor := resource.NewFlattenListVisitor(updates, resourceMapper)
err := createVisitor.Visit(func(info *resource.Info, incomingErr error) error {
results.version = defaultVersion
if err := createAndRefresh(info); err != nil {
return err
}
cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, false, "created")
return nil
})
return err
}
func visitAnnotation(cmd *cobra.Command, f cmdutil.Factory, updates *resource.Info, resourceMapper *resource.Mapper, encoder runtime.Encoder) ([]runtime.Object, error) {
mutatedObjects := []runtime.Object{}
annotationVisitor := resource.NewFlattenListVisitor(updates, resourceMapper)
// iterate through all items to apply annotations
err := annotationVisitor.Visit(func(info *resource.Info, incomingErr error) error {
// put configuration annotation in "updates"
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, encoder); err != nil {
return err
}
if cmdutil.ShouldRecord(cmd, info) {
if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil {
return err
}
}
mutatedObjects = append(mutatedObjects, info.Object)
return nil
})
return mutatedObjects, err
}
type EditMode string
const (
NormalEditMode EditMode = "normal_mode"
EditBeforeCreateMode EditMode = "edit_before_create_mode"
)
// editReason preserves a message about the reason this file must be edited again
type editReason struct {
head string
other []string
}
// editHeader includes a list of reasons the edit must be retried
type editHeader struct {
reasons []editReason
}
// writeTo outputs the current header information into a stream
func (h *editHeader) writeTo(w io.Writer) error {
fmt.Fprint(w, `# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
`)
for _, r := range h.reasons {
if len(r.other) > 0 {
fmt.Fprintf(w, "# %s:\n", r.head)
} else {
fmt.Fprintf(w, "# %s\n", r.head)
}
for _, o := range r.other {
fmt.Fprintf(w, "# * %s\n", o)
}
fmt.Fprintln(w, "#")
}
return nil
}
func (h *editHeader) flush() {
h.reasons = []editReason{}
}
type editPrinterOptions struct {
printer kubectl.ResourcePrinter
ext string
addHeader bool
}
// editResults capture the result of an update
type editResults struct {
header editHeader
retryable int
notfound int
edit []*resource.Info
file string
version schema.GroupVersion
}
func (r *editResults) addError(err error, info *resource.Info) string {
switch {
case errors.IsInvalid(err):
r.edit = append(r.edit, info)
reason := editReason{
head: fmt.Sprintf("%s %q was not valid", info.Mapping.Resource, info.Name),
}
if err, ok := err.(errors.APIStatus); ok {
if details := err.Status().Details; details != nil {
for _, cause := range details.Causes {
reason.other = append(reason.other, fmt.Sprintf("%s: %s", cause.Field, cause.Message))
}
}
}
r.header.reasons = append(r.header.reasons, reason)
return fmt.Sprintf("error: %s %q is invalid", info.Mapping.Resource, info.Name)
case errors.IsNotFound(err):
r.notfound++
return fmt.Sprintf("error: %s %q could not be found on the server", info.Mapping.Resource, info.Name)
default:
r.retryable++
return fmt.Sprintf("error: %s %q could not be patched: %v", info.Mapping.Resource, info.Name, err)
}
}
// preservedFile writes out a message about the provided file if it exists to the
// provided output stream when an error happens. Used to notify the user where
// their updates were preserved.
func preservedFile(err error, path string, out io.Writer) error {
if len(path) > 0 {
if _, err := os.Stat(path); !os.IsNotExist(err) {
fmt.Fprintf(out, "A copy of your changes has been stored to %q\n", path)
}
}
return err
}
// hasLines returns true if any line in the provided stream is non empty - has non-whitespace
// characters, or the first non-whitespace character is a '#' indicating a comment. Returns
// any errors encountered reading the stream.
func hasLines(r io.Reader) (bool, error) {
// TODO: if any files we read have > 64KB lines, we'll need to switch to bytes.ReadLine
// TODO: probably going to be secrets
s := bufio.NewScanner(r)
for s.Scan() {
if line := strings.TrimSpace(s.Text()); len(line) > 0 && line[0] != '#' {
return true, nil
}
}
if err := s.Err(); err != nil && err != io.EOF {
return false, err
}
return false, nil
}
// stripComments will transform a YAML file into JSON, thus dropping any comments
// in it. Note that if the given file has a syntax error, the transformation will
// fail and we will manually drop all comments from the file.
func stripComments(file []byte) []byte {
stripped := file
stripped, err := yaml.ToJSON(stripped)
if err != nil {
stripped = manualStrip(file)
}
return stripped
}
// manualStrip is used for dropping comments from a YAML file
func manualStrip(file []byte) []byte {
stripped := []byte{}
lines := bytes.Split(file, []byte("\n"))
for i, line := range lines {
if bytes.HasPrefix(bytes.TrimSpace(line), []byte("#")) {
continue
}
stripped = append(stripped, line...)
if i < len(lines)-1 {
stripped = append(stripped, '\n')
}
}
return stripped
}

326
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/exec.go generated vendored Normal file
View file

@ -0,0 +1,326 @@
/*
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"
"net/url"
dockerterm "github.com/docker/docker/pkg/term"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/api"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
"k8s.io/kubernetes/pkg/util/interrupt"
"k8s.io/kubernetes/pkg/util/term"
)
var (
exec_example = templates.Examples(`
# Get output from running 'date' from pod 123456-7890, using the first container by default
kubectl exec 123456-7890 date
# Get output from running 'date' in ruby-container from pod 123456-7890
kubectl exec 123456-7890 -c ruby-container date
# Switch to raw terminal mode, sends stdin to 'bash' in ruby-container from pod 123456-7890
# and sends stdout/stderr from 'bash' back to the client
kubectl exec 123456-7890 -c ruby-container -i -t -- bash -il`)
)
const (
execUsageStr = "expected 'exec POD_NAME COMMAND [ARG1] [ARG2] ... [ARGN]'.\nPOD_NAME and COMMAND are required arguments for the exec command"
)
func NewCmdExec(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command {
options := &ExecOptions{
StreamOptions: StreamOptions{
In: cmdIn,
Out: cmdOut,
Err: cmdErr,
},
Executor: &DefaultRemoteExecutor{},
}
cmd := &cobra.Command{
Use: "exec POD [-c CONTAINER] -- COMMAND [args...]",
Short: "Execute a command in a container",
Long: "Execute a command in a container.",
Example: exec_example,
Run: func(cmd *cobra.Command, args []string) {
argsLenAtDash := cmd.ArgsLenAtDash()
cmdutil.CheckErr(options.Complete(f, cmd, args, argsLenAtDash))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.Run())
},
}
cmd.Flags().StringVarP(&options.PodName, "pod", "p", "", "Pod name")
// TODO support UID
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")
return cmd
}
// RemoteExecutor defines the interface accepted by the Exec command - provided for test stubbing
type RemoteExecutor interface {
Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.TerminalSizeQueue) error
}
// DefaultRemoteExecutor is the standard implementation of remote command execution
type DefaultRemoteExecutor struct{}
func (*DefaultRemoteExecutor) Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.TerminalSizeQueue) error {
exec, err := remotecommand.NewExecutor(config, method, url)
if err != nil {
return err
}
return exec.Stream(remotecommand.StreamOptions{
SupportedProtocols: remotecommandserver.SupportedStreamingProtocols,
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
Tty: tty,
TerminalSizeQueue: terminalSizeQueue,
})
}
type StreamOptions struct {
Namespace string
PodName string
ContainerName string
Stdin bool
TTY bool
// minimize unnecessary output
Quiet bool
// InterruptParent, if set, is used to handle interrupts while attached
InterruptParent *interrupt.Handler
In io.Reader
Out io.Writer
Err io.Writer
// for testing
overrideStreams func() (io.ReadCloser, io.Writer, io.Writer)
isTerminalIn func(t term.TTY) bool
}
// ExecOptions declare the arguments accepted by the Exec command
type ExecOptions struct {
StreamOptions
Command []string
FullCmdName string
SuggestedCmdUsage string
Executor RemoteExecutor
PodClient coreclient.PodsGetter
Config *restclient.Config
}
// Complete verifies command line arguments and loads data from the command environment
func (p *ExecOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []string, argsLenAtDash int) error {
// Let kubectl exec follow rules for `--`, see #13004 issue
if len(p.PodName) == 0 && (len(argsIn) == 0 || argsLenAtDash == 0) {
return cmdutil.UsageError(cmd, execUsageStr)
}
if len(p.PodName) != 0 {
printDeprecationWarning("exec POD_NAME", "-p POD_NAME")
if len(argsIn) < 1 {
return cmdutil.UsageError(cmd, execUsageStr)
}
p.Command = argsIn
} else {
p.PodName = argsIn[0]
p.Command = argsIn[1:]
if len(p.Command) < 1 {
return cmdutil.UsageError(cmd, execUsageStr)
}
}
cmdParent := cmd.Parent()
if cmdParent != nil {
p.FullCmdName = cmdParent.CommandPath()
}
if len(p.FullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "describe") {
p.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe pod/%s' to see all of the containers in this pod.", p.FullCmdName, p.PodName)
}
namespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}
p.Namespace = namespace
config, err := f.ClientConfig()
if err != nil {
return err
}
p.Config = config
clientset, err := f.ClientSet()
if err != nil {
return err
}
p.PodClient = clientset.Core()
return nil
}
// Validate checks that the provided exec options are specified.
func (p *ExecOptions) Validate() error {
if len(p.PodName) == 0 {
return fmt.Errorf("pod name must be specified")
}
if len(p.Command) == 0 {
return fmt.Errorf("you must specify at least one command for the container")
}
if p.Out == nil || p.Err == nil {
return fmt.Errorf("both output and error output must be provided")
}
if p.Executor == nil || p.PodClient == nil || p.Config == nil {
return fmt.Errorf("client, client config, and executor must be provided")
}
return nil
}
func (o *StreamOptions) setupTTY() term.TTY {
t := term.TTY{
Parent: o.InterruptParent,
Out: o.Out,
}
if !o.Stdin {
// need to nil out o.In to make sure we don't create a stream for stdin
o.In = nil
o.TTY = false
return t
}
t.In = o.In
if !o.TTY {
return t
}
if o.isTerminalIn == nil {
o.isTerminalIn = func(tty term.TTY) bool {
return tty.IsTerminalIn()
}
}
if !o.isTerminalIn(t) {
o.TTY = false
if o.Err != nil {
fmt.Fprintln(o.Err, "Unable to use a TTY - input is not a terminal or the right kind of file")
}
return t
}
// if we get to here, the user wants to attach stdin, wants a TTY, and o.In is a terminal, so we
// can safely set t.Raw to true
t.Raw = true
if o.overrideStreams == nil {
// use dockerterm.StdStreams() to get the right I/O handles on Windows
o.overrideStreams = dockerterm.StdStreams
}
stdin, stdout, _ := o.overrideStreams()
o.In = stdin
t.In = stdin
if o.Out != nil {
o.Out = stdout
t.Out = stdout
}
return t
}
// Run executes a validated remote execution against a pod.
func (p *ExecOptions) Run() error {
pod, err := p.PodClient.Pods(p.Namespace).Get(p.PodName, metav1.GetOptions{})
if err != nil {
return err
}
if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed {
return fmt.Errorf("cannot exec into a container in a completed pod; current phase is %s", pod.Status.Phase)
}
containerName := p.ContainerName
if len(containerName) == 0 {
if len(pod.Spec.Containers) > 1 {
usageString := fmt.Sprintf("Defaulting container name to %s.", pod.Spec.Containers[0].Name)
if len(p.SuggestedCmdUsage) > 0 {
usageString = fmt.Sprintf("%s\n%s", usageString, p.SuggestedCmdUsage)
}
fmt.Fprintf(p.Err, "%s\n", usageString)
}
containerName = pod.Spec.Containers[0].Name
}
// ensure we can recover the terminal while attached
t := p.setupTTY()
var sizeQueue term.TerminalSizeQueue
if t.Raw {
// this call spawns a goroutine to monitor/update the terminal size
sizeQueue = t.MonitorSize(t.GetSize())
// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
// true
p.Err = nil
}
fn := func() error {
restClient, err := restclient.RESTClientFor(p.Config)
if err != nil {
return err
}
// TODO: consider abstracting into a client invocation or client helper
req := restClient.Post().
Resource("pods").
Name(pod.Name).
Namespace(pod.Namespace).
SubResource("exec").
Param("container", containerName)
req.VersionedParams(&api.PodExecOptions{
Container: containerName,
Command: p.Command,
Stdin: p.Stdin,
Stdout: p.Out != nil,
Stderr: p.Err != nil,
TTY: t.Raw,
}, api.ParameterCodec)
return p.Executor.Execute("POST", req.URL(), p.Config, p.In, p.Out, p.Err, t.Raw, sizeQueue)
}
if err := t.Safe(fn); err != nil {
return err
}
return nil
}

381
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/exec_test.go generated vendored Normal file
View file

@ -0,0 +1,381 @@
/*
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 (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strings"
"testing"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/util/term"
)
type fakeRemoteExecutor struct {
method string
url *url.URL
execErr error
}
func (f *fakeRemoteExecutor) Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.TerminalSizeQueue) error {
f.method = method
f.url = url
return f.execErr
}
func TestPodAndContainer(t *testing.T) {
tests := []struct {
args []string
argsLenAtDash int
p *ExecOptions
name string
expectError bool
expectedPod string
expectedContainer string
expectedArgs []string
}{
{
p: &ExecOptions{},
argsLenAtDash: -1,
expectError: true,
name: "empty",
},
{
p: &ExecOptions{StreamOptions: StreamOptions{PodName: "foo"}},
argsLenAtDash: -1,
expectError: true,
name: "no cmd",
},
{
p: &ExecOptions{StreamOptions: StreamOptions{PodName: "foo", ContainerName: "bar"}},
argsLenAtDash: -1,
expectError: true,
name: "no cmd, w/ container",
},
{
p: &ExecOptions{StreamOptions: StreamOptions{PodName: "foo"}},
args: []string{"cmd"},
argsLenAtDash: -1,
expectedPod: "foo",
expectedArgs: []string{"cmd"},
name: "pod in flags",
},
{
p: &ExecOptions{},
args: []string{"foo", "cmd"},
argsLenAtDash: 0,
expectError: true,
name: "no pod, pod name is behind dash",
},
{
p: &ExecOptions{},
args: []string{"foo"},
argsLenAtDash: -1,
expectError: true,
name: "no cmd, w/o flags",
},
{
p: &ExecOptions{},
args: []string{"foo", "cmd"},
argsLenAtDash: -1,
expectedPod: "foo",
expectedArgs: []string{"cmd"},
name: "cmd, w/o flags",
},
{
p: &ExecOptions{},
args: []string{"foo", "cmd"},
argsLenAtDash: 1,
expectedPod: "foo",
expectedArgs: []string{"cmd"},
name: "cmd, cmd is behind dash",
},
{
p: &ExecOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
args: []string{"foo", "cmd"},
argsLenAtDash: -1,
expectedPod: "foo",
expectedContainer: "bar",
expectedArgs: []string{"cmd"},
name: "cmd, container in flag",
},
}
for _, test := range tests {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { return nil, nil }),
}
tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
cmd := &cobra.Command{}
options := test.p
err := options.Complete(f, cmd, test.args, test.argsLenAtDash)
if test.expectError && err == nil {
t.Errorf("unexpected non-error (%s)", test.name)
}
if !test.expectError && err != nil {
t.Errorf("unexpected error: %v (%s)", err, test.name)
}
if err != nil {
continue
}
if options.PodName != test.expectedPod {
t.Errorf("expected: %s, got: %s (%s)", test.expectedPod, options.PodName, test.name)
}
if options.ContainerName != test.expectedContainer {
t.Errorf("expected: %s, got: %s (%s)", test.expectedContainer, options.ContainerName, test.name)
}
if !reflect.DeepEqual(test.expectedArgs, options.Command) {
t.Errorf("expected: %v, got %v (%s)", test.expectedArgs, options.Command, test.name)
}
}
}
func TestExec(t *testing.T) {
version := api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version
tests := []struct {
name, podPath, execPath, container string
pod *api.Pod
execErr bool
}{
{
name: "pod exec",
podPath: "/api/" + version + "/namespaces/test/pods/foo",
execPath: "/api/" + version + "/namespaces/test/pods/foo/exec",
pod: execPod(),
},
{
name: "pod exec error",
podPath: "/api/" + version + "/namespaces/test/pods/foo",
execPath: "/api/" + version + "/namespaces/test/pods/foo/exec",
pod: execPod(),
execErr: true,
},
}
for _, test := range tests {
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
// Ensures no GET is performed when deleting by name
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.ClientConfig = defaultClientConfig()
bufOut := bytes.NewBuffer([]byte{})
bufErr := bytes.NewBuffer([]byte{})
bufIn := bytes.NewBuffer([]byte{})
ex := &fakeRemoteExecutor{}
if test.execErr {
ex.execErr = fmt.Errorf("exec error")
}
params := &ExecOptions{
StreamOptions: StreamOptions{
PodName: "foo",
ContainerName: "bar",
In: bufIn,
Out: bufOut,
Err: bufErr,
},
Executor: ex,
}
cmd := &cobra.Command{}
args := []string{"test", "command"}
if err := params.Complete(f, cmd, args, -1); err != nil {
t.Fatal(err)
}
err := params.Run()
if test.execErr && err != ex.execErr {
t.Errorf("%s: Unexpected exec error: %v", test.name, err)
continue
}
if !test.execErr && err != nil {
t.Errorf("%s: Unexpected error: %v", test.name, err)
continue
}
if test.execErr {
continue
}
if ex.url.Path != test.execPath {
t.Errorf("%s: Did not get expected path for exec request", test.name)
continue
}
if ex.method != "POST" {
t.Errorf("%s: Did not get method for exec request: %s", test.name, ex.method)
}
}
}
func execPod() *api.Pod {
return &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{
{
Name: "bar",
},
},
},
Status: api.PodStatus{
Phase: api.PodRunning,
},
}
}
func TestSetupTTY(t *testing.T) {
stderr := &bytes.Buffer{}
// test 1 - don't attach stdin
o := &StreamOptions{
// InterruptParent: ,
Stdin: false,
In: &bytes.Buffer{},
Out: &bytes.Buffer{},
Err: stderr,
TTY: true,
}
tty := o.setupTTY()
if o.In != nil {
t.Errorf("don't attach stdin: o.In should be nil")
}
if tty.In != nil {
t.Errorf("don't attach stdin: tty.In should be nil")
}
if o.TTY {
t.Errorf("don't attach stdin: o.TTY should be false")
}
if tty.Raw {
t.Errorf("don't attach stdin: tty.Raw should be false")
}
if len(stderr.String()) > 0 {
t.Errorf("don't attach stdin: stderr wasn't empty: %s", stderr.String())
}
// tests from here on attach stdin
// test 2 - don't request a TTY
o.Stdin = true
o.In = &bytes.Buffer{}
o.TTY = false
tty = o.setupTTY()
if o.In == nil {
t.Errorf("attach stdin, no TTY: o.In should not be nil")
}
if tty.In != o.In {
t.Errorf("attach stdin, no TTY: tty.In should equal o.In")
}
if o.TTY {
t.Errorf("attach stdin, no TTY: o.TTY should be false")
}
if tty.Raw {
t.Errorf("attach stdin, no TTY: tty.Raw should be false")
}
if len(stderr.String()) > 0 {
t.Errorf("attach stdin, no TTY: stderr wasn't empty: %s", stderr.String())
}
// test 3 - request a TTY, but stdin is not a terminal
o.Stdin = true
o.In = &bytes.Buffer{}
o.Err = stderr
o.TTY = true
tty = o.setupTTY()
if o.In == nil {
t.Errorf("attach stdin, TTY, not a terminal: o.In should not be nil")
}
if tty.In != o.In {
t.Errorf("attach stdin, TTY, not a terminal: tty.In should equal o.In")
}
if o.TTY {
t.Errorf("attach stdin, TTY, not a terminal: o.TTY should be false")
}
if tty.Raw {
t.Errorf("attach stdin, TTY, not a terminal: tty.Raw should be false")
}
if !strings.Contains(stderr.String(), "input is not a terminal") {
t.Errorf("attach stdin, TTY, not a terminal: expected 'input is not a terminal' to stderr")
}
// test 4 - request a TTY, stdin is a terminal
o.Stdin = true
o.In = &bytes.Buffer{}
stderr.Reset()
o.TTY = true
overrideStdin := ioutil.NopCloser(&bytes.Buffer{})
overrideStdout := &bytes.Buffer{}
overrideStderr := &bytes.Buffer{}
o.overrideStreams = func() (io.ReadCloser, io.Writer, io.Writer) {
return overrideStdin, overrideStdout, overrideStderr
}
o.isTerminalIn = func(tty term.TTY) bool {
return true
}
tty = o.setupTTY()
if o.In != overrideStdin {
t.Errorf("attach stdin, TTY, is a terminal: o.In should equal overrideStdin")
}
if tty.In != o.In {
t.Errorf("attach stdin, TTY, is a terminal: tty.In should equal o.In")
}
if !o.TTY {
t.Errorf("attach stdin, TTY, is a terminal: o.TTY should be true")
}
if !tty.Raw {
t.Errorf("attach stdin, TTY, is a terminal: tty.Raw should be true")
}
if len(stderr.String()) > 0 {
t.Errorf("attach stdin, TTY, is a terminal: stderr wasn't empty: %s", stderr.String())
}
if o.Out != overrideStdout {
t.Errorf("attach stdin, TTY, is a terminal: o.Out should equal overrideStdout")
}
if tty.Out != o.Out {
t.Errorf("attach stdin, TTY, is a terminal: tty.Out should equal o.Out")
}
}

119
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/explain.go generated vendored Normal file
View file

@ -0,0 +1,119 @@
/*
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"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
var (
explainLong = templates.LongDesc(`
Documentation of resources.
` + valid_resources)
explainExamples = templates.Examples(`
# Get the documentation of the resource and its fields
kubectl explain pods
# Get the documentation of a specific field of a resource
kubectl explain pods.spec.containers`)
)
// NewCmdExplain returns a cobra command for swagger docs
func NewCmdExplain(f cmdutil.Factory, out, cmdErr io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "explain RESOURCE",
Short: "Documentation of resources",
Long: explainLong,
Example: explainExamples,
Run: func(cmd *cobra.Command, args []string) {
err := RunExplain(f, out, cmdErr, cmd, args)
cmdutil.CheckErr(err)
},
}
cmd.Flags().Bool("recursive", false, "Print the fields of fields (Currently only 1 level deep)")
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
// RunExplain executes the appropriate steps to print a model's documentation
func RunExplain(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, args []string) error {
if len(args) == 0 {
fmt.Fprint(cmdErr, "You must specify the type of resource to explain. ", valid_resources)
return cmdutil.UsageError(cmd, "Required resource not specified.")
}
if len(args) > 1 {
return cmdutil.UsageError(cmd, "We accept only this format: explain RESOURCE")
}
recursive := cmdutil.GetFlagBool(cmd, "recursive")
apiVersionString := cmdutil.GetFlagString(cmd, "api-version")
apiVersion := schema.GroupVersion{}
mapper, _ := f.Object()
// TODO: After we figured out the new syntax to separate group and resource, allow
// the users to use it in explain (kubectl explain <group><syntax><resource>).
// Refer to issue #16039 for why we do this. Refer to PR #15808 that used "/" syntax.
inModel, fieldsPath, err := kubectl.SplitAndParseResourceRequest(args[0], mapper)
if err != nil {
return err
}
// TODO: We should deduce the group for a resource by discovering the supported resources at server.
fullySpecifiedGVR, groupResource := schema.ParseResourceArg(inModel)
gvk := schema.GroupVersionKind{}
if fullySpecifiedGVR != nil {
gvk, _ = mapper.KindFor(*fullySpecifiedGVR)
}
if gvk.Empty() {
gvk, err = mapper.KindFor(groupResource.WithVersion(""))
if err != nil {
return err
}
}
if len(apiVersionString) == 0 {
groupMeta, err := api.Registry.Group(gvk.Group)
if err != nil {
return err
}
apiVersion = groupMeta.GroupVersion
} else {
apiVersion, err = schema.ParseGroupVersion(apiVersionString)
if err != nil {
return nil
}
}
schema, err := f.SwaggerSchema(apiVersion.WithKind(gvk.Kind))
if err != nil {
return err
}
return kubectl.PrintModelDescription(inModel, fieldsPath, out, schema, recursive)
}

286
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/expose.go generated vendored Normal file
View file

@ -0,0 +1,286 @@
/*
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"
"regexp"
"strings"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
"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"
)
var (
expose_resources = `pod (po), service (svc), replicationcontroller (rc), deployment (deploy), replicaset (rs)`
expose_long = templates.LongDesc(`
Expose a resource as a new Kubernetes service.
Looks up a deployment, service, replica set, replication controller or pod by name and uses the selector
for that resource as the selector for a new service on the specified port. A deployment or replica set
will be exposed as a service only if its selector is convertible to a selector that service supports,
i.e. when the selector contains only the matchLabels component. Note that if no port is specified via
--port and the exposed resource has multiple ports, all will be re-used by the new service. Also if no
labels are specified, the new service will re-use the labels from the resource it exposes.
Possible resources include (case insensitive):
` + expose_resources)
expose_example = templates.Examples(`
# Create a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000.
kubectl expose rc nginx --port=80 --target-port=8000
# Create a service for a replication controller identified by type and name specified in "nginx-controller.yaml", which serves on port 80 and connects to the containers on port 8000.
kubectl expose -f nginx-controller.yaml --port=80 --target-port=8000
# Create a service for a pod valid-pod, which serves on port 444 with the name "frontend"
kubectl expose pod valid-pod --port=444 --name=frontend
# Create a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx-https"
kubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https
# Create a service for a replicated streaming application on port 4100 balancing UDP traffic and named 'video-stream'.
kubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream
# Create a service for a replicated nginx using replica set, which serves on port 80 and connects to the containers on port 8000.
kubectl expose rs nginx --port=80 --target-port=8000
# Create a service for an nginx deployment, which serves on port 80 and connects to the containers on port 8000.
kubectl expose deployment nginx --port=80 --target-port=8000`)
)
func NewCmdExposeService(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}
validArgs, argAliases := []string{}, []string{}
resources := regexp.MustCompile(`\s*,`).Split(expose_resources, -1)
for _, r := range resources {
validArgs = append(validArgs, strings.Fields(r)[0])
argAliases = kubectl.ResourceAliases(validArgs)
}
cmd := &cobra.Command{
Use: "expose (-f FILENAME | TYPE NAME) [--port=port] [--protocol=TCP|UDP] [--target-port=number-or-name] [--name=name] [--external-ip=external-ip-of-service] [--type=type]",
Short: "Take a replication controller, service, deployment or pod and expose it as a new Kubernetes Service",
Long: expose_long,
Example: expose_example,
Run: func(cmd *cobra.Command, args []string) {
err := RunExpose(f, out, cmd, args, options)
cmdutil.CheckErr(err)
},
ValidArgs: validArgs,
ArgAliases: argAliases,
}
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().String("generator", "service/v2", "The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'.")
cmd.Flags().String("protocol", "", "The network protocol for the service to be created. Default is 'TCP'.")
cmd.Flags().String("port", "", "The port that the service should serve on. Copied from the resource being exposed, if unspecified")
cmd.Flags().String("type", "", "Type for this service: ClusterIP, NodePort, or LoadBalancer. Default is 'ClusterIP'.")
// TODO: remove create-external-load-balancer in code on or after Aug 25, 2016.
cmd.Flags().Bool("create-external-load-balancer", false, "If true, create an external load balancer for this service (trumped by --type). Implementation is cloud provider dependent. Default is 'false'.")
cmd.Flags().MarkDeprecated("create-external-load-balancer", "use --type=\"LoadBalancer\" instead")
cmd.Flags().String("load-balancer-ip", "", "IP to assign to the Load Balancer. If empty, an ephemeral IP will be created and used (cloud-provider specific).")
cmd.Flags().String("selector", "", "A label selector to use for this service. Only equality-based selector requirements are supported. If empty (the default) infer the selector from the replication controller or replica set.")
cmd.Flags().StringP("labels", "l", "", "Labels to apply to the service created by this call.")
cmd.Flags().String("container-port", "", "Synonym for --target-port")
cmd.Flags().MarkDeprecated("container-port", "--container-port will be removed in the future, please use --target-port instead")
cmd.Flags().String("target-port", "", "Name or number for the port on the container that the service should direct traffic to. Optional.")
cmd.Flags().String("external-ip", "", "Additional external IP address (not managed by Kubernetes) to accept for the service. If this IP is routed to a node, the service can be accessed by this IP in addition to its generated service IP.")
cmd.Flags().String("overrides", "", "An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field.")
cmd.Flags().String("name", "", "The name for the newly created object.")
cmd.Flags().String("session-affinity", "", "If non-empty, set the session affinity for the service to this; legal values: 'None', 'ClientIP'")
cmd.Flags().String("cluster-ip", "", "ClusterIP to be assigned to the service. Leave empty to auto-allocate, or set to 'None' to create a headless service.")
usage := "identifying the resource to expose a service"
cmdutil.AddFilenameOptionFlags(cmd, options, usage)
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
return cmd
}
func RunExpose(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error {
namespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
mapper, typer := f.Object()
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
ResourceTypeOrNameArgs(false, args...).
Flatten().
Do()
err = r.Err()
if err != nil {
return cmdutil.UsageError(cmd, err.Error())
}
// Get the generator, setup and validate all required parameters
generatorName := cmdutil.GetFlagString(cmd, "generator")
generators := f.Generators("expose")
generator, found := generators[generatorName]
if !found {
return cmdutil.UsageError(cmd, fmt.Sprintf("generator %q not found.", generatorName))
}
names := generator.ParamNames()
err = r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
mapping := info.ResourceMapping()
if err := f.CanBeExposed(mapping.GroupVersionKind.GroupKind()); err != nil {
return err
}
params := kubectl.MakeParams(cmd, names)
name := info.Name
if len(name) > validation.DNS1035LabelMaxLength {
name = name[:validation.DNS1035LabelMaxLength]
}
params["default-name"] = name
// For objects that need a pod selector, derive it from the exposed object in case a user
// didn't explicitly specify one via --selector
if s, found := params["selector"]; found && kubectl.IsZero(s) {
s, err := f.MapBasedSelectorForObject(info.Object)
if err != nil {
return cmdutil.UsageError(cmd, fmt.Sprintf("couldn't retrieve selectors via --selector flag or introspection: %s", err))
}
params["selector"] = s
}
isHeadlessService := params["cluster-ip"] == "None"
// For objects that need a port, derive it from the exposed object in case a user
// didn't explicitly specify one via --port
if port, found := params["port"]; found && kubectl.IsZero(port) {
ports, err := f.PortsForObject(info.Object)
if err != nil {
return cmdutil.UsageError(cmd, fmt.Sprintf("couldn't find port via --port flag or introspection: %s", err))
}
switch len(ports) {
case 0:
if !isHeadlessService {
return cmdutil.UsageError(cmd, "couldn't find port via --port flag or introspection")
}
case 1:
params["port"] = ports[0]
default:
params["ports"] = strings.Join(ports, ",")
}
}
// Always try to derive protocols from the exposed object, may use
// different protocols for different ports.
if _, found := params["protocol"]; found {
protocolsMap, err := f.ProtocolsForObject(info.Object)
if err != nil {
return cmdutil.UsageError(cmd, fmt.Sprintf("couldn't find protocol via introspection: %s", err))
}
if protocols := kubectl.MakeProtocols(protocolsMap); !kubectl.IsZero(protocols) {
params["protocols"] = protocols
}
}
if kubectl.IsZero(params["labels"]) {
labels, err := f.LabelsForObject(info.Object)
if err != nil {
return err
}
params["labels"] = kubectl.MakeLabels(labels)
}
if err = kubectl.ValidateParams(names, params); err != nil {
return err
}
// Check for invalid flags used against the present generator.
if err := kubectl.EnsureFlagsValid(cmd, generators, generatorName); err != nil {
return err
}
// Generate new object
object, err := generator.Generate(params)
if err != nil {
return err
}
if inline := cmdutil.GetFlagString(cmd, "overrides"); len(inline) > 0 {
codec := runtime.NewCodec(f.JSONEncoder(), f.Decoder(true))
object, err = cmdutil.Merge(codec, object, inline, mapping.GroupVersionKind.Kind)
if err != nil {
return err
}
}
resourceMapper := &resource.Mapper{
ObjectTyper: typer,
RESTMapper: mapper,
ClientMapper: resource.ClientMapperFunc(f.ClientForMapping),
Decoder: f.Decoder(true),
}
info, err = resourceMapper.InfoForObject(object, nil)
if err != nil {
return err
}
if cmdutil.ShouldRecord(cmd, info) {
if err := cmdutil.RecordChangeCause(object, f.Command()); err != nil {
return err
}
}
info.Refresh(object, true)
if cmdutil.GetDryRunFlag(cmd) {
if len(cmdutil.GetFlagString(cmd, "output")) > 0 {
return f.PrintObject(cmd, mapper, object, out)
}
cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, true, "exposed")
return nil
}
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil {
return err
}
// Serialize the object with the annotation applied.
object, err = resource.NewHelper(info.Client, info.Mapping).Create(namespace, false, object)
if err != nil {
return err
}
if len(cmdutil.GetFlagString(cmd, "output")) > 0 {
return f.PrintObject(cmd, mapper, object, out)
}
cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, false, "exposed")
return nil
})
if err != nil {
return err
}
return nil
}

502
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/expose_test.go generated vendored Normal file
View file

@ -0,0 +1,502 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"fmt"
"net/http"
"strings"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient/fake"
"k8s.io/kubernetes/pkg/kubectl"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/util/intstr"
)
func TestRunExposeService(t *testing.T) {
tests := []struct {
name string
args []string
ns string
calls map[string]string
input runtime.Object
flags map[string]string
output runtime.Object
expected string
status int
}{
{
name: "expose-service-from-service-no-selector-defined",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Protocol: api.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"app": "go"},
},
},
expected: "service \"foo\" exposed",
status: 200,
},
{
name: "expose-service-from-service",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Protocol: api.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"func": "stream"},
},
},
expected: "service \"foo\" exposed",
status: 200,
},
{
name: "no-name-passed-from-the-cli",
args: []string{"service", "mayor"},
ns: "default",
calls: map[string]string{
"GET": "/namespaces/default/services/mayor",
"POST": "/namespaces/default/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "mayor", Namespace: "default", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Selector: map[string]string{"run": "this"},
},
},
// No --name flag specified below. Service will use the rc's name passed via the 'default-name' parameter
flags: map[string]string{"selector": "run=this", "port": "80", "labels": "runas=amayor"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "mayor", Namespace: "", Labels: map[string]string{"runas": "amayor"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Protocol: api.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(80),
},
},
Selector: map[string]string{"run": "this"},
},
},
expected: "service \"mayor\" exposed",
status: 200,
},
{
name: "expose-service",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "type": "LoadBalancer", "dry-run": "true"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Protocol: api.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"func": "stream"},
Type: api.ServiceTypeLoadBalancer,
},
},
status: 200,
},
{
name: "expose-affinity-service",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "type": "LoadBalancer", "session-affinity": "ClientIP", "dry-run": "true"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Protocol: api.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"func": "stream"},
Type: api.ServiceTypeLoadBalancer,
SessionAffinity: api.ServiceAffinityClientIP,
},
},
status: 200,
},
{
name: "expose-service-cluster-ip",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "10.10.10.10", "dry-run": "true"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Protocol: api.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"func": "stream"},
ClusterIP: "10.10.10.10",
},
},
expected: "service \"foo\" exposed",
status: 200,
},
{
name: "expose-headless-service",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "true"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Protocol: api.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"func": "stream"},
ClusterIP: api.ClusterIPNone,
},
},
expected: "service \"foo\" exposed",
status: 200,
},
{
name: "expose-headless-service-no-port",
args: []string{"service", "baz"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/baz",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"selector": "func=stream", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "true"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{},
Selector: map[string]string{"func": "stream"},
ClusterIP: api.ClusterIPNone,
},
},
expected: "service \"foo\" exposed",
status: 200,
},
{
name: "expose-from-file",
args: []string{},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/redis-master",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "redis-master", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Selector: map[string]string{"app": "go"},
},
},
flags: map[string]string{"filename": "../../../examples/guestbook/redis-master-service.yaml", "selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "dry-run": "true"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Labels: map[string]string{"svc": "test"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Protocol: api.ProtocolUDP,
Port: 14,
TargetPort: intstr.FromInt(14),
},
},
Selector: map[string]string{"func": "stream"},
},
},
status: 200,
},
{
name: "truncate-name",
args: []string{"pod", "a-name-that-is-toooo-big-for-a-service-because-it-can-only-handle-63-characters"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/pods/a-name-that-is-toooo-big-for-a-service-because-it-can-only-handle-63-characters",
"POST": "/namespaces/test/services",
},
input: &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
},
flags: map[string]string{"selector": "svc=frompod", "port": "90", "labels": "svc=frompod", "generator": "service/v2"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "a-name-that-is-toooo-big-for-a-service-because-it-can-only-handle-63-characters", Namespace: "", Labels: map[string]string{"svc": "frompod"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Protocol: api.ProtocolTCP,
Port: 90,
TargetPort: intstr.FromInt(90),
},
},
Selector: map[string]string{"svc": "frompod"},
},
},
expected: "service \"a-name-that-is-toooo-big-for-a-service-because-it-can-only-hand\" exposed",
status: 200,
},
{
name: "expose-multiport-object",
args: []string{"service", "foo"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/foo",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Protocol: api.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(80),
},
{
Protocol: api.ProtocolTCP,
Port: 443,
TargetPort: intstr.FromInt(443),
},
},
},
},
flags: map[string]string{"selector": "svc=fromfoo", "generator": "service/v2", "name": "fromfoo", "dry-run": "true"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "fromfoo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Name: "port-1",
Protocol: api.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(80),
},
{
Name: "port-2",
Protocol: api.ProtocolTCP,
Port: 443,
TargetPort: intstr.FromInt(443),
},
},
Selector: map[string]string{"svc": "fromfoo"},
},
},
status: 200,
},
{
name: "expose-multiprotocol-object",
args: []string{"service", "foo"},
ns: "test",
calls: map[string]string{
"GET": "/namespaces/test/services/foo",
"POST": "/namespaces/test/services",
},
input: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Protocol: api.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(80),
},
{
Protocol: api.ProtocolUDP,
Port: 8080,
TargetPort: intstr.FromInt(8080),
},
{
Protocol: api.ProtocolUDP,
Port: 8081,
TargetPort: intstr.FromInt(8081),
},
},
},
},
flags: map[string]string{"selector": "svc=fromfoo", "generator": "service/v2", "name": "fromfoo", "dry-run": "true"},
output: &api.Service{
ObjectMeta: api.ObjectMeta{Name: "fromfoo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{
Name: "port-1",
Protocol: api.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(80),
},
{
Name: "port-2",
Protocol: api.ProtocolUDP,
Port: 8080,
TargetPort: intstr.FromInt(8080),
},
{
Name: "port-3",
Protocol: api.ProtocolUDP,
Port: 8081,
TargetPort: intstr.FromInt(8081),
},
},
Selector: map[string]string{"svc": "fromfoo"},
},
},
status: 200,
},
}
for _, test := range tests {
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Printer = &kubectl.JSONPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.calls[m] && m == "GET":
return &http.Response{StatusCode: test.status, Header: defaultHeader(), Body: objBody(codec, test.input)}, nil
case p == test.calls[m] && m == "POST":
return &http.Response{StatusCode: test.status, Header: defaultHeader(), Body: objBody(codec, test.output)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = test.ns
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdExposeService(f, buf)
cmd.SetOutput(buf)
for flag, value := range test.flags {
cmd.Flags().Set(flag, value)
}
cmd.Run(cmd, test.args)
out := buf.String()
if _, ok := test.flags["dry-run"]; ok {
buf.Reset()
if err := tf.Printer.PrintObj(test.output, buf); err != nil {
t.Errorf("%s: Unexpected error: %v", test.name, err)
continue
}
test.expected = fmt.Sprintf("service %q exposed (dry run)", test.flags["name"])
}
if !strings.Contains(out, test.expected) {
t.Errorf("%s: Unexpected output! Expected\n%s\ngot\n%s", test.name, test.expected, out)
}
}
}

523
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/get.go generated vendored Normal file
View file

@ -0,0 +1,523 @@
/*
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"
"github.com/spf13/cobra"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/watch"
"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/util/i18n"
"k8s.io/kubernetes/pkg/util/interrupt"
)
// GetOptions 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 GetOptions struct {
resource.FilenameOptions
Raw string
}
var (
get_long = templates.LongDesc(`
Display one or many resources.
` + valid_resources + `
This command will hide resources that have completed. For instance, pods that are in the Succeeded or Failed phases.
You can see the full results for any resource by providing the '--show-all' flag.
By specifying the output as 'template' and providing a Go template as the value
of the --template flag, you can filter the attributes of the fetched resource(s).`)
get_example = templates.Examples(`
# List all pods in ps output format.
kubectl get pods
# List all pods in ps output format with more information (such as node name).
kubectl get pods -o wide
# List a single replication controller with specified NAME in ps output format.
kubectl get replicationcontroller web
# List a single pod in JSON output format.
kubectl get -o json pod web-pod-13je7
# List a pod identified by type and name specified in "pod.yaml" in JSON output format.
kubectl get -f pod.yaml -o json
# Return only the phase value of the specified pod.
kubectl get -o template pod/web-pod-13je7 --template={{.status.phase}}
# List all replication controllers and services together in ps output format.
kubectl get rc,services
# List one or more resources by their type and names.
kubectl get rc/web service/frontend pods/web-pod-13je7
# List all resources with different types.
kubectl get all`)
)
// NewCmdGet creates a command object for the generic "get" action, which
// retrieves one or more resources from a server.
func NewCmdGet(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command {
options := &GetOptions{}
// retrieve a list of handled resources from printer as valid args
validArgs, argAliases := []string{}, []string{}
p, err := f.Printer(nil, kubectl.PrintOptions{
ColumnLabels: []string{},
})
cmdutil.CheckErr(err)
if p != nil {
validArgs = p.HandledResources()
argAliases = kubectl.ResourceAliases(validArgs)
}
cmd := &cobra.Command{
Use: "get [(-o|--output=)json|yaml|wide|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=...] (TYPE [NAME | -l label] | TYPE/NAME ...) [flags]",
Short: "Display one or many resources",
Long: get_long,
Example: get_example,
Run: func(cmd *cobra.Command, args []string) {
err := RunGet(f, out, errOut, cmd, args, options)
cmdutil.CheckErr(err)
},
SuggestFor: []string{"list", "ps"},
ValidArgs: validArgs,
ArgAliases: argAliases,
}
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.")
cmd.Flags().BoolP("watch", "w", false, "After listing/getting the requested object, watch for changes.")
cmd.Flags().Bool("watch-only", false, "Watch for changes to the requested object(s), without listing/getting first.")
cmd.Flags().Bool("show-kind", false, "If present, list the resource type for the requested object(s).")
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().StringSliceP("label-columns", "L", []string{}, "Accepts a comma separated list of labels that are going to be presented as columns. Names are case-sensitive. You can also use multiple flag options like -L label1 -L label2...")
cmd.Flags().Bool("export", false, "If true, use 'export' for the resources. Exported resources are stripped of cluster-specific information.")
usage := "identifying the resource to get from a server."
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmdutil.AddInclude3rdPartyFlags(cmd)
cmd.Flags().StringVar(&options.Raw, "raw", options.Raw, "Raw URI to request from the server. Uses the transport specified by the kubeconfig file.")
return cmd
}
// RunGet implements the generic Get command
// TODO: convert all direct flag accessors to a struct and pass that instead of cmd
func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, options *GetOptions) error {
if len(options.Raw) > 0 {
restClient, err := f.RESTClient()
if err != nil {
return err
}
stream, err := restClient.Get().RequestURI(options.Raw).Stream()
if err != nil {
return err
}
defer stream.Close()
_, err = io.Copy(out, stream)
if err != nil && err != io.EOF {
return err
}
return nil
}
selector := cmdutil.GetFlagString(cmd, "selector")
allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces")
showKind := cmdutil.GetFlagBool(cmd, "show-kind")
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
filterFuncs := f.DefaultResourceFilterFunc()
filterOpts := f.DefaultResourceFilterOptions(cmd, allNamespaces)
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
if allNamespaces {
enforceNamespace = false
}
if len(args) == 0 && cmdutil.IsFilenameEmpty(options.Filenames) {
fmt.Fprint(errOut, "You must specify the type of resource to get. ", valid_resources)
fullCmdName := cmd.Parent().CommandPath()
usageString := "Required resource not specified."
if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "explain") {
usageString = fmt.Sprintf("%s\nUse \"%s explain <resource>\" for a detailed description of that resource (e.g. %[2]s explain pods).", usageString, fullCmdName)
}
return cmdutil.UsageError(cmd, usageString)
}
// always show resources when getting by name or filename
argsHasNames, err := resource.HasNames(args)
if err != nil {
return err
}
if len(options.Filenames) > 0 || argsHasNames {
cmd.Flag("show-all").Value.Set("true")
}
export := cmdutil.GetFlagBool(cmd, "export")
// handle watch separately since we cannot watch multiple resource types
isWatch, isWatchOnly := cmdutil.GetFlagBool(cmd, "watch"), cmdutil.GetFlagBool(cmd, "watch-only")
if isWatch || isWatchOnly {
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
FilenameParam(enforceNamespace, &options.FilenameOptions).
SelectorParam(selector).
ExportParam(export).
ResourceTypeOrNameArgs(true, args...).
SingleResourceType().
Latest().
Do()
err := r.Err()
if err != nil {
return err
}
infos, err := r.Infos()
if err != nil {
return err
}
if len(infos) != 1 {
return i18n.Errorf("watch is only supported on individual resources and resource collections - %d resources were found", len(infos))
}
info := infos[0]
mapping := info.ResourceMapping()
printer, err := f.PrinterForMapping(cmd, mapping, allNamespaces)
if err != nil {
return err
}
obj, err := r.Object()
if err != nil {
return err
}
// watching from resourceVersion 0, starts the watch at ~now and
// will return an initial watch event. Starting form ~now, rather
// the rv of the object will insure that we start the watch from
// inside the watch window, which the rv of the object might not be.
rv := "0"
isList := meta.IsListType(obj)
if isList {
// the resourceVersion of list objects is ~now but won't return
// an initial watch event
rv, err = mapping.MetadataAccessor.ResourceVersion(obj)
if err != nil {
return err
}
}
// print the current object
filteredResourceCount := 0
if !isWatchOnly {
if err := printer.PrintObj(obj, out); err != nil {
return fmt.Errorf("unable to output the provided object: %v", err)
}
filteredResourceCount++
cmdutil.PrintFilterCount(filteredResourceCount, mapping.Resource, filterOpts)
}
// print watched changes
w, err := r.Watch(rv)
if err != nil {
return err
}
first := true
filteredResourceCount = 0
intr := interrupt.New(nil, w.Stop)
intr.Run(func() error {
_, err := watch.Until(0, w, func(e watch.Event) (bool, error) {
if !isList && first {
// drop the initial watch event in the single resource case
first = false
return false, nil
}
err := printer.PrintObj(e.Object, out)
if err != nil {
return false, err
}
filteredResourceCount++
cmdutil.PrintFilterCount(filteredResourceCount, mapping.Resource, filterOpts)
return false, nil
})
return err
})
return nil
}
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
FilenameParam(enforceNamespace, &options.FilenameOptions).
SelectorParam(selector).
ExportParam(export).
ResourceTypeOrNameArgs(true, args...).
ContinueOnError().
Latest().
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
printer, generic, err := cmdutil.PrinterForCommand(cmd)
if err != nil {
return err
}
if generic {
// we flattened the data from the builder, so we have individual items, but now we'd like to either:
// 1. if there is more than one item, combine them all into a single list
// 2. if there is a single item and that item is a list, leave it as its specific list
// 3. if there is a single item and it is not a a list, leave it as a single item
var errs []error
singleItemImplied := false
infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos()
if err != nil {
if singleItemImplied {
return err
}
errs = append(errs, err)
}
if len(infos) == 0 && len(errs) == 0 {
outputEmptyListWarning(errOut)
}
res := ""
if len(infos) > 0 {
res = infos[0].ResourceMapping().Resource
}
var obj runtime.Object
if !singleItemImplied || len(infos) > 1 {
// we have more than one item, so coerce all items into a list
list := &unstructured.UnstructuredList{
Object: map[string]interface{}{
"kind": "List",
"apiVersion": "v1",
"metadata": map[string]interface{}{},
},
}
for _, info := range infos {
list.Items = append(list.Items, info.Object.(*unstructured.Unstructured))
}
obj = list
} else {
obj = infos[0].Object
}
isList := meta.IsListType(obj)
if isList {
filteredResourceCount, items, err := cmdutil.FilterResourceList(obj, filterFuncs, filterOpts)
if err != nil {
return err
}
// take the filtered items and create a new list for display
list := &unstructured.UnstructuredList{
Object: map[string]interface{}{
"kind": "List",
"apiVersion": "v1",
"metadata": map[string]interface{}{},
},
}
if listMeta, err := meta.ListAccessor(obj); err == nil {
list.Object["selfLink"] = listMeta.GetSelfLink()
list.Object["resourceVersion"] = listMeta.GetResourceVersion()
}
for _, item := range items {
list.Items = append(list.Items, item.(*unstructured.Unstructured))
}
if err := printer.PrintObj(list, out); err != nil {
errs = append(errs, err)
}
cmdutil.PrintFilterCount(filteredResourceCount, res, filterOpts)
return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
}
filteredResourceCount := 0
if isFiltered, err := filterFuncs.Filter(obj, filterOpts); !isFiltered {
if err != nil {
glog.V(2).Infof("Unable to filter resource: %v", err)
} else if err := printer.PrintObj(obj, out); err != nil {
errs = append(errs, err)
}
} else if isFiltered {
filteredResourceCount++
}
cmdutil.PrintFilterCount(filteredResourceCount, res, filterOpts)
return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
}
allErrs := []error{}
errs := sets.NewString()
infos, err := r.Infos()
if err != nil {
allErrs = append(allErrs, err)
}
if len(infos) == 0 && len(allErrs) == 0 {
outputEmptyListWarning(errOut)
}
objs := make([]runtime.Object, len(infos))
for ix := range infos {
objs[ix] = infos[ix].Object
}
sorting, err := cmd.Flags().GetString("sort-by")
if err != nil {
return err
}
var sorter *kubectl.RuntimeSort
if len(sorting) > 0 && len(objs) > 1 {
// TODO: questionable
if sorter, err = kubectl.SortObjects(f.Decoder(true), objs, sorting); err != nil {
return err
}
}
// use the default printer for each object
printer = nil
var lastMapping *meta.RESTMapping
w := kubectl.GetNewTabWriter(out)
filteredResourceCount := 0
if resource.MultipleTypesRequested(args) || cmdutil.MustPrintWithKinds(objs, infos, sorter) {
showKind = true
}
for ix := range objs {
var mapping *meta.RESTMapping
var original runtime.Object
if sorter != nil {
mapping = infos[sorter.OriginalPosition(ix)].Mapping
original = infos[sorter.OriginalPosition(ix)].Object
} else {
mapping = infos[ix].Mapping
original = infos[ix].Object
}
if printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource {
if printer != nil {
w.Flush()
cmdutil.PrintFilterCount(filteredResourceCount, lastMapping.Resource, filterOpts)
}
printer, err = f.PrinterForMapping(cmd, mapping, allNamespaces)
if err != nil {
if !errs.Has(err.Error()) {
errs.Insert(err.Error())
allErrs = append(allErrs, err)
}
continue
}
// add linebreak between resource groups (if there is more than one)
// skip linebreak above first resource group
noHeaders := cmdutil.GetFlagBool(cmd, "no-headers")
if lastMapping != nil && !noHeaders {
fmt.Fprintf(errOut, "%s\n", "")
}
lastMapping = mapping
}
// try to convert before apply filter func
decodedObj, _ := kubectl.DecodeUnknownObject(original)
// filter objects if filter has been defined for current object
if isFiltered, err := filterFuncs.Filter(decodedObj, filterOpts); isFiltered {
if err == nil {
filteredResourceCount++
continue
}
if !errs.Has(err.Error()) {
errs.Insert(err.Error())
allErrs = append(allErrs, err)
}
}
if resourcePrinter, found := printer.(*kubectl.HumanReadablePrinter); found {
resourceName := resourcePrinter.GetResourceKind()
if mapping != nil {
if resourceName == "" {
resourceName = mapping.Resource
}
if alias, ok := kubectl.ResourceShortFormFor(mapping.Resource); ok {
resourceName = alias
} else if resourceName == "" {
resourceName = "none"
}
} else {
resourceName = "none"
}
if showKind {
resourcePrinter.EnsurePrintWithKind(resourceName)
}
if err := printer.PrintObj(decodedObj, w); err != nil {
if !errs.Has(err.Error()) {
errs.Insert(err.Error())
allErrs = append(allErrs, err)
}
}
continue
}
if err := printer.PrintObj(decodedObj, w); err != nil {
if !errs.Has(err.Error()) {
errs.Insert(err.Error())
allErrs = append(allErrs, err)
}
continue
}
}
w.Flush()
if printer != nil && lastMapping != nil {
cmdutil.PrintFilterCount(filteredResourceCount, lastMapping.Resource, filterOpts)
}
return utilerrors.NewAggregate(allErrs)
}
// outputEmptyListWarning outputs a warning indicating that no items are available to display
func outputEmptyListWarning(out io.Writer) error {
_, err := fmt.Fprintf(out, "%s\n", "No resources found.")
return err
}

898
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/get_test.go generated vendored Normal file
View file

@ -0,0 +1,898 @@
/*
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 (
"bytes"
encjson "encoding/json"
"io"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"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/runtime/serializer/json"
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
apitesting "k8s.io/kubernetes/pkg/api/testing"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake"
restclientwatch "k8s.io/kubernetes/pkg/client/restclient/watch"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList) {
pods := &api.PodList{
ListMeta: metav1.ListMeta{
ResourceVersion: "15",
},
Items: []api.Pod{
{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: apitesting.DeepEqualSafePodSpec(),
},
{
ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"},
Spec: apitesting.DeepEqualSafePodSpec(),
},
},
}
svc := &api.ServiceList{
ListMeta: metav1.ListMeta{
ResourceVersion: "16",
},
Items: []api.Service{
{
ObjectMeta: api.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: api.ObjectMeta{Name: "rc1", Namespace: "test", ResourceVersion: "18"},
Spec: api.ReplicationControllerSpec{
Replicas: 1,
},
},
},
}
return pods, svc, rc
}
func testComponentStatusData() *api.ComponentStatusList {
good := api.ComponentStatus{
Conditions: []api.ComponentCondition{
{Type: api.ComponentHealthy, Status: api.ConditionTrue, Message: "ok"},
},
ObjectMeta: api.ObjectMeta{Name: "servergood"},
}
bad := api.ComponentStatus{
Conditions: []api.ComponentCondition{
{Type: api.ComponentHealthy, Status: api.ConditionFalse, Message: "", Error: "bad status: 500"},
},
ObjectMeta: api.ObjectMeta{Name: "serverbad"},
}
unknown := api.ComponentStatus{
Conditions: []api.ComponentCondition{
{Type: api.ComponentHealthy, Status: api.ConditionUnknown, Message: "", Error: "fizzbuzz error"},
},
ObjectMeta: api.ObjectMeta{Name: "serverunknown"},
}
return &api.ComponentStatusList{
Items: []api.ComponentStatus{good, bad, unknown},
}
}
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
func TestGetUnknownSchemaObject(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory()
_, _, codec, _ := cmdtesting.NewTestFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))},
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"type", "foo"})
expected := []runtime.Object{cmdtesting.NewInternalType("", "", "foo")}
actual := tf.Printer.(*testPrinter).Objects
if len(actual) != len(expected) {
t.Fatal(actual)
}
for i, obj := range actual {
expectedJSON := runtime.EncodeOrDie(codec, expected[i])
expectedMap := map[string]interface{}{}
if err := encjson.Unmarshal([]byte(expectedJSON), &expectedMap); err != nil {
t.Fatal(err)
}
actualJSON := runtime.EncodeOrDie(api.Codecs.LegacyCodec(), obj)
actualMap := map[string]interface{}{}
if err := encjson.Unmarshal([]byte(actualJSON), &actualMap); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expectedMap, actualMap) {
t.Errorf("unexpected object: \n%#v\n%#v", expectedMap, actualMap)
}
}
}
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
func TestGetSchemaObject(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Mapper = testapi.Default.RESTMapper()
tf.Typer = api.Scheme
codec := testapi.Default.Codec()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.ReplicationController{ObjectMeta: api.ObjectMeta{Name: "foo"}})},
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: "v1"}}}
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.Run(cmd, []string{"replicationcontrollers", "foo"})
if !strings.Contains(buf.String(), "\"foo\"") {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestGetObjects(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])},
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"pods", "foo"})
expected := []runtime.Object{&pods.Items[0]}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func TestGetSortedObjects(t *testing.T) {
pods := &api.PodList{
ListMeta: metav1.ListMeta{
ResourceVersion: "15",
},
Items: []api.Pod{
{
ObjectMeta: api.ObjectMeta{Name: "c", Namespace: "test", ResourceVersion: "10"},
Spec: apitesting.DeepEqualSafePodSpec(),
},
{
ObjectMeta: api.ObjectMeta{Name: "b", Namespace: "test", ResourceVersion: "11"},
Spec: apitesting.DeepEqualSafePodSpec(),
},
{
ObjectMeta: api.ObjectMeta{Name: "a", Namespace: "test", ResourceVersion: "9"},
Spec: apitesting.DeepEqualSafePodSpec(),
},
},
}
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: "v1"}}}
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
// sorting with metedata.name
cmd.Flags().Set("sort-by", ".metadata.name")
cmd.Run(cmd, []string{"pods"})
// expect sorted: a,b,c
expected := []runtime.Object{&pods.Items[2], &pods.Items[1], &pods.Items[0]}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func verifyObjects(t *testing.T, expected, actual []runtime.Object) {
var actualObj runtime.Object
var err error
if len(actual) != len(expected) {
t.Fatal(actual)
}
for i, obj := range actual {
switch obj.(type) {
case runtime.Unstructured, *runtime.Unknown:
actualObj, err = runtime.Decode(
api.Codecs.UniversalDecoder(),
[]byte(runtime.EncodeOrDie(api.Codecs.LegacyCodec(), obj)))
default:
actualObj = obj
err = nil
}
if err != nil {
t.Fatal(err)
}
if !api.Semantic.DeepEqual(expected[i], actualObj) {
t.Errorf("unexpected object: \n%#v\n%#v", expected[i], actualObj)
}
}
}
func TestGetObjectsIdentifiedByFile(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])},
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Flags().Set("filename", "../../../examples/storage/cassandra/cassandra-controller.yaml")
cmd.Run(cmd, []string{})
expected := []runtime.Object{&pods.Items[0]}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func TestGetListObjects(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"pods"})
expected, err := extractResourceList([]runtime.Object{pods})
if err != nil {
t.Fatal(err)
}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func extractResourceList(objs []runtime.Object) ([]runtime.Object, error) {
finalObjs := []runtime.Object{}
for _, obj := range objs {
items, err := meta.ExtractList(obj)
if err != nil {
return nil, err
}
for _, item := range items {
finalObjs = append(finalObjs, item)
}
}
return finalObjs, nil
}
func TestGetAllListObjects(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Flags().Set("show-all", "true")
cmd.Run(cmd, []string{"pods"})
expected, err := extractResourceList([]runtime.Object{pods})
if err != nil {
t.Fatal(err)
}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func TestGetListComponentStatus(t *testing.T) {
statuses := testComponentStatusData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, statuses)},
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"componentstatuses"})
expected, err := extractResourceList([]runtime.Object{statuses})
if err != nil {
t.Fatal(err)
}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func TestGetMultipleTypeObjects(t *testing.T) {
pods, svc, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.URL.Path {
case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
case "/namespaces/test/services":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"pods,services"})
expected, err := extractResourceList([]runtime.Object{pods, svc})
if err != nil {
t.Fatal(err)
}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func TestGetMultipleTypeObjectsAsList(t *testing.T) {
pods, svc, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.URL.Path {
case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
case "/namespaces/test/services":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Flags().Set("output", "json")
cmd.Run(cmd, []string{"pods,services"})
if tf.Printer.(*testPrinter).Objects != nil {
t.Errorf("unexpected print to default printer")
}
out, err := runtime.Decode(codec, buf.Bytes())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
list, err := meta.ExtractList(out)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if errs := runtime.DecodeList(list, codec); len(errs) > 0 {
t.Fatalf("unexpected error: %v", errs)
}
if err := meta.SetList(out, list); err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := &api.List{
Items: []runtime.Object{
&pods.Items[0],
&pods.Items[1],
&svc.Items[0],
},
}
if !reflect.DeepEqual(expected, out) {
t.Errorf("unexpected output: %#v", out)
}
}
func TestGetMultipleTypeObjectsWithSelector(t *testing.T) {
pods, svc, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
if req.URL.Query().Get(metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String())) != "a=b" {
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
}
switch req.URL.Path {
case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
case "/namespaces/test/services":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Flags().Set("selector", "a=b")
cmd.Run(cmd, []string{"pods,services"})
expected, err := extractResourceList([]runtime.Object{pods, svc})
if err != nil {
t.Fatal(err)
}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) {
_, svc, _ := testData()
node := &api.Node{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: api.NodeSpec{
ExternalID: "ext",
},
}
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.URL.Path {
case "/nodes/foo":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, node)}, nil
case "/namespaces/test/services/bar":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"services/bar", "node/foo"})
expected := []runtime.Object{&svc.Items[0], node}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func TestGetByNameForcesFlag(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])},
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"pods", "foo"})
showAllFlag, _ := cmd.Flags().GetBool("show-all")
if !showAllFlag {
t.Errorf("expected showAll to be true when getting resource by name")
}
}
func watchTestData() ([]api.Pod, []watch.Event) {
pods := []api.Pod{
{
ObjectMeta: api.ObjectMeta{
Name: "bar",
Namespace: "test",
ResourceVersion: "9",
},
Spec: apitesting.DeepEqualSafePodSpec(),
},
{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: "test",
ResourceVersion: "10",
},
Spec: apitesting.DeepEqualSafePodSpec(),
},
}
events := []watch.Event{
// current state events
{
Type: watch.Added,
Object: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "bar",
Namespace: "test",
ResourceVersion: "9",
},
Spec: apitesting.DeepEqualSafePodSpec(),
},
},
{
Type: watch.Added,
Object: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: "test",
ResourceVersion: "10",
},
Spec: apitesting.DeepEqualSafePodSpec(),
},
},
// resource events
{
Type: watch.Modified,
Object: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: "test",
ResourceVersion: "11",
},
Spec: apitesting.DeepEqualSafePodSpec(),
},
},
{
Type: watch.Deleted,
Object: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: "test",
ResourceVersion: "12",
},
Spec: apitesting.DeepEqualSafePodSpec(),
},
},
}
return pods, events
}
func TestWatchSelector(t *testing.T) {
pods, events := watchTestData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
podList := &api.PodList{
Items: pods,
ListMeta: metav1.ListMeta{
ResourceVersion: "10",
},
}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
if req.URL.Query().Get(metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String())) != "a=b" {
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
}
switch req.URL.Path {
case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, podList)}, nil
case "/watch/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[2:])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Flags().Set("watch", "true")
cmd.Flags().Set("selector", "a=b")
cmd.Run(cmd, []string{"pods"})
expected := []runtime.Object{podList, events[2].Object, events[3].Object}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func TestWatchResource(t *testing.T) {
pods, events := watchTestData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods[1])}, nil
case "/watch/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[1:])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Flags().Set("watch", "true")
cmd.Run(cmd, []string{"pods", "foo"})
expected := []runtime.Object{&pods[1], events[2].Object, events[3].Object}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func TestWatchResourceIdentifiedByFile(t *testing.T) {
pods, events := watchTestData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.URL.Path {
case "/namespaces/test/replicationcontrollers/cassandra":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods[1])}, nil
case "/watch/namespaces/test/replicationcontrollers/cassandra":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[1:])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Flags().Set("watch", "true")
cmd.Flags().Set("filename", "../../../examples/storage/cassandra/cassandra-controller.yaml")
cmd.Run(cmd, []string{})
expected := []runtime.Object{&pods[1], events[2].Object, events[3].Object}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func TestWatchOnlyResource(t *testing.T) {
pods, events := watchTestData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods[1])}, nil
case "/watch/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[1:])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Flags().Set("watch-only", "true")
cmd.Run(cmd, []string{"pods", "foo"})
expected := []runtime.Object{events[2].Object, events[3].Object}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func TestWatchOnlyList(t *testing.T) {
pods, events := watchTestData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
podList := &api.PodList{
Items: pods,
ListMeta: metav1.ListMeta{
ResourceVersion: "10",
},
}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.URL.Path {
case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, podList)}, nil
case "/watch/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[2:])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf, errBuf)
cmd.SetOutput(buf)
cmd.Flags().Set("watch-only", "true")
cmd.Run(cmd, []string{"pods"})
expected := []runtime.Object{events[2].Object, events[3].Object}
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func watchBody(codec runtime.Codec, events []watch.Event) io.ReadCloser {
buf := bytes.NewBuffer([]byte{})
enc := restclientwatch.NewEncoder(streaming.NewEncoder(buf, codec), codec)
for i := range events {
enc.Encode(&events[i])
}
return json.Framer.NewFrameReader(ioutil.NopCloser(buf))
}

82
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/help.go generated vendored Normal file
View file

@ -0,0 +1,82 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"io"
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
var help_long = templates.LongDesc(`
Help provides help for any command in the application.
Simply type kubectl help [path to command] for full details.`)
func NewCmdHelp(f cmdutil.Factory, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "help [command] | STRING_TO_SEARCH",
Short: "Help about any command",
Long: help_long,
Run: RunHelp,
}
return cmd
}
func RunHelp(cmd *cobra.Command, args []string) {
foundCmd, _, err := cmd.Root().Find(args)
// NOTE(andreykurilin): actually, I did not find any cases when foundCmd can be nil,
// but let's make this check since it is included in original code of initHelpCmd
// from github.com/spf13/cobra
if foundCmd == nil {
cmd.Printf("Unknown help topic %#q.\n", args)
cmd.Root().Usage()
} else if err != nil {
// print error message at first, since it can contain suggestions
cmd.Println(err)
argsString := strings.Join(args, " ")
var matchedMsgIsPrinted bool = false
for _, foundCmd := range foundCmd.Commands() {
if strings.Contains(foundCmd.Short, argsString) {
if !matchedMsgIsPrinted {
cmd.Printf("Matchers of string '%s' in short descriptions of commands: \n", argsString)
matchedMsgIsPrinted = true
}
cmd.Printf(" %-14s %s\n", foundCmd.Name(), foundCmd.Short)
}
}
if !matchedMsgIsPrinted {
// if nothing is found, just print usage
cmd.Root().Usage()
}
} else {
if len(args) == 0 {
// help message for help command :)
foundCmd = cmd
}
helpFunc := foundCmd.HelpFunc()
helpFunc(foundCmd, args)
}
}

344
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/label.go generated vendored Normal file
View file

@ -0,0 +1,344 @@
/*
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 (
"encoding/json"
"fmt"
"io"
"reflect"
"strings"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/kubernetes/pkg/api"
"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/util/strategicpatch"
)
// LabelOptions have the data required to perform the label operation
type LabelOptions struct {
// Filename options
resource.FilenameOptions
// Common user flags
overwrite bool
local bool
dryrun bool
all bool
resourceVersion string
selector string
outputFormat string
// results of arg parsing
resources []string
newLabels map[string]string
removeLabels []string
// Common shared fields
out io.Writer
}
var (
label_long = templates.LongDesc(`
Update the labels on a resource.
* A label must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters.
* If --overwrite is true, then existing labels can be overwritten, otherwise attempting to overwrite a label will result in an error.
* If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used.`)
label_example = templates.Examples(`
# Update pod 'foo' with the label 'unhealthy' and the value 'true'.
kubectl label pods foo unhealthy=true
# Update pod 'foo' with the label 'status' and the value 'unhealthy', overwriting any existing value.
kubectl label --overwrite pods foo status=unhealthy
# Update all pods in the namespace
kubectl label pods --all status=unhealthy
# Update a pod identified by the type and name in "pod.json"
kubectl label -f pod.json status=unhealthy
# Update pod 'foo' only if the resource is unchanged from version 1.
kubectl label pods foo status=unhealthy --resource-version=1
# Update pod 'foo' by removing a label named 'bar' if it exists.
# Does not require the --overwrite flag.
kubectl label pods foo bar-`)
)
func NewCmdLabel(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &LabelOptions{}
// retrieve a list of handled resources from printer as valid args
validArgs, argAliases := []string{}, []string{}
p, err := f.Printer(nil, kubectl.PrintOptions{
ColumnLabels: []string{},
})
cmdutil.CheckErr(err)
if p != nil {
validArgs = p.HandledResources()
argAliases = kubectl.ResourceAliases(validArgs)
}
cmd := &cobra.Command{
Use: "label [--overwrite] (-f FILENAME | TYPE NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--resource-version=version]",
Short: "Update the labels on a resource",
Long: fmt.Sprintf(label_long, validation.LabelValueMaxLength),
Example: label_example,
Run: func(cmd *cobra.Command, args []string) {
if err := options.Complete(f, out, cmd, args); err != nil {
cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
}
if err := options.Validate(); err != nil {
cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
}
cmdutil.CheckErr(options.RunLabel(f, cmd))
},
ValidArgs: validArgs,
ArgAliases: argAliases,
}
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().Bool("overwrite", false, "If true, allow labels to be overwritten, otherwise reject label updates that overwrite existing labels.")
cmd.Flags().Bool("local", false, "If true, label will NOT contact api-server but run locally.")
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.")
cmd.Flags().Bool("all", false, "select all resources in the namespace of the specified resource types")
cmd.Flags().String("resource-version", "", "If non-empty, the labels 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 labels"
cmdutil.AddFilenameOptionFlags(cmd, &options.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 *LabelOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) (err error) {
o.out = out
o.local = cmdutil.GetFlagBool(cmd, "local")
o.overwrite = cmdutil.GetFlagBool(cmd, "overwrite")
o.all = cmdutil.GetFlagBool(cmd, "all")
o.resourceVersion = cmdutil.GetFlagString(cmd, "resource-version")
o.selector = cmdutil.GetFlagString(cmd, "selector")
o.outputFormat = cmdutil.GetFlagString(cmd, "output")
o.dryrun = cmdutil.GetDryRunFlag(cmd)
resources, labelArgs, err := cmdutil.GetResourcesAndPairs(args, "label")
if err != nil {
return err
}
o.resources = resources
o.newLabels, o.removeLabels, err = parseLabels(labelArgs)
return err
}
// Validate checks to the LabelOptions to see if there is sufficient information run the command.
func (o *LabelOptions) Validate() error {
if len(o.resources) < 1 && cmdutil.IsFilenameEmpty(o.FilenameOptions.Filenames) {
return fmt.Errorf("one or more resources must be specified as <resource> <name> or <resource>/<name>")
}
if len(o.newLabels) < 1 && len(o.removeLabels) < 1 {
return fmt.Errorf("at least one label update is required")
}
return nil
}
// RunLabel does the work
func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
changeCause := f.Command()
mapper, typer := f.Object()
b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
Flatten()
if !o.local {
b = b.SelectorParam(o.selector).
ResourceTypeOrNameArgs(o.all, o.resources...).
Latest()
}
one := false
r := b.Do().IntoSingleItemImplied(&one)
if err := r.Err(); err != nil {
return err
}
// only apply resource version locking on a single resource
if !one && len(o.resourceVersion) > 0 {
return fmt.Errorf("--resource-version may only be used with a single resource")
}
// TODO: support bulk generic output a la Get
return r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
var outputObj runtime.Object
dataChangeMsg := "not labeled"
if o.dryrun || o.local {
err = labelFunc(info.Object, o.overwrite, o.resourceVersion, o.newLabels, o.removeLabels)
if err != nil {
return err
}
outputObj = info.Object
} else {
obj, err := cmdutil.MaybeConvertObject(info.Object, info.Mapping.GroupVersionKind.GroupVersion(), info.Mapping)
if err != nil {
return err
}
name, namespace := info.Name, info.Namespace
oldData, err := json.Marshal(obj)
if err != nil {
return err
}
accessor, err := meta.Accessor(obj)
if err != nil {
return err
}
for _, label := range o.removeLabels {
if _, ok := accessor.GetLabels()[label]; !ok {
fmt.Fprintf(o.out, "label %q not found.\n", label)
}
}
if err := labelFunc(obj, o.overwrite, o.resourceVersion, o.newLabels, o.removeLabels); err != nil {
return err
}
if cmdutil.ShouldRecord(cmd, info) {
if err := cmdutil.RecordChangeCause(obj, changeCause); err != nil {
return err
}
}
newData, err := json.Marshal(obj)
if err != nil {
return err
}
if !reflect.DeepEqual(oldData, newData) {
dataChangeMsg = "labeled"
}
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj)
createdPatch := err == nil
if err != nil {
glog.V(2).Infof("couldn't compute patch: %v", err)
}
mapping := info.ResourceMapping()
client, err := f.ClientForMapping(mapping)
if err != nil {
return err
}
helper := resource.NewHelper(client, mapping)
if createdPatch {
outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes)
} else {
outputObj, err = helper.Replace(namespace, name, false, obj)
}
if err != nil {
return err
}
}
if o.outputFormat != "" {
return f.PrintObject(cmd, mapper, outputObj, o.out)
}
cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, o.dryrun, dataChangeMsg)
return nil
})
}
func validateNoOverwrites(accessor meta.Object, labels map[string]string) error {
allErrs := []error{}
for key := range labels {
if value, found := accessor.GetLabels()[key]; found {
allErrs = append(allErrs, fmt.Errorf("'%s' already has a value (%s), and --overwrite is false", key, value))
}
}
return utilerrors.NewAggregate(allErrs)
}
func parseLabels(spec []string) (map[string]string, []string, error) {
labels := map[string]string{}
var remove []string
for _, labelSpec := range spec {
if strings.Index(labelSpec, "=") != -1 {
parts := strings.Split(labelSpec, "=")
if len(parts) != 2 {
return nil, nil, fmt.Errorf("invalid label spec: %v", labelSpec)
}
if errs := validation.IsValidLabelValue(parts[1]); len(errs) != 0 {
return nil, nil, fmt.Errorf("invalid label value: %q: %s", labelSpec, strings.Join(errs, ";"))
}
labels[parts[0]] = parts[1]
} else if strings.HasSuffix(labelSpec, "-") {
remove = append(remove, labelSpec[:len(labelSpec)-1])
} else {
return nil, nil, fmt.Errorf("unknown label spec: %v", labelSpec)
}
}
for _, removeLabel := range remove {
if _, found := labels[removeLabel]; found {
return nil, nil, fmt.Errorf("can not both modify and remove a label in the same command")
}
}
return labels, remove, nil
}
func labelFunc(obj runtime.Object, overwrite bool, resourceVersion string, labels map[string]string, remove []string) error {
accessor, err := meta.Accessor(obj)
if err != nil {
return err
}
if !overwrite {
if err := validateNoOverwrites(accessor, labels); err != nil {
return err
}
}
objLabels := accessor.GetLabels()
if objLabels == nil {
objLabels = make(map[string]string)
}
for key, value := range labels {
objLabels[key] = value
}
for _, label := range remove {
delete(objLabels, label)
}
accessor.SetLabels(objLabels)
if len(resourceVersion) != 0 {
accessor.SetResourceVersion(resourceVersion)
}
return nil
}

482
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/label_test.go generated vendored Normal file
View file

@ -0,0 +1,482 @@
/*
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 (
"bytes"
"net/http"
"reflect"
"strings"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
func TestValidateLabels(t *testing.T) {
tests := []struct {
meta *api.ObjectMeta
labels map[string]string
expectErr bool
test string
}{
{
meta: &api.ObjectMeta{
Labels: map[string]string{
"a": "b",
"c": "d",
},
},
labels: map[string]string{
"a": "c",
"d": "b",
},
test: "one shared",
expectErr: true,
},
{
meta: &api.ObjectMeta{
Labels: map[string]string{
"a": "b",
"c": "d",
},
},
labels: map[string]string{
"b": "d",
"c": "a",
},
test: "second shared",
expectErr: true,
},
{
meta: &api.ObjectMeta{
Labels: map[string]string{
"a": "b",
"c": "d",
},
},
labels: map[string]string{
"b": "a",
"d": "c",
},
test: "no overlap",
},
{
meta: &api.ObjectMeta{},
labels: map[string]string{
"b": "a",
"d": "c",
},
test: "no labels",
},
}
for _, test := range tests {
err := validateNoOverwrites(test.meta, test.labels)
if test.expectErr && err == nil {
t.Errorf("%s: unexpected non-error", test.test)
}
if !test.expectErr && err != nil {
t.Errorf("%s: unexpected error: %v", test.test, err)
}
}
}
func TestParseLabels(t *testing.T) {
tests := []struct {
labels []string
expected map[string]string
expectedRemove []string
expectErr bool
}{
{
labels: []string{"a=b", "c=d"},
expected: map[string]string{"a": "b", "c": "d"},
},
{
labels: []string{},
expected: map[string]string{},
},
{
labels: []string{"a=b", "c=d", "e-"},
expected: map[string]string{"a": "b", "c": "d"},
expectedRemove: []string{"e"},
},
{
labels: []string{"ab", "c=d"},
expectErr: true,
},
{
labels: []string{"a=b", "c=d", "a-"},
expectErr: true,
},
{
labels: []string{"a="},
expected: map[string]string{"a": ""},
},
{
labels: []string{"a=%^$"},
expectErr: true,
},
}
for _, test := range tests {
labels, remove, err := parseLabels(test.labels)
if test.expectErr && err == nil {
t.Errorf("unexpected non-error: %v", test)
}
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v %v", err, test)
}
if !reflect.DeepEqual(labels, test.expected) {
t.Errorf("expected: %v, got %v", test.expected, labels)
}
if !reflect.DeepEqual(remove, test.expectedRemove) {
t.Errorf("expected: %v, got %v", test.expectedRemove, remove)
}
}
}
func TestLabelFunc(t *testing.T) {
tests := []struct {
obj runtime.Object
overwrite bool
version string
labels map[string]string
remove []string
expected runtime.Object
expectErr bool
}{
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b"},
},
},
labels: map[string]string{"a": "b"},
expectErr: true,
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b"},
},
},
labels: map[string]string{"a": "c"},
overwrite: true,
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "c"},
},
},
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b"},
},
},
labels: map[string]string{"c": "d"},
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b", "c": "d"},
},
},
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b"},
},
},
labels: map[string]string{"c": "d"},
version: "2",
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b", "c": "d"},
ResourceVersion: "2",
},
},
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b"},
},
},
labels: map[string]string{},
remove: []string{"a"},
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{},
},
},
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b", "c": "d"},
},
},
labels: map[string]string{"e": "f"},
remove: []string{"a"},
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{
"c": "d",
"e": "f",
},
},
},
},
{
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{},
},
labels: map[string]string{"a": "b"},
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b"},
},
},
},
}
for _, test := range tests {
err := labelFunc(test.obj, test.overwrite, test.version, test.labels, test.remove)
if test.expectErr {
if err == nil {
t.Errorf("unexpected non-error: %v", test)
}
continue
}
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v %v", err, test)
}
if !reflect.DeepEqual(test.obj, test.expected) {
t.Errorf("expected: %v, got %v", test.expected, test.obj)
}
}
}
func TestLabelErrors(t *testing.T) {
testCases := map[string]struct {
args []string
flags map[string]string
errFn func(error) bool
}{
"no args": {
args: []string{},
errFn: func(err error) bool { return strings.Contains(err.Error(), "one or more resources must be specified") },
},
"not enough labels": {
args: []string{"pods"},
errFn: func(err error) bool { return strings.Contains(err.Error(), "at least one label update is required") },
},
"no resources": {
args: []string{"pods-"},
errFn: func(err error) bool { return strings.Contains(err.Error(), "one or more resources must be specified") },
},
"no resources 2": {
args: []string{"pods=bar"},
errFn: func(err error) bool { return strings.Contains(err.Error(), "one or more resources must be specified") },
},
"resources but no selectors": {
args: []string{"pods", "app=bar"},
errFn: func(err error) bool {
return strings.Contains(err.Error(), "resource(s) were provided, but no name, label selector, or --all flag specified")
},
},
"multiple resources but no selectors": {
args: []string{"pods,deployments", "app=bar"},
errFn: func(err error) bool {
return strings.Contains(err.Error(), "resource(s) were provided, but no name, label selector, or --all flag specified")
},
},
}
for k, testCase := range testCases {
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdLabel(f, buf)
cmd.SetOutput(buf)
for k, v := range testCase.flags {
cmd.Flags().Set(k, v)
}
opts := LabelOptions{}
err := opts.Complete(f, buf, cmd, testCase.args)
if err == nil {
err = opts.Validate()
}
if err == nil {
err = opts.RunLabel(f, cmd)
}
if !testCase.errFn(err) {
t.Errorf("%s: unexpected error: %v", k, err)
continue
}
if tf.Printer.(*testPrinter).Objects != nil {
t.Errorf("unexpected print to default printer")
}
if buf.Len() > 0 {
t.Errorf("buffer should be empty: %s", string(buf.Bytes()))
}
}
}
func TestLabelForResourceFromFile(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method {
case "GET":
switch req.URL.Path {
case "/namespaces/test/replicationcontrollers/cassandra":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
case "PATCH":
switch req.URL.Path {
case "/namespaces/test/replicationcontrollers/cassandra":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
default:
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdLabel(f, buf)
opts := LabelOptions{FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../examples/storage/cassandra/cassandra-controller.yaml"}}}
err := opts.Complete(f, buf, cmd, []string{"a=b"})
if err == nil {
err = opts.Validate()
}
if err == nil {
err = opts.RunLabel(f, cmd)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(buf.String(), "labeled") {
t.Errorf("did not set labels: %s", buf.String())
}
}
func TestLabelLocal(t *testing.T) {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdLabel(f, buf)
cmd.Flags().Set("local", "true")
opts := LabelOptions{FilenameOptions: resource.FilenameOptions{
Filenames: []string{"../../../examples/storage/cassandra/cassandra-controller.yaml"}}}
err := opts.Complete(f, buf, cmd, []string{"a=b"})
if err == nil {
err = opts.Validate()
}
if err == nil {
err = opts.RunLabel(f, cmd)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(buf.String(), "labeled") {
t.Errorf("did not set labels: %s", buf.String())
}
}
func TestLabelMultipleObjects(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method {
case "GET":
switch req.URL.Path {
case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
case "PATCH":
switch req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
case "/namespaces/test/pods/bar":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[1])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
default:
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdLabel(f, buf)
cmd.Flags().Set("all", "true")
opts := LabelOptions{}
err := opts.Complete(f, buf, cmd, []string{"pods", "a=b"})
if err == nil {
err = opts.Validate()
}
if err == nil {
err = opts.RunLabel(f, cmd)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if strings.Count(buf.String(), "labeled") != len(pods.Items) {
t.Errorf("not all labels are set: %s", buf.String())
}
}

254
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/logs.go generated vendored Normal file
View file

@ -0,0 +1,254 @@
/*
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 (
"errors"
"io"
"math"
"os"
"time"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
var (
logs_example = templates.Examples(`
# Return snapshot logs from pod nginx with only one container
kubectl logs nginx
# Return snapshot logs for the pods defined by label app=nginx
kubectl logs -lapp=nginx
# Return snapshot of previous terminated ruby container logs from pod web-1
kubectl logs -p -c ruby web-1
# Begin streaming the logs of the ruby container in pod web-1
kubectl logs -f -c ruby web-1
# Display only the most recent 20 lines of output in pod nginx
kubectl logs --tail=20 nginx
# Show all logs from pod nginx written in the last hour
kubectl logs --since=1h nginx`)
selectorTail int64 = 10
)
const (
logsUsageStr = "expected 'logs POD_NAME [CONTAINER_NAME]'.\nPOD_NAME is a required argument for the logs command"
)
type LogsOptions struct {
Namespace string
ResourceArg string
Options runtime.Object
Mapper meta.RESTMapper
Typer runtime.ObjectTyper
ClientMapper resource.ClientMapper
Decoder runtime.Decoder
Object runtime.Object
LogsForObject func(object, options runtime.Object) (*restclient.Request, error)
Out io.Writer
}
// NewCmdLog creates a new pod logs command
func NewCmdLogs(f cmdutil.Factory, out io.Writer) *cobra.Command {
o := &LogsOptions{}
cmd := &cobra.Command{
Use: "logs [-f] [-p] POD [-c CONTAINER]",
Short: "Print the logs for a container in a pod",
Long: "Print the logs for a container in a pod. If the pod has only one container, the container name is optional.",
Example: logs_example,
PreRun: func(cmd *cobra.Command, args []string) {
if len(os.Args) > 1 && os.Args[1] == "log" {
printDeprecationWarning("logs", "log")
}
},
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, out, cmd, args))
if err := o.Validate(); err != nil {
cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
}
cmdutil.CheckErr(o.RunLogs())
},
Aliases: []string{"log"},
}
cmd.Flags().BoolP("follow", "f", false, "Specify if the logs should be streamed.")
cmd.Flags().Bool("timestamps", false, "Include timestamps on each line in the log output")
cmd.Flags().Int64("limit-bytes", 0, "Maximum bytes of logs to return. Defaults to no limit.")
cmd.Flags().BoolP("previous", "p", false, "If true, print the logs for the previous instance of the container in a pod if it exists.")
cmd.Flags().Int64("tail", -1, "Lines of recent log file to display. Defaults to -1 with no selector, showing all log lines otherwise 10, if a selector is provided.")
cmd.Flags().String("since-time", "", "Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used.")
cmd.Flags().Duration("since", 0, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
cmd.Flags().StringP("container", "c", "", "Print the logs of this container")
cmd.Flags().Bool("interactive", false, "If true, prompt the user for input when required.")
cmd.Flags().MarkDeprecated("interactive", "This flag is no longer respected and there is no replacement.")
cmdutil.AddInclude3rdPartyFlags(cmd)
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on.")
return cmd
}
func (o *LogsOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error {
containerName := cmdutil.GetFlagString(cmd, "container")
selector := cmdutil.GetFlagString(cmd, "selector")
switch len(args) {
case 0:
if len(selector) == 0 {
return cmdutil.UsageError(cmd, logsUsageStr)
}
case 1:
o.ResourceArg = args[0]
if len(selector) != 0 {
return cmdutil.UsageError(cmd, "only a selector (-l) or a POD name is allowed")
}
case 2:
if cmd.Flag("container").Changed {
return cmdutil.UsageError(cmd, "only one of -c or an inline [CONTAINER] arg is allowed")
}
o.ResourceArg = args[0]
containerName = args[1]
default:
return cmdutil.UsageError(cmd, logsUsageStr)
}
var err error
o.Namespace, _, err = f.DefaultNamespace()
if err != nil {
return err
}
logOptions := &api.PodLogOptions{
Container: containerName,
Follow: cmdutil.GetFlagBool(cmd, "follow"),
Previous: cmdutil.GetFlagBool(cmd, "previous"),
Timestamps: cmdutil.GetFlagBool(cmd, "timestamps"),
}
if sinceTime := cmdutil.GetFlagString(cmd, "since-time"); len(sinceTime) > 0 {
t, err := api.ParseRFC3339(sinceTime, metav1.Now)
if err != nil {
return err
}
logOptions.SinceTime = &t
}
if limit := cmdutil.GetFlagInt64(cmd, "limit-bytes"); limit != 0 {
logOptions.LimitBytes = &limit
}
if tail := cmdutil.GetFlagInt64(cmd, "tail"); tail != -1 {
logOptions.TailLines = &tail
}
if sinceSeconds := cmdutil.GetFlagDuration(cmd, "since"); sinceSeconds != 0 {
// round up to the nearest second
sec := int64(math.Ceil(float64(sinceSeconds) / float64(time.Second)))
logOptions.SinceSeconds = &sec
}
o.Options = logOptions
o.LogsForObject = f.LogsForObject
o.ClientMapper = resource.ClientMapperFunc(f.ClientForMapping)
o.Out = out
if len(selector) != 0 {
if logOptions.Follow {
return cmdutil.UsageError(cmd, "only one of follow (-f) or selector (-l) is allowed")
}
if len(logOptions.Container) != 0 {
return cmdutil.UsageError(cmd, "a container cannot be specified when using a selector (-l)")
}
if logOptions.TailLines == nil {
logOptions.TailLines = &selectorTail
}
}
mapper, typer := f.Object()
decoder := f.Decoder(true)
if o.Object == nil {
builder := resource.NewBuilder(mapper, typer, o.ClientMapper, decoder).
NamespaceParam(o.Namespace).DefaultNamespace().
SingleResourceType()
if o.ResourceArg != "" {
builder.ResourceNames("pods", o.ResourceArg)
}
if selector != "" {
builder.ResourceTypes("pods").SelectorParam(selector)
}
infos, err := builder.Do().Infos()
if err != nil {
return err
}
if selector == "" && len(infos) != 1 {
return errors.New("expected a resource")
}
o.Object = infos[0].Object
}
return nil
}
func (o LogsOptions) Validate() error {
logsOptions, ok := o.Options.(*api.PodLogOptions)
if !ok {
return errors.New("unexpected logs options object")
}
if errs := validation.ValidatePodLogOptions(logsOptions); len(errs) > 0 {
return errs.ToAggregate()
}
return nil
}
// RunLogs retrieves a pod log
func (o LogsOptions) RunLogs() error {
switch t := o.Object.(type) {
case *api.PodList:
for _, p := range t.Items {
if err := o.getLogs(&p); err != nil {
return err
}
}
return nil
default:
return o.getLogs(o.Object)
}
}
func (o LogsOptions) getLogs(obj runtime.Object) error {
req, err := o.LogsForObject(obj, o.Options)
if err != nil {
return err
}
readCloser, err := req.Stream()
if err != nil {
return err
}
defer readCloser.Close()
_, err = io.Copy(o.Out, readCloser)
return err
}

141
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/logs_test.go generated vendored Normal file
View file

@ -0,0 +1,141 @@
/*
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 (
"bytes"
"io/ioutil"
"net/http"
"os"
"strings"
"testing"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestLog(t *testing.T) {
tests := []struct {
name, version, podPath, logPath, container string
pod *api.Pod
}{
{
name: "v1 - pod log",
version: "v1",
podPath: "/namespaces/test/pods/foo",
logPath: "/api/v1/namespaces/test/pods/foo/log",
pod: testPod(),
},
}
for _, test := range tests {
logContent := "test log content"
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
case p == test.logPath && m == "GET":
body := ioutil.NopCloser(bytes.NewBufferString(logContent))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
// Ensures no GET is performed when deleting by name
t.Errorf("%s: unexpected request: %#v\n%#v", test.name, req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdLogs(f, buf)
cmd.Flags().Set("namespace", "test")
cmd.Run(cmd, []string{"foo"})
if buf.String() != logContent {
t.Errorf("%s: did not get expected log content. Got: %s", test.name, buf.String())
}
}
}
func testPod() *api.Pod {
return &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{
{
Name: "bar",
},
},
},
}
}
func TestValidateLogFlags(t *testing.T) {
f, _, _, _ := cmdtesting.NewAPIFactory()
tests := []struct {
name string
flags map[string]string
expected string
}{
{
name: "since & since-time",
flags: map[string]string{"since": "1h", "since-time": "2006-01-02T15:04:05Z"},
expected: "at most one of `sinceTime` or `sinceSeconds` may be specified",
},
{
name: "negative limit-bytes",
flags: map[string]string{"limit-bytes": "-100"},
expected: "must be greater than 0",
},
{
name: "negative tail",
flags: map[string]string{"tail": "-100"},
expected: "must be greater than or equal to 0",
},
}
for _, test := range tests {
cmd := NewCmdLogs(f, bytes.NewBuffer([]byte{}))
out := ""
for flag, value := range test.flags {
cmd.Flags().Set(flag, value)
}
// checkErr breaks tests in case of errors, plus we just
// need to check errors returned by the command validation
o := &LogsOptions{}
cmd.Run = func(cmd *cobra.Command, args []string) {
o.Complete(f, os.Stdout, cmd, args)
out = o.Validate().Error()
}
cmd.Run(cmd, []string{"foo"})
if !strings.Contains(out, test.expected) {
t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expected, out)
}
}
}

48
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/options.go generated vendored Normal file
View file

@ -0,0 +1,48 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"io"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
"github.com/spf13/cobra"
)
var (
options_example = templates.Examples(`
# Print flags inherited by all commands
kubectl options`)
)
// NewCmdOptions implements the options command
func NewCmdOptions(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "options",
Short: "Print the list of flags inherited by all commands",
Long: "Print the list of flags inherited by all commands",
Example: options_example,
Run: func(cmd *cobra.Command, args []string) {
cmd.Usage()
},
}
templates.UseOptionsTemplates(cmd)
return cmd
}

269
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/patch.go generated vendored Normal file
View file

@ -0,0 +1,269 @@
/*
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 (
"encoding/json"
"fmt"
"io"
"reflect"
"strings"
jsonpatch "github.com/evanphx/json-patch"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/kubernetes/pkg/api"
"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/util/strategicpatch"
)
var patchTypes = map[string]api.PatchType{"json": api.JSONPatchType, "merge": api.MergePatchType, "strategic": api.StrategicMergePatchType}
// PatchOptions 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 PatchOptions struct {
resource.FilenameOptions
Local bool
OutputFormat string
}
var (
patch_long = templates.LongDesc(`
Update field(s) of a resource using strategic merge patch
JSON and YAML formats are accepted.
Please refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable.`)
patch_example = templates.Examples(`
# Partially update a node using strategic merge patch
kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}'
# Partially update a node identified by the type and name specified in "node.json" using strategic merge patch
kubectl patch -f node.json -p '{"spec":{"unschedulable":true}}'
# Update a container's image; spec.containers[*].name is required because it's a merge key
kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve-hostname","image":"new image"}]}}'
# Update a container's image using a json patch with positional arrays
kubectl patch pod valid-pod --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"new image"}]'`)
)
func NewCmdPatch(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &PatchOptions{}
// retrieve a list of handled resources from printer as valid args
validArgs, argAliases := []string{}, []string{}
p, err := f.Printer(nil, kubectl.PrintOptions{
ColumnLabels: []string{},
})
cmdutil.CheckErr(err)
if p != nil {
validArgs = p.HandledResources()
argAliases = kubectl.ResourceAliases(validArgs)
}
cmd := &cobra.Command{
Use: "patch (-f FILENAME | TYPE NAME) -p PATCH",
Short: "Update field(s) of a resource using strategic merge patch",
Long: patch_long,
Example: patch_example,
Run: func(cmd *cobra.Command, args []string) {
options.OutputFormat = cmdutil.GetFlagString(cmd, "output")
err := RunPatch(f, out, cmd, args, options)
cmdutil.CheckErr(err)
},
ValidArgs: validArgs,
ArgAliases: argAliases,
}
cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.")
cmd.MarkFlagRequired("patch")
cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List()))
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
usage := "identifying the resource to update"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.Flags().BoolVar(&options.Local, "local", false, "If true, patch will operate on the content of the file, not the server-side resource.")
return cmd
}
func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *PatchOptions) error {
switch {
case options.Local && len(args) != 0:
return fmt.Errorf("cannot specify --local and server resources")
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
patchType := api.StrategicMergePatchType
patchTypeString := strings.ToLower(cmdutil.GetFlagString(cmd, "type"))
if len(patchTypeString) != 0 {
ok := false
patchType, ok = patchTypes[patchTypeString]
if !ok {
return cmdutil.UsageError(cmd, fmt.Sprintf("--type must be one of %v, not %q", sets.StringKeySet(patchTypes).List(), patchTypeString))
}
}
patch := cmdutil.GetFlagString(cmd, "patch")
if len(patch) == 0 {
return cmdutil.UsageError(cmd, "Must specify -p to patch")
}
patchBytes, err := yaml.ToJSON([]byte(patch))
if err != nil {
return fmt.Errorf("unable to parse %q: %v", patch, err)
}
mapper, typer := f.Object()
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &options.FilenameOptions).
ResourceTypeOrNameArgs(false, args...).
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
}
name, namespace := info.Name, info.Namespace
mapping := info.ResourceMapping()
client, err := f.ClientForMapping(mapping)
if err != nil {
return err
}
if !options.Local {
dataChangedMsg := "not patched"
helper := resource.NewHelper(client, mapping)
patchedObj, err := helper.Patch(namespace, name, patchType, patchBytes)
if err != nil {
return err
}
if cmdutil.ShouldRecord(cmd, info) {
// don't return an error on failure. The patch itself succeeded, its only the hint for that change that failed
// don't bother checking for failures of this replace, because a failure to indicate the hint doesn't fail the command
// also, don't force the replacement. If the replacement fails on a resourceVersion conflict, then it means this
// record hint is likely to be invalid anyway, so avoid the bad hint
patch, err := cmdutil.ChangeResourcePatch(info, f.Command())
if err == nil {
helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch)
}
}
count++
oldData, err := json.Marshal(info.Object)
if err != nil {
return err
}
newData, err := json.Marshal(patchedObj)
if err != nil {
return err
}
if !reflect.DeepEqual(oldData, newData) {
dataChangedMsg = "patched"
}
if options.OutputFormat == "name" || len(options.OutputFormat) == 0 {
cmdutil.PrintSuccess(mapper, options.OutputFormat == "name", out, "", name, false, dataChangedMsg)
}
return nil
}
count++
patchedObj, err := api.Scheme.DeepCopy(info.VersionedObject)
if err != nil {
return err
}
originalObjJS, err := runtime.Encode(api.Codecs.LegacyCodec(mapping.GroupVersionKind.GroupVersion()), info.VersionedObject.(runtime.Object))
if err != nil {
return err
}
originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, patchedObj.(runtime.Object))
if err != nil {
return err
}
targetObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), originalPatchedObjJS)
if err != nil {
return err
}
// TODO: if we ever want to go generic, this allows a clean -o yaml without trying to print columns or anything
// rawExtension := &runtime.Unknown{
// Raw: originalPatchedObjJS,
// }
printer, err := f.PrinterForMapping(cmd, mapping, false)
if err != nil {
return err
}
if err := printer.PrintObj(targetObj, out); err != nil {
return err
}
return nil
})
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("no objects passed to patch")
}
return nil
}
func getPatchedJSON(patchType api.PatchType, originalJS, patchJS []byte, obj runtime.Object) ([]byte, error) {
switch patchType {
case api.JSONPatchType:
patchObj, err := jsonpatch.DecodePatch(patchJS)
if err != nil {
return nil, err
}
return patchObj.Apply(originalJS)
case api.MergePatchType:
return jsonpatch.MergePatch(originalJS, patchJS)
case api.StrategicMergePatchType:
return strategicpatch.StrategicMergePatchData(originalJS, patchJS, obj)
default:
// only here as a safety net - go-restful filters content-type
return nil, fmt.Errorf("unknown Content-Type header for patch: %v", patchType)
}
}

91
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/patch_test.go generated vendored Normal file
View file

@ -0,0 +1,91 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"net/http"
"testing"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestPatchObject(t *testing.T) {
_, svc, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdPatch(f, buf)
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("patch", `{"spec":{"type":"NodePort"}}`)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{"services/frontend"})
// uses the name from the file, not the response
if buf.String() != "frontend\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestPatchObjectFromFile(t *testing.T) {
_, svc, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdPatch(f, buf)
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("patch", `{"spec":{"type":"NodePort"}}`)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml")
cmd.Run(cmd, []string{})
// uses the name from the file, not the response
if buf.String() != "frontend\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}

201
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/portforward.go generated vendored Normal file
View file

@ -0,0 +1,201 @@
/*
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"
"net/url"
"os"
"os/signal"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/api"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/unversioned/portforward"
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
// PortForwardOptions contains all the options for running the port-forward cli command.
type PortForwardOptions struct {
Namespace string
PodName string
RESTClient *restclient.RESTClient
Config *restclient.Config
PodClient coreclient.PodsGetter
Ports []string
PortForwarder portForwarder
StopChannel chan struct{}
ReadyChannel chan struct{}
}
var (
portforward_example = templates.Examples(`
# Listen on ports 5000 and 6000 locally, forwarding data to/from ports 5000 and 6000 in the pod
kubectl port-forward mypod 5000 6000
# Listen on port 8888 locally, forwarding to 5000 in the pod
kubectl port-forward mypod 8888:5000
# Listen on a random port locally, forwarding to 5000 in the pod
kubectl port-forward mypod :5000
# Listen on a random port locally, forwarding to 5000 in the pod
kubectl port-forward mypod 0:5000`)
)
func NewCmdPortForward(f cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.Command {
opts := &PortForwardOptions{
PortForwarder: &defaultPortForwarder{
cmdOut: cmdOut,
cmdErr: cmdErr,
},
}
cmd := &cobra.Command{
Use: "port-forward POD [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N]",
Short: "Forward one or more local ports to a pod",
Long: "Forward one or more local ports to a pod.",
Example: portforward_example,
Run: func(cmd *cobra.Command, args []string) {
if err := opts.Complete(f, cmd, args, cmdOut, cmdErr); err != nil {
cmdutil.CheckErr(err)
}
if err := opts.Validate(); err != nil {
cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
}
if err := opts.RunPortForward(); err != nil {
cmdutil.CheckErr(err)
}
},
}
cmd.Flags().StringP("pod", "p", "", "Pod name")
// TODO support UID
return cmd
}
type portForwarder interface {
ForwardPorts(method string, url *url.URL, opts PortForwardOptions) error
}
type defaultPortForwarder struct {
cmdOut, cmdErr io.Writer
}
func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, opts PortForwardOptions) error {
dialer, err := remotecommand.NewExecutor(opts.Config, method, url)
if err != nil {
return err
}
fw, err := portforward.New(dialer, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.cmdOut, f.cmdErr)
if err != nil {
return err
}
return fw.ForwardPorts()
}
// Complete completes all the required options for port-forward cmd.
func (o *PortForwardOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string, cmdOut io.Writer, cmdErr io.Writer) error {
var err error
o.PodName = cmdutil.GetFlagString(cmd, "pod")
if len(o.PodName) == 0 && len(args) == 0 {
return cmdutil.UsageError(cmd, "POD is required for port-forward")
}
if len(o.PodName) != 0 {
printDeprecationWarning("port-forward POD", "-p POD")
o.Ports = args
} else {
o.PodName = args[0]
o.Ports = args[1:]
}
o.Namespace, _, err = f.DefaultNamespace()
if err != nil {
return err
}
clientset, err := f.ClientSet()
if err != nil {
return err
}
o.PodClient = clientset.Core()
o.Config, err = f.ClientConfig()
if err != nil {
return err
}
o.RESTClient, err = f.RESTClient()
if err != nil {
return err
}
o.StopChannel = make(chan struct{}, 1)
o.ReadyChannel = make(chan struct{})
return nil
}
// Validate validates all the required options for port-forward cmd.
func (o PortForwardOptions) Validate() error {
if len(o.PodName) == 0 {
return fmt.Errorf("pod name must be specified")
}
if len(o.Ports) < 1 {
return fmt.Errorf("at least 1 PORT is required for port-forward")
}
if o.PortForwarder == nil || o.PodClient == nil || o.RESTClient == nil || o.Config == nil {
return fmt.Errorf("client, client config, restClient, and portforwarder must be provided")
}
return nil
}
// RunPortForward implements all the necessary functionality for port-forward cmd.
func (o PortForwardOptions) RunPortForward() error {
pod, err := o.PodClient.Pods(o.Namespace).Get(o.PodName, metav1.GetOptions{})
if err != nil {
return err
}
if pod.Status.Phase != api.PodRunning {
return fmt.Errorf("unable to forward port because pod is not running. Current status=%v", pod.Status.Phase)
}
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
defer signal.Stop(signals)
go func() {
<-signals
if o.StopChannel != nil {
close(o.StopChannel)
}
}()
req := o.RESTClient.Post().
Resource("pods").
Namespace(o.Namespace).
Name(pod.Name).
SubResource("portforward")
return o.PortForwarder.ForwardPorts("POST", req.URL(), o)
}

View file

@ -0,0 +1,135 @@
/*
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"
"net/http"
"net/url"
"os"
"testing"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
type fakePortForwarder struct {
method string
url *url.URL
pfErr error
}
func (f *fakePortForwarder) ForwardPorts(method string, url *url.URL, opts PortForwardOptions) error {
f.method = method
f.url = url
return f.pfErr
}
func testPortForward(t *testing.T, flags map[string]string, args []string) {
version := api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version
tests := []struct {
name string
podPath, pfPath, container string
pod *api.Pod
pfErr bool
}{
{
name: "pod portforward",
podPath: "/api/" + version + "/namespaces/test/pods/foo",
pfPath: "/api/" + version + "/namespaces/test/pods/foo/portforward",
pod: execPod(),
},
{
name: "pod portforward error",
podPath: "/api/" + version + "/namespaces/test/pods/foo",
pfPath: "/api/" + version + "/namespaces/test/pods/foo/portforward",
pod: execPod(),
pfErr: true,
},
}
for _, test := range tests {
var err error
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
// Ensures no GET is performed when deleting by name
t.Errorf("%s: unexpected request: %#v\n%#v", test.name, req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
ff := &fakePortForwarder{}
if test.pfErr {
ff.pfErr = fmt.Errorf("pf error")
}
opts := &PortForwardOptions{}
cmd := NewCmdPortForward(f, os.Stdout, os.Stderr)
cmd.Run = func(cmd *cobra.Command, args []string) {
if err = opts.Complete(f, cmd, args, os.Stdout, os.Stderr); err != nil {
return
}
opts.PortForwarder = ff
if err = opts.Validate(); err != nil {
return
}
err = opts.RunPortForward()
}
for name, value := range flags {
cmd.Flags().Set(name, value)
}
cmd.Run(cmd, args)
if test.pfErr && err != ff.pfErr {
t.Errorf("%s: Unexpected port-forward error: %v", test.name, err)
}
if !test.pfErr && err != nil {
t.Errorf("%s: Unexpected error: %v", test.name, err)
}
if test.pfErr {
continue
}
if ff.url.Path != test.pfPath {
t.Errorf("%s: Did not get expected path for portforward request", test.name)
}
if ff.method != "POST" {
t.Errorf("%s: Did not get method for attach request: %s", test.name, ff.method)
}
}
}
func TestPortForward(t *testing.T) {
testPortForward(t, nil, []string{"foo", ":5000", ":1000"})
}
func TestPortForwardWithPFlag(t *testing.T) {
testPortForward(t, map[string]string{"pod": "foo"}, []string{":5000", ":1000"})
}

141
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/proxy.go generated vendored Normal file
View file

@ -0,0 +1,141 @@
/*
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 (
"errors"
"fmt"
"io"
"net"
"strings"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
var (
default_port = 8001
proxy_long = templates.LongDesc(`
To proxy all of the kubernetes api and nothing else, use:
$ kubectl proxy --api-prefix=/
To proxy only part of the kubernetes api and also some static files:
$ kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/
The above lets you 'curl localhost:8001/api/v1/pods'.
To proxy the entire kubernetes api at a different root, use:
$ kubectl proxy --api-prefix=/custom/
The above lets you 'curl localhost:8001/custom/api/v1/pods'`)
proxy_example = templates.Examples(`
# Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/
kubectl proxy --port=8011 --www=./local/www/
# Run a proxy to kubernetes apiserver on an arbitrary local port.
# The chosen port for the server will be output to stdout.
kubectl proxy --port=0
# Run a proxy to kubernetes apiserver, changing the api prefix to k8s-api
# This makes e.g. the pods api available at localhost:8001/k8s-api/v1/pods/
kubectl proxy --api-prefix=/k8s-api`)
)
func NewCmdProxy(f cmdutil.Factory, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix]",
Short: "Run a proxy to the Kubernetes API server",
Long: proxy_long,
Example: proxy_example,
Run: func(cmd *cobra.Command, args []string) {
err := RunProxy(f, out, cmd)
cmdutil.CheckErr(err)
},
}
cmd.Flags().StringP("www", "w", "", "Also serve static files from the given directory under the specified prefix.")
cmd.Flags().StringP("www-prefix", "P", "/static/", "Prefix to serve static files under, if static file directory is specified.")
cmd.Flags().StringP("api-prefix", "", "/", "Prefix to serve the proxied API under.")
cmd.Flags().String("accept-paths", kubectl.DefaultPathAcceptRE, "Regular expression for paths that the proxy should accept.")
cmd.Flags().String("reject-paths", kubectl.DefaultPathRejectRE, "Regular expression for paths that the proxy should reject.")
cmd.Flags().String("accept-hosts", kubectl.DefaultHostAcceptRE, "Regular expression for hosts that the proxy should accept.")
cmd.Flags().String("reject-methods", kubectl.DefaultMethodRejectRE, "Regular expression for HTTP methods that the proxy should reject.")
cmd.Flags().IntP("port", "p", default_port, "The port on which to run the proxy. Set to 0 to pick a random port.")
cmd.Flags().StringP("address", "", "127.0.0.1", "The IP address on which to serve on.")
cmd.Flags().Bool("disable-filter", false, "If true, disable request filtering in the proxy. This is dangerous, and can leave you vulnerable to XSRF attacks, when used with an accessible port.")
cmd.Flags().StringP("unix-socket", "u", "", "Unix socket on which to run the proxy.")
return cmd
}
func RunProxy(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error {
path := cmdutil.GetFlagString(cmd, "unix-socket")
port := cmdutil.GetFlagInt(cmd, "port")
address := cmdutil.GetFlagString(cmd, "address")
if port != default_port && path != "" {
return errors.New("Don't specify both --unix-socket and --port")
}
clientConfig, err := f.ClientConfig()
if err != nil {
return err
}
staticPrefix := cmdutil.GetFlagString(cmd, "www-prefix")
if !strings.HasSuffix(staticPrefix, "/") {
staticPrefix += "/"
}
apiProxyPrefix := cmdutil.GetFlagString(cmd, "api-prefix")
if !strings.HasSuffix(apiProxyPrefix, "/") {
apiProxyPrefix += "/"
}
filter := &kubectl.FilterServer{
AcceptPaths: kubectl.MakeRegexpArrayOrDie(cmdutil.GetFlagString(cmd, "accept-paths")),
RejectPaths: kubectl.MakeRegexpArrayOrDie(cmdutil.GetFlagString(cmd, "reject-paths")),
AcceptHosts: kubectl.MakeRegexpArrayOrDie(cmdutil.GetFlagString(cmd, "accept-hosts")),
}
if cmdutil.GetFlagBool(cmd, "disable-filter") {
if path == "" {
glog.Warning("Request filter disabled, your proxy is vulnerable to XSRF attacks, please be cautious")
}
filter = nil
}
server, err := kubectl.NewProxyServer(cmdutil.GetFlagString(cmd, "www"), apiProxyPrefix, staticPrefix, filter, clientConfig)
// Separate listening from serving so we can report the bound port
// when it is chosen by os (eg: port == 0)
var l net.Listener
if path == "" {
l, err = server.Listen(address, port)
} else {
l, err = server.ListenUnix(path)
}
if err != nil {
glog.Fatal(err)
}
fmt.Fprintf(out, "Starting to serve on %s", l.Addr().String())
glog.Fatal(server.ServeOnListener(l))
return nil
}

297
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/replace.go generated vendored Normal file
View file

@ -0,0 +1,297 @@
/*
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"
"io/ioutil"
"os"
"path/filepath"
"github.com/spf13/cobra"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/wait"
"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"
)
var (
replace_long = templates.LongDesc(`
Replace a resource by filename or stdin.
JSON and YAML formats are accepted. If replacing an existing resource, the
complete resource spec must be provided. This can be obtained by
$ kubectl get TYPE NAME -o yaml
Please refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable.`)
replace_example = templates.Examples(`
# Replace a pod using the data in pod.json.
kubectl replace -f ./pod.json
# Replace a pod based on the JSON passed into stdin.
cat pod.json | kubectl replace -f -
# Update a single-container pod's image version (tag) to v4
kubectl get pod mypod -o yaml | sed 's/\(image: myimage\):.*$/\1:v4/' | kubectl replace -f -
# Force replace, delete and then re-create the resource
kubectl replace --force -f ./pod.json`)
)
func NewCmdReplace(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}
cmd := &cobra.Command{
Use: "replace -f FILENAME",
// update is deprecated.
Aliases: []string{"update"},
Short: "Replace a resource by filename or stdin",
Long: replace_long,
Example: replace_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
err := RunReplace(f, out, cmd, args, options)
cmdutil.CheckErr(err)
},
}
usage := "to use to replace the resource."
cmdutil.AddFilenameOptionFlags(cmd, options, usage)
cmd.MarkFlagRequired("filename")
cmd.Flags().Bool("force", false, "Delete and re-create the specified resource")
cmd.Flags().Bool("cascade", false, "Only relevant during a force replace. If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController).")
cmd.Flags().Int("grace-period", -1, "Only relevant during a force replace. Period of time in seconds given to the old resource to terminate gracefully. Ignored if negative.")
cmd.Flags().Duration("timeout", 0, "Only relevant during a force replace. 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).")
cmdutil.AddValidateFlags(cmd)
cmdutil.AddOutputFlagsForMutation(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
func RunReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error {
if len(os.Args) > 1 && os.Args[1] == "update" {
printDeprecationWarning("replace", "update")
}
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
if err != nil {
return err
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
force := cmdutil.GetFlagBool(cmd, "force")
if cmdutil.IsFilenameEmpty(options.Filenames) {
return cmdutil.UsageError(cmd, "Must specify --filename to replace")
}
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name"
if force {
return forceReplace(f, out, cmd, args, shortOutput, options)
}
if cmdutil.GetFlagInt(cmd, "grace-period") >= 0 {
return fmt.Errorf("--grace-period must have --force specified")
}
if cmdutil.GetFlagDuration(cmd, "timeout") != 0 {
return fmt.Errorf("--timeout must have --force specified")
}
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
return r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil {
return cmdutil.AddSourceToErr("replacing", info.Source, err)
}
if cmdutil.ShouldRecord(cmd, info) {
if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil {
return cmdutil.AddSourceToErr("replacing", info.Source, err)
}
}
// Serialize the object with the annotation applied.
obj, err := resource.NewHelper(info.Client, info.Mapping).Replace(info.Namespace, info.Name, true, info.Object)
if err != nil {
return cmdutil.AddSourceToErr("replacing", info.Source, err)
}
info.Refresh(obj, true)
f.PrintObjectSpecificMessage(obj, out)
cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "replaced")
return nil
})
}
func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool, options *resource.FilenameOptions) error {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
if err != nil {
return err
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
for i, filename := range options.Filenames {
if filename == "-" {
tempDir, err := ioutil.TempDir("", "kubectl_replace_")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)
tempFilename := filepath.Join(tempDir, "resource.stdin")
err = cmdutil.DumpReaderToFile(os.Stdin, tempFilename)
if err != nil {
return err
}
options.Filenames[i] = tempFilename
}
}
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
ResourceTypeOrNameArgs(false, args...).RequireObject(false).
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
//Replace will create a resource if it doesn't exist already, so ignore not found error
ignoreNotFound := true
timeout := cmdutil.GetFlagDuration(cmd, "timeout")
gracePeriod := cmdutil.GetFlagInt(cmd, "grace-period")
waitForDeletion := false
if gracePeriod == 0 {
// 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.
gracePeriod = 1
waitForDeletion = true
}
// By default use a reaper to delete all related resources.
if cmdutil.GetFlagBool(cmd, "cascade") {
glog.Warningf("\"cascade\" is set, kubectl will delete and re-create all resources managed by this resource (e.g. Pods created by a ReplicationController). Consider using \"kubectl rolling-update\" if you want to update a ReplicationController together with its Pods.")
err = ReapResult(r, f, out, cmdutil.GetFlagBool(cmd, "cascade"), ignoreNotFound, timeout, gracePeriod, waitForDeletion, shortOutput, mapper, false)
} else {
err = DeleteResult(r, out, ignoreNotFound, shortOutput, mapper)
}
if err != nil {
return err
}
if timeout == 0 {
timeout = kubectl.Timeout
}
r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
return wait.PollImmediate(kubectl.Interval, timeout, func() (bool, error) {
if err := info.Get(); !errors.IsNotFound(err) {
return false, err
}
return true, nil
})
})
r = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
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, f.JSONEncoder()); err != nil {
return err
}
if cmdutil.ShouldRecord(cmd, info) {
if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil {
return cmdutil.AddSourceToErr("replacing", info.Source, err)
}
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
if err != nil {
return err
}
count++
info.Refresh(obj, true)
f.PrintObjectSpecificMessage(obj, out)
cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "replaced")
return nil
})
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("no objects passed to replace")
}
return nil
}

View file

@ -0,0 +1,249 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"net/http"
"strings"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient/fake"
"k8s.io/kubernetes/pkg/client/typed/dynamic"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestReplaceObject(t *testing.T) {
_, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
deleted := false
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/api/v1/namespaces/test" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == http.MethodDelete:
deleted = true
fallthrough
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == http.MethodPut:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == http.MethodGet:
statusCode := http.StatusOK
if deleted {
statusCode = http.StatusNotFound
}
return &http.Response{StatusCode: statusCode, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdReplace(f, buf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
// uses the name from the file, not the response
if buf.String() != "replicationcontroller/rc1\n" {
t.Errorf("unexpected output: %s", buf.String())
}
buf.Reset()
cmd.Flags().Set("force", "true")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontroller/redis-master\nreplicationcontroller/rc1\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestReplaceMultipleObject(t *testing.T) {
_, svc, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
redisMasterDeleted := false
frontendDeleted := false
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/api/v1/namespaces/test" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == http.MethodDelete:
redisMasterDeleted = true
fallthrough
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == http.MethodPut:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == http.MethodGet:
statusCode := http.StatusOK
if redisMasterDeleted {
statusCode = http.StatusNotFound
}
return &http.Response{StatusCode: statusCode, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
case p == "/namespaces/test/services/frontend" && m == http.MethodDelete:
frontendDeleted = true
fallthrough
case p == "/namespaces/test/services/frontend" && m == http.MethodPut:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
case p == "/namespaces/test/services/frontend" && m == http.MethodGet:
statusCode := http.StatusOK
if frontendDeleted {
statusCode = http.StatusNotFound
}
return &http.Response{StatusCode: statusCode, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
case p == "/namespaces/test/services" && m == http.MethodPost:
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdReplace(f, buf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontroller/rc1\nservice/baz\n" {
t.Errorf("unexpected output: %s", buf.String())
}
buf.Reset()
cmd.Flags().Set("force", "true")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontroller/redis-master\nservice/frontend\nreplicationcontroller/rc1\nservice/baz\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestReplaceDirectory(t *testing.T) {
_, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
created := map[string]bool{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/api/v1/namespaces/test" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && m == http.MethodPut:
created[p] = true
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && m == http.MethodGet:
statusCode := http.StatusNotFound
if created[p] {
statusCode = http.StatusOK
}
return &http.Response{StatusCode: statusCode, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && m == http.MethodDelete:
delete(created, p)
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers") && m == http.MethodPost:
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdReplace(f, buf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy")
cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontroller/rc1\nreplicationcontroller/rc1\nreplicationcontroller/rc1\n" {
t.Errorf("unexpected output: %s", buf.String())
}
buf.Reset()
cmd.Flags().Set("force", "true")
cmd.Flags().Set("cascade", "false")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontroller/frontend\nreplicationcontroller/redis-master\nreplicationcontroller/redis-slave\n"+
"replicationcontroller/rc1\nreplicationcontroller/rc1\nreplicationcontroller/rc1\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestForceReplaceObjectNotFound(t *testing.T) {
_, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/api/v1/namespaces/test" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil
case p == "/namespaces/test/replicationcontrollers/redis-master" && (m == http.MethodGet || m == http.MethodDelete):
return &http.Response{StatusCode: http.StatusNotFound, Header: defaultHeader(), Body: stringBody("")}, nil
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdReplace(f, buf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("force", "true")
cmd.Flags().Set("cascade", "false")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontroller/rc1\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}

View file

@ -0,0 +1,416 @@
/*
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 (
"bytes"
"fmt"
"io"
"os"
"time"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/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/resource"
"k8s.io/kubernetes/pkg/util/intstr"
)
var (
rollingUpdate_long = templates.LongDesc(`
Perform a rolling update of the given ReplicationController.
Replaces the specified replication controller with a new replication controller by updating one pod at a time to use the
new PodTemplate. The new-controller.json must specify the same namespace as the
existing replication controller and overwrite at least one (common) label in its replicaSelector.
![Workflow](http://kubernetes.io/images/docs/kubectl_rollingupdate.svg)`)
rollingUpdate_example = templates.Examples(`
# Update pods of frontend-v1 using new replication controller data in frontend-v2.json.
kubectl rolling-update frontend-v1 -f frontend-v2.json
# Update pods of frontend-v1 using JSON data passed into stdin.
cat frontend-v2.json | kubectl rolling-update frontend-v1 -f -
# Update the pods of frontend-v1 to frontend-v2 by just changing the image, and switching the
# name of the replication controller.
kubectl rolling-update frontend-v1 frontend-v2 --image=image:v2
# Update the pods of frontend by just changing the image, and keeping the old name.
kubectl rolling-update frontend --image=image:v2
# Abort and reverse an existing rollout in progress (from frontend-v1 to frontend-v2).
kubectl rolling-update frontend-v1 frontend-v2 --rollback`)
)
var (
updatePeriod, _ = time.ParseDuration("1m0s")
timeout, _ = time.ParseDuration("5m0s")
pollInterval, _ = time.ParseDuration("3s")
)
func NewCmdRollingUpdate(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}
cmd := &cobra.Command{
Use: "rolling-update OLD_CONTROLLER_NAME ([NEW_CONTROLLER_NAME] --image=NEW_CONTAINER_IMAGE | -f NEW_CONTROLLER_SPEC)",
// rollingupdate is deprecated.
Aliases: []string{"rollingupdate"},
Short: "Perform a rolling update of the given ReplicationController",
Long: rollingUpdate_long,
Example: rollingUpdate_example,
Run: func(cmd *cobra.Command, args []string) {
err := RunRollingUpdate(f, out, cmd, args, options)
cmdutil.CheckErr(err)
},
}
cmd.Flags().Duration("update-period", updatePeriod, `Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
cmd.Flags().Duration("poll-interval", pollInterval, `Time delay between polling for replication controller status after the update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
cmd.Flags().Duration("timeout", timeout, `Max time to wait for a replication controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
usage := "Filename or URL to file to use to create the new replication controller."
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
cmd.MarkFlagRequired("filename")
cmd.Flags().String("image", "", "Image to use for upgrading the replication controller. Must be distinct from the existing image (either new image or new image tag). Can not be used with --filename/-f")
cmd.MarkFlagRequired("image")
cmd.Flags().String("deployment-label-key", "deployment", "The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise")
cmd.Flags().String("container", "", "Container name which will have its image upgraded. Only relevant when --image is specified, ignored otherwise. Required when using --image on a multi-container pod")
cmd.Flags().String("image-pull-policy", "", "Explicit policy for when to pull container images. Required when --image is same as existing image, ignored otherwise.")
cmd.Flags().Bool("rollback", false, "If true, this is a request to abort an existing rollout that is partially rolled out. It effectively reverses current and next and runs a rollout")
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
func validateArguments(cmd *cobra.Command, filenames, args []string) error {
deploymentKey := cmdutil.GetFlagString(cmd, "deployment-label-key")
image := cmdutil.GetFlagString(cmd, "image")
rollback := cmdutil.GetFlagBool(cmd, "rollback")
errors := []error{}
if len(deploymentKey) == 0 {
errors = append(errors, cmdutil.UsageError(cmd, "--deployment-label-key can not be empty"))
}
if len(filenames) > 1 {
errors = append(errors, cmdutil.UsageError(cmd, "May only specify a single filename for new controller"))
}
if !rollback {
if len(filenames) == 0 && len(image) == 0 {
errors = append(errors, cmdutil.UsageError(cmd, "Must specify --filename or --image for new controller"))
} else if len(filenames) != 0 && len(image) != 0 {
errors = append(errors, cmdutil.UsageError(cmd, "--filename and --image can not both be specified"))
}
} else {
if len(filenames) != 0 || len(image) != 0 {
errors = append(errors, cmdutil.UsageError(cmd, "Don't specify --filename or --image on rollback"))
}
}
if len(args) < 1 {
errors = append(errors, cmdutil.UsageError(cmd, "Must specify the controller to update"))
}
return utilerrors.NewAggregate(errors)
}
func RunRollingUpdate(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error {
if len(os.Args) > 1 && os.Args[1] == "rollingupdate" {
printDeprecationWarning("rolling-update", "rollingupdate")
}
err := validateArguments(cmd, options.Filenames, args)
if err != nil {
return err
}
deploymentKey := cmdutil.GetFlagString(cmd, "deployment-label-key")
filename := ""
image := cmdutil.GetFlagString(cmd, "image")
pullPolicy := cmdutil.GetFlagString(cmd, "image-pull-policy")
oldName := args[0]
rollback := cmdutil.GetFlagBool(cmd, "rollback")
period := cmdutil.GetFlagDuration(cmd, "update-period")
interval := cmdutil.GetFlagDuration(cmd, "poll-interval")
timeout := cmdutil.GetFlagDuration(cmd, "timeout")
dryrun := cmdutil.GetDryRunFlag(cmd)
outputFormat := cmdutil.GetFlagString(cmd, "output")
container := cmdutil.GetFlagString(cmd, "container")
if len(options.Filenames) > 0 {
filename = options.Filenames[0]
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
clientset, err := f.ClientSet()
if err != nil {
return err
}
coreClient := clientset.Core()
var newRc *api.ReplicationController
// fetch rc
oldRc, err := coreClient.ReplicationControllers(cmdNamespace).Get(oldName, metav1.GetOptions{})
if err != nil {
if !errors.IsNotFound(err) || len(image) == 0 || len(args) > 1 {
return err
}
// We're in the middle of a rename, look for an RC with a source annotation of oldName
newRc, err := kubectl.FindSourceController(coreClient, cmdNamespace, oldName)
if err != nil {
return err
}
return kubectl.Rename(coreClient, newRc, oldName)
}
var keepOldName bool
var replicasDefaulted bool
mapper, typer := f.Object()
if len(filename) != 0 {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
if err != nil {
return err
}
request := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
Schema(schema).
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &resource.FilenameOptions{Recursive: false, Filenames: []string{filename}}).
Do()
obj, err := request.Object()
if err != nil {
return err
}
var ok bool
// Handle filename input from stdin. The resource builder always returns an api.List
// when creating resource(s) from a stream.
if list, ok := obj.(*api.List); ok {
if len(list.Items) > 1 {
return cmdutil.UsageError(cmd, "%s specifies multiple items", filename)
}
if len(list.Items) == 0 {
return cmdutil.UsageError(cmd, "please make sure %s exists and is not empty", filename)
}
obj = list.Items[0]
}
newRc, ok = obj.(*api.ReplicationController)
if !ok {
if gvks, _, err := typer.ObjectKinds(obj); err == nil {
return cmdutil.UsageError(cmd, "%s contains a %v not a ReplicationController", filename, gvks[0])
}
glog.V(4).Infof("Object %#v is not a ReplicationController", obj)
return cmdutil.UsageError(cmd, "%s does not specify a valid ReplicationController", filename)
}
infos, err := request.Infos()
if err != nil || len(infos) != 1 {
glog.V(2).Infof("was not able to recover adequate information to discover if .spec.replicas was defaulted")
} else {
replicasDefaulted = isReplicasDefaulted(infos[0])
}
}
// If the --image option is specified, we need to create a new rc with at least one different selector
// than the old rc. This selector is the hash of the rc, with a suffix to provide uniqueness for
// same-image updates.
if len(image) != 0 {
codec := api.Codecs.LegacyCodec(clientset.CoreClient.RESTClient().APIVersion())
keepOldName = len(args) == 1
newName := findNewName(args, oldRc)
if newRc, err = kubectl.LoadExistingNextReplicationController(coreClient, cmdNamespace, newName); err != nil {
return err
}
if newRc != nil {
if inProgressImage := newRc.Spec.Template.Spec.Containers[0].Image; inProgressImage != image {
return cmdutil.UsageError(cmd, "Found existing in-progress update to image (%s).\nEither continue in-progress update with --image=%s or rollback with --rollback", inProgressImage, inProgressImage)
}
fmt.Fprintf(out, "Found existing update in progress (%s), resuming.\n", newRc.Name)
} else {
config := &kubectl.NewControllerConfig{
Namespace: cmdNamespace,
OldName: oldName,
NewName: newName,
Image: image,
Container: container,
DeploymentKey: deploymentKey,
}
if oldRc.Spec.Template.Spec.Containers[0].Image == image {
if len(pullPolicy) == 0 {
return cmdutil.UsageError(cmd, "--image-pull-policy (Always|Never|IfNotPresent) must be provided when --image is the same as existing container image")
}
config.PullPolicy = api.PullPolicy(pullPolicy)
}
newRc, err = kubectl.CreateNewControllerFromCurrentController(coreClient, codec, config)
if err != nil {
return err
}
}
// Update the existing replication controller with pointers to the 'next' controller
// and adding the <deploymentKey> label if necessary to distinguish it from the 'next' controller.
oldHash, err := api.HashObject(oldRc, codec)
if err != nil {
return err
}
// If new image is same as old, the hash may not be distinct, so add a suffix.
oldHash += "-orig"
oldRc, err = kubectl.UpdateExistingReplicationController(coreClient, coreClient, oldRc, cmdNamespace, newRc.Name, deploymentKey, oldHash, out)
if err != nil {
return err
}
}
if rollback {
keepOldName = len(args) == 1
newName := findNewName(args, oldRc)
if newRc, err = kubectl.LoadExistingNextReplicationController(coreClient, cmdNamespace, newName); err != nil {
return err
}
if newRc == nil {
return cmdutil.UsageError(cmd, "Could not find %s to rollback.\n", newName)
}
}
if oldName == newRc.Name {
return cmdutil.UsageError(cmd, "%s cannot have the same name as the existing ReplicationController %s",
filename, oldName)
}
updater := kubectl.NewRollingUpdater(newRc.Namespace, coreClient, coreClient)
// To successfully pull off a rolling update the new and old rc have to differ
// by at least one selector. Every new pod should have the selector and every
// old pod should not have the selector.
var hasLabel bool
for key, oldValue := range oldRc.Spec.Selector {
if newValue, ok := newRc.Spec.Selector[key]; ok && newValue != oldValue {
hasLabel = true
break
}
}
if !hasLabel {
return cmdutil.UsageError(cmd, "%s must specify a matching key with non-equal value in Selector for %s",
filename, oldName)
}
// TODO: handle scales during rolling update
if replicasDefaulted {
newRc.Spec.Replicas = oldRc.Spec.Replicas
}
if dryrun {
oldRcData := &bytes.Buffer{}
newRcData := &bytes.Buffer{}
if outputFormat == "" {
oldRcData.WriteString(oldRc.Name)
newRcData.WriteString(newRc.Name)
} else {
if err := f.PrintObject(cmd, mapper, oldRc, oldRcData); err != nil {
return err
}
if err := f.PrintObject(cmd, mapper, newRc, newRcData); err != nil {
return err
}
}
fmt.Fprintf(out, "Rolling from:\n%s\nTo:\n%s\n", string(oldRcData.Bytes()), string(newRcData.Bytes()))
return nil
}
updateCleanupPolicy := kubectl.DeleteRollingUpdateCleanupPolicy
if keepOldName {
updateCleanupPolicy = kubectl.RenameRollingUpdateCleanupPolicy
}
config := &kubectl.RollingUpdaterConfig{
Out: out,
OldRc: oldRc,
NewRc: newRc,
UpdatePeriod: period,
Interval: interval,
Timeout: timeout,
CleanupPolicy: updateCleanupPolicy,
MaxUnavailable: intstr.FromInt(0),
MaxSurge: intstr.FromInt(1),
}
if rollback {
err = kubectl.AbortRollingUpdate(config)
if err != nil {
return err
}
coreClient.ReplicationControllers(config.NewRc.Namespace).Update(config.NewRc)
}
err = updater.Update(config)
if err != nil {
return err
}
message := "rolling updated"
if keepOldName {
newRc.Name = oldName
} else {
message = fmt.Sprintf("rolling updated to %q", newRc.Name)
}
newRc, err = coreClient.ReplicationControllers(cmdNamespace).Get(newRc.Name, metav1.GetOptions{})
if err != nil {
return err
}
if outputFormat != "" {
return f.PrintObject(cmd, mapper, newRc, out)
}
kinds, _, err := api.Scheme.ObjectKinds(newRc)
if err != nil {
return err
}
_, res := meta.KindToResource(kinds[0])
cmdutil.PrintSuccess(mapper, false, out, res.Resource, oldName, dryrun, message)
return nil
}
func findNewName(args []string, oldRc *api.ReplicationController) string {
if len(args) >= 2 {
return args[1]
}
if oldRc != nil {
newName, _ := kubectl.GetNextControllerAnnotation(oldRc)
return newName
}
return ""
}
func isReplicasDefaulted(info *resource.Info) bool {
if info == nil || info.VersionedObject == nil {
// was unable to recover versioned info
return false
}
switch t := info.VersionedObject.(type) {
case *v1.ReplicationController:
return t.Spec.Replicas == nil
}
return false
}

View file

@ -0,0 +1,92 @@
/*
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 (
"bytes"
"testing"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestValidateArgs(t *testing.T) {
f, _, _, _ := cmdtesting.NewAPIFactory()
tests := []struct {
flags map[string]string
filenames []string
args []string
expectErr bool
testName string
}{
{
expectErr: true,
testName: "nothing",
},
{
flags: map[string]string{},
args: []string{"foo"},
expectErr: true,
testName: "no file, no image",
},
{
filenames: []string{"bar.yaml"},
args: []string{"foo"},
testName: "valid file example",
},
{
flags: map[string]string{
"image": "foo:v2",
},
args: []string{"foo"},
testName: "missing second image name",
},
{
flags: map[string]string{
"image": "foo:v2",
},
args: []string{"foo", "foo-v2"},
testName: "valid image example",
},
{
flags: map[string]string{
"image": "foo:v2",
},
filenames: []string{"bar.yaml"},
args: []string{"foo", "foo-v2"},
expectErr: true,
testName: "both filename and image example",
},
}
for _, test := range tests {
out := &bytes.Buffer{}
cmd := NewCmdRollingUpdate(f, out)
if test.flags != nil {
for key, val := range test.flags {
cmd.Flags().Set(key, val)
}
}
err := validateArguments(cmd, test.filenames, test.args)
if err != nil && !test.expectErr {
t.Errorf("unexpected error: %v (%s)", err, test.testName)
}
if err == nil && test.expectErr {
t.Errorf("unexpected non-error (%s)", test.testName)
}
}
}

49
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/BUILD generated vendored Normal file
View file

@ -0,0 +1,49 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"rollout.go",
"rollout_history.go",
"rollout_pause.go",
"rollout_resume.go",
"rollout_status.go",
"rollout_undo.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/kubectl: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/resource:go_default_library",
"//pkg/util/interrupt:go_default_library",
"//vendor:github.com/renstrom/dedent",
"//vendor:github.com/spf13/cobra",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apimachinery/pkg/watch",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,60 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rollout
import (
"io"
"github.com/renstrom/dedent"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
var (
rollout_long = templates.LongDesc(`
Manage a deployment using subcommands like "kubectl rollout undo deployment/abc"`)
rollout_example = templates.Examples(`
# Rollback to the previous deployment
kubectl rollout undo deployment/abc`)
rollout_valid_resources = dedent.Dedent(`
Valid resource types include:
* deployments
`)
)
func NewCmdRollout(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "rollout SUBCOMMAND",
Short: "Manage a deployment rollout",
Long: rollout_long,
Example: rollout_example,
Run: cmdutil.DefaultSubCommandRun(errOut),
}
// subcommands
cmd.AddCommand(NewCmdRolloutHistory(f, out))
cmd.AddCommand(NewCmdRolloutPause(f, out))
cmd.AddCommand(NewCmdRolloutResume(f, out))
cmd.AddCommand(NewCmdRolloutUndo(f, out))
cmd.AddCommand(NewCmdRolloutStatus(f, out))
return cmd
}

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