vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

View File

@ -0,0 +1,150 @@
{
"Rules": [{
"SelectorRegexp": "k8s[.]io/kubernetes/pkg",
"AllowedPrefixes": [
"k8s.io/kubernetes/pkg/api",
"k8s.io/kubernetes/pkg/api/events",
"k8s.io/kubernetes/pkg/api/legacyscheme",
"k8s.io/kubernetes/pkg/api/pod",
"k8s.io/kubernetes/pkg/api/ref",
"k8s.io/kubernetes/pkg/api/resource",
"k8s.io/kubernetes/pkg/api/service",
"k8s.io/kubernetes/pkg/api/v1/pod",
"k8s.io/kubernetes/pkg/api/v1/service",
"k8s.io/kubernetes/pkg/apis/admissionregistration",
"k8s.io/kubernetes/pkg/apis/admissionregistration/install",
"k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1",
"k8s.io/kubernetes/pkg/apis/apps",
"k8s.io/kubernetes/pkg/apis/apps/install",
"k8s.io/kubernetes/pkg/apis/apps/v1",
"k8s.io/kubernetes/pkg/apis/apps/v1beta1",
"k8s.io/kubernetes/pkg/apis/apps/v1beta2",
"k8s.io/kubernetes/pkg/apis/authentication",
"k8s.io/kubernetes/pkg/apis/authentication/install",
"k8s.io/kubernetes/pkg/apis/authentication/v1",
"k8s.io/kubernetes/pkg/apis/authentication/v1beta1",
"k8s.io/kubernetes/pkg/apis/authorization",
"k8s.io/kubernetes/pkg/apis/authorization/install",
"k8s.io/kubernetes/pkg/apis/authorization/v1",
"k8s.io/kubernetes/pkg/apis/authorization/v1beta1",
"k8s.io/kubernetes/pkg/apis/autoscaling",
"k8s.io/kubernetes/pkg/apis/autoscaling/install",
"k8s.io/kubernetes/pkg/apis/autoscaling/v1",
"k8s.io/kubernetes/pkg/apis/autoscaling/v2beta1",
"k8s.io/kubernetes/pkg/apis/batch",
"k8s.io/kubernetes/pkg/apis/batch/install",
"k8s.io/kubernetes/pkg/apis/batch/v1",
"k8s.io/kubernetes/pkg/apis/batch/v1beta1",
"k8s.io/kubernetes/pkg/apis/batch/v2alpha1",
"k8s.io/kubernetes/pkg/apis/certificates",
"k8s.io/kubernetes/pkg/apis/certificates/install",
"k8s.io/kubernetes/pkg/apis/certificates/v1beta1",
"k8s.io/kubernetes/pkg/apis/componentconfig",
"k8s.io/kubernetes/pkg/apis/componentconfig/install",
"k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1",
"k8s.io/kubernetes/pkg/apis/core",
"k8s.io/kubernetes/pkg/apis/core/helper",
"k8s.io/kubernetes/pkg/apis/core/helper/qos",
"k8s.io/kubernetes/pkg/apis/core/install",
"k8s.io/kubernetes/pkg/apis/core/v1",
"k8s.io/kubernetes/pkg/apis/core/v1/helper",
"k8s.io/kubernetes/pkg/apis/core/v1/helper/qos",
"k8s.io/kubernetes/pkg/apis/core/validation",
"k8s.io/kubernetes/pkg/apis/extensions",
"k8s.io/kubernetes/pkg/apis/extensions/install",
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1",
"k8s.io/kubernetes/pkg/apis/networking",
"k8s.io/kubernetes/pkg/apis/networking/install",
"k8s.io/kubernetes/pkg/apis/networking/v1",
"k8s.io/kubernetes/pkg/apis/policy",
"k8s.io/kubernetes/pkg/apis/policy/install",
"k8s.io/kubernetes/pkg/apis/policy/v1beta1",
"k8s.io/kubernetes/pkg/apis/rbac",
"k8s.io/kubernetes/pkg/apis/rbac/install",
"k8s.io/kubernetes/pkg/apis/rbac/v1",
"k8s.io/kubernetes/pkg/apis/rbac/v1alpha1",
"k8s.io/kubernetes/pkg/apis/rbac/v1beta1",
"k8s.io/kubernetes/pkg/apis/scheduling",
"k8s.io/kubernetes/pkg/apis/scheduling/install",
"k8s.io/kubernetes/pkg/apis/scheduling/v1alpha1",
"k8s.io/kubernetes/pkg/apis/settings",
"k8s.io/kubernetes/pkg/apis/settings/install",
"k8s.io/kubernetes/pkg/apis/settings/v1alpha1",
"k8s.io/kubernetes/pkg/apis/storage",
"k8s.io/kubernetes/pkg/apis/storage/install",
"k8s.io/kubernetes/pkg/apis/storage/util",
"k8s.io/kubernetes/pkg/apis/storage/v1",
"k8s.io/kubernetes/pkg/apis/storage/v1beta1",
"k8s.io/kubernetes/pkg/capabilities",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/scheme",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/networking/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/settings/internalversion",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/storage/internalversion",
"k8s.io/kubernetes/pkg/client/metrics/prometheus",
"k8s.io/kubernetes/pkg/client/unversioned",
"k8s.io/kubernetes/pkg/cloudprovider",
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws",
"k8s.io/kubernetes/pkg/controller",
"k8s.io/kubernetes/pkg/controller/daemon",
"k8s.io/kubernetes/pkg/controller/daemon/util",
"k8s.io/kubernetes/pkg/controller/deployment/util",
"k8s.io/kubernetes/pkg/controller/history",
"k8s.io/kubernetes/pkg/controller/statefulset",
"k8s.io/kubernetes/pkg/credentialprovider",
"k8s.io/kubernetes/pkg/credentialprovider/aws",
"k8s.io/kubernetes/pkg/features",
"k8s.io/kubernetes/pkg/fieldpath",
"k8s.io/kubernetes/pkg/generated",
"k8s.io/kubernetes/pkg/kubectl",
"k8s.io/kubernetes/pkg/kubelet/apis",
"k8s.io/kubernetes/pkg/kubelet/qos",
"k8s.io/kubernetes/pkg/kubelet/types",
"k8s.io/kubernetes/pkg/master/ports",
"k8s.io/kubernetes/pkg/printers",
"k8s.io/kubernetes/pkg/printers/internalversion",
"k8s.io/kubernetes/pkg/registry/rbac/reconciliation",
"k8s.io/kubernetes/pkg/registry/rbac/validation",
"k8s.io/kubernetes/pkg/security/apparmor",
"k8s.io/kubernetes/pkg/serviceaccount",
"k8s.io/kubernetes/pkg/util/file",
"k8s.io/kubernetes/pkg/util/goroutinemap",
"k8s.io/kubernetes/pkg/util/hash",
"k8s.io/kubernetes/pkg/util/interrupt",
"k8s.io/kubernetes/pkg/util/io",
"k8s.io/kubernetes/pkg/util/labels",
"k8s.io/kubernetes/pkg/util/metrics",
"k8s.io/kubernetes/pkg/util/mount",
"k8s.io/kubernetes/pkg/util/net/sets",
"k8s.io/kubernetes/pkg/util/node",
"k8s.io/kubernetes/pkg/util/nsenter",
"k8s.io/kubernetes/pkg/util/parsers",
"k8s.io/kubernetes/pkg/util/pointer",
"k8s.io/kubernetes/pkg/util/slice",
"k8s.io/kubernetes/pkg/util/taints",
"k8s.io/kubernetes/pkg/version",
"k8s.io/kubernetes/pkg/version/prometheus",
"k8s.io/kubernetes/pkg/volume",
"k8s.io/kubernetes/pkg/volume/util",
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm",
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates",
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util",
"k8s.io/kubernetes/plugin/pkg/scheduler/api",
"k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache",
"k8s.io/kubernetes/plugin/pkg/scheduler/util"
],
"ForbiddenPrefixes": []
}]
}

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

@ -0,0 +1,217 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"autoscale_test.go",
"clusterrolebinding_test.go",
"configmap_test.go",
"delete_test.go",
"deployment_test.go",
"env_file_test.go",
"generate_test.go",
"history_test.go",
"namespace_test.go",
"pdb_test.go",
"priorityclass_test.go",
"quota_test.go",
"resource_filter_test.go",
"rolebinding_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",
"sorting_printer_test.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl",
library = ":go_default_library",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/api/testing:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:go_default_library",
"//pkg/kubectl/util:go_default_library",
"//pkg/printers:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/api/apps/v1beta1:go_default_library",
"//vendor/k8s.io/api/autoscaling/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
"//vendor/k8s.io/api/batch/v2alpha1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
"//vendor/k8s.io/api/scheduling/v1alpha1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
"//vendor/k8s.io/client-go/util/testing:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"apply.go",
"autoscale.go",
"bash_comp_utils.go",
"clusterrolebinding.go",
"configmap.go",
"delete.go",
"deployment.go",
"doc.go",
"env_file.go",
"generate.go",
"history.go",
"interfaces.go",
"kubectl.go",
"namespace.go",
"pdb.go",
"priorityclass.go",
"quota.go",
"resource_filter.go",
"rolebinding.go",
"rollback.go",
"rolling_updater.go",
"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",
"sorting_printer.go",
"versioned_client.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/v1/pod:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/v1:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/apps/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:go_default_library",
"//pkg/client/unversioned:go_default_library",
"//pkg/controller/daemon:go_default_library",
"//pkg/controller/deployment/util:go_default_library",
"//pkg/controller/statefulset:go_default_library",
"//pkg/credentialprovider:go_default_library",
"//pkg/kubectl/apps:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/util:go_default_library",
"//pkg/kubectl/util/hash:go_default_library",
"//pkg/kubectl/util/slice:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/api/apps/v1beta1:go_default_library",
"//vendor/k8s.io/api/autoscaling/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
"//vendor/k8s.io/api/batch/v2alpha1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
"//vendor/k8s.io/api/scheduling/v1alpha1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/apps/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/util/integer:go_default_library",
"//vendor/k8s.io/client-go/util/jsonpath:go_default_library",
"//vendor/k8s.io/client-go/util/retry:go_default_library",
"//vendor/vbom.ml/util/sortorder:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubectl/apply:all-srcs",
"//pkg/kubectl/apps:all-srcs",
"//pkg/kubectl/categories:all-srcs",
"//pkg/kubectl/cmd:all-srcs",
"//pkg/kubectl/explain:all-srcs",
"//pkg/kubectl/metricsutil:all-srcs",
"//pkg/kubectl/plugins:all-srcs",
"//pkg/kubectl/proxy:all-srcs",
"//pkg/kubectl/resource:all-srcs",
"//pkg/kubectl/scheme:all-srcs",
"//pkg/kubectl/testing:all-srcs",
"//pkg/kubectl/util:all-srcs",
"//pkg/kubectl/validation: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

146
vendor/k8s.io/kubernetes/pkg/kubectl/apply.go generated vendored Normal file
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 kubectl
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
api "k8s.io/kubernetes/pkg/apis/core"
"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[api.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[api.LastAppliedConfigAnnotation] = string(original)
return info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annots)
}
// GetModifiedConfiguration retrieves the modified configuration of the object.
// If annotate is true, it embeds the result as an annotation in the modified
// configuration. If an object was read from the command input, it will use that
// version of the object. Otherwise, it will use the version from the server.
func GetModifiedConfiguration(info *resource.Info, annotate bool, codec runtime.Encoder) ([]byte, error) {
// First serialize the object without the annotation to prevent recursion,
// then add that serialization to it as the annotation and serialize it again.
var modified []byte
// Otherwise, use the server side version of the object.
accessor := info.Mapping.MetadataAccessor
// Get the current annotations from the object.
annots, err := accessor.Annotations(info.Object)
if err != nil {
return nil, err
}
if annots == nil {
annots = map[string]string{}
}
original := annots[api.LastAppliedConfigAnnotation]
delete(annots, api.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[api.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[api.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)
}

35
vendor/k8s.io/kubernetes/pkg/kubectl/apply/BUILD generated vendored Normal file
View File

@ -0,0 +1,35 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"element.go",
"empty_element.go",
"list_element.go",
"map_element.go",
"primitive_element.go",
"type_element.go",
"visitor.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/apply",
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubectl/apply/parse:all-srcs",
"//pkg/kubectl/apply/strategy:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

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

@ -0,0 +1,34 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
// This package is used for creating and applying patches generated
// from a last recorded config, local config, remote object.
// Example usage for a test:
//
//fakeSchema := tst.Fake{Path: swaggerPath}
//s, err := fakeSchema.OpenAPISchema()
//Expect(err).To(BeNil())
//resources, err := openapi.NewOpenAPIData(s)
//Expect(err).To(BeNil())
//elementParser := parse.Factory{resources}
//
//obj, err := parser.CreateElement(recorded, local, remote)
//Expect(err).Should(Not(HaveOccurred()))
//
//merged, err := obj.Merge(strategy.Create(strategy.Options{}))
//

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

@ -0,0 +1,421 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
import (
"fmt"
)
// Element contains the record, local, and remote value for a field in an object
// and metadata about the field read from openapi.
// Calling Merge on an element will apply the passed in strategy to Element -
// e.g. either replacing the whole element with the local copy or merging each
// of the recorded, local and remote fields of the element.
type Element interface {
// FieldMeta specifies which merge strategy to use for this element
FieldMeta
// Merge merges the recorded, local and remote values in the element using the Strategy
// provided as an argument. Calls the type specific method on the Strategy - following the
// "Accept" method from the "Visitor" pattern.
// e.g. Merge on a ListElement will call Strategy.MergeList(self)
// Returns the Result of the merged elements
Merge(Strategy) (Result, error)
// HasRecorded returns true if the field was explicitly
// present in the recorded source. This is to differentiate between
// undefined and set to null
HasRecorded() bool
// GetRecorded returns the field value from the recorded source of the object
GetRecorded() interface{}
// HasLocal returns true if the field was explicitly
// present in the recorded source. This is to differentiate between
// undefined and set to null
HasLocal() bool
// GetLocal returns the field value from the local source of the object
GetLocal() interface{}
// HasRemote returns true if the field was explicitly
// present in the remote source. This is to differentiate between
// undefined and set to null
HasRemote() bool
// GetRemote returns the field value from the remote source of the object
GetRemote() interface{}
}
// FieldMeta defines the strategy used to apply a Patch for an element
type FieldMeta interface {
// GetFieldMergeType returns the type of merge strategy to use for this field
// maybe "merge", "replace" or "retainkeys"
// TODO: There maybe multiple strategies, so this may need to be a slice, map, or struct
// Address this in a follow up in the PR to introduce retainkeys strategy
GetFieldMergeType() string
// GetFieldMergeKeys returns the merge key to use when the MergeType is "merge" and underlying type is a list
GetFieldMergeKeys() MergeKeys
// GetFieldType returns the openapi field type - e.g. primitive, array, map, type, reference
GetFieldType() string
}
// FieldMetaImpl implements FieldMeta
type FieldMetaImpl struct {
// MergeType is the type of merge strategy to use for this field
// maybe "merge", "replace" or "retainkeys"
MergeType string
// MergeKeys are the merge keys to use when the MergeType is "merge" and underlying type is a list
MergeKeys MergeKeys
// Type is the openapi type of the field - "list", "primitive", "map"
Type string
// Name contains of the field
Name string
}
// GetFieldMergeType implements FieldMeta.GetFieldMergeType
func (s FieldMetaImpl) GetFieldMergeType() string {
return s.MergeType
}
// GetFieldMergeKeys implements FieldMeta.GetFieldMergeKeys
func (s FieldMetaImpl) GetFieldMergeKeys() MergeKeys {
return s.MergeKeys
}
// GetFieldType implements FieldMeta.GetFieldType
func (s FieldMetaImpl) GetFieldType() string {
return s.Type
}
// MergeKeyValue records the value of the mergekey for an item in a list
type MergeKeyValue map[string]string
// Equal returns true if the MergeKeyValues share the same value,
// representing the same item in a list
func (v MergeKeyValue) Equal(o MergeKeyValue) bool {
if len(v) != len(o) {
return false
}
for key, v1 := range v {
if v2, found := o[key]; !found || v1 != v2 {
return false
}
}
return true
}
// MergeKeys is the set of fields on an object that uniquely identify
// and is used when merging lists to identify the "same" object
// independent of the ordering of the objects
type MergeKeys []string
// GetMergeKeyValue parses the MergeKeyValue from an item in a list
func (mk MergeKeys) GetMergeKeyValue(i interface{}) (MergeKeyValue, error) {
result := MergeKeyValue{}
if len(mk) <= 0 {
return result, fmt.Errorf("merge key must have at least 1 value to merge")
}
m, ok := i.(map[string]interface{})
if !ok {
return result, fmt.Errorf("cannot use mergekey %v for primitive item in list %v", mk, i)
}
for _, field := range mk {
if value, found := m[field]; !found {
result[field] = ""
} else {
result[field] = fmt.Sprintf("%v", value)
}
}
return result, nil
}
type source int
const (
recorded source = iota
local
remote
)
// CombinedPrimitiveSlice implements a slice of primitives
type CombinedPrimitiveSlice struct {
Items []*PrimitiveListItem
}
// PrimitiveListItem represents a single value in a slice of primitives
type PrimitiveListItem struct {
// Value is the value of the primitive, should match recorded, local and remote
Value interface{}
RawElementData
}
// Contains returns true if the slice contains the l
func (s *CombinedPrimitiveSlice) lookup(l interface{}) *PrimitiveListItem {
val := fmt.Sprintf("%v", l)
for _, i := range s.Items {
if fmt.Sprintf("%v", i.Value) == val {
return i
}
}
return nil
}
func (s *CombinedPrimitiveSlice) upsert(l interface{}) *PrimitiveListItem {
// Return the item if it exists
if item := s.lookup(l); item != nil {
return item
}
// Otherwise create a new item and append to the list
item := &PrimitiveListItem{
Value: l,
}
s.Items = append(s.Items, item)
return item
}
// UpsertRecorded adds l to the slice. If there is already a value of l in the
// slice for either the local or remote, set on that value as the recorded value
// Otherwise append a new item to the list with the recorded value.
func (s *CombinedPrimitiveSlice) UpsertRecorded(l interface{}) {
v := s.upsert(l)
v.recorded = l
v.recordedSet = true
}
// UpsertLocal adds l to the slice. If there is already a value of l in the
// slice for either the recorded or remote, set on that value as the local value
// Otherwise append a new item to the list with the local value.
func (s *CombinedPrimitiveSlice) UpsertLocal(l interface{}) {
v := s.upsert(l)
v.local = l
v.localSet = true
}
// UpsertRemote adds l to the slice. If there is already a value of l in the
// slice for either the local or recorded, set on that value as the remote value
// Otherwise append a new item to the list with the remote value.
func (s *CombinedPrimitiveSlice) UpsertRemote(l interface{}) {
v := s.upsert(l)
v.remote = l
v.remoteSet = true
}
// ListItem represents a single value in a slice of maps or types
type ListItem struct {
// KeyValue is the merge key value of the item
KeyValue MergeKeyValue
// RawElementData contains the field values
RawElementData
}
// CombinedMapSlice is a slice of maps or types with merge keys
type CombinedMapSlice struct {
Items []*ListItem
}
// Lookup returns the ListItem matching the merge key, or nil if not found.
func (s *CombinedMapSlice) lookup(v MergeKeyValue) *ListItem {
for _, i := range s.Items {
if i.KeyValue.Equal(v) {
return i
}
}
return nil
}
func (s *CombinedMapSlice) upsert(key MergeKeys, l interface{}) (*ListItem, error) {
// Get the identity of the item
val, err := key.GetMergeKeyValue(l)
if err != nil {
return nil, err
}
// Return the item if it exists
if item := s.lookup(val); item != nil {
return item, nil
}
// Otherwise create a new item and append to the list
item := &ListItem{
KeyValue: val,
}
s.Items = append(s.Items, item)
return item, nil
}
// UpsertRecorded adds l to the slice. If there is already a value of l sharing
// l's merge key in the slice for either the local or remote, set l the recorded value
// Otherwise append a new item to the list with the recorded value.
func (s *CombinedMapSlice) UpsertRecorded(key MergeKeys, l interface{}) error {
item, err := s.upsert(key, l)
if err != nil {
return err
}
item.recorded = l
item.recordedSet = true
return nil
}
// UpsertLocal adds l to the slice. If there is already a value of l sharing
// l's merge key in the slice for either the recorded or remote, set l the local value
// Otherwise append a new item to the list with the local value.
func (s *CombinedMapSlice) UpsertLocal(key MergeKeys, l interface{}) error {
item, err := s.upsert(key, l)
if err != nil {
return err
}
item.local = l
item.localSet = true
return nil
}
// UpsertRemote adds l to the slice. If there is already a value of l sharing
// l's merge key in the slice for either the recorded or local, set l the remote value
// Otherwise append a new item to the list with the remote value.
func (s *CombinedMapSlice) UpsertRemote(key MergeKeys, l interface{}) error {
item, err := s.upsert(key, l)
if err != nil {
return err
}
item.remote = l
item.remoteSet = true
return nil
}
// IsDrop returns true if the field represented by e should be dropped from the merged object
func IsDrop(e Element) bool {
// Specified in the last value recorded value and since deleted from the local
removed := e.HasRecorded() && !e.HasLocal()
// Specified locally and explicitly set to null
setToNil := e.HasLocal() && e.GetLocal() == nil
return removed || setToNil
}
// IsAdd returns true if the field represented by e should have the local value directly
// added to the merged object instead of merging the recorded, local and remote values
func IsAdd(e Element) bool {
// If it isn't already present in the remote value and is present in the local value
return e.HasLocal() && !e.HasRemote()
}
// NewRawElementData returns a new RawElementData, setting IsSet to true for
// non-nil values, and leaving IsSet false for nil values.
// Note: use this only when you want a nil-value to be considered "unspecified"
// (ignore) and not "unset" (deleted).
func NewRawElementData(recorded, local, remote interface{}) RawElementData {
data := RawElementData{}
if recorded != nil {
data.SetRecorded(recorded)
}
if local != nil {
data.SetLocal(local)
}
if remote != nil {
data.SetRemote(remote)
}
return data
}
// RawElementData contains the raw recorded, local and remote data
// and metadata about whethere or not each was set
type RawElementData struct {
HasElementData
recorded interface{}
local interface{}
remote interface{}
}
// SetRecorded sets the recorded value
func (b *RawElementData) SetRecorded(value interface{}) {
b.recorded = value
b.recordedSet = true
}
// SetLocal sets the recorded value
func (b *RawElementData) SetLocal(value interface{}) {
b.local = value
b.localSet = true
}
// SetRemote sets the recorded value
func (b *RawElementData) SetRemote(value interface{}) {
b.remote = value
b.remoteSet = true
}
// GetRecorded implements Element.GetRecorded
func (b RawElementData) GetRecorded() interface{} {
// https://golang.org/doc/faq#nil_error
if b.recorded == nil {
return nil
}
return b.recorded
}
// GetLocal implements Element.GetLocal
func (b RawElementData) GetLocal() interface{} {
// https://golang.org/doc/faq#nil_error
if b.local == nil {
return nil
}
return b.local
}
// GetRemote implements Element.GetRemote
func (b RawElementData) GetRemote() interface{} {
// https://golang.org/doc/faq#nil_error
if b.remote == nil {
return nil
}
return b.remote
}
// HasElementData contains whether a field was set in the recorded, local and remote sources
type HasElementData struct {
recordedSet bool
localSet bool
remoteSet bool
}
// HasRecorded implements Element.HasRecorded
func (e HasElementData) HasRecorded() bool {
return e.recordedSet
}
// HasLocal implements Element.HasLocal
func (e HasElementData) HasLocal() bool {
return e.localSet
}
// HasRemote implements Element.HasRemote
func (e HasElementData) HasRemote() bool {
return e.remoteSet
}

View File

@ -0,0 +1,70 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
// EmptyElement is a placeholder for when no value is set for a field so its type is unknown
type EmptyElement struct {
// FieldMetaImpl contains metadata about the field from openapi
FieldMetaImpl
}
// Merge implements Element.Merge
func (e EmptyElement) Merge(v Strategy) (Result, error) {
return v.MergeEmpty(e)
}
// IsAdd implements Element.IsAdd
func (e EmptyElement) IsAdd() bool {
return false
}
// IsDelete implements Element.IsDelete
func (e EmptyElement) IsDelete() bool {
return false
}
// GetRecorded implements Element.GetRecorded
func (e EmptyElement) GetRecorded() interface{} {
return nil
}
// GetLocal implements Element.GetLocal
func (e EmptyElement) GetLocal() interface{} {
return nil
}
// GetRemote implements Element.GetRemote
func (e EmptyElement) GetRemote() interface{} {
return nil
}
// HasRecorded implements Element.HasRecorded
func (e EmptyElement) HasRecorded() bool {
return false
}
// HasLocal implements Element.HasLocal
func (e EmptyElement) HasLocal() bool {
return false
}
// HasRemote implements Element.IsAdd
func (e EmptyElement) HasRemote() bool {
return false
}
var _ Element = &EmptyElement{}

View File

@ -0,0 +1,67 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
// ListElement contains the recorded, local and remote values for a field
// of type list
type ListElement struct {
// FieldMetaImpl contains metadata about the field from openapi
FieldMetaImpl
ListElementData
// Values contains the combined recorded-local-remote value of each item in the list
// Present for lists that can be merged only. Contains the items
// from each of the 3 lists merged into single Elements using
// the merge-key.
Values []Element
}
// Merge implements Element.Merge
func (e ListElement) Merge(v Strategy) (Result, error) {
return v.MergeList(e)
}
var _ Element = &ListElement{}
// ListElementData contains the recorded, local and remote data for a list
type ListElementData struct {
RawElementData
}
// GetRecordedList returns the Recorded value as a list
func (e ListElementData) GetRecordedList() []interface{} {
return sliceCast(e.recorded)
}
// GetLocalList returns the Local value as a list
func (e ListElementData) GetLocalList() []interface{} {
return sliceCast(e.local)
}
// GetRemoteList returns the Remote value as a list
func (e ListElementData) GetRemoteList() []interface{} {
return sliceCast(e.remote)
}
// sliceCast casts i to a slice if it is non-nil, otherwise returns nil
func sliceCast(i interface{}) []interface{} {
if i == nil {
return nil
}
return i.([]interface{})
}

View File

@ -0,0 +1,72 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
// MapElement contains the recorded, local and remote values for a field
// of type map
type MapElement struct {
// FieldMetaImpl contains metadata about the field from openapi
FieldMetaImpl
// MapElementData contains the value a field was set to
MapElementData
// Values contains the combined recorded-local-remote value of each item in the map
// Values contains the values in mapElement. Element must contain
// a Name matching its key in Values
Values map[string]Element
}
// Merge implements Element.Merge
func (e MapElement) Merge(v Strategy) (Result, error) {
return v.MergeMap(e)
}
// GetValues implements Element.GetValues
func (e MapElement) GetValues() map[string]Element {
return e.Values
}
var _ Element = &MapElement{}
// MapElementData contains the recorded, local and remote data for a map or type
type MapElementData struct {
RawElementData
}
// GetRecordedMap returns the Recorded value as a map
func (e MapElementData) GetRecordedMap() map[string]interface{} {
return mapCast(e.recorded)
}
// GetLocalMap returns the Local value as a map
func (e MapElementData) GetLocalMap() map[string]interface{} {
return mapCast(e.local)
}
// GetRemoteMap returns the Remote value as a map
func (e MapElementData) GetRemoteMap() map[string]interface{} {
return mapCast(e.remote)
}
// mapCast casts i to a map if it is non-nil, otherwise returns nil
func mapCast(i interface{}) map[string]interface{} {
if i == nil {
return nil
}
return i.(map[string]interface{})
}

53
vendor/k8s.io/kubernetes/pkg/kubectl/apply/parse/BUILD generated vendored Normal file
View File

@ -0,0 +1,53 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"factory.go",
"item.go",
"list_element.go",
"map_element.go",
"openapi.go",
"primitive_element.go",
"type_element.go",
"util.go",
"visitor.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/apply/parse",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl/apply:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
],
)
go_test(
name = "go_default_xtest",
srcs = ["suite_test.go"],
data = [
"//api/openapi-spec:swagger-spec",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/apply/parse_test",
deps = [
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
"//vendor/github.com/onsi/ginkgo/types:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,120 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package parse
import (
"fmt"
"reflect"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
)
// Factory creates an Element by combining object values from recorded, local and remote sources with
// the metadata from an openapi schema.
type Factory struct {
// Resources contains the openapi field metadata for the object models
Resources openapi.Resources
}
// CreateElement returns an Element by collating the recorded, local and remote field values
func (b *Factory) CreateElement(recorded, local, remote map[string]interface{}) (apply.Element, error) {
// Create an Item from the 3 values. Use empty name for field
visitor := &ElementBuildingVisitor{b.Resources}
gvk, err := getCommonGroupVersionKind(recorded, local, remote)
if err != nil {
return nil, err
}
// Get the openapi object metadata
s := visitor.resources.LookupResource(gvk)
oapiKind, err := getKind(s)
if err != nil {
return nil, err
}
data := apply.NewRawElementData(recorded, local, remote)
fieldName := ""
item, err := visitor.getItem(oapiKind, fieldName, data)
if err != nil {
return nil, err
}
// Collate each field of the item into a combined Element
return item.CreateElement(visitor)
}
// getItem returns the appropriate Item based on the underlying type of the arguments
func (v *ElementBuildingVisitor) getItem(s proto.Schema, name string, data apply.RawElementData) (Item, error) {
kind, err := getType(data.GetRecorded(), data.GetLocal(), data.GetRemote())
if err != nil {
return nil, err
}
if kind == nil {
// All of the items values are nil.
return &emptyItem{Name: name}, nil
}
// Create an item matching the type
switch kind.Kind() {
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint,
reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64,
reflect.String:
p, err := getPrimitive(s)
if err != nil {
return nil, fmt.Errorf("expected openapi Primitive, was %T for %v (%v)", s, kind, err)
}
return &primitiveItem{name, p, data}, nil
case reflect.Array, reflect.Slice:
a, err := getArray(s)
if err != nil {
return nil, fmt.Errorf("expected openapi Array, was %T for %v (%v)", s, kind, err)
}
return &listItem{
Name: name,
Array: a,
ListElementData: apply.ListElementData{
RawElementData: data,
},
}, nil
case reflect.Map:
if k, err := getKind(s); err == nil {
return &typeItem{
Name: name,
Type: k,
MapElementData: apply.MapElementData{
RawElementData: data,
},
}, nil
}
// If it looks like a map, and no openapi type is found, default to mapItem
m, err := getMap(s)
if err != nil {
return nil, fmt.Errorf("expected openapi Kind or Map, was %T for %v (%v)", s, kind, err)
}
return &mapItem{
Name: name,
Map: m,
MapElementData: apply.MapElementData{
RawElementData: data,
},
}, nil
}
return nil, fmt.Errorf("unsupported type type %v", kind)
}

View File

@ -0,0 +1,120 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package parse
import (
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// Item wraps values from 3 sources (recorded, local, remote).
// The values are not collated
type Item interface {
// CreateElement merges the values in the item into a combined Element
CreateElement(ItemVisitor) (apply.Element, error)
}
// primitiveItem contains a recorded, local, and remote value
type primitiveItem struct {
Name string
Primitive *proto.Primitive
apply.RawElementData
}
func (i *primitiveItem) CreateElement(v ItemVisitor) (apply.Element, error) {
return v.CreatePrimitiveElement(i)
}
func (i *primitiveItem) GetMeta() proto.Schema {
// https://golang.org/doc/faq#nil_error
if i.Primitive != nil {
return i.Primitive
}
return nil
}
// listItem contains a recorded, local, and remote list
type listItem struct {
Name string
Array *proto.Array
apply.ListElementData
}
func (i *listItem) CreateElement(v ItemVisitor) (apply.Element, error) {
return v.CreateListElement(i)
}
func (i *listItem) GetMeta() proto.Schema {
// https://golang.org/doc/faq#nil_error
if i.Array != nil {
return i.Array
}
return nil
}
// mapItem contains a recorded, local, and remote map
type mapItem struct {
Name string
Map *proto.Map
apply.MapElementData
}
func (i *mapItem) CreateElement(v ItemVisitor) (apply.Element, error) {
return v.CreateMapElement(i)
}
func (i *mapItem) GetMeta() proto.Schema {
// https://golang.org/doc/faq#nil_error
if i.Map != nil {
return i.Map
}
return nil
}
// mapItem contains a recorded, local, and remote map
type typeItem struct {
Name string
Type *proto.Kind
apply.MapElementData
}
func (i *typeItem) GetMeta() proto.Schema {
// https://golang.org/doc/faq#nil_error
if i.Type != nil {
return i.Type
}
return nil
}
func (i *typeItem) CreateElement(v ItemVisitor) (apply.Element, error) {
return v.CreateTypeElement(i)
}
// emptyItem contains no values
type emptyItem struct {
Name string
}
func (i *emptyItem) CreateElement(v ItemVisitor) (apply.Element, error) {
e := &apply.EmptyElement{}
e.Name = i.Name
return e, nil
}

View File

@ -0,0 +1,199 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package parse
import (
"fmt"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// Contains the heavy lifting for finding tuples of matching elements in lists based on the merge key
// and then uses the canonical order derived from the orders in the recorded, local and remote lists.
// replaceListElement builds a ListElement for a listItem.
// Uses the "merge" strategy to identify "same" elements across lists by a "merge key"
func (v ElementBuildingVisitor) mergeListElement(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
subtype := getSchemaType(item.Array.SubType)
switch subtype {
case "primitive":
return v.doPrimitiveList(meta, item)
case "map", "kind", "reference":
return v.doMapList(meta, item)
default:
return nil, fmt.Errorf("Cannot merge lists with subtype %s", subtype)
}
}
// doPrimitiveList merges 3 lists of primitives together
// tries to maintain ordering
func (v ElementBuildingVisitor) doPrimitiveList(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
result := &apply.ListElement{
FieldMetaImpl: apply.FieldMetaImpl{
MergeType: apply.MergeStrategy,
Name: item.Name,
},
ListElementData: item.ListElementData,
Values: []apply.Element{},
}
// Use locally defined order, then add remote, then add recorded.
orderedKeys := &apply.CombinedPrimitiveSlice{}
// Locally defined items come first and retain their order
// as defined locally
for _, l := range item.GetLocalList() {
orderedKeys.UpsertLocal(l)
}
// Mixin remote values, adding any that are not present locally
for _, l := range item.GetRemoteList() {
orderedKeys.UpsertRemote(l)
}
// Mixin recorded values, adding any that are not present locally
// or remotely
for _, l := range item.GetRecordedList() {
orderedKeys.UpsertRecorded(l)
}
for i, l := range orderedKeys.Items {
var s proto.Schema
if item.Array != nil && item.Array.SubType != nil {
s = item.Array.SubType
}
subitem, err := v.getItem(s, fmt.Sprintf("%d", i), l.RawElementData)
if err != nil {
return nil, err
}
// Convert the Item to an Element
newelem, err := subitem.CreateElement(v)
if err != nil {
return nil, err
}
// Append the element to the list
result.Values = append(result.Values, newelem)
}
return result, nil
}
// doMapList merges 3 lists of maps together by collating their values.
// tries to retain ordering
func (v ElementBuildingVisitor) doMapList(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
key := meta.GetFieldMergeKeys()
result := &apply.ListElement{
FieldMetaImpl: apply.FieldMetaImpl{
MergeType: apply.MergeStrategy,
MergeKeys: key,
Name: item.Name,
},
ListElementData: item.ListElementData,
Values: []apply.Element{},
}
// Use locally defined order, then add remote, then add recorded.
orderedKeys := &apply.CombinedMapSlice{}
// Locally defined items come first and retain their order
// as defined locally
for _, l := range item.GetLocalList() {
orderedKeys.UpsertLocal(key, l)
}
// Mixin remote values, adding any that are not present locally
for _, l := range item.GetRemoteList() {
orderedKeys.UpsertRemote(key, l)
}
// Mixin recorded values, adding any that are not present locally
// or remotely
for _, l := range item.GetRecordedList() {
orderedKeys.UpsertRecorded(key, l)
}
for i, l := range orderedKeys.Items {
var s proto.Schema
if item.Array != nil && item.Array.SubType != nil {
s = item.Array.SubType
}
subitem, err := v.getItem(s, fmt.Sprintf("%d", i), l.RawElementData)
if err != nil {
return nil, err
}
// Build the element fully
newelem, err := subitem.CreateElement(v)
if err != nil {
return nil, err
}
// Append the element to the list
result.Values = append(result.Values, newelem)
}
return result, nil
}
// replaceListElement builds a new ListElement from a listItem
// Uses the "replace" strategy and identify "same" elements across lists by their index
func (v ElementBuildingVisitor) replaceListElement(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
meta.Name = item.Name
result := &apply.ListElement{
FieldMetaImpl: meta,
ListElementData: item.ListElementData,
Values: []apply.Element{},
}
// Use the max length to iterate over the slices
for i := 0; i < max(len(item.GetRecordedList()), len(item.GetLocalList()), len(item.GetRemoteList())); i++ {
// Lookup the item from each list
data := apply.RawElementData{}
if recorded, recordedSet := boundsSafeLookup(i, item.GetRecordedList()); recordedSet {
data.SetRecorded(recorded)
}
if local, localSet := boundsSafeLookup(i, item.GetLocalList()); localSet {
data.SetLocal(local)
}
if remote, remoteSet := boundsSafeLookup(i, item.GetRemoteList()); remoteSet {
data.SetRemote(remote)
}
// Create the Item
var s proto.Schema
if item.Array != nil && item.Array.SubType != nil {
s = item.Array.SubType
}
subitem, err := v.getItem(s, fmt.Sprintf("%d", i), data)
if err != nil {
return nil, err
}
// Build the element
newelem, err := subitem.CreateElement(v)
if err != nil {
return nil, err
}
// Append the element to the list
result.Values = append(result.Values, newelem)
}
return result, nil
}

View File

@ -0,0 +1,89 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package parse
import (
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// mapElement builds a new mapElement from a mapItem
func (v ElementBuildingVisitor) mapElement(meta apply.FieldMetaImpl, item *mapItem) (*apply.MapElement, error) {
// Function to return schema type of the map values
var fn schemaFn = func(string) proto.Schema {
// All map values share the same schema
if item.Map != nil && item.Map.SubType != nil {
return item.Map.SubType
}
return nil
}
// Collect same fields from multiple maps into a map of elements
values, err := v.createMapValues(fn, meta, item.MapElementData)
if err != nil {
return nil, err
}
// Return the result
return &apply.MapElement{
FieldMetaImpl: meta,
MapElementData: item.MapElementData,
Values: values,
}, nil
}
// schemaFn returns the schema for a field or map value based on its name or key
type schemaFn func(key string) proto.Schema
// createMapValues combines the recorded, local and remote values from
// data into a map of elements.
func (v ElementBuildingVisitor) createMapValues(
schemaFn schemaFn,
meta apply.FieldMetaImpl,
data apply.MapElementData) (map[string]apply.Element, error) {
// Collate each key in the map
values := map[string]apply.Element{}
for _, key := range keysUnion(data.GetRecordedMap(), data.GetLocalMap(), data.GetRemoteMap()) {
combined := apply.RawElementData{}
if recorded, recordedSet := nilSafeLookup(key, data.GetRecordedMap()); recordedSet {
combined.SetRecorded(recorded)
}
if local, localSet := nilSafeLookup(key, data.GetLocalMap()); localSet {
combined.SetLocal(local)
}
if remote, remoteSet := nilSafeLookup(key, data.GetRemoteMap()); remoteSet {
combined.SetRemote(remote)
}
// Create an item for the field
field, err := v.getItem(schemaFn(key), key, combined)
if err != nil {
return nil, err
}
// Build the element for this field
element, err := field.CreateElement(v)
if err != nil {
return nil, err
}
// Add the field element to the map
values[key] = element
}
return values, nil
}

View File

@ -0,0 +1,227 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package parse
import (
"fmt"
"strings"
"k8s.io/kube-openapi/pkg/util/proto"
)
// Contains functions for casting openapi interfaces to their underlying types
// getSchemaType returns the string type of the schema - e.g. array, primitive, map, kind, reference
func getSchemaType(schema proto.Schema) string {
if schema == nil {
return ""
}
visitor := &baseSchemaVisitor{}
schema.Accept(visitor)
return visitor.Kind
}
// getKind converts schema to an *proto.Kind object
func getKind(schema proto.Schema) (*proto.Kind, error) {
if schema == nil {
return nil, nil
}
visitor := &kindSchemaVisitor{}
schema.Accept(visitor)
return visitor.Result, visitor.Err
}
// getArray converts schema to an *proto.Array object
func getArray(schema proto.Schema) (*proto.Array, error) {
if schema == nil {
return nil, nil
}
visitor := &arraySchemaVisitor{}
schema.Accept(visitor)
return visitor.Result, visitor.Err
}
// getMap converts schema to an *proto.Map object
func getMap(schema proto.Schema) (*proto.Map, error) {
if schema == nil {
return nil, nil
}
visitor := &mapSchemaVisitor{}
schema.Accept(visitor)
return visitor.Result, visitor.Err
}
// getPrimitive converts schema to an *proto.Primitive object
func getPrimitive(schema proto.Schema) (*proto.Primitive, error) {
if schema == nil {
return nil, nil
}
visitor := &primitiveSchemaVisitor{}
schema.Accept(visitor)
return visitor.Result, visitor.Err
}
type baseSchemaVisitor struct {
Err error
Kind string
}
// VisitArray implements openapi
func (v *baseSchemaVisitor) VisitArray(array *proto.Array) {
v.Kind = "array"
v.Err = fmt.Errorf("Array type not expected")
}
// MergeMap implements openapi
func (v *baseSchemaVisitor) VisitMap(*proto.Map) {
v.Kind = "map"
v.Err = fmt.Errorf("Map type not expected")
}
// MergePrimitive implements openapi
func (v *baseSchemaVisitor) VisitPrimitive(*proto.Primitive) {
v.Kind = "primitive"
v.Err = fmt.Errorf("Primitive type not expected")
}
// VisitKind implements openapi
func (v *baseSchemaVisitor) VisitKind(*proto.Kind) {
v.Kind = "kind"
v.Err = fmt.Errorf("Kind type not expected")
}
// VisitReference implements openapi
func (v *baseSchemaVisitor) VisitReference(reference proto.Reference) {
v.Kind = "reference"
v.Err = fmt.Errorf("Reference type not expected")
}
type kindSchemaVisitor struct {
baseSchemaVisitor
Result *proto.Kind
}
// VisitKind implements openapi
func (v *kindSchemaVisitor) VisitKind(result *proto.Kind) {
v.Result = result
v.Kind = "kind"
}
// VisitReference implements openapi
func (v *kindSchemaVisitor) VisitReference(reference proto.Reference) {
reference.SubSchema().Accept(v)
if v.Err == nil {
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
}
}
func copyExtensions(field string, from, to map[string]interface{}) error {
// Copy extensions from field to type for references
for key, val := range from {
if curr, found := to[key]; found {
// Don't allow the same extension to be defined both on the field and on the type
return fmt.Errorf("Cannot override value for extension %s on field %s from %v to %v",
key, field, curr, val)
}
to[key] = val
}
return nil
}
type mapSchemaVisitor struct {
baseSchemaVisitor
Result *proto.Map
}
// MergeMap implements openapi
func (v *mapSchemaVisitor) VisitMap(result *proto.Map) {
v.Result = result
v.Kind = "map"
}
// VisitReference implements openapi
func (v *mapSchemaVisitor) VisitReference(reference proto.Reference) {
reference.SubSchema().Accept(v)
if v.Err == nil {
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
}
}
type arraySchemaVisitor struct {
baseSchemaVisitor
Result *proto.Array
}
// VisitArray implements openapi
func (v *arraySchemaVisitor) VisitArray(result *proto.Array) {
v.Result = result
v.Kind = "array"
v.Err = copySubElementPatchStrategy(result.Path.String(), result.GetExtensions(), result.SubType.GetExtensions())
}
// copyPatchStrategy copies the strategies to subelements to the subtype
// e.g. PodTemplate.Volumes is a []Volume with "x-kubernetes-patch-strategy": "merge,retainKeys"
// the "retainKeys" strategy applies to merging Volumes, and must be copied to the sub element
func copySubElementPatchStrategy(field string, from, to map[string]interface{}) error {
// Check if the parent has a patch strategy extension
if ext, found := from["x-kubernetes-patch-strategy"]; found {
strategy, ok := ext.(string)
if !ok {
return fmt.Errorf("Expected string value for x-kubernetes-patch-strategy on %s, was %T",
field, ext)
}
// Check of the parent patch strategy has a sub patch strategy, and if so copy to the sub type
if strings.Contains(strategy, ",") {
strategies := strings.Split(strategy, ",")
if len(strategies) != 2 {
// Only 1 sub strategy is supported
return fmt.Errorf(
"Expected between 0 and 2 elements for x-kubernetes-patch-merge-strategy by got %v",
strategies)
}
to["x-kubernetes-patch-strategy"] = strategies[1]
}
}
return nil
}
// MergePrimitive implements openapi
func (v *arraySchemaVisitor) VisitReference(reference proto.Reference) {
reference.SubSchema().Accept(v)
if v.Err == nil {
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
}
}
type primitiveSchemaVisitor struct {
baseSchemaVisitor
Result *proto.Primitive
}
// MergePrimitive implements openapi
func (v *primitiveSchemaVisitor) VisitPrimitive(result *proto.Primitive) {
v.Result = result
v.Kind = "primitive"
}
// VisitReference implements openapi
func (v *primitiveSchemaVisitor) VisitReference(reference proto.Reference) {
reference.SubSchema().Accept(v)
if v.Err == nil {
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
}
}

View File

@ -0,0 +1,28 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package parse
import "k8s.io/kubernetes/pkg/kubectl/apply"
// primitiveElement builds a new primitiveElement from a PrimitiveItem
func (v ElementBuildingVisitor) primitiveElement(item *primitiveItem) (*apply.PrimitiveElement, error) {
meta := apply.FieldMetaImpl{Name: item.Name}
return &apply.PrimitiveElement{
FieldMetaImpl: meta,
RawElementData: item.RawElementData,
}, nil
}

View File

@ -0,0 +1,49 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package parse_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/config"
. "github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
"fmt"
"testing"
)
func TestOpenapi(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t, "Openapi Suite", []Reporter{newlineReporter{}})
}
// Print a newline after the default newlineReporter due to issue
// https://github.com/jstemmer/go-junit-report/issues/31
type newlineReporter struct{}
func (newlineReporter) SpecSuiteWillBegin(config GinkgoConfigType, summary *SuiteSummary) {}
func (newlineReporter) BeforeSuiteDidRun(setupSummary *SetupSummary) {}
func (newlineReporter) AfterSuiteDidRun(setupSummary *SetupSummary) {}
func (newlineReporter) SpecWillRun(specSummary *SpecSummary) {}
func (newlineReporter) SpecDidComplete(specSummary *SpecSummary) {}
// SpecSuiteDidEnd Prints a newline between "35 Passed | 0 Failed | 0 Pending | 0 Skipped" and "--- PASS:"
func (newlineReporter) SpecSuiteDidEnd(summary *SuiteSummary) { fmt.Printf("\n") }

View File

@ -0,0 +1,46 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package parse
import (
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// typeElement builds a new mapElement from a typeItem
func (v ElementBuildingVisitor) typeElement(meta apply.FieldMetaImpl, item *typeItem) (*apply.TypeElement, error) {
// Function to get the schema of a field from its key
var fn schemaFn = func(key string) proto.Schema {
if item.Type != nil && item.Type.Fields != nil {
return item.Type.Fields[key]
}
return nil
}
// Collect same fields from multiple maps into a map of elements
values, err := v.createMapValues(fn, meta, item.MapElementData)
if err != nil {
return nil, err
}
// Return the result
return &apply.TypeElement{
FieldMetaImpl: meta,
MapElementData: item.MapElementData,
Values: values,
}, nil
}

View File

@ -0,0 +1,181 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package parse
import (
"fmt"
"reflect"
"strings"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// nilSafeLookup returns the value from the map if the map is non-nil
func nilSafeLookup(key string, from map[string]interface{}) (interface{}, bool) {
if from != nil {
value, found := from[key]
return value, found
}
// Not present
return nil, false
}
// boundsSafeLookup returns the value from the slice if the slice is non-nil and
// the index is in bounds.
func boundsSafeLookup(index int, from []interface{}) (interface{}, bool) {
if from != nil && len(from) > index {
return from[index], true
}
return nil, false
}
// keysUnion returns a slice containing the union of the keys present in the arguments
func keysUnion(maps ...map[string]interface{}) []string {
keys := map[string]interface{}{}
for _, m := range maps {
for k := range m {
keys[k] = nil
}
}
result := []string{}
for key := range keys {
result = append(result, key)
}
return result
}
// max returns the argument with the highest value
func max(values ...int) int {
v := 0
for _, i := range values {
if i > v {
v = i
}
}
return v
}
// getType returns the type of the arguments. If the arguments don't have matching
// types, getType returns an error. Nil types matching everything.
func getType(args ...interface{}) (reflect.Type, error) {
var last interface{}
for _, next := range args {
// Skip nil values
if next == nil {
continue
}
// Set the first non-nil value we find and continue
if last == nil {
last = next
continue
}
// Verify the types of the values match
if reflect.TypeOf(last).Kind() != reflect.TypeOf(next).Kind() {
return nil, fmt.Errorf("missmatching non-nil types for the same field: %T %T", last, next)
}
}
return reflect.TypeOf(last), nil
}
// getFieldMeta parses the metadata about the field from the openapi spec
func getFieldMeta(s proto.Schema, name string) (apply.FieldMetaImpl, error) {
m := apply.FieldMetaImpl{}
if s != nil {
ext := s.GetExtensions()
if e, found := ext["x-kubernetes-patch-strategy"]; found {
strategy, ok := e.(string)
if !ok {
return apply.FieldMetaImpl{}, fmt.Errorf("Expected string for x-kubernetes-patch-strategy by got %T", s)
}
// Take the first strategy if there are substrategies.
// Sub strategies are copied to sub types in openapi.go
strategies := strings.Split(strategy, ",")
if len(strategies) > 2 {
return apply.FieldMetaImpl{}, fmt.Errorf("Expected between 0 and 2 elements for x-kubernetes-patch-merge-strategy by got %v", strategies)
}
// For lists, choose the strategy for this type, not the subtype
m.MergeType = strategies[0]
}
if k, found := ext["x-kubernetes-patch-merge-key"]; found {
key, ok := k.(string)
if !ok {
return apply.FieldMetaImpl{}, fmt.Errorf("Expected string for x-kubernetes-patch-merge-key by got %T", k)
}
m.MergeKeys = apply.MergeKeys(strings.Split(key, ","))
}
}
m.Name = name
return m, nil
}
// getCommonGroupVersionKind verifies that the recorded, local and remote all share
// the same GroupVersionKind and returns the value
func getCommonGroupVersionKind(recorded, local, remote map[string]interface{}) (schema.GroupVersionKind, error) {
recordedGVK, err := getGroupVersionKind(recorded)
if err != nil {
return schema.GroupVersionKind{}, err
}
localGVK, err := getGroupVersionKind(local)
if err != nil {
return schema.GroupVersionKind{}, err
}
remoteGVK, err := getGroupVersionKind(remote)
if err != nil {
return schema.GroupVersionKind{}, err
}
if !reflect.DeepEqual(recordedGVK, localGVK) || !reflect.DeepEqual(localGVK, remoteGVK) {
return schema.GroupVersionKind{},
fmt.Errorf("group version kinds do not match (recorded: %v local: %v remote: %v)",
recordedGVK, localGVK, remoteGVK)
}
return recordedGVK, nil
}
// getGroupVersionKind returns the GroupVersionKind of the object
func getGroupVersionKind(config map[string]interface{}) (schema.GroupVersionKind, error) {
gvk := schema.GroupVersionKind{}
if gv, found := config["apiVersion"]; found {
casted, ok := gv.(string)
if !ok {
return gvk, fmt.Errorf("Expected string for apiVersion, found %T", gv)
}
s := strings.Split(casted, "/")
if len(s) != 1 {
gvk.Group = s[0]
}
gvk.Version = s[len(s)-1]
} else {
return gvk, fmt.Errorf("Missing apiVersion in Kind %v", config)
}
if k, found := config["kind"]; found {
casted, ok := k.(string)
if !ok {
return gvk, fmt.Errorf("Expected string for kind, found %T", k)
}
gvk.Kind = casted
} else {
return gvk, fmt.Errorf("Missing kind in Kind %v", config)
}
return gvk, nil
}

View File

@ -0,0 +1,79 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package parse
import (
"k8s.io/kubernetes/pkg/kubectl/apply"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
)
// ItemVisitor provides an interface for Items to Accept and call
// the Visit function that corresponds to its actual type.
type ItemVisitor interface {
// CreatePrimitiveElement builds an Element for a primitiveItem
CreatePrimitiveElement(*primitiveItem) (apply.Element, error)
// CreateListElement builds an Element for a listItem
CreateListElement(*listItem) (apply.Element, error)
// CreateMapElement builds an Element for a mapItem
CreateMapElement(*mapItem) (apply.Element, error)
// CreateTypeElement builds an Element for a typeItem
CreateTypeElement(*typeItem) (apply.Element, error)
}
// ElementBuildingVisitor creates an Elements from Items
// An Element combines the values from the Item with the field metadata.
type ElementBuildingVisitor struct {
resources openapi.Resources
}
// CreatePrimitiveElement creates a primitiveElement
func (v ElementBuildingVisitor) CreatePrimitiveElement(item *primitiveItem) (apply.Element, error) {
return v.primitiveElement(item)
}
// CreateListElement creates a ListElement
func (v ElementBuildingVisitor) CreateListElement(item *listItem) (apply.Element, error) {
meta, err := getFieldMeta(item.GetMeta(), item.Name)
if err != nil {
return nil, err
}
if meta.GetFieldMergeType() == apply.MergeStrategy {
return v.mergeListElement(meta, item)
}
return v.replaceListElement(meta, item)
}
// CreateMapElement creates a mapElement
func (v ElementBuildingVisitor) CreateMapElement(item *mapItem) (apply.Element, error) {
meta, err := getFieldMeta(item.GetMeta(), item.Name)
if err != nil {
return nil, err
}
return v.mapElement(meta, item)
}
// CreateTypeElement creates a typeElement
func (v ElementBuildingVisitor) CreateTypeElement(item *typeItem) (apply.Element, error) {
meta, err := getFieldMeta(item.GetMeta(), item.Name)
if err != nil {
return nil, err
}
return v.typeElement(meta, item)
}

View File

@ -0,0 +1,34 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
// PrimitiveElement contains the recorded, local and remote values for a field
// of type primitive
type PrimitiveElement struct {
// FieldMetaImpl contains metadata about the field from openapi
FieldMetaImpl
// RawElementData contains the values the field was set to
RawElementData
}
// Merge implements Element.Merge
func (e PrimitiveElement) Merge(v Strategy) (Result, error) {
return v.MergePrimitive(e)
}
var _ Element = &PrimitiveElement{}

View File

@ -0,0 +1,71 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"merge.go",
"merge_visitor.go",
"replace_visitor.go",
"retain_keys_visitor.go",
"strategic_visitor.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/apply/strategy",
visibility = ["//visibility:public"],
deps = ["//pkg/kubectl/apply:go_default_library"],
)
go_test(
name = "go_default_xtest",
srcs = [
"merge_map_list_test.go",
"merge_map_test.go",
"merge_primitive_list_test.go",
"merge_primitive_test.go",
"replace_map_list_test.go",
"replace_map_test.go",
"replace_primitive_list_test.go",
"retain_keys_test.go",
"suite_test.go",
"utils_test.go",
],
data = [
":swagger-spec",
"//api/openapi-spec:swagger-spec",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/apply/strategy_test",
deps = [
":go_default_library",
"//pkg/kubectl/apply:go_default_library",
"//pkg/kubectl/apply/parse:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/cmd/util/openapi/testing:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
"//vendor/github.com/onsi/ginkgo/types:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "swagger-spec",
srcs = glob([
"**/*.json",
]),
)

View File

@ -0,0 +1,17 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy

View File

@ -0,0 +1,33 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy
import "k8s.io/kubernetes/pkg/kubectl/apply"
// Options controls how a merge will be executed
type Options struct {
// FailOnConflict when true will fail patch creation if the recorded and remote
// have 2 fields set for the same value that cannot be merged.
// e.g. primitive values, list values with replace strategy, and map values with do
// strategy
FailOnConflict bool
}
// Create returns a new apply.Visitor for merging multiple objects together
func Create(options Options) apply.Strategy {
return createDelegatingStrategy(options)
}

View File

@ -0,0 +1,650 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
)
var _ = Describe("Merging fields of type list-of-map with openapi", func() {
Context("where one of the items has been deleted resulting in the containers being empty", func() {
It("should set the containers field to null", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image
- name: item2
image: image2
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items has been deleted", func() {
It("should be deleted from the result", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item-keep
image: image-keep
- name: item-delete
image: image-delete
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item-keep
image: image-keep
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item-keep
image: image-keep
- name: item-delete
image: image-delete
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item-keep
image: image-keep
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items is only in the remote", func() {
It("should leave the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item2
image: image2
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item2
image: image2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item2
image: image2
- name: item
image: image
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items differs from the remote value and is missing from the recorded", func() {
It("should update the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:1
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items differs from the remote value but matches the recorded", func() {
It("should update the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:1
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items is missing from the remote but matches the recorded", func() {
It("should add the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items is missing from the remote and missing from the recorded ", func() {
It("should add the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: item
image: image:2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where the order of the resolved, local and remote lists differs", func() {
It("should keep the order specified in local and append items appears only in remote", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: recorded-local
image: recorded:b
timeoutSeconds: 2
- name: recorded-remote
image: recorded:c
timeoutSeconds: 3
- name: recorded-local-remote
image: recorded:d
timeoutSeconds: 4
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: local
image: local:a
initialDelaySeconds: 15
- name: recorded-local-remote
image: local:b
initialDelaySeconds: 16
- name: local-remote
image: local:c
initialDelaySeconds: 17
- name: recorded-local
image: local:d
initialDelaySeconds: 18
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: remote
image: remote:a
imagePullPolicy: Always
- name: recorded-remote
image: remote:b
imagePullPolicy: Always
- name: local-remote
image: remote:c
imagePullPolicy: Always
- name: recorded-local-remote
image: remote:d
imagePullPolicy: Always
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: local
image: local:a
initialDelaySeconds: 15
- name: recorded-local-remote
image: local:b
imagePullPolicy: Always
initialDelaySeconds: 16
- name: local-remote
image: local:c
imagePullPolicy: Always
initialDelaySeconds: 17
- name: recorded-local
image: local:d
initialDelaySeconds: 18
- name: remote
image: remote:a
imagePullPolicy: Always
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})
var _ = Describe("Merging fields of type list-of-map with openapi containing a multi-field mergekey", func() {
var resources openapi.Resources
BeforeEach(func() {
resources = tst.NewFakeResources("test_swagger.json")
})
Context("where one of the items has been deleted", func() {
It("should delete the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
- containerPort: 8080
protocol: UDP
hostPort: 2022
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
- containerPort: 8080
protocol: UDP
hostPort: 2022
hostIP: "127.0.0.1"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
`)
runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, resources)
})
})
Context("where one of the items has been updated", func() {
It("should merge updates to the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
- containerPort: 8080
protocol: UDP
hostPort: 2021
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2023
- containerPort: 8080
protocol: UDP
hostPort: 2022
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
- containerPort: 8080
protocol: UDP
hostPort: 2021
hostIP: "127.0.0.1"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2023
- containerPort: 8080
protocol: UDP
hostPort: 2022
hostIP: "127.0.0.1"
`)
runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, resources)
})
})
Context("where one of the items has been added", func() {
It("should add the item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
- containerPort: 8080
protocol: UDP
hostPort: 2022
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
hostIP: "127.0.0.1"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
ports:
- containerPort: 8080
protocol: TCP
hostPort: 2020
hostIP: "127.0.0.1"
- containerPort: 8080
protocol: UDP
hostPort: 2022
`)
runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, resources)
})
})
})
var _ = Describe("Merging fields of type list-of-map with openapi", func() {
Context("containing a replace-keys sub strategy", func() {
It("should apply the replace-key strategy when merging the item", func() {
})
})
})

View File

@ -0,0 +1,164 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Merging fields of type map with openapi for some fields", func() {
Context("where a field has been deleted", func() {
It("should delete the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo1:
bar: "baz1"
image: "1"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo2: null
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo1:
bar: "baz1"
image: "1"
foo2:
bar: "baz2"
image: "2"
foo3:
bar: "baz3"
image: "3"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo3:
bar: "baz3"
image: "3"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been added", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo1:
bar: "baz1"
image: "1"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo1:
bar: "baz1"
image: "1"
foo2:
bar: "baz2"
image: "2"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo1:
bar: "baz1"
image: "1"
foo2:
bar: "baz2"
image: "2"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been updated", func() {
It("should update the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo1:
bar: "baz1=1"
image: "1-1"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo1:
bar: "baz1-1"
image: "1-1"
foo2:
bar: "baz2-1"
image: "2-1"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 2
foo1:
bar: "baz1-0"
image: "1-0"
foo2:
bar: "baz2-0"
image: "2-0"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
foo1:
bar: "baz1-1"
image: "1-1"
foo2:
bar: "baz2-1"
image: "2-1"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})

View File

@ -0,0 +1,189 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Merging fields of type list-of-primitive with openapi", func() {
Context("where one of the items has been deleted", func() {
It("should delete the deleted item", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
- "c"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "c"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
- "c"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "c"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items is only on the remote", func() {
It("should move the remote-only item to the end but keep it", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "c"
- "b"
- "a"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
- "c"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where one of the items is repeated", func() {
It("should de-duplicate the repeated items", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
- "a"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where some items are deleted and others are on remote only", func() {
It("should retain the correct items in the correct order", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "b"
- "c"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "c"
- "a"
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "d"
- "b"
- "c"
- "a"
- "e"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
metadata:
finalizers:
- "a"
- "c"
- "d"
- "e"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})

View File

@ -0,0 +1,540 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Merging fields of type map with openapi", func() {
Context("where a field has been deleted", func() {
It("should delete the field when it is the only field in the map", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - recorded/remote match
paused: true
# delete - recorded/remote differ
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - not present in recorded
replicas: null
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should delete the field when there are other fields in the map", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - recorded/remote match
paused: true
# delete - recorded/remote differ
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - not present in recorded
replicas: null
# keep
revisionHistoryLimit: 1
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
revisionHistoryLimit: 1
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
revisionHistoryLimit: 1
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been added", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
paused: true
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# Add this - it is missing from recorded and remote
replicas: 3
# Add this - it is missing from remote but matches recorded
paused: true
# Add this - it is missing from remote and differs from recorded
progressDeadlineSeconds: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been updated", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
paused: true
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# Missing from recorded
replicas: 3
# Matches the recorded
paused: true
# Differs from recorded
progressDeadlineSeconds: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 2
paused: false
progressDeadlineSeconds: 3
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should update the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 2
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 2
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
replicas: 3
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})
var _ = Describe("Merging fields of type map without openapi", func() {
Context("where a field has been deleted", func() {
It("should delete the field when it is the only field in the map", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
# delete - recorded/remote match
paused: true
# delete - recorded/remote differ
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
# delete - not present in recorded
replicas: null
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should delete the field when there are other fields in the map", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
# delete - recorded/remote match
paused: true
# delete - recorded/remote differ
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
# delete - not present in recorded
replicas: null
# keep
revisionHistoryLimit: 1
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
revisionHistoryLimit: 1
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
revisionHistoryLimit: 1
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been added", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
paused: true
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
# Add this - it is missing from recorded and remote
replicas: 3
# Add this - it is missing from remote but matches recorded
paused: true
# Add this - it is missing from remote and differs from recorded
progressDeadlineSeconds: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been updated", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
paused: true
progressDeadlineSeconds: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
# Matches recorded
replicas: 3
# Matches the recorded
paused: true
# Differs from recorded
progressDeadlineSeconds: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 2
paused: false
progressDeadlineSeconds: 3
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 3
paused: true
progressDeadlineSeconds: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should update the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 2
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 3
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 2
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
replicas: 3
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})
var _ = Describe("Merging fields of type map with openapi", func() {
Context("where a field has been deleted", func() {
It("should delete the field when it is the only field in the map", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - recorded/remote match
foo: true
# delete - recorded/remote differ
bar: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - not present in recorded
baz: null
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
baz: 3
foo: true
bar: 2
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should delete the field when there are other fields in the map", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - recorded/remote match
foo: true
# delete - recorded/remote differ
bar: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# delete - not present in recorded
baz: null
# keep
biz: 1
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
bar: 3
foo: true
baz: 2
biz: 1
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
biz: 1
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been added", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo: true
biz: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# Add this - it is missing from recorded and remote
baz: 3
# Add this - it is missing from remote but matches recorded
foo: true
# Add this - it is missing from remote and differs from recorded
biz: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
baz: 3
foo: true
biz: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been updated", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
foo: true
baz: 1
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
# Missing from recorded
bar: 3
# Matches the recorded
foo: true
# Differs from recorded
baz: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
bar: 2
foo: false
baz: 3
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
bar: 3
foo: true
baz: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})

View File

@ -0,0 +1,148 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy
import (
"fmt"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
func createMergeStrategy(options Options, strategic *delegatingStrategy) mergeStrategy {
return mergeStrategy{
strategic,
options,
}
}
// mergeStrategy merges the values in an Element into a single Result
type mergeStrategy struct {
strategic *delegatingStrategy
options Options
}
// MergeList merges the lists in a ListElement into a single Result
func (v mergeStrategy) MergeList(e apply.ListElement) (apply.Result, error) {
// No merge logic if adding or deleting a field
if result, done := v.doAddOrDelete(e); done {
return result, nil
}
// Merge each item in the list and append it to the list
merged := []interface{}{}
for _, value := range e.Values {
// Recursively merge the list element before adding the value to the list
m, err := value.Merge(v.strategic)
if err != nil {
return apply.Result{}, err
}
switch m.Operation {
case apply.SET:
// Keep the list item value
merged = append(merged, m.MergedResult)
case apply.DROP:
// Drop the list item value
default:
panic(fmt.Errorf("Unexpected result operation type %+v", m))
}
}
if len(merged) == 0 {
// If the list is empty, return a nil entry
return apply.Result{Operation: apply.SET, MergedResult: nil}, nil
}
// Return the merged list, and tell the caller to keep it
return apply.Result{Operation: apply.SET, MergedResult: merged}, nil
}
// MergeMap merges the maps in a MapElement into a single Result
func (v mergeStrategy) MergeMap(e apply.MapElement) (apply.Result, error) {
// No merge logic if adding or deleting a field
if result, done := v.doAddOrDelete(e); done {
return result, nil
}
return v.doMergeMap(e.GetValues())
}
// MergeMap merges the type instances in a TypeElement into a single Result
func (v mergeStrategy) MergeType(e apply.TypeElement) (apply.Result, error) {
// No merge logic if adding or deleting a field
if result, done := v.doAddOrDelete(e); done {
return result, nil
}
return v.doMergeMap(e.GetValues())
}
// do merges a recorded, local and remote map into a new object
func (v mergeStrategy) doMergeMap(e map[string]apply.Element) (apply.Result, error) {
// Merge each item in the list
merged := map[string]interface{}{}
for key, value := range e {
// Recursively merge the map element before adding the value to the map
result, err := value.Merge(v.strategic)
if err != nil {
return apply.Result{}, err
}
switch result.Operation {
case apply.SET:
// Keep the map item value
merged[key] = result.MergedResult
case apply.DROP:
// Drop the map item value
default:
panic(fmt.Errorf("Unexpected result operation type %+v", result))
}
}
// Return the merged map, and tell the caller to keep it
if len(merged) == 0 {
// Special case the empty map to set the field value to nil, but keep the field key
// This is how the tests expect the structures to look when parsed from yaml
return apply.Result{Operation: apply.SET, MergedResult: nil}, nil
}
return apply.Result{Operation: apply.SET, MergedResult: merged}, nil
}
func (v mergeStrategy) doAddOrDelete(e apply.Element) (apply.Result, bool) {
if apply.IsAdd(e) {
return apply.Result{Operation: apply.SET, MergedResult: e.GetLocal()}, true
}
// Delete the List
if apply.IsDrop(e) {
return apply.Result{Operation: apply.DROP}, true
}
return apply.Result{}, false
}
// MergePrimitive returns and error. Primitive elements can't be merged, only replaced.
func (v mergeStrategy) MergePrimitive(diff apply.PrimitiveElement) (apply.Result, error) {
return apply.Result{}, fmt.Errorf("Cannot merge primitive element %v", diff.Name)
}
// MergeEmpty returns an empty result
func (v mergeStrategy) MergeEmpty(diff apply.EmptyElement) (apply.Result, error) {
return apply.Result{Operation: apply.SET}, nil
}
var _ apply.Strategy = &mergeStrategy{}

View File

@ -0,0 +1,73 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Replacing fields of type list without openapi", func() {
Context("where a field is has been updated", func() {
It("should replace the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
bar:
- name: bar1
value: 1
- name: bar2
value: 2
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
bar:
- name: bar1
value: 1
- name: bar2
value: 2
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
bar:
- name: bar1
value: 1
- name: bar3
value: 3
- name: bar4
value: 4
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
bar:
- name: bar1
value: 1
- name: bar2
value: 2
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})

View File

@ -0,0 +1,80 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
)
var _ = Describe("Replacing fields of type map with openapi for some fields", func() {
var resources openapi.Resources
BeforeEach(func() {
resources = tst.NewFakeResources("test_swagger.json")
})
Context("where a field is has been updated", func() {
It("should update the field", func() {
recorded := create(`
apiVersion: extensions/v1beta1
kind: ReplicaSet
spec:
template:
containers:
- name: container1
image: image1
`)
local := create(`
apiVersion: extensions/v1beta1
kind: ReplicaSet
spec:
template:
containers:
- name: container1
image: image1
`)
remote := create(`
apiVersion: extensions/v1beta1
kind: ReplicaSet
spec:
template:
containers:
- name: container1
image: image1
- name: container2
image: image2
- name: container3
image: image3
`)
expected := create(`
apiVersion: extensions/v1beta1
kind: ReplicaSet
spec:
template:
containers:
- name: container1
image: image1
`)
// Use modified swagger for ReplicaSet spec
runWith(strategy.Create(strategy.Options{}), recorded, local, remote, expected, resources)
})
})
})

View File

@ -0,0 +1,723 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Replacing fields of type list with openapi", func() {
Context("where the field has been deleted", func() {
It("should delete the field if present in recorded and missing from local.", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- z
- "y"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should delete the field if missing in recorded and set to null in local.", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- z
- "y"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where the field is has been added", func() {
It("should add the field when missing from recorded", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should add the field when even when present in recorded", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should add the field when the parent field is missing as well", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been updated", func() {
It("should replace the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
- z
- "y"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should replace the field even if recorded matches", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
- z
- "y"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
It("should replace the field even if the only change is ordering", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- e
- c
- f
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
spec:
template:
spec:
containers:
- name: container
command:
- f
- e
- c
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})
var _ = Describe("Replacing fields of type list with openapi for the type, but not the field", func() {
Context("where a field is has been updated", func() {
It("should replace the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
otherstuff:
- name: container1
command:
- e
- f
- g
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
spec:
template:
spec:
containers:
- name: container
command:
- a
- b
- c
- z
- "y"
otherstuff:
- name: container1
command:
- s
- d
- f
- name: container2
command:
- h
- i
- j
- name: container3
command:
- k
- l
- m
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- name: container
command:
- c
- e
- f
otherstuff:
- name: container1
command:
- e
- f
- g
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})
var _ = Describe("Replacing fields of type list without openapi", func() {
Context("where the field has been deleted", func() {
It("should delete the field.", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
arguments:
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
- z
- "y"
# explicitly delete this
arguments:
- a
- b
- z
- "y"
# keep this
env:
- a
- b
- z
- "y"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
env:
- a
- b
- z
- "y"
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where the field is has been added", func() {
It("should add the field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
- z
- "y"
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
# missing from recorded - add
command:
- a
- b
- z
- "y"
# missing from recorded - add
arguments:
- c
- d
- q
- w
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
- z
- "y"
arguments:
- c
- d
- q
- w
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where a field is has been updated", func() {
It("should replace field", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
- c
env:
- s
- "t"
- u
`)
local := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
- z
- "y"
arguments:
- c
- d
- q
- w
env:
- s
- "t"
- u
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
spec:
template:
command:
- a
- b
- c
- z
- "y"
arguments:
- c
- d
- i
env:
- u
- s
- "t"
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Foo
spec:
template:
command:
- a
- b
- z
- "y"
arguments:
- c
- d
- q
- w
env:
- s
- "t"
- u
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})

View File

@ -0,0 +1,100 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy
import (
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// replaceVisitor creates a patch to replace a remote field value with a local field value
type replaceStrategy struct {
strategic *delegatingStrategy
options Options
}
func createReplaceStrategy(options Options, strategic *delegatingStrategy) replaceStrategy {
return replaceStrategy{
strategic,
options,
}
}
// MergeList returns a result by merging the recorded, local and remote values
// - replacing the remote value with the local value
func (v replaceStrategy) MergeList(e apply.ListElement) (apply.Result, error) {
return v.doReplace(e)
}
// MergeMap returns a result by merging the recorded, local and remote values
// - replacing the remote value with the local value
func (v replaceStrategy) MergeMap(e apply.MapElement) (apply.Result, error) {
return v.doReplace(e)
}
// MergeType returns a result by merging the recorded, local and remote values
// - replacing the remote value with the local value
func (v replaceStrategy) MergeType(e apply.TypeElement) (apply.Result, error) {
return v.doReplace(e)
}
// MergePrimitive returns a result by merging the recorded, local and remote values
// - replacing the remote value with the local value
func (v replaceStrategy) MergePrimitive(e apply.PrimitiveElement) (apply.Result, error) {
return v.doReplace(e)
}
// MergeEmpty
func (v replaceStrategy) MergeEmpty(e apply.EmptyElement) (apply.Result, error) {
return apply.Result{Operation: apply.SET}, nil
}
// replace returns the local value if specified, otherwise it returns the remote value
// this works regardless of the approach
func (v replaceStrategy) doReplace(e apply.Element) (apply.Result, error) {
// TODO: Check for conflicts
if result, done := v.doAddOrDelete(e); done {
return result, nil
}
if e.HasLocal() {
// Specified locally, set the local value
return apply.Result{Operation: apply.SET, MergedResult: e.GetLocal()}, nil
} else if e.HasRemote() {
// Not specified locally, set the remote value
return apply.Result{Operation: apply.SET, MergedResult: e.GetRemote()}, nil
} else {
// Only specified in the recorded, drop the field.
return apply.Result{Operation: apply.DROP, MergedResult: e.GetRemote()}, nil
}
}
// doAddOrDelete will check if the field should be either added or deleted. If either is true, it will
// true the operation and true. Otherwise it will return false.
func (v replaceStrategy) doAddOrDelete(e apply.Element) (apply.Result, bool) {
if apply.IsAdd(e) {
return apply.Result{Operation: apply.SET, MergedResult: e.GetLocal()}, true
}
// Delete the List
if apply.IsDrop(e) {
return apply.Result{Operation: apply.DROP}, true
}
return apply.Result{}, false
}
var _ apply.Strategy = &replaceStrategy{}

View File

@ -0,0 +1,195 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy_test
import (
. "github.com/onsi/ginkgo"
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
)
var _ = Describe("Merging fields with the retainkeys strategy", func() {
Context("where some fields are only defined remotely", func() {
It("should drop those fields ", func() {
recorded := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
`)
local := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
type: Recreate
`)
remote := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
`)
expected := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
type: Recreate
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where some fields are defined both locally and remotely", func() {
It("should merge those fields", func() {
recorded := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
`)
local := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 2
`)
remote := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
`)
expected := create(`
apiVersion: extensions/v1beta1
kind: Deployment
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 2
maxSurge: 1
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where the elements are in a list and some fields are only defined remotely", func() {
It("should drop those fields ", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
volumes:
- name: cache-volume
emptyDir:
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
volumes:
- name: cache-volume
hostPath:
path: /tmp/cache-volume
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
volumes:
- name: cache-volume
emptyDir:
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
Context("where the elements are in a list", func() {
It("the fields defined both locally and remotely should be merged", func() {
recorded := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
`)
local := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
volumes:
- name: cache-volume
hostPath:
path: /tmp/cache-volume
emptyDir:
`)
remote := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
volumes:
- name: cache-volume
hostPath:
path: /tmp/cache-volume
type: Directory
`)
expected := create(`
apiVersion: apps/v1beta1
kind: Deployment
spec:
template:
spec:
volumes:
- name: cache-volume
hostPath:
path: /tmp/cache-volume
type: Directory
emptyDir:
`)
run(strategy.Create(strategy.Options{}), recorded, local, remote, expected)
})
})
})

View File

@ -0,0 +1,77 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy
import (
"fmt"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
func createRetainKeysStrategy(options Options, strategic *delegatingStrategy) retainKeysStrategy {
return retainKeysStrategy{
&mergeStrategy{strategic, options},
strategic,
options,
}
}
// retainKeysStrategy merges the values in an Element into a single Result,
// dropping any fields omitted from the local copy. (but merging values when
// defined locally and remotely)
type retainKeysStrategy struct {
merge *mergeStrategy
strategic *delegatingStrategy
options Options
}
// MergeMap merges the type instances in a TypeElement into a single Result
// keeping only the fields defined locally, but merging their values with
// the remote values.
func (v retainKeysStrategy) MergeType(e apply.TypeElement) (apply.Result, error) {
// No merge logic if adding or deleting a field
if result, done := v.merge.doAddOrDelete(&e); done {
return result, nil
}
elem := map[string]apply.Element{}
for key := range e.GetLocalMap() {
elem[key] = e.GetValues()[key]
}
return v.merge.doMergeMap(elem)
}
// MergeMap returns an error. Only TypeElements can have retainKeys.
func (v retainKeysStrategy) MergeMap(e apply.MapElement) (apply.Result, error) {
return apply.Result{}, fmt.Errorf("Cannot use retainkeys with map element %v", e.Name)
}
// MergeList returns an error. Only TypeElements can have retainKeys.
func (v retainKeysStrategy) MergeList(e apply.ListElement) (apply.Result, error) {
return apply.Result{}, fmt.Errorf("Cannot use retainkeys with list element %v", e.Name)
}
// MergePrimitive returns an error. Only TypeElements can have retainKeys.
func (v retainKeysStrategy) MergePrimitive(diff apply.PrimitiveElement) (apply.Result, error) {
return apply.Result{}, fmt.Errorf("Cannot use retainkeys with primitive element %v", diff.Name)
}
// MergeEmpty returns an empty result
func (v retainKeysStrategy) MergeEmpty(diff apply.EmptyElement) (apply.Result, error) {
return v.merge.MergeEmpty(diff)
}
var _ apply.Strategy = &retainKeysStrategy{}

View File

@ -0,0 +1,99 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy
import (
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// delegatingStrategy delegates merging fields to other visitor implementations
// based on the merge strategy preferred by the field.
type delegatingStrategy struct {
options Options
merge mergeStrategy
replace replaceStrategy
retainKeys retainKeysStrategy
}
// createDelegatingStrategy returns a new delegatingStrategy
func createDelegatingStrategy(options Options) *delegatingStrategy {
v := &delegatingStrategy{
options: options,
}
v.replace = createReplaceStrategy(options, v)
v.merge = createMergeStrategy(options, v)
v.retainKeys = createRetainKeysStrategy(options, v)
return v
}
// MergeList delegates visiting a list based on the field patch strategy.
// Defaults to "replace"
func (v delegatingStrategy) MergeList(diff apply.ListElement) (apply.Result, error) {
switch diff.GetFieldMergeType() {
case apply.MergeStrategy:
return v.merge.MergeList(diff)
case apply.ReplaceStrategy:
return v.replace.MergeList(diff)
case apply.RetainKeysStrategy:
return v.retainKeys.MergeList(diff)
default:
return v.replace.MergeList(diff)
}
}
// MergeMap delegates visiting a map based on the field patch strategy.
// Defaults to "merge"
func (v delegatingStrategy) MergeMap(diff apply.MapElement) (apply.Result, error) {
switch diff.GetFieldMergeType() {
case apply.MergeStrategy:
return v.merge.MergeMap(diff)
case apply.ReplaceStrategy:
return v.replace.MergeMap(diff)
case apply.RetainKeysStrategy:
return v.retainKeys.MergeMap(diff)
default:
return v.merge.MergeMap(diff)
}
}
// MergeType delegates visiting a map based on the field patch strategy.
// Defaults to "merge"
func (v delegatingStrategy) MergeType(diff apply.TypeElement) (apply.Result, error) {
switch diff.GetFieldMergeType() {
case apply.MergeStrategy:
return v.merge.MergeType(diff)
case apply.ReplaceStrategy:
return v.replace.MergeType(diff)
case apply.RetainKeysStrategy:
return v.retainKeys.MergeType(diff)
default:
return v.merge.MergeType(diff)
}
}
// MergePrimitive delegates visiting a primitive to the ReplaceVisitorSingleton.
func (v delegatingStrategy) MergePrimitive(diff apply.PrimitiveElement) (apply.Result, error) {
// Always replace primitives
return v.replace.MergePrimitive(diff)
}
// MergeEmpty
func (v delegatingStrategy) MergeEmpty(diff apply.EmptyElement) (apply.Result, error) {
return v.merge.MergeEmpty(diff)
}
var _ apply.Strategy = &delegatingStrategy{}

View File

@ -0,0 +1,49 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/config"
. "github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
"fmt"
"testing"
)
func TestOpenapi(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t, "Openapi Suite", []Reporter{newlineReporter{}})
}
// Print a newline after the default newlineReporter due to issue
// https://github.com/jstemmer/go-junit-report/issues/31
type newlineReporter struct{}
func (newlineReporter) SpecSuiteWillBegin(config GinkgoConfigType, summary *SuiteSummary) {}
func (newlineReporter) BeforeSuiteDidRun(setupSummary *SetupSummary) {}
func (newlineReporter) AfterSuiteDidRun(setupSummary *SetupSummary) {}
func (newlineReporter) SpecWillRun(specSummary *SpecSummary) {}
func (newlineReporter) SpecDidComplete(specSummary *SpecSummary) {}
// SpecSuiteDidEnd Prints a newline between "35 Passed | 0 Failed | 0 Pending | 0 Skipped" and "--- PASS:"
func (newlineReporter) SpecSuiteDidEnd(summary *SuiteSummary) { fmt.Printf("\n") }

View File

@ -0,0 +1,250 @@
{
"swagger": "2.0",
"info": {
"title": "Kubernetes",
"version": "v1.9.0"
},
"paths": {
},
"definitions": {
"io.k8s.api.core.v1.Container": {
"description": "A single application container that you want to run within a pod.",
"required": [
"name",
"image"
],
"properties": {
"image": {
"description": "Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images",
"type": "string"
},
"name": {
"description": "Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated.",
"type": "string"
},
"ports": {
"description": "List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default \"0.0.0.0\" address inside a container will be accessible from the network. Cannot be updated.",
"type": "array",
"items": {
"$ref": "#/definitions/io.k8s.api.core.v1.ContainerPort"
},
"x-kubernetes-patch-merge-key": "containerPort,protocol",
"x-kubernetes-patch-strategy": "merge"
}
}
},
"io.k8s.api.core.v1.ContainerPort": {
"description": "ContainerPort represents a network port in a single container.",
"required": [
"containerPort"
],
"properties": {
"containerPort": {
"description": "Number of port to expose on the pod's IP address. This must be a valid port number, 0 \u003c x \u003c 65536.",
"type": "integer",
"format": "int32"
},
"hostIP": {
"description": "What host IP to bind the external port to.",
"type": "string"
},
"hostPort": {
"description": "Number of port to expose on the host. If specified, this must be a valid port number, 0 \u003c x \u003c 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this.",
"type": "integer",
"format": "int32"
},
"name": {
"description": "If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services.",
"type": "string"
},
"protocol": {
"description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\".",
"type": "string"
}
}
},
"io.k8s.api.apps.v1beta1.Deployment": {
"description": "DEPRECATED - This group version of Deployment is deprecated by apps/v1beta2/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"description": "Standard object metadata.",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
},
"spec": {
"description": "Specification of the desired behavior of the Deployment.",
"$ref": "#/definitions/io.k8s.api.apps.v1beta1.DeploymentSpec"
},
},
"x-kubernetes-group-version-kind": [
{
"group": "apps",
"kind": "Deployment",
"version": "v1beta1"
}
]
},
"io.k8s.api.apps.v1beta1.DeploymentSpec": {
"description": "DeploymentSpec is the specification of the desired behavior of the Deployment.",
"required": [
"template"
],
"properties": {
"minReadySeconds": {
"description": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)",
"type": "integer",
"format": "int32"
},
"paused": {
"description": "Indicates that the deployment is paused.",
"type": "boolean"
},
"progressDeadlineSeconds": {
"description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Once autoRollback is implemented, the deployment controller will automatically rollback failed deployments. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.",
"type": "integer",
"format": "int32"
},
"replicas": {
"description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.",
"type": "integer",
"format": "int32"
},
"revisionHistoryLimit": {
"description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 2.",
"type": "integer",
"format": "int32"
},
"template": {
"description": "Template describes the pods that will be created.",
"$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec"
}
}
},
"io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": {
"description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.",
"properties": {
"annotations": {
"description": "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"labels": {
"description": "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"name": {
"description": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names",
"type": "string"
},
"namespace": {
"description": "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces",
"type": "string"
}
}
},
"io.k8s.api.core.v1.PodTemplateSpec": {
"description": "PodTemplateSpec describes the data a pod should have when created from a template",
"properties": {
"metadata": {
"description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
},
"spec": {
"description": "Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status",
"$ref": "#/definitions/io.k8s.api.core.v1.PodSpec"
}
}
},
"io.k8s.api.core.v1.PodSpec": {
"description": "PodSpec is a description of a pod.",
"required": [
"containers"
],
"properties": {
"containers": {
"description": "List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated.",
"type": "array",
"items": {
"$ref": "#/definitions/io.k8s.api.core.v1.Container"
},
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
}
}
},
"io.k8s.api.extensions.v1beta1.ReplicaSet": {
"description": "DEPRECATED - This group version of ReplicaSet is deprecated by apps/v1beta2/ReplicaSet. See the release notes for more information. ReplicaSet represents the configuration of a ReplicaSet.",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"description": "If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
},
"spec": {
"description": "Spec defines the specification of the desired behavior of the ReplicaSet. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status",
"$ref": "#/definitions/io.k8s.api.extensions.v1beta1.ReplicaSetSpec",
"x-kubernetes-patch-strategy": "replace"
}
},
"x-kubernetes-group-version-kind": [
{
"group": "extensions",
"kind": "ReplicaSet",
"version": "v1beta1"
}
]
},
"io.k8s.api.extensions.v1beta1.ReplicaSetSpec": {
"description": "ReplicaSetSpec is the specification of a ReplicaSet.",
"properties": {
"minReadySeconds": {
"description": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)",
"type": "integer",
"format": "int32"
},
"replicas": {
"description": "Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller",
"type": "integer",
"format": "int32"
},
"template": {
"description": "Template is the object that describes the pod that will be created if insufficient replicas are detected. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template",
"$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec"
}
}
}
},
"securityDefinitions": {
"BearerToken": {
"description": "Bearer Token authentication",
"type": "apiKey",
"name": "authorization",
"in": "header"
}
},
"security": [
{
"BearerToken": []
}
]
}

View File

@ -0,0 +1,66 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strategy_test
import (
. "github.com/onsi/gomega"
"fmt"
"path/filepath"
"strings"
"github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/kubernetes/pkg/kubectl/apply"
"k8s.io/kubernetes/pkg/kubectl/apply/parse"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
)
var fakeResources = tst.NewFakeResources(filepath.Join("..", "..", "..", "..", "api", "openapi-spec", "swagger.json"))
// run parses the openapi and runs the tests
func run(instance apply.Strategy, recorded, local, remote, expected map[string]interface{}) {
runWith(instance, recorded, local, remote, expected, fakeResources)
}
func runWith(instance apply.Strategy, recorded, local, remote, expected map[string]interface{}, resources openapi.Resources) {
parseFactory := parse.Factory{Resources: resources}
parsed, err := parseFactory.CreateElement(recorded, local, remote)
Expect(err).Should(Not(HaveOccurred()))
merged, err := parsed.Merge(instance)
Expect(err).ShouldNot(HaveOccurred())
Expect(merged.Operation).Should(Equal(apply.SET))
Expect(merged.MergedResult).Should(Equal(expected), diff.ObjectDiff(merged.MergedResult, expected))
}
// create parses the yaml string into a map[string]interface{}. Verifies that the string does not have
// any tab characters.
func create(config string) map[string]interface{} {
result := map[string]interface{}{}
// The yaml parser will throw an obscure error if there are tabs in the yaml. Check for this
Expect(strings.Contains(config, "\t")).To(
BeFalse(), fmt.Sprintf("Yaml %s cannot contain tabs", config))
Expect(yaml.Unmarshal([]byte(config), &result)).Should(
Not(HaveOccurred()), fmt.Sprintf("Could not parse config:\n\n%s\n", config))
return result
}

View File

@ -0,0 +1,43 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
// TypeElement contains the recorded, local and remote values for a field
// that is a complex type
type TypeElement struct {
// FieldMetaImpl contains metadata about the field from openapi
FieldMetaImpl
MapElementData
// Values contains the combined recorded-local-remote value of each field in the type
// Values contains the values in mapElement. Element must contain
// a Name matching its key in Values
Values map[string]Element
}
// Merge implements Element.Merge
func (e TypeElement) Merge(v Strategy) (Result, error) {
return v.MergeType(e)
}
// GetValues implements Element.GetValues
func (e TypeElement) GetValues() map[string]Element {
return e.Values
}
var _ Element = &TypeElement{}

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

@ -0,0 +1,68 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
// Strategy implements a strategy for merging recorded, local and remote values contained
// in an element and returns the merged result.
// Follows the visitor pattern
type Strategy interface {
// MergeList is invoked by ListElements when Merge is called
MergeList(ListElement) (Result, error)
// MergeMap is invoked by MapElements when Merge is called
MergeMap(MapElement) (Result, error)
// MergeType is invoked by TypeElements when Merge is called
MergeType(TypeElement) (Result, error)
// MergePrimitive is invoked by PrimitiveElements when Merge is called
MergePrimitive(PrimitiveElement) (Result, error)
// MergeEmpty is invoked by EmptyElements when Merge is called
MergeEmpty(EmptyElement) (Result, error)
}
// Operation records whether a field should be set or dropped
type Operation int
const (
// ERROR is an error during merge
ERROR Operation = iota
// SET sets the field on an object
SET
// DROP drops the field from an object
DROP
)
// Result is the result of merging fields
type Result struct {
// Operation is the operation that should be performed for the merged field
Operation Operation
// MergedResult is the new merged value
MergedResult interface{}
}
const (
// MergeStrategy is the strategy to merge the local and remote values
MergeStrategy = "merge"
// RetainKeysStrategy is the strategy to merge the local and remote values, but drop any fields not defined locally
RetainKeysStrategy = "retainKeys"
// ReplaceStrategy is the strategy to replace the remote value with the local value
ReplaceStrategy = "replace"
)

43
vendor/k8s.io/kubernetes/pkg/kubectl/apps/BUILD generated vendored Normal file
View File

@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["kind_visitor.go"],
importpath = "k8s.io/kubernetes/pkg/kubectl/apps",
deps = ["//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_xtest",
srcs = [
"apps_suite_test.go",
"kind_visitor_test.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/apps_test",
deps = [
":go_default_library",
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
"//vendor/github.com/onsi/ginkgo/types:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
],
)

View File

@ -0,0 +1,49 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apps_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/config"
. "github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
"fmt"
"testing"
)
func TestApps(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t, "Apps Suite", []Reporter{newlineReporter{}})
}
// Print a newline after the default newlineReporter due to issue
// https://github.com/jstemmer/go-junit-report/issues/31
type newlineReporter struct{}
func (newlineReporter) SpecSuiteWillBegin(config GinkgoConfigType, summary *SuiteSummary) {}
func (newlineReporter) BeforeSuiteDidRun(setupSummary *SetupSummary) {}
func (newlineReporter) AfterSuiteDidRun(setupSummary *SetupSummary) {}
func (newlineReporter) SpecWillRun(specSummary *SpecSummary) {}
func (newlineReporter) SpecDidComplete(specSummary *SpecSummary) {}
// SpecSuiteDidEnd Prints a newline between "35 Passed | 0 Failed | 0 Pending | 0 Skipped" and "--- PASS:"
func (newlineReporter) SpecSuiteDidEnd(summary *SuiteSummary) { fmt.Printf("\n") }

View File

@ -0,0 +1,89 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apps
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// KindVisitor is used with GroupKindElement to call a particular function depending on the
// Kind of a schema.GroupKind
type KindVisitor interface {
VisitDaemonSet(kind GroupKindElement)
VisitDeployment(kind GroupKindElement)
VisitJob(kind GroupKindElement)
VisitPod(kind GroupKindElement)
VisitReplicaSet(kind GroupKindElement)
VisitReplicationController(kind GroupKindElement)
VisitStatefulSet(kind GroupKindElement)
VisitCronJob(kind GroupKindElement)
}
// GroupKindElement defines a Kubernetes API group elem
type GroupKindElement schema.GroupKind
// Accept calls the Visit method on visitor that corresponds to elem's Kind
func (elem GroupKindElement) Accept(visitor KindVisitor) error {
switch {
case elem.GroupMatch("apps", "extensions") && elem.Kind == "DaemonSet":
visitor.VisitDaemonSet(elem)
case elem.GroupMatch("apps", "extensions") && elem.Kind == "Deployment":
visitor.VisitDeployment(elem)
case elem.GroupMatch("batch") && elem.Kind == "Job":
visitor.VisitJob(elem)
case elem.GroupMatch("", "core") && elem.Kind == "Pod":
visitor.VisitPod(elem)
case elem.GroupMatch("apps", "extensions") && elem.Kind == "ReplicaSet":
visitor.VisitReplicaSet(elem)
case elem.GroupMatch("", "core") && elem.Kind == "ReplicationController":
visitor.VisitReplicationController(elem)
case elem.GroupMatch("apps") && elem.Kind == "StatefulSet":
visitor.VisitStatefulSet(elem)
case elem.GroupMatch("batch") && elem.Kind == "CronJob":
visitor.VisitCronJob(elem)
default:
return fmt.Errorf("no visitor method exists for %v", elem)
}
return nil
}
// GroupMatch returns true if and only if elem's group matches one
// of the group arguments
func (elem GroupKindElement) GroupMatch(groups ...string) bool {
for _, g := range groups {
if elem.Group == g {
return true
}
}
return false
}
// NoOpKindVisitor implements KindVisitor with no-op functions.
type NoOpKindVisitor struct{}
var _ KindVisitor = &NoOpKindVisitor{}
func (*NoOpKindVisitor) VisitDaemonSet(kind GroupKindElement) {}
func (*NoOpKindVisitor) VisitDeployment(kind GroupKindElement) {}
func (*NoOpKindVisitor) VisitJob(kind GroupKindElement) {}
func (*NoOpKindVisitor) VisitPod(kind GroupKindElement) {}
func (*NoOpKindVisitor) VisitReplicaSet(kind GroupKindElement) {}
func (*NoOpKindVisitor) VisitReplicationController(kind GroupKindElement) {}
func (*NoOpKindVisitor) VisitStatefulSet(kind GroupKindElement) {}
func (*NoOpKindVisitor) VisitCronJob(kind GroupKindElement) {}

View File

@ -0,0 +1,185 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apps_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/kubernetes/pkg/kubectl/apps"
)
var _ = Describe("When KindVisitor accepts a GroupKind", func() {
var visitor *TestKindVisitor
BeforeEach(func() {
visitor = &TestKindVisitor{map[string]int{}}
})
It("should Visit DaemonSet iff the Kind is a DaemonSet", func() {
kind := apps.GroupKindElement{
Kind: "DaemonSet",
Group: "apps",
}
Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{
"DaemonSet": 1,
}))
kind = apps.GroupKindElement{
Kind: "DaemonSet",
Group: "extensions",
}
Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{
"DaemonSet": 2,
}))
})
It("should Visit Deployment iff the Kind is a Deployment", func() {
kind := apps.GroupKindElement{
Kind: "Deployment",
Group: "apps",
}
Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{
"Deployment": 1,
}))
kind = apps.GroupKindElement{
Kind: "Deployment",
Group: "extensions",
}
Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{
"Deployment": 2,
}))
})
It("should Visit Job iff the Kind is a Job", func() {
kind := apps.GroupKindElement{
Kind: "Job",
Group: "batch",
}
Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{
"Job": 1,
}))
})
It("should Visit Pod iff the Kind is a Pod", func() {
kind := apps.GroupKindElement{
Kind: "Pod",
Group: "",
}
Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{
"Pod": 1,
}))
kind = apps.GroupKindElement{
Kind: "Pod",
Group: "core",
}
Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{
"Pod": 2,
}))
})
It("should Visit ReplicationController iff the Kind is a ReplicationController", func() {
kind := apps.GroupKindElement{
Kind: "ReplicationController",
Group: "",
}
Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{
"ReplicationController": 1,
}))
kind = apps.GroupKindElement{
Kind: "ReplicationController",
Group: "core",
}
Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{
"ReplicationController": 2,
}))
})
It("should Visit ReplicaSet iff the Kind is a ReplicaSet", func() {
kind := apps.GroupKindElement{
Kind: "ReplicaSet",
Group: "extensions",
}
Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{
"ReplicaSet": 1,
}))
})
It("should Visit StatefulSet iff the Kind is a StatefulSet", func() {
kind := apps.GroupKindElement{
Kind: "StatefulSet",
Group: "apps",
}
Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{
"StatefulSet": 1,
}))
})
It("should Visit CronJob iff the Kind is a CronJob", func() {
kind := apps.GroupKindElement{
Kind: "CronJob",
Group: "batch",
}
Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{
"CronJob": 1,
}))
})
It("should give an error if the Kind is unknown", func() {
kind := apps.GroupKindElement{
Kind: "Unknown",
Group: "apps",
}
Expect(kind.Accept(visitor)).Should(HaveOccurred())
Expect(visitor.visits).To(Equal(map[string]int{}))
})
})
// TestKindVisitor increments a value each time a Visit method was called
type TestKindVisitor struct {
visits map[string]int
}
var _ apps.KindVisitor = &TestKindVisitor{}
func (t *TestKindVisitor) Visit(kind apps.GroupKindElement) { t.visits[kind.Kind] += 1 }
func (t *TestKindVisitor) VisitDaemonSet(kind apps.GroupKindElement) { t.Visit(kind) }
func (t *TestKindVisitor) VisitDeployment(kind apps.GroupKindElement) { t.Visit(kind) }
func (t *TestKindVisitor) VisitJob(kind apps.GroupKindElement) { t.Visit(kind) }
func (t *TestKindVisitor) VisitPod(kind apps.GroupKindElement) { t.Visit(kind) }
func (t *TestKindVisitor) VisitReplicaSet(kind apps.GroupKindElement) { t.Visit(kind) }
func (t *TestKindVisitor) VisitReplicationController(kind apps.GroupKindElement) { t.Visit(kind) }
func (t *TestKindVisitor) VisitStatefulSet(kind apps.GroupKindElement) { t.Visit(kind) }
func (t *TestKindVisitor) VisitCronJob(kind apps.GroupKindElement) { t.Visit(kind) }

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

@ -0,0 +1,115 @@
/*
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"
autoscalingv1 "k8s.io/api/autoscaling/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
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 := autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.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
}

154
vendor/k8s.io/kubernetes/pkg/kubectl/autoscale_test.go generated vendored Normal file
View File

@ -0,0 +1,154 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubectl
import (
"reflect"
"testing"
autoscalingv1 "k8s.io/api/autoscaling/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestHPAGenerate(t *testing.T) {
tests := []struct {
name string
params map[string]interface{}
expected *autoscalingv1.HorizontalPodAutoscaler
expectErr bool
}{
{
name: "valid case",
params: map[string]interface{}{
"name": "foo",
"min": "1",
"max": "10",
"cpu-percent": "80",
"scaleRef-kind": "kind",
"scaleRef-name": "name",
"scaleRef-apiVersion": "apiVersion",
},
expected: &autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
TargetCPUUtilizationPercentage: newInt32(80),
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "kind",
Name: "name",
APIVersion: "apiVersion",
},
MaxReplicas: int32(10),
MinReplicas: newInt32(1),
},
},
expectErr: false,
},
{
name: "'name' is a required parameter",
params: map[string]interface{}{
"scaleRef-kind": "kind",
"scaleRef-name": "name",
"scaleRef-apiVersion": "apiVersion",
},
expectErr: true,
},
{
name: "'max' is a required parameter",
params: map[string]interface{}{
"default-name": "foo",
"scaleRef-kind": "kind",
"scaleRef-name": "name",
"scaleRef-apiVersion": "apiVersion",
},
expectErr: true,
},
{
name: "'max' must be greater than or equal to 'min'",
params: map[string]interface{}{
"name": "foo",
"min": "10",
"max": "1",
"scaleRef-kind": "kind",
"scaleRef-name": "name",
"scaleRef-apiVersion": "apiVersion",
},
expectErr: true,
},
{
name: "cpu-percent must be an integer if specified",
params: map[string]interface{}{
"name": "foo",
"min": "1",
"max": "10",
"cpu-percent": "",
"scaleRef-kind": "kind",
"scaleRef-name": "name",
"scaleRef-apiVersion": "apiVersion",
},
expectErr: true,
},
{
name: "'min' must be an integer if specified",
params: map[string]interface{}{
"name": "foo",
"min": "foo",
"max": "10",
"cpu-percent": "60",
"scaleRef-kind": "kind",
"scaleRef-name": "name",
"scaleRef-apiVersion": "apiVersion",
},
expectErr: true,
},
{
name: "'max' must be an integer if specified",
params: map[string]interface{}{
"name": "foo",
"min": "1",
"max": "bar",
"cpu-percent": "90",
"scaleRef-kind": "kind",
"scaleRef-name": "name",
"scaleRef-apiVersion": "apiVersion",
},
expectErr: true,
},
}
generator := HorizontalPodAutoscalerV1{}
for _, test := range tests {
obj, err := generator.Generate(test.params)
if test.expectErr && err != nil {
continue
}
if !test.expectErr && err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err)
}
if test.expectErr && err == nil {
t.Errorf("[%s] expect error, got nil", test.name)
}
if !reflect.DeepEqual(obj.(*autoscalingv1.HorizontalPodAutoscaler), test.expected) {
t.Errorf("[%s] want:\n%#v\ngot:\n%#v", test.name, test.expected, obj.(*autoscalingv1.HorizontalPodAutoscaler))
}
}
}
func newInt32(value int) *int32 {
v := int32(value)
return &v
}

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

42
vendor/k8s.io/kubernetes/pkg/kubectl/categories/BUILD generated vendored Normal file
View File

@ -0,0 +1,42 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["categories.go"],
importpath = "k8s.io/kubernetes/pkg/kubectl/categories",
visibility = ["//visibility:public"],
deps = [
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["categories_test.go"],
importpath = "k8s.io/kubernetes/pkg/kubectl/categories",
library = ":go_default_library",
deps = [
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,181 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package categories
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
)
type CategoryExpander interface {
Expand(category string) ([]schema.GroupResource, bool)
}
type SimpleCategoryExpander struct {
Expansions map[string][]schema.GroupResource
}
func (e SimpleCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
ret, ok := e.Expansions[category]
return ret, ok
}
type discoveryCategoryExpander struct {
fallbackExpander CategoryExpander
discoveryClient discovery.DiscoveryInterface
}
// NewDiscoveryCategoryExpander returns a category expander that makes use of the "categories" fields from
// the API, found through the discovery client. In case of any error or no category found (which likely
// means we're at a cluster prior to categories support, fallback to the expander provided.
func NewDiscoveryCategoryExpander(fallbackExpander CategoryExpander, client discovery.DiscoveryInterface) (discoveryCategoryExpander, error) {
if client == nil {
panic("Please provide discovery client to shortcut expander")
}
return discoveryCategoryExpander{fallbackExpander: fallbackExpander, discoveryClient: client}, nil
}
func (e discoveryCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
apiResourceLists, _ := e.discoveryClient.ServerResources()
if len(apiResourceLists) == 0 {
return e.fallbackExpander.Expand(category)
}
discoveredExpansions := map[string][]schema.GroupResource{}
for _, apiResourceList := range apiResourceLists {
gv, err := schema.ParseGroupVersion(apiResourceList.GroupVersion)
if err != nil {
return e.fallbackExpander.Expand(category)
}
for _, apiResource := range apiResourceList.APIResources {
if categories := apiResource.Categories; len(categories) > 0 {
for _, category := range categories {
groupResource := schema.GroupResource{
Group: gv.Group,
Resource: apiResource.Name,
}
discoveredExpansions[category] = append(discoveredExpansions[category], groupResource)
}
}
}
}
if len(discoveredExpansions) == 0 {
// We don't know if the server really don't have any resource with categories,
// or we're on a cluster version prior to categories support. Anyways, fallback.
return e.fallbackExpander.Expand(category)
}
ret, ok := discoveredExpansions[category]
return ret, ok
}
type discoveryFilteredExpander struct {
delegate CategoryExpander
discoveryClient discovery.DiscoveryInterface
}
// NewDiscoveryFilteredExpander returns a category expander that filters the returned groupresources by
// what the server has available
func NewDiscoveryFilteredExpander(delegate CategoryExpander, client discovery.DiscoveryInterface) (discoveryFilteredExpander, error) {
if client == nil {
panic("Please provide discovery client to shortcut expander")
}
return discoveryFilteredExpander{delegate: delegate, discoveryClient: client}, nil
}
func (e discoveryFilteredExpander) Expand(category string) ([]schema.GroupResource, bool) {
delegateExpansion, ok := e.delegate.Expand(category)
// Check if we have access to server resources
apiResources, err := e.discoveryClient.ServerResources()
if err != nil {
return delegateExpansion, ok
}
availableResources, err := discovery.GroupVersionResources(apiResources)
if err != nil {
return delegateExpansion, ok
}
available := []schema.GroupResource{}
for _, requestedResource := range delegateExpansion {
for availableResource := range availableResources {
if requestedResource.Group == availableResource.Group &&
requestedResource.Resource == availableResource.Resource {
available = append(available, requestedResource)
break
}
}
}
return available, ok
}
type UnionCategoryExpander []CategoryExpander
func (u UnionCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
ret := []schema.GroupResource{}
ok := false
for _, expansion := range u {
curr, currOk := expansion.Expand(category)
for _, currGR := range curr {
found := false
for _, existing := range ret {
if existing == currGR {
found = true
break
}
}
if !found {
ret = append(ret, currGR)
}
}
ok = ok || currOk
}
return ret, ok
}
// legacyUserResources are the resource names that apply to the primary, user facing resources used by
// client tools. They are in deletion-first order - dependent resources should be last.
// Should remain exported in order to expose a current list of resources to downstream
// composition that wants to build on the concept of 'all' for their CLIs.
var legacyUserResources = []schema.GroupResource{
{Group: "", Resource: "pods"},
{Group: "", Resource: "replicationcontrollers"},
{Group: "", Resource: "services"},
{Group: "apps", Resource: "statefulsets"},
{Group: "autoscaling", Resource: "horizontalpodautoscalers"},
{Group: "batch", Resource: "jobs"},
{Group: "batch", Resource: "cronjobs"},
{Group: "extensions", Resource: "daemonsets"},
{Group: "extensions", Resource: "deployments"},
{Group: "extensions", Resource: "replicasets"},
}
// LegacyCategoryExpander is the old hardcoded expansion
var LegacyCategoryExpander CategoryExpander = SimpleCategoryExpander{
Expansions: map[string][]schema.GroupResource{
"all": legacyUserResources,
},
}

View File

@ -0,0 +1,186 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package categories
import (
"reflect"
"testing"
"github.com/googleapis/gnostic/OpenAPIv2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
)
func TestCategoryExpansion(t *testing.T) {
tests := []struct {
name string
arg string
expected []schema.GroupResource
expectedOk bool
}{
{
name: "no-replacement",
arg: "service",
expected: nil,
},
{
name: "all-replacement",
arg: "all",
expected: []schema.GroupResource{
{Resource: "pods"},
{Resource: "replicationcontrollers"},
{Resource: "services"},
{Resource: "statefulsets", Group: "apps"},
{Resource: "horizontalpodautoscalers", Group: "autoscaling"},
{Resource: "jobs", Group: "batch"},
{Resource: "cronjobs", Group: "batch"},
{Resource: "daemonsets", Group: "extensions"},
{Resource: "deployments", Group: "extensions"},
{Resource: "replicasets", Group: "extensions"},
},
expectedOk: true,
},
}
for _, test := range tests {
actual, actualOk := LegacyCategoryExpander.Expand(test.arg)
if e, a := test.expected, actual; !reflect.DeepEqual(e, a) {
t.Errorf("%s: expected %s, got %s", test.name, e, a)
}
if e, a := test.expectedOk, actualOk; e != a {
t.Errorf("%s: expected %v, got %v", test.name, e, a)
}
}
}
func TestDiscoveryCategoryExpander(t *testing.T) {
tests := []struct {
category string
serverResponse []*metav1.APIResourceList
expected []schema.GroupResource
}{
{
category: "all",
serverResponse: []*metav1.APIResourceList{
{
GroupVersion: "batch/v1",
APIResources: []metav1.APIResource{
{
Name: "jobs",
ShortNames: []string{"jz"},
Categories: []string{"all"},
},
},
},
},
expected: []schema.GroupResource{
{
Group: "batch",
Resource: "jobs",
},
},
},
{
category: "all",
serverResponse: []*metav1.APIResourceList{
{
GroupVersion: "batch/v1",
APIResources: []metav1.APIResource{
{
Name: "jobs",
ShortNames: []string{"jz"},
},
},
},
},
},
{
category: "targaryens",
serverResponse: []*metav1.APIResourceList{
{
GroupVersion: "batch/v1",
APIResources: []metav1.APIResource{
{
Name: "jobs",
ShortNames: []string{"jz"},
Categories: []string{"all"},
},
},
},
},
},
}
dc := &fakeDiscoveryClient{}
for _, test := range tests {
dc.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {
return test.serverResponse, nil
}
expander, err := NewDiscoveryCategoryExpander(SimpleCategoryExpander{}, dc)
if err != nil {
t.Fatalf("unexpected error %v", err)
}
expanded, _ := expander.Expand(test.category)
if !reflect.DeepEqual(expanded, test.expected) {
t.Errorf("expected %v, got %v", test.expected, expanded)
}
}
}
type fakeDiscoveryClient struct {
serverResourcesHandler func() ([]*metav1.APIResourceList, error)
}
var _ discovery.DiscoveryInterface = &fakeDiscoveryClient{}
func (c *fakeDiscoveryClient) RESTClient() restclient.Interface {
return &fake.RESTClient{}
}
func (c *fakeDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
return nil, nil
}
func (c *fakeDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
return &metav1.APIResourceList{}, nil
}
func (c *fakeDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
return c.serverResourcesHandler()
}
func (c *fakeDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return nil, nil
}
func (c *fakeDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
return nil, nil
}
func (c *fakeDiscoveryClient) ServerVersion() (*version.Info, error) {
return &version.Info{}, nil
}
func (c *fakeDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
return &openapi_v2.Document{}, nil
}

View File

@ -0,0 +1,158 @@
/*
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"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
)
// 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{}
userStrings, found := genericParams["user"]
if found {
fromFileArray, isArray := userStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", userStrings)
}
delegate.Users = fromFileArray
delete(genericParams, "user")
}
groupStrings, found := genericParams["group"]
if found {
fromLiteralArray, isArray := groupStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", groupStrings)
}
delegate.Groups = fromLiteralArray
delete(genericParams, "group")
}
saStrings, found := genericParams["serviceaccount"]
if found {
fromLiteralArray, isArray := saStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", saStrings)
}
delegate.ServiceAccounts = fromLiteralArray
delete(genericParams, "serviceaccount")
}
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},
}
}
// 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 := &rbacv1beta1.ClusterRoleBinding{}
clusterRoleBinding.Name = s.Name
clusterRoleBinding.RoleRef = rbacv1beta1.RoleRef{
APIGroup: rbacv1beta1.GroupName,
Kind: "ClusterRole",
Name: s.ClusterRole,
}
for _, user := range sets.NewString(s.Users...).List() {
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1beta1.Subject{
Kind: rbacv1beta1.UserKind,
APIGroup: rbacv1beta1.GroupName,
Name: user,
})
}
for _, group := range sets.NewString(s.Groups...).List() {
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1beta1.Subject{
Kind: rbacv1beta1.GroupKind,
APIGroup: rbacv1beta1.GroupName,
Name: group,
})
}
for _, sa := range sets.NewString(s.ServiceAccounts...).List() {
tokens := strings.Split(sa, ":")
if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" {
return nil, fmt.Errorf("serviceaccount must be <namespace>:<name>")
}
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1beta1.Subject{
Kind: rbacv1beta1.ServiceAccountKind,
APIGroup: "",
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
}

View File

@ -0,0 +1,229 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubectl
import (
"reflect"
"testing"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestClusterRoleBindingGenerate(t *testing.T) {
tests := []struct {
name string
params map[string]interface{}
expected *rbacv1beta1.ClusterRoleBinding
expectErr bool
}{
{
name: "valid case 1",
params: map[string]interface{}{
"name": "foo",
"clusterrole": "admin",
"user": []string{"user"},
"group": []string{"group"},
"serviceaccount": []string{"ns1:name1"},
},
expected: &rbacv1beta1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
RoleRef: rbacv1beta1.RoleRef{
APIGroup: rbacv1beta1.GroupName,
Kind: "ClusterRole",
Name: "admin",
},
Subjects: []rbacv1beta1.Subject{
{
APIGroup: rbacv1beta1.GroupName,
Kind: rbacv1beta1.UserKind,
Name: "user",
},
{
APIGroup: rbacv1beta1.GroupName,
Kind: rbacv1beta1.GroupKind,
Name: "group",
},
{
Kind: rbacv1beta1.ServiceAccountKind,
APIGroup: "",
Namespace: "ns1",
Name: "name1",
},
},
},
expectErr: false,
},
{
name: "valid case 2",
params: map[string]interface{}{
"name": "foo",
"clusterrole": "admin",
"user": []string{"user1", "user2"},
"group": []string{"group1", "group2"},
"serviceaccount": []string{"ns1:name1", "ns2:name2"},
},
expected: &rbacv1beta1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
RoleRef: rbacv1beta1.RoleRef{
APIGroup: rbacv1beta1.GroupName,
Kind: "ClusterRole",
Name: "admin",
},
Subjects: []rbacv1beta1.Subject{
{
APIGroup: rbacv1beta1.GroupName,
Kind: rbacv1beta1.UserKind,
Name: "user1",
},
{
APIGroup: rbacv1beta1.GroupName,
Kind: rbacv1beta1.UserKind,
Name: "user2",
},
{
APIGroup: rbacv1beta1.GroupName,
Kind: rbacv1beta1.GroupKind,
Name: "group1",
},
{
APIGroup: rbacv1beta1.GroupName,
Kind: rbacv1beta1.GroupKind,
Name: "group2",
},
{
Kind: rbacv1beta1.ServiceAccountKind,
APIGroup: "",
Namespace: "ns1",
Name: "name1",
},
{
Kind: rbacv1beta1.ServiceAccountKind,
APIGroup: "",
Namespace: "ns2",
Name: "name2",
},
},
},
expectErr: false,
},
{
name: "valid case 3",
params: map[string]interface{}{
"name": "foo",
"clusterrole": "admin",
},
expected: &rbacv1beta1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
RoleRef: rbacv1beta1.RoleRef{
APIGroup: rbacv1beta1.GroupName,
Kind: "ClusterRole",
Name: "admin",
},
},
expectErr: false,
},
{
name: "invalid serviceaccount, expected format: <namespace:name>",
params: map[string]interface{}{
"name": "role",
"clusterrole": "admin",
"user": []string{"user"},
"group": []string{"group"},
"serviceaccount": []string{"ns1-name1"},
},
expectErr: true,
},
{
name: "name must be specified",
params: map[string]interface{}{
"name": "",
"clusterrole": "admin",
"user": []string{"user"},
"group": []string{"group"},
"serviceaccount": []string{"ns1:name1"},
},
expectErr: true,
},
{
name: "clusterrole must be specified",
params: map[string]interface{}{
"name": "foo",
"clusterrole": "",
"user": []string{"user"},
"group": []string{"group"},
"serviceaccount": []string{"ns1:name1"},
},
expectErr: true,
},
{
name: "expected user []string",
params: map[string]interface{}{
"name": "role",
"clusterrole": "admin",
"user": "user",
"group": []string{"group"},
"serviceaccount": []string{"ns1:name1"},
},
expectErr: true,
},
{
name: "expected group []string",
params: map[string]interface{}{
"name": "role",
"clusterrole": "admin",
"user": []string{"user"},
"group": "group",
"serviceaccount": []string{"ns1:name1"},
},
expectErr: true,
},
{
name: "expected serviceaccount []string",
params: map[string]interface{}{
"name": "role",
"clusterrole": "admin",
"user": []string{"user"},
"group": []string{"group"},
"serviceaccount": "ns1",
},
expectErr: true,
},
}
generator := ClusterRoleBindingGeneratorV1{}
for i := range tests {
obj, err := generator.Generate(tests[i].params)
if !tests[i].expectErr && err != nil {
t.Errorf("[%d] unexpected error: %v", i, err)
}
if tests[i].expectErr && err != nil {
continue
}
if tests[i].expectErr && err == nil {
t.Errorf("[%s] expect error, got nil", tests[i].name)
}
if !reflect.DeepEqual(obj.(*rbacv1beta1.ClusterRoleBinding), tests[i].expected) {
t.Errorf("\n[%s] want:\n%#v\ngot:\n%#v", tests[i].name, tests[i].expected, obj.(*rbacv1beta1.ClusterRoleBinding))
}
}
}

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

@ -0,0 +1,282 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"alpha.go",
"annotate.go",
"apiversions.go",
"apply.go",
"apply_edit_last_applied.go",
"apply_set_last_applied.go",
"apply_view_last_applied.go",
"attach.go",
"autoscale.go",
"certificates.go",
"clusterinfo.go",
"clusterinfo_dump.go",
"cmd.go",
"completion.go",
"convert.go",
"cp.go",
"create.go",
"create_clusterrole.go",
"create_clusterrolebinding.go",
"create_configmap.go",
"create_deployment.go",
"create_namespace.go",
"create_pdb.go",
"create_priorityclass.go",
"create_quota.go",
"create_role.go",
"create_rolebinding.go",
"create_secret.go",
"create_service.go",
"create_serviceaccount.go",
"delete.go",
"describe.go",
"diff.go",
"drain.go",
"edit.go",
"exec.go",
"explain.go",
"expose.go",
"help.go",
"label.go",
"logs.go",
"options.go",
"patch.go",
"plugin.go",
"portforward.go",
"proxy.go",
"replace.go",
"rollingupdate.go",
"run.go",
"scale.go",
"taint.go",
"top.go",
"top_node.go",
"top_pod.go",
"version.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd",
visibility = [
"//build/visible_to:pkg_kubectl_cmd_CONSUMERS",
],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/validation:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/unversioned:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/apply/parse:go_default_library",
"//pkg/kubectl/apply/strategy:go_default_library",
"//pkg/kubectl/cmd/auth:go_default_library",
"//pkg/kubectl/cmd/config:go_default_library",
"//pkg/kubectl/cmd/resource:go_default_library",
"//pkg/kubectl/cmd/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/cmd/util/openapi:go_default_library",
"//pkg/kubectl/explain:go_default_library",
"//pkg/kubectl/metricsutil:go_default_library",
"//pkg/kubectl/plugins:go_default_library",
"//pkg/kubectl/proxy:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//pkg/kubectl/util:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/kubectl/util/term:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",
"//pkg/util/interrupt:go_default_library",
"//pkg/util/taints:go_default_library",
"//pkg/version:go_default_library",
"//vendor/github.com/daviddengcn/go-colortext:go_default_library",
"//vendor/github.com/docker/distribution/reference:go_default_library",
"//vendor/github.com/docker/docker/pkg/term:go_default_library",
"//vendor/github.com/evanphx/json-patch:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/jonboulle/clockwork:go_default_library",
"//vendor/github.com/renstrom/dedent:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/jsonmergepatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/portforward:go_default_library",
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/k8s.io/client-go/transport/spdy:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"annotate_test.go",
"apply_test.go",
"attach_test.go",
"clusterinfo_dump_test.go",
"cmd_test.go",
"convert_test.go",
"cp_test.go",
"create_clusterrole_test.go",
"create_clusterrolebinding_test.go",
"create_configmap_test.go",
"create_deployment_test.go",
"create_namespace_test.go",
"create_pdb_test.go",
"create_priorityclass_test.go",
"create_quota_test.go",
"create_role_test.go",
"create_rolebinding_test.go",
"create_secret_test.go",
"create_service_test.go",
"create_serviceaccount_test.go",
"create_test.go",
"delete_test.go",
"describe_test.go",
"diff_test.go",
"drain_test.go",
"edit_test.go",
"exec_test.go",
"expose_test.go",
"label_test.go",
"logs_test.go",
"patch_test.go",
"plugin_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 = [
"testdata",
"//api/openapi-spec:swagger-spec",
"//examples:config",
"//test/fixtures",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd",
library = ":go_default_library",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/ref:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/api/testing:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/plugins:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/kubectl/util/term:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",
"//pkg/util/strings:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/policy/v1beta1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch/testing:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/k8s.io/metrics/pkg/apis/metrics/v1alpha1:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubectl/cmd/auth:all-srcs",
"//pkg/kubectl/cmd/config:all-srcs",
"//pkg/kubectl/cmd/resource:all-srcs",
"//pkg/kubectl/cmd/rollout:all-srcs",
"//pkg/kubectl/cmd/set:all-srcs",
"//pkg/kubectl/cmd/templates:all-srcs",
"//pkg/kubectl/cmd/testdata/edit:all-srcs",
"//pkg/kubectl/cmd/testing:all-srcs",
"//pkg/kubectl/cmd/util:all-srcs",
],
tags = ["automanaged"],
visibility = [
"//build/visible_to:pkg_kubectl_cmd_CONSUMERS",
],
)

51
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/alpha.go generated vendored Normal file
View File

@ -0,0 +1,51 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
// NewCmdAlpha creates a command that acts as an alternate root command for features in alpha
func NewCmdAlpha(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "alpha",
Short: i18n.T("Commands for features in alpha"),
Long: templates.LongDesc(i18n.T("These commands correspond to alpha features that are not enabled in Kubernetes clusters by default.")),
}
// Alpha commands should be added here. As features graduate from alpha they should move
// from here to the CommandGroups defined by NewKubeletCommand() in cmd.go.
//cmd.AddCommand(NewCmdDebug(f, in, out, err))
cmd.AddCommand(NewCmdDiff(f, out, err))
// NewKubeletCommand() will hide the alpha command if it has no subcommands. Overriding
// the help function ensures a reasonable message if someone types the hidden command anyway.
if !cmd.HasSubCommands() {
cmd.SetHelpFunc(func(*cobra.Command, []string) {
cmd.Println(i18n.T("No alpha commands are available in this version of kubectl"))
})
}
return cmd
}

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

@ -0,0 +1,363 @@
/*
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"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
"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/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
)
// 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 (
annotateLong = templates.LongDesc(`
Update the annotations on one or more resources
All Kubernetes objects support the ability to store additional data with the object as
annotations. Annotations are key/value pairs that can be larger than labels and include
arbitrary string values such as structured JSON. Tools and system extensions may use
annotations to store their own data.
Attempting to set an annotation that already exists will fail unless --overwrite is set.
If --resource-version is specified and does not match the current resource version on
the server the command will fail.`)
annotateExample = templates.Examples(i18n.T(`
# 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, printers.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: annotateLong + "\n\n" + cmdutil.ValidResourceTypeList(f),
Example: annotateExample,
Run: func(cmd *cobra.Command, args []string) {
if err := options.Complete(out, cmd, args); err != nil {
cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, "%v", err))
}
if err := options.Validate(); err != nil {
cmdutil.CheckErr(cmdutil.UsageErrorf(cmd, "%v", err))
}
cmdutil.CheckErr(options.RunAnnotate(f, cmd))
},
ValidArgs: validArgs,
ArgAliases: argAliases,
}
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddIncludeUninitializedFlag(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, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2).")
cmd.Flags().Bool("all", false, "Select all resources, including uninitialized ones, in the namespace of the specified resource types.")
cmd.Flags().String("resource-version", "", i18n.T("If non-empty, the annotation update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource."))
usage := "identifying the resource to update the annotation"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
// Complete adapts from the command line args and factory to the data required.
func (o *AnnotateOptions) Complete(out io.Writer, cmd *cobra.Command, args []string) (err error) {
o.out = out
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.IsFilenameSliceEmpty(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(cmd, false)
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
b := f.NewBuilder().
Unstructured().
LocalParam(o.local).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
Flatten()
if !o.local {
b = b.LabelSelectorParam(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
var obj runtime.Object
obj, err = info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion())
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 := jsonpatch.CreateMergePatch(oldData, newData)
createdPatch := err == nil
if err != nil {
glog.V(2).Infof("couldn't compute patch: %v", err)
}
mapping := info.ResourceMapping()
client, err := f.UnstructuredClientForMapping(mapping)
if err != nil {
return err
}
helper := resource.NewHelper(client, mapping)
if createdPatch {
outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes)
} else {
outputObj, err = helper.Replace(namespace, name, false, obj)
}
if err != nil {
return err
}
}
mapper := r.Mapper().RESTMapper
if len(o.outputFormat) > 0 {
return f.PrintObject(cmd, o.local, mapper, outputObj, o.out)
}
f.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 metav1.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,633 @@
/*
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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest/fake"
api "k8s.io/kubernetes/pkg/apis/core"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestValidateAnnotationOverwrites(t *testing.T) {
tests := []struct {
meta *metav1.ObjectMeta
annotations map[string]string
expectErr bool
scenario string
}{
{
meta: &metav1.ObjectMeta{
Annotations: map[string]string{
"a": "A",
"b": "B",
},
},
annotations: map[string]string{
"a": "a",
"c": "C",
},
scenario: "share first annotation",
expectErr: true,
},
{
meta: &metav1.ObjectMeta{
Annotations: map[string]string{
"a": "A",
"c": "C",
},
},
annotations: map[string]string{
"b": "B",
"c": "c",
},
scenario: "share second annotation",
expectErr: true,
},
{
meta: &metav1.ObjectMeta{
Annotations: map[string]string{
"a": "A",
"c": "C",
},
},
annotations: map[string]string{
"b": "B",
"d": "D",
},
scenario: "no overlap",
},
{
meta: &metav1.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, "fake.kubernetes.io/annotation=" + testJSON},
expected: map[string]string{"url": testURL, "fake.kubernetes.io/annotation": 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,
},
{
annotations: []string{"-"},
expectedErr: "invalid annotation format: -",
scenario: "incorrect annotation input (missing key)",
expectErr: true,
},
{
annotations: []string{"=bar"},
expectedErr: "invalid annotation format: =bar",
scenario: "incorrect annotation input (missing key)",
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: metav1.ObjectMeta{
Annotations: map[string]string{"a": "b"},
},
},
annotations: map[string]string{"a": "b"},
expectErr: true,
},
{
obj: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"a": "b"},
},
},
annotations: map[string]string{"a": "c"},
overwrite: true,
expected: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"a": "c"},
},
},
},
{
obj: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"a": "b"},
},
},
annotations: map[string]string{"c": "d"},
expected: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"a": "b", "c": "d"},
},
},
},
{
obj: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"a": "b"},
},
},
annotations: map[string]string{"c": "d"},
version: "2",
expected: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"a": "b", "c": "d"},
ResourceVersion: "2",
},
},
},
{
obj: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"a": "b"},
},
},
annotations: map[string]string{},
remove: []string{"a"},
expected: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{},
},
},
},
{
obj: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"a": "b", "c": "d"},
},
},
annotations: map[string]string{"e": "f"},
remove: []string{"a"},
expected: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"c": "d",
"e": "f",
},
},
},
},
{
obj: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"a": "b", "c": "d"},
},
},
annotations: map[string]string{"e": "f"},
remove: []string{"g"},
expected: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"a": "b",
"c": "d",
"e": "f",
},
},
},
},
{
obj: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"a": "b", "c": "d"},
},
},
remove: []string{"e"},
expected: &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"a": "b",
"c": "d",
},
},
},
},
{
obj: &api.Pod{
ObjectMeta: metav1.ObjectMeta{},
},
annotations: map[string]string{"a": "b"},
expected: &api.Pod{
ObjectMeta: metav1.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")
},
},
"wrong annotations": {
args: []string{"pods", "-"},
errFn: func(err error) bool {
return strings.Contains(err.Error(), "at least one annotation update is required")
},
},
"wrong annotations 2": {
args: []string{"pods", "=bar"},
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 = defaultClientConfig()
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(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, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
NegotiatedSerializer: unstructuredSerializer,
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 = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(f, buf)
cmd.SetOutput(buf)
options := &AnnotateOptions{}
args := []string{"pods/foo", "a=b", "c-"}
if err := options.Complete(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, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
NegotiatedSerializer: unstructuredSerializer,
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 = defaultClientConfig()
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(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, _, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
NegotiatedSerializer: unstructuredSerializer,
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 = defaultClientConfig()
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(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, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
NegotiatedSerializer: unstructuredSerializer,
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 = defaultClientConfig()
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(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,71 @@
/*
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"
"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"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
var (
apiversionsExample = templates.Examples(i18n.T(`
# Print the supported API versions
kubectl api-versions`))
)
func NewCmdApiVersions(f cmdutil.Factory, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "api-versions",
Short: "Print the supported API versions on the server, in the form of \"group/version\"",
Long: "Print the supported API versions on the server, in the form of \"group/version\"",
Example: apiversionsExample,
Run: func(cmd *cobra.Command, args []string) {
err := RunApiVersions(f, out)
cmdutil.CheckErr(err)
},
}
return cmd
}
func RunApiVersions(f cmdutil.Factory, w io.Writer) error {
discoveryclient, err := f.DiscoveryClient()
if err != nil {
return err
}
// Always request fresh data from the server
discoveryclient.Invalidate()
groupList, err := discoveryclient.ServerGroups()
if err != nil {
return fmt.Errorf("Couldn't get available api versions from server: %v\n", err)
}
apiVersions := metav1.ExtractGroupVersions(groupList)
sort.Strings(apiVersions)
for _, v := range apiVersions {
fmt.Fprintln(w, v)
}
return nil
}

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

@ -0,0 +1,703 @@
/*
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/golang/glog"
"github.com/jonboulle/clockwork"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/jsonmergepatch"
"k8s.io/apimachinery/pkg/util/mergepatch"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait"
oapi "k8s.io/kube-openapi/pkg/util/proto"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
type ApplyOptions struct {
FilenameOptions resource.FilenameOptions
Selector string
Force bool
Prune bool
Cascade bool
GracePeriod int
PruneResources []pruneResource
Timeout time.Duration
cmdBaseName string
}
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
)
var (
applyLong = templates.LongDesc(i18n.T(`
Apply a configuration to a resource by filename or stdin.
The resource name must be specified. 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.`))
applyExample = templates.Examples(i18n.T(`
# 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`))
warningNoLastAppliedConfigAnnotation = "Warning: %[1]s apply should be used on resource created by either %[1]s create --save-config or %[1]s apply\n"
)
func NewCmdApply(baseName string, f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
var options ApplyOptions
// Store baseName for use in printing warnings / messages involving the base command name.
// This is useful for downstream command that wrap this one.
options.cmdBaseName = baseName
cmd := &cobra.Command{
Use: "apply -f FILENAME",
Short: i18n.T("Apply a configuration to a resource by filename or stdin"),
Long: applyLong,
Example: applyExample,
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, including the uninitialized ones, that do not appear in the configs and are created by either apply or create --save-config. Should be used with either -l or --all.")
cmd.Flags().BoolVar(&options.Cascade, "cascade", true, "Only relevant during a prune or a force apply. If true, cascade the deletion of the resources managed by pruned or deleted resources (e.g. Pods created by a ReplicationController).")
cmd.Flags().IntVar(&options.GracePeriod, "grace-period", -1, "Only relevant during a prune or a force apply. Period of time in seconds given to pruned or deleted resources to terminate gracefully. Ignored if negative.")
cmd.Flags().BoolVar(&options.Force, "force", false, fmt.Sprintf("Delete and re-create the specified resource, when PATCH encounters conflict and has retried for %d times.", maxPatchRetry))
cmd.Flags().DurationVar(&options.Timeout, "timeout", 0, "Only relevant during a force apply. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).")
cmdutil.AddValidateFlags(cmd)
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().Bool("all", false, "Select all resources in the namespace of the specified resource types.")
cmd.Flags().StringArray("prune-whitelist", []string{}, "Overwrite the default whitelist with <group/version/kind> for --prune")
cmd.Flags().Bool("openapi-patch", true, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.")
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd)
// apply subcommands
cmd.AddCommand(NewCmdApplyViewLastApplied(f, out, errOut))
cmd.AddCommand(NewCmdApplySetLastApplied(f, out, errOut))
cmd.AddCommand(NewCmdApplyEditLastApplied(f, out, errOut))
return cmd
}
func validateArgs(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return cmdutil.UsageErrorf(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(mapper meta.RESTMapper, 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)
}
if gvk[0] == "core" {
gvk[0] = ""
}
mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk[0], Kind: gvk[2]}, gvk[1])
if err != nil {
return pruneResources, err
}
var namespaced bool
namespaceScope := mapping.Scope.Name()
switch namespaceScope {
case meta.RESTScopeNameNamespace:
namespaced = true
case meta.RESTScopeNameRoot:
namespaced = false
default:
return pruneResources, fmt.Errorf("Unknown namespace scope: %q", namespaceScope)
}
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"))
if err != nil {
return err
}
var openapiSchema openapi.Resources
if cmdutil.GetFlagBool(cmd, "openapi-patch") {
openapiSchema, err = f.OpenAPISchema()
if err != nil {
openapiSchema = nil
}
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
// include the uninitialized objects by default if --prune is true
// unless explicitly set --include-uninitialized=false
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, options.Prune)
r := f.NewBuilder().
Unstructured().
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &options.FilenameOptions).
LabelSelectorParam(options.Selector).
IncludeUninitialized(includeUninitialized).
Flatten().
Do()
if err := r.Err(); err != nil {
return err
}
if options.Prune {
options.PruneResources, err = parsePruneResources(r.Mapper().RESTMapper, cmdutil.GetFlagStringArray(cmd, "prune-whitelist"))
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)
mapper := r.Mapper().RESTMapper
visitedUids := sets.NewString()
visitedNamespaces := sets.NewString()
count := 0
err = r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
if info.Namespaced() {
visitedNamespaces.Insert(info.Namespace)
}
// Add change-cause annotation to resource info if it should be recorded
if cmdutil.ShouldRecord(cmd, info) {
recordInObj := info.Object
if err := cmdutil.RecordChangeCause(recordInObj, f.Command(cmd, false)); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
}
// 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 !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 f.PrintResourceInfoForCommand(cmd, info, out)
}
f.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[api.LastAppliedConfigAnnotation]; !ok {
fmt.Fprintf(errOut, warningNoLastAppliedConfigAnnotation, options.cmdBaseName)
}
overwrite := cmdutil.GetFlagBool(cmd, "overwrite")
helper := resource.NewHelper(info.Client, info.Mapping)
patcher := &patcher{
encoder: encoder,
decoder: decoder,
mapping: info.Mapping,
helper: helper,
clientFunc: f.UnstructuredClientForMapping,
clientsetFunc: f.ClientSet,
overwrite: overwrite,
backOff: clockwork.NewRealClock(),
force: options.Force,
cascade: options.Cascade,
timeout: options.Timeout,
gracePeriod: options.GracePeriod,
openapiSchema: openapiSchema,
}
patchBytes, patchedObject, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name, errOut)
if err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
}
info.Refresh(patchedObject, true)
if uid, err := info.Mapping.UID(info.Object); err != nil {
return err
} else {
visitedUids.Insert(string(uid))
}
if string(patchBytes) == "{}" {
count++
f.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "unchanged")
return nil
}
}
count++
if len(output) > 0 && !shortOutput {
return f.PrintResourceInfoForCommand(cmd, info, out)
}
f.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
}
p := pruner{
mapper: mapper,
clientFunc: f.UnstructuredClientForMapping,
clientsetFunc: f.ClientSet,
labelSelector: options.Selector,
visitedUids: visitedUids,
cascade: options.Cascade,
dryRun: dryRun,
gracePeriod: options.GracePeriod,
out: out,
}
namespacedRESTMappings, nonNamespacedRESTMappings, err := getRESTMappings(mapper, &(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(f, n, m, shortOutput, includeUninitialized); err != nil {
return fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err)
}
}
}
for _, m := range nonNamespacedRESTMappings {
if err := p.prune(f, metav1.NamespaceNone, m, shortOutput, includeUninitialized); err != nil {
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(mapper meta.RESTMapper, 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", "Ingress", true},
{"extensions", "v1beta1", "ReplicaSet", true},
{"apps", "v1beta1", "StatefulSet", true},
{"apps", "v1beta1", "Deployment", true},
}
}
for _, resource := range *pruneResources {
addedMapping, err := mapper.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.Interface, error)
visitedUids sets.String
labelSelector string
fieldSelector string
cascade bool
dryRun bool
gracePeriod int
out io.Writer
}
func (p *pruner) prune(f cmdutil.Factory, namespace string, mapping *meta.RESTMapping, shortOutput, includeUninitialized bool) error {
c, err := p.clientFunc(mapping)
if err != nil {
return err
}
objList, err := resource.NewHelper(c, mapping).List(
namespace,
mapping.GroupVersionKind.Version,
false,
&metav1.ListOptions{
LabelSelector: p.labelSelector,
FieldSelector: p.fieldSelector,
IncludeUninitialized: includeUninitialized,
},
)
if err != nil {
return err
}
objs, err := meta.ExtractList(objList)
if err != nil {
return err
}
for _, obj := range objs {
annots, err := mapping.MetadataAccessor.Annotations(obj)
if err != nil {
return err
}
if _, ok := annots[api.LastAppliedConfigAnnotation]; !ok {
// don't prune resources not created with apply
continue
}
uid, err := mapping.UID(obj)
if err != nil {
return err
}
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); err != nil {
return err
}
}
f.PrintSuccess(p.mapper, shortOutput, p.out, mapping.Resource, name, p.dryRun, "pruned")
}
return nil
}
func (p *pruner) delete(namespace, name string, mapping *meta.RESTMapping) error {
c, err := p.clientFunc(mapping)
if err != nil {
return err
}
return runDelete(namespace, name, mapping, c, nil, p.cascade, p.gracePeriod, p.clientsetFunc)
}
func runDelete(namespace, name string, mapping *meta.RESTMapping, c resource.RESTClient, helper *resource.Helper, cascade bool, gracePeriod int, clientsetFunc func() (internalclientset.Interface, error)) error {
if !cascade {
if helper == nil {
helper = resource.NewHelper(c, mapping)
}
return helper.Delete(namespace, name)
}
cs, err := clientsetFunc()
if err != nil {
return err
}
r, err := kubectl.ReaperFor(mapping.GroupVersionKind.GroupKind(), cs)
if err != nil {
if _, ok := err.(*kubectl.NoSuchReaperError); !ok {
return err
}
return resource.NewHelper(c, mapping).Delete(namespace, name)
}
var options *metav1.DeleteOptions
if gracePeriod >= 0 {
options = metav1.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 {
c, err := p.clientFunc(p.mapping)
if err != nil {
return err
}
return runDelete(namespace, name, p.mapping, c, p.helper, p.cascade, p.gracePeriod, p.clientsetFunc)
}
type patcher struct {
encoder runtime.Encoder
decoder runtime.Decoder
mapping *meta.RESTMapping
helper *resource.Helper
clientFunc resource.ClientMapperFunc
clientsetFunc func() (internalclientset.Interface, error)
overwrite bool
backOff clockwork.Clock
force bool
cascade bool
timeout time.Duration
gracePeriod int
openapiSchema openapi.Resources
}
func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
// Serialize the current configuration of the object from the server.
current, err := runtime.Encode(p.encoder, obj)
if err != nil {
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("serializing current configuration from:\n%v\nfor:", obj), source, err)
}
// Retrieve the original configuration of the object from the annotation.
original, err := kubectl.GetOriginalConfiguration(p.mapping, obj)
if err != nil {
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("retrieving original configuration from:\n%v\nfor:", obj), source, err)
}
var patchType types.PatchType
var patch []byte
var lookupPatchMeta strategicpatch.LookupPatchMeta
var schema oapi.Schema
createPatchErrFormat := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
// 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 := scheme.Scheme.New(p.mapping.GroupVersionKind)
switch {
case runtime.IsNotRegisteredError(err):
// fall back to generic JSON merge patch
patchType = types.MergePatchType
preconditions := []mergepatch.PreconditionFunc{mergepatch.RequireKeyUnchanged("apiVersion"),
mergepatch.RequireKeyUnchanged("kind"), mergepatch.RequireMetadataKeyUnchanged("name")}
patch, err = jsonmergepatch.CreateThreeWayJSONMergePatch(original, modified, current, preconditions...)
if err != nil {
if mergepatch.IsPreconditionFailed(err) {
return nil, nil, fmt.Errorf("%s", "At least one of apiVersion, kind and name was changed")
}
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
}
case err != nil:
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("getting instance of versioned object for %v:", p.mapping.GroupVersionKind), source, err)
case err == nil:
// Compute a three way strategic merge patch to send to server.
patchType = types.StrategicMergePatchType
// Try to use openapi first if the openapi spec is available and can successfully calculate the patch.
// Otherwise, fall back to baked-in types.
if p.openapiSchema != nil {
if schema = p.openapiSchema.LookupResource(p.mapping.GroupVersionKind); schema != nil {
lookupPatchMeta = strategicpatch.PatchMetaFromOpenAPI{Schema: schema}
if openapiPatch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.overwrite); err != nil {
fmt.Fprintf(errOut, "warning: error calculating patch from openapi spec: %v\n", err)
} else {
patchType = types.StrategicMergePatchType
patch = openapiPatch
}
}
}
if patch == nil {
lookupPatchMeta, err = strategicpatch.NewPatchMetaFromStruct(versionedObject)
if err != nil {
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
}
patch, err = strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.overwrite)
if err != nil {
return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
}
}
}
if string(patch) == "{}" {
return patch, obj, nil
}
patchedObj, err := p.helper.Patch(namespace, name, patchType, patch)
return patch, patchedObj, err
}
func (p *patcher) patch(current runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
var getErr error
patchBytes, patchObject, err := p.patchSimple(current, modified, source, namespace, name, errOut)
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, nil, getErr
}
patchBytes, patchObject, err = p.patchSimple(current, modified, source, namespace, name, errOut)
}
if err != nil && p.force {
patchBytes, patchObject, err = p.deleteAndCreate(modified, namespace, name)
}
return patchBytes, patchObject, err
}
func (p *patcher) deleteAndCreate(modified []byte, namespace, name string) ([]byte, runtime.Object, error) {
err := p.delete(namespace, name)
if err != nil {
return modified, nil, 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, nil, err
}
versionedObject, _, err := p.decoder.Decode(modified, nil, nil)
if err != nil {
return modified, nil, err
}
createdObject, err := p.helper.Create(namespace, true, versionedObject)
return modified, createdObject, err
}

View File

@ -0,0 +1,105 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"io"
"runtime"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
"k8s.io/kubernetes/pkg/printers"
)
var (
applyEditLastAppliedLong = templates.LongDesc(`
Edit the latest last-applied-configuration annotations of resources from the default editor.
The edit-last-applied 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 default format is YAML. To edit in JSON, specify "-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.`)
applyEditLastAppliedExample = templates.Examples(`
# Edit the last-applied-configuration annotations by type/name in YAML.
kubectl apply edit-last-applied deployment/nginx
# Edit the last-applied-configuration annotations by file in JSON.
kubectl apply edit-last-applied -f deploy.yaml -o json`)
)
func NewCmdApplyEditLastApplied(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := &editor.EditOptions{
EditMode: editor.ApplyEditMode,
}
// retrieve a list of handled resources from printer as valid args
validArgs, argAliases := []string{}, []string{}
p, err := f.Printer(nil, printers.PrintOptions{
ColumnLabels: []string{},
})
cmdutil.CheckErr(err)
if p != nil {
validArgs = p.HandledResources()
argAliases = kubectl.ResourceAliases(validArgs)
}
cmd := &cobra.Command{
Use: "edit-last-applied (RESOURCE/NAME | -f FILENAME)",
Short: "Edit latest last-applied-configuration annotations of a resource/object",
Long: applyEditLastAppliedLong,
Example: applyEditLastAppliedExample,
Run: func(cmd *cobra.Command, args []string) {
options.ChangeCause = f.Command(cmd, false)
if err := options.Complete(f, out, errOut, args, cmd); err != nil {
cmdutil.CheckErr(err)
}
if err := options.Run(); err != nil {
cmdutil.CheckErr(err)
}
},
ValidArgs: validArgs,
ArgAliases: argAliases,
}
usage := "to use to edit the resource"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.Flags().StringVarP(&options.Output, "output", "o", "yaml", "Output format. One of: yaml|json.")
cmd.Flags().BoolVar(&options.WindowsLineEndings, "windows-line-endings", runtime.GOOS == "windows",
"Defaults to the line ending native to your platform.")
cmdutil.AddRecordVarFlag(cmd, &options.Record)
cmdutil.AddIncludeUninitializedFlag(cmd)
return cmd
}

View File

@ -0,0 +1,235 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"encoding/json"
"fmt"
"io"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
apijson "k8s.io/apimachinery/pkg/util/json"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
type SetLastAppliedOptions struct {
FilenameOptions resource.FilenameOptions
Selector string
InfoList []*resource.Info
Mapper meta.RESTMapper
Typer runtime.ObjectTyper
Namespace string
EnforceNamespace bool
DryRun bool
ShortOutput bool
CreateAnnotation bool
Output string
Codec runtime.Encoder
PatchBufferList []PatchBuffer
Factory cmdutil.Factory
Out io.Writer
ErrOut io.Writer
}
type PatchBuffer struct {
Patch []byte
PatchType types.PatchType
}
var (
applySetLastAppliedLong = templates.LongDesc(i18n.T(`
Set the latest last-applied-configuration annotations by setting it to match the contents of a file.
This results in the last-applied-configuration being updated as though 'kubectl apply -f <file>' was run,
without updating any other parts of the object.`))
applySetLastAppliedExample = templates.Examples(i18n.T(`
# Set the last-applied-configuration of a resource to match the contents of a file.
kubectl apply set-last-applied -f deploy.yaml
# Execute set-last-applied against each configuration file in a directory.
kubectl apply set-last-applied -f path/
# Set the last-applied-configuration of a resource to match the contents of a file, will create the annotation if it does not already exist.
kubectl apply set-last-applied -f deploy.yaml --create-annotation=true
`))
)
func NewCmdApplySetLastApplied(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
options := &SetLastAppliedOptions{Out: out, ErrOut: err}
cmd := &cobra.Command{
Use: "set-last-applied -f FILENAME",
Short: i18n.T("Set the last-applied-configuration annotation on a live object to match the contents of a file."),
Long: applySetLastAppliedLong,
Example: applySetLastAppliedExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd))
cmdutil.CheckErr(options.Validate(f, cmd))
cmdutil.CheckErr(options.RunSetLastApplied(f, cmd))
},
}
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().BoolVar(&options.CreateAnnotation, "create-annotation", false, "Will create 'last-applied-configuration' annotations if current objects doesn't have one")
usage := "that contains the last-applied-configuration annotations"
kubectl.AddJsonFilenameFlag(cmd, &options.FilenameOptions.Filenames, "Filename, directory, or URL to files "+usage)
return cmd
}
func (o *SetLastAppliedOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run")
o.Output = cmdutil.GetFlagString(cmd, "output")
o.ShortOutput = o.Output == "name"
o.Codec = f.JSONEncoder()
var err error
o.Mapper, o.Typer = f.Object()
if err != nil {
return err
}
o.Namespace, o.EnforceNamespace, err = f.DefaultNamespace()
return err
}
func (o *SetLastAppliedOptions) Validate(f cmdutil.Factory, cmd *cobra.Command) error {
r := f.NewBuilder().
Unstructured().
NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(o.EnforceNamespace, &o.FilenameOptions).
Flatten().
Do()
err := r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
patchBuf, diffBuf, patchType, err := editor.GetApplyPatch(info.Object, o.Codec)
if err != nil {
return err
}
// Verify the object exists in the cluster before trying to patch it.
if err := info.Get(); err != nil {
if errors.IsNotFound(err) {
return err
} else {
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err)
}
}
originalBuf, err := kubectl.GetOriginalConfiguration(info.Mapping, info.Object)
if err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err)
}
if originalBuf == nil && !o.CreateAnnotation {
return cmdutil.UsageErrorf(cmd, "no last-applied-configuration annotation found on resource: %s, to create the annotation, run the command with --create-annotation", info.Name)
}
//only add to PatchBufferList when changed
if !bytes.Equal(cmdutil.StripComments(originalBuf), cmdutil.StripComments(diffBuf)) {
p := PatchBuffer{Patch: patchBuf, PatchType: patchType}
o.PatchBufferList = append(o.PatchBufferList, p)
o.InfoList = append(o.InfoList, info)
} else {
fmt.Fprintf(o.Out, "set-last-applied %s: no changes required.\n", info.Name)
}
return nil
})
return err
}
func (o *SetLastAppliedOptions) RunSetLastApplied(f cmdutil.Factory, cmd *cobra.Command) error {
for i, patch := range o.PatchBufferList {
info := o.InfoList[i]
if !o.DryRun {
mapping := info.ResourceMapping()
client, err := f.UnstructuredClientForMapping(mapping)
if err != nil {
return err
}
helper := resource.NewHelper(client, mapping)
patchedObj, err := helper.Patch(o.Namespace, info.Name, patch.PatchType, patch.Patch)
if err != nil {
return err
}
if len(o.Output) > 0 && !o.ShortOutput {
info.Refresh(patchedObj, false)
return f.PrintResourceInfoForCommand(cmd, info, o.Out)
}
f.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, o.DryRun, "configured")
} else {
err := o.formatPrinter(o.Output, patch.Patch, o.Out)
if err != nil {
return err
}
f.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, o.DryRun, "configured")
}
}
return nil
}
func (o *SetLastAppliedOptions) formatPrinter(output string, buf []byte, w io.Writer) error {
yamlOutput, err := yaml.JSONToYAML(buf)
if err != nil {
return err
}
switch output {
case "json":
jsonBuffer := &bytes.Buffer{}
err = json.Indent(jsonBuffer, buf, "", " ")
if err != nil {
return err
}
fmt.Fprintf(w, "%s\n", jsonBuffer.String())
case "yaml":
fmt.Fprintf(w, "%s\n", string(yamlOutput))
}
return nil
}
func (o *SetLastAppliedOptions) getPatch(info *resource.Info) ([]byte, []byte, error) {
objMap := map[string]map[string]map[string]string{}
metadataMap := map[string]map[string]string{}
annotationsMap := map[string]string{}
localFile, err := runtime.Encode(o.Codec, info.Object)
if err != nil {
return nil, localFile, err
}
annotationsMap[api.LastAppliedConfigAnnotation] = string(localFile)
metadataMap["annotations"] = annotationsMap
objMap["metadata"] = metadataMap
jsonString, err := apijson.Marshal(objMap)
return jsonString, localFile, err
}

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,167 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"encoding/json"
"fmt"
"io"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
type ViewLastAppliedOptions struct {
FilenameOptions resource.FilenameOptions
Selector string
LastAppliedConfigurationList []string
OutputFormat string
All bool
Factory cmdutil.Factory
Out io.Writer
ErrOut io.Writer
}
var (
applyViewLastAppliedLong = templates.LongDesc(i18n.T(`
View the latest last-applied-configuration annotations by type/name or file.
The default output will be printed to stdout in YAML format. One can use -o option
to change output format.`))
applyViewLastAppliedExample = templates.Examples(i18n.T(`
# View the last-applied-configuration annotations by type/name in YAML.
kubectl apply view-last-applied deployment/nginx
# View the last-applied-configuration annotations by file in JSON
kubectl apply view-last-applied -f deploy.yaml -o json`))
)
func NewCmdApplyViewLastApplied(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
options := &ViewLastAppliedOptions{Out: out, ErrOut: err}
cmd := &cobra.Command{
Use: "view-last-applied (TYPE [NAME | -l label] | TYPE/NAME | -f FILENAME)",
Short: i18n.T("View latest last-applied-configuration annotations of a resource/object"),
Long: applyViewLastAppliedLong,
Example: applyViewLastAppliedExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.ValidateOutputArgs(cmd))
cmdutil.CheckErr(options.Complete(f, args))
cmdutil.CheckErr(options.Validate(cmd))
cmdutil.CheckErr(options.RunApplyViewLastApplied())
},
}
cmd.Flags().StringP("output", "o", "", "Output format. Must be one of yaml|json")
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVar(&options.All, "all", false, "Select all resources in the namespace of the specified resource types")
usage := "that contains the last-applied-configuration annotations"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
return cmd
}
func (o *ViewLastAppliedOptions) Complete(f cmdutil.Factory, args []string) error {
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
r := f.NewBuilder().
Unstructured().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
ResourceTypeOrNameArgs(enforceNamespace, args...).
SelectAllParam(o.All).
LabelSelectorParam(o.Selector).
Latest().
Flatten().
Do()
err = r.Err()
if err != nil {
return err
}
err = r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
configString, err := kubectl.GetOriginalConfiguration(info.Mapping, info.Object)
if err != nil {
return err
}
if configString == nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("no last-applied-configuration annotation found on resource: %s\n", info.Name), info.Source, err)
}
o.LastAppliedConfigurationList = append(o.LastAppliedConfigurationList, string(configString))
return nil
})
if err != nil {
return err
}
return nil
}
func (o *ViewLastAppliedOptions) Validate(cmd *cobra.Command) error {
return nil
}
func (o *ViewLastAppliedOptions) RunApplyViewLastApplied() error {
for _, str := range o.LastAppliedConfigurationList {
switch o.OutputFormat {
case "json":
jsonBuffer := &bytes.Buffer{}
err := json.Indent(jsonBuffer, []byte(str), "", " ")
if err != nil {
return err
}
fmt.Fprint(o.Out, string(jsonBuffer.Bytes()))
case "yaml":
yamlOutput, err := yaml.JSONToYAML([]byte(str))
if err != nil {
return err
}
fmt.Fprint(o.Out, string(yamlOutput))
}
}
return nil
}
func (o *ViewLastAppliedOptions) ValidateOutputArgs(cmd *cobra.Command) error {
format := cmdutil.GetFlagString(cmd, "output")
switch format {
case "json":
o.OutputFormat = "json"
return nil
// If flag -o is not specified, use yaml as default
case "yaml", "":
o.OutputFormat = "yaml"
return nil
default:
return cmdutil.UsageErrorf(cmd, "Unexpected -o output mode: %s, the flag 'output' must be one of yaml|json", format)
}
}

326
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach.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 (
"errors"
"fmt"
"io"
"net/url"
"time"
"github.com/golang/glog"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
var (
attachExample = templates.Examples(i18n.T(`
# 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
# Get output from the first pod of a ReplicaSet named nginx
kubectl attach rs/nginx
`))
)
const (
defaultPodAttachTimeout = 60 * time.Second
defaultPodLogsTimeout = 20 * time.Second
)
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 | TYPE/NAME) -c CONTAINER",
Short: i18n.T("Attach to a running container"),
Long: "Attach to a process that is already running inside an existing container.",
Example: attachExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.Run())
},
}
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodAttachTimeout)
cmd.Flags().StringVarP(&options.ContainerName, "container", "c", "", "Container name. If omitted, the first container in the pod will be chosen")
cmd.Flags().BoolVarP(&options.Stdin, "stdin", "i", false, "Pass stdin to the container")
cmd.Flags().BoolVarP(&options.TTY, "tty", "t", false, "Stdin is a TTY")
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 remotecommand.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 remotecommand.TerminalSizeQueue) error {
exec, err := remotecommand.NewSPDYExecutor(config, method, url)
if err != nil {
return err
}
return exec.Stream(remotecommand.StreamOptions{
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
GetPodTimeout time.Duration
Config *restclient.Config
}
// Complete verifies command line arguments and loads data from the command environment
func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []string) error {
if len(argsIn) == 0 {
return cmdutil.UsageErrorf(cmd, "at least 1 argument is required for attach")
}
if len(argsIn) > 2 {
return cmdutil.UsageErrorf(cmd, "expected POD, TYPE/NAME, or TYPE NAME, (at most 2 arguments) saw %d: %v", len(argsIn), argsIn)
}
namespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}
p.GetPodTimeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
if err != nil {
return cmdutil.UsageErrorf(cmd, err.Error())
}
builder := f.NewBuilder().
Internal().
NamespaceParam(namespace).DefaultNamespace()
switch len(argsIn) {
case 1:
builder.ResourceNames("pods", argsIn[0])
case 2:
builder.ResourceNames(argsIn[0], argsIn[1])
}
obj, err := builder.Do().Object()
if err != nil {
return err
}
attachablePod, err := f.AttachablePodForObject(obj, p.GetPodTimeout)
if err != nil {
return err
}
p.PodName = attachablePod.Name
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, errors.New("pod name must be specified"))
}
if p.Out == nil || p.Err == nil {
allErrs = append(allErrs, errors.New("both output and error output must be provided"))
}
if p.Attach == nil || p.PodClient == nil || p.Config == nil {
allErrs = append(allErrs, errors.New("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 remotecommand.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,
}, legacyscheme.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
}

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

@ -0,0 +1,382 @@
/*
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"
"time"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
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 remotecommand.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
timeout time.Duration
obj runtime.Object
}{
{
p: &AttachOptions{},
expectError: true,
name: "empty",
timeout: 1,
},
{
p: &AttachOptions{},
args: []string{"one", "two", "three"},
expectError: true,
name: "too many args",
timeout: 2,
},
{
p: &AttachOptions{},
args: []string{"foo"},
expectedPod: "foo",
name: "no container, no flags",
obj: attachPod(),
timeout: defaultPodLogsTimeout,
},
{
p: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
args: []string{"foo"},
expectedPod: "foo",
expectedContainer: "bar",
name: "container in flag",
obj: attachPod(),
timeout: 10000000,
},
{
p: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "initfoo"}},
args: []string{"foo"},
expectedPod: "foo",
expectedContainer: "initfoo",
name: "init container in flag",
obj: attachPod(),
timeout: 30,
},
{
p: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
args: []string{"foo", "-c", "wrong"},
expectError: true,
name: "non-existing container in flag",
obj: attachPod(),
timeout: 10,
},
{
p: &AttachOptions{},
args: []string{"pods", "foo"},
expectedPod: "foo",
name: "no container, no flags, pods and name",
obj: attachPod(),
timeout: 10000,
},
{
p: &AttachOptions{},
args: []string{"pod/foo"},
expectedPod: "foo",
name: "no container, no flags, pod/name",
obj: attachPod(),
timeout: 1,
},
{
p: &AttachOptions{},
args: []string{"pod/foo"},
expectedPod: "foo",
name: "invalid get pod timeout value",
obj: attachPod(),
expectError: true,
timeout: 0,
},
}
for _, test := range tests {
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion,
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
if test.obj != nil {
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, test.obj)}, nil
}
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
cmd := &cobra.Command{}
options := test.p
cmdutil.AddPodRunningTimeoutFlag(cmd, test.timeout)
err := options.Complete(f, cmd, test.args)
if test.expectError && err == nil {
t.Errorf("%s: unexpected non-error", test.name)
}
if !test.expectError && err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
}
if err != nil {
continue
}
if options.PodName != test.expectedPod {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedPod, options.PodName)
}
if options.ContainerName != test.expectedContainer {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedContainer, options.ContainerName)
}
}
}
func TestAttach(t *testing.T) {
version := legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version
tests := []struct {
name, version, podPath, fetchPodPath, attachPath, container string
pod *api.Pod
remoteAttachErr bool
exepctedErr string
}{
{
name: "pod attach",
version: version,
podPath: "/api/" + version + "/namespaces/test/pods/foo",
fetchPodPath: "/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",
fetchPodPath: "/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",
fetchPodPath: "/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{
GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion,
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
case p == test.fetchPodPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
// Ensures no GET is performed when deleting by name
t.Errorf("%s: unexpected request: %s %#v\n%#v", p, req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
bufOut := bytes.NewBuffer([]byte{})
bufErr := bytes.NewBuffer([]byte{})
bufIn := bytes.NewBuffer([]byte{})
remoteAttach := &fakeRemoteAttach{}
if test.remoteAttachErr {
remoteAttach.err = fmt.Errorf("attach error")
}
params := &AttachOptions{
StreamOptions: StreamOptions{
ContainerName: test.container,
In: bufIn,
Out: bufOut,
Err: bufErr,
},
Attach: remoteAttach,
GetPodTimeout: 1000,
}
cmd := &cobra.Command{}
cmdutil.AddPodRunningTimeoutFlag(cmd, 1000)
if err := params.Complete(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 := legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version
tests := []struct {
name, container, version, podPath, fetchPodPath, expectedErr, expectedOut string
pod *api.Pod
stdin, tty bool
}{
{
name: "fallback tty if not supported",
version: version,
podPath: "/api/" + version + "/namespaces/test/pods/foo",
fetchPodPath: "/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{
GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion,
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
case p == test.fetchPodPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
bufOut := bytes.NewBuffer([]byte{})
bufErr := bytes.NewBuffer([]byte{})
bufIn := bytes.NewBuffer([]byte{})
ex := &fakeRemoteAttach{}
params := &AttachOptions{
StreamOptions: StreamOptions{
ContainerName: test.container,
In: bufIn,
Out: bufOut,
Err: bufErr,
Stdin: test.stdin,
TTY: test.tty,
},
Attach: ex,
GetPodTimeout: 1000,
}
cmd := &cobra.Command{}
cmdutil.AddPodRunningTimeoutFlag(cmd, 1000)
if err := params.Complete(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: metav1.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,
},
}
}

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

@ -0,0 +1,61 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"auth.go",
"cani.go",
"reconcile.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/auth",
visibility = [
"//build/visible_to:pkg_kubectl_cmd_auth_CONSUMERS",
],
deps = [
"//pkg/apis/authorization:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library",
"//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/registry/rbac/reconciliation:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = [
"//build/visible_to:pkg_kubectl_cmd_auth_CONSUMERS",
],
)
go_test(
name = "go_default_test",
srcs = ["cani_test.go"],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/auth",
library = ":go_default_library",
deps = [
"//pkg/kubectl/cmd/testing:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
],
)

40
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/auth/auth.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
/*
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 auth
import (
"io"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
func NewCmdAuth(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
// Parent command to which all subcommands are added.
cmds := &cobra.Command{
Use: "auth",
Short: "Inspect authorization",
Long: `Inspect authorization`,
Run: cmdutil.DefaultSubCommandRun(errOut),
}
cmds.AddCommand(NewCmdCanI(f, out, errOut))
cmds.AddCommand(NewCmdReconcile(f, out, errOut))
return cmds
}

240
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/auth/cani.go generated vendored Normal file
View File

@ -0,0 +1,240 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
internalauthorizationclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
// CanIOptions 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 CanIOptions struct {
AllNamespaces bool
Quiet bool
Namespace string
SelfSARClient internalauthorizationclient.SelfSubjectAccessReviewsGetter
Verb string
Resource schema.GroupVersionResource
NonResourceURL string
Subresource string
ResourceName string
Out io.Writer
Err io.Writer
}
var (
canILong = templates.LongDesc(`
Check whether an action is allowed.
VERB is a logical Kubernetes API verb like 'get', 'list', 'watch', 'delete', etc.
TYPE is a Kubernetes resource. Shortcuts and groups will be resolved.
NONRESOURCEURL is a partial URL starts with "/".
NAME is the name of a particular Kubernetes resource.`)
canIExample = templates.Examples(`
# Check to see if I can create pods in any namespace
kubectl auth can-i create pods --all-namespaces
# Check to see if I can list deployments in my current namespace
kubectl auth can-i list deployments.extensions
# Check to see if I can do everything in my current namespace ("*" means all)
kubectl auth can-i '*' '*'
# Check to see if I can get the job named "bar" in namespace "foo"
kubectl auth can-i list jobs.batch/bar -n foo
# Check to see if I can read pod logs
kubectl auth can-i get pods --subresource=log
# Check to see if I can access the URL /logs/
kubectl auth can-i get /logs/`)
)
func NewCmdCanI(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
o := &CanIOptions{
Out: out,
Err: err,
}
cmd := &cobra.Command{
Use: "can-i VERB [TYPE | TYPE/NAME | NONRESOURCEURL]",
Short: "Check whether an action is allowed",
Long: canILong,
Example: canIExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, args))
cmdutil.CheckErr(o.Validate())
allowed, err := o.RunAccessCheck()
if err == nil {
if o.Quiet && !allowed {
os.Exit(1)
}
}
cmdutil.CheckErr(err)
},
}
cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", o.AllNamespaces, "If true, check the specified action in all namespaces.")
cmd.Flags().BoolVarP(&o.Quiet, "quiet", "q", o.Quiet, "If true, suppress output and just return the exit code.")
cmd.Flags().StringVar(&o.Subresource, "subresource", "", "SubResource such as pod/log or deployment/scale")
return cmd
}
func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
if o.Quiet {
o.Out = ioutil.Discard
}
switch len(args) {
case 2:
o.Verb = args[0]
if strings.HasPrefix(args[1], "/") {
o.NonResourceURL = args[1]
break
}
resourceTokens := strings.SplitN(args[1], "/", 2)
restMapper, _ := f.Object()
o.Resource = o.resourceFor(restMapper, resourceTokens[0])
if len(resourceTokens) > 1 {
o.ResourceName = resourceTokens[1]
}
default:
return errors.New("you must specify two or three arguments: verb, resource, and optional resourceName")
}
var err error
client, err := f.ClientSet()
if err != nil {
return err
}
o.SelfSARClient = client.Authorization()
o.Namespace = ""
if !o.AllNamespaces {
o.Namespace, _, err = f.DefaultNamespace()
if err != nil {
return err
}
}
return nil
}
func (o *CanIOptions) Validate() error {
if o.NonResourceURL != "" {
if o.Subresource != "" {
return fmt.Errorf("--subresource can not be used with NonResourceURL")
}
if o.Resource != (schema.GroupVersionResource{}) || o.ResourceName != "" {
return fmt.Errorf("NonResourceURL and ResourceName can not specified together")
}
}
return nil
}
func (o *CanIOptions) RunAccessCheck() (bool, error) {
var sar *authorizationapi.SelfSubjectAccessReview
if o.NonResourceURL == "" {
sar = &authorizationapi.SelfSubjectAccessReview{
Spec: authorizationapi.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationapi.ResourceAttributes{
Namespace: o.Namespace,
Verb: o.Verb,
Group: o.Resource.Group,
Resource: o.Resource.Resource,
Subresource: o.Subresource,
Name: o.ResourceName,
},
},
}
} else {
sar = &authorizationapi.SelfSubjectAccessReview{
Spec: authorizationapi.SelfSubjectAccessReviewSpec{
NonResourceAttributes: &authorizationapi.NonResourceAttributes{
Verb: o.Verb,
Path: o.NonResourceURL,
},
},
}
}
response, err := o.SelfSARClient.SelfSubjectAccessReviews().Create(sar)
if err != nil {
return false, err
}
if response.Status.Allowed {
fmt.Fprintln(o.Out, "yes")
} else {
fmt.Fprint(o.Out, "no")
if len(response.Status.Reason) > 0 {
fmt.Fprintf(o.Out, " - %v", response.Status.Reason)
}
if len(response.Status.EvaluationError) > 0 {
fmt.Fprintf(o.Out, " - %v", response.Status.EvaluationError)
}
fmt.Fprintln(o.Out)
}
return response.Status.Allowed, nil
}
func (o *CanIOptions) resourceFor(mapper meta.RESTMapper, resourceArg string) schema.GroupVersionResource {
if resourceArg == "*" {
return schema.GroupVersionResource{Resource: resourceArg}
}
fullySpecifiedGVR, groupResource := schema.ParseResourceArg(strings.ToLower(resourceArg))
gvr := schema.GroupVersionResource{}
if fullySpecifiedGVR != nil {
gvr, _ = mapper.ResourceFor(*fullySpecifiedGVR)
}
if gvr.Empty() {
var err error
gvr, err = mapper.ResourceFor(groupResource.WithVersion(""))
if err != nil {
if len(groupResource.Group) == 0 {
fmt.Fprintf(o.Err, "Warning: the server doesn't have a resource type '%s'\n", groupResource.Resource)
} else {
fmt.Fprintf(o.Err, "Warning: the server doesn't have a resource type '%s' in group '%s'\n", groupResource.Resource, groupResource.Group)
}
return schema.GroupVersionResource{Resource: resourceArg}
}
}
return gvr
}

View File

@ -0,0 +1,177 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
func TestRunAccessCheck(t *testing.T) {
tests := []struct {
name string
o *CanIOptions
args []string
allowed bool
serverErr error
expectedBodyStrings []string
}{
{
name: "restmapping for args",
o: &CanIOptions{},
args: []string{"get", "replicaset"},
allowed: true,
expectedBodyStrings: []string{
`{"resourceAttributes":{"namespace":"test","verb":"get","group":"extensions","resource":"replicasets"}}`,
},
},
{
name: "simple success",
o: &CanIOptions{},
args: []string{"get", "deployments.extensions/foo"},
allowed: true,
expectedBodyStrings: []string{
`{"resourceAttributes":{"namespace":"test","verb":"get","group":"extensions","resource":"deployments","name":"foo"}}`,
},
},
{
name: "all namespaces",
o: &CanIOptions{
AllNamespaces: true,
},
args: []string{"get", "deployments.extensions/foo"},
allowed: true,
expectedBodyStrings: []string{
`{"resourceAttributes":{"verb":"get","group":"extensions","resource":"deployments","name":"foo"}}`,
},
},
{
name: "disallowed",
o: &CanIOptions{
AllNamespaces: true,
},
args: []string{"get", "deployments.extensions/foo"},
allowed: false,
expectedBodyStrings: []string{
`{"resourceAttributes":{"verb":"get","group":"extensions","resource":"deployments","name":"foo"}}`,
},
},
{
name: "forcedError",
o: &CanIOptions{
AllNamespaces: true,
},
args: []string{"get", "deployments.extensions/foo"},
allowed: false,
serverErr: fmt.Errorf("forcedError"),
expectedBodyStrings: []string{
`{"resourceAttributes":{"verb":"get","group":"extensions","resource":"deployments","name":"foo"}}`,
},
},
{
name: "sub resource",
o: &CanIOptions{
AllNamespaces: true,
Subresource: "log",
},
args: []string{"get", "pods"},
allowed: true,
expectedBodyStrings: []string{
`{"resourceAttributes":{"verb":"get","resource":"pods","subresource":"log"}}`,
},
},
{
name: "nonResourceURL",
o: &CanIOptions{},
args: []string{"get", "/logs"},
allowed: true,
expectedBodyStrings: []string{
`{"nonResourceAttributes":{"path":"/logs","verb":"get"}}`,
},
},
}
for _, test := range tests {
test.o.Out = ioutil.Discard
test.o.Err = ioutil.Discard
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
expectPath := "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews"
if req.URL.Path != expectPath {
t.Errorf("%s: expected %v, got %v", test.name, expectPath, req.URL.Path)
return nil, nil
}
bodyBits, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Errorf("%s: %v", test.name, err)
return nil, nil
}
body := string(bodyBits)
for _, expectedBody := range test.expectedBodyStrings {
if !strings.Contains(body, expectedBody) {
t.Errorf("%s expecting %s in %s", test.name, expectedBody, body)
}
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewBufferString(
fmt.Sprintf(`{"kind":"SelfSubjectAccessReview","apiVersion":"authorization.k8s.io/v1","status":{"allowed":%v}}`, test.allowed),
)),
},
test.serverErr
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}}
if err := test.o.Complete(f, test.args); err != nil {
t.Errorf("%s: %v", test.name, err)
continue
}
actualAllowed, err := test.o.RunAccessCheck()
switch {
case test.serverErr == nil && err == nil:
// pass
case err != nil && test.serverErr != nil && strings.Contains(err.Error(), test.serverErr.Error()):
// pass
default:
t.Errorf("%s: expected %v, got %v", test.name, test.serverErr, err)
continue
}
if actualAllowed != test.allowed {
t.Errorf("%s: expected %v, got %v", test.name, test.allowed, actualAllowed)
continue
}
}
}

View File

@ -0,0 +1,237 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"errors"
"io"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/apis/rbac"
internalcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
internalrbacclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
"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/registry/rbac/reconciliation"
)
// ReconcileOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
// referencing the cmd.Flags()
type ReconcileOptions struct {
Visitor resource.Visitor
RBACClient internalrbacclient.RbacInterface
NamespaceClient internalcoreclient.NamespaceInterface
Print func(*resource.Info) error
Out io.Writer
Err io.Writer
}
var (
reconcileLong = templates.LongDesc(`
Reconciles rules for RBAC Role, RoleBinding, ClusterRole, and ClusterRole binding objects.
This is preferred to 'apply' for RBAC resources so that proper rule coverage checks are done.`)
reconcileExample = templates.Examples(`
# Reconcile rbac resources from a file
kubectl auth reconcile -f my-rbac-rules.yaml`)
)
func NewCmdReconcile(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
fileOptions := &resource.FilenameOptions{}
o := &ReconcileOptions{
Out: out,
Err: err,
}
cmd := &cobra.Command{
Use: "reconcile -f FILENAME",
Short: "Reconciles rules for RBAC Role, RoleBinding, ClusterRole, and ClusterRole binding objects",
Long: reconcileLong,
Example: reconcileExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(cmd, f, args, fileOptions))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.RunReconcile())
},
}
cmdutil.AddPrinterFlags(cmd)
usage := "identifying the resource to reconcile."
cmdutil.AddFilenameOptionFlags(cmd, fileOptions, usage)
cmd.MarkFlagRequired("filename")
return cmd
}
func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args []string, options *resource.FilenameOptions) error {
if len(args) > 0 {
return errors.New("no arguments are allowed")
}
namespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
r := f.NewBuilder().
Internal().
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
Flatten().
Do()
if err := r.Err(); err != nil {
return err
}
o.Visitor = r
client, err := f.ClientSet()
if err != nil {
return err
}
o.RBACClient = client.Rbac()
o.NamespaceClient = client.Core().Namespaces()
mapper, _ := f.Object()
dryRun := false
output := cmdutil.GetFlagString(cmd, "output")
shortOutput := output == "name"
o.Print = func(info *resource.Info) error {
if len(output) > 0 && !shortOutput {
return f.PrintResourceInfoForCommand(cmd, info, o.Out)
}
f.PrintSuccess(mapper, shortOutput, o.Out, info.Mapping.Resource, info.Name, dryRun, "reconciled")
return nil
}
return nil
}
func (o *ReconcileOptions) Validate() error {
if o.Visitor == nil {
return errors.New("ReconcileOptions.Visitor must be set")
}
if o.RBACClient == nil {
return errors.New("ReconcileOptions.RBACClient must be set")
}
if o.NamespaceClient == nil {
return errors.New("ReconcileOptions.NamespaceClient must be set")
}
if o.Print == nil {
return errors.New("ReconcileOptions.Print must be set")
}
if o.Out == nil {
return errors.New("ReconcileOptions.Out must be set")
}
if o.Err == nil {
return errors.New("ReconcileOptions.Err must be set")
}
return nil
}
func (o *ReconcileOptions) RunReconcile() error {
return o.Visitor.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
// shallowInfoCopy this is used to later twiddle the Object for printing
// we really need more straightforward printing options
shallowInfoCopy := *info
switch t := info.Object.(type) {
case *rbac.Role:
reconcileOptions := reconciliation.ReconcileRoleOptions{
Confirm: true,
RemoveExtraPermissions: false,
Role: reconciliation.RoleRuleOwner{Role: t},
Client: reconciliation.RoleModifier{
NamespaceClient: o.NamespaceClient,
Client: o.RBACClient,
},
}
result, err := reconcileOptions.Run()
if err != nil {
return err
}
shallowInfoCopy.Object = result.Role.GetObject()
o.Print(&shallowInfoCopy)
case *rbac.ClusterRole:
reconcileOptions := reconciliation.ReconcileRoleOptions{
Confirm: true,
RemoveExtraPermissions: false,
Role: reconciliation.ClusterRoleRuleOwner{ClusterRole: t},
Client: reconciliation.ClusterRoleModifier{
Client: o.RBACClient.ClusterRoles(),
},
}
result, err := reconcileOptions.Run()
if err != nil {
return err
}
shallowInfoCopy.Object = result.Role.GetObject()
o.Print(&shallowInfoCopy)
case *rbac.RoleBinding:
reconcileOptions := reconciliation.ReconcileRoleBindingOptions{
Confirm: true,
RemoveExtraSubjects: false,
RoleBinding: reconciliation.RoleBindingAdapter{RoleBinding: t},
Client: reconciliation.RoleBindingClientAdapter{
Client: o.RBACClient,
NamespaceClient: o.NamespaceClient,
},
}
result, err := reconcileOptions.Run()
if err != nil {
return err
}
shallowInfoCopy.Object = result.RoleBinding.GetObject()
o.Print(&shallowInfoCopy)
case *rbac.ClusterRoleBinding:
reconcileOptions := reconciliation.ReconcileRoleBindingOptions{
Confirm: true,
RemoveExtraSubjects: false,
RoleBinding: reconciliation.ClusterRoleBindingAdapter{ClusterRoleBinding: t},
Client: reconciliation.ClusterRoleBindingClientAdapter{
Client: o.RBACClient.ClusterRoleBindings(),
},
}
result, err := reconcileOptions.Run()
if err != nil {
return err
}
shallowInfoCopy.Object = result.RoleBinding.GetObject()
o.Print(&shallowInfoCopy)
default:
glog.V(1).Infof("skipping %#v", info.Object.GetObjectKind())
// skip ignored resources
}
return nil
})
}

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

@ -0,0 +1,205 @@
/*
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"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"github.com/spf13/cobra"
)
var (
autoscaleLong = templates.LongDesc(i18n.T(`
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(i18n.T(`
# Auto scale a deployment "foo", with the number of pods between 2 and 10, no 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: i18n.T("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", i18n.T("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", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
cmdutil.AddDryRunFlag(cmd)
usage := "identifying the resource to autoscale."
cmdutil.AddFilenameOptionFlags(cmd, options, usage)
cmdutil.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
}
r := f.NewBuilder().
Internal().
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.UsageErrorf(cmd, "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
}
mapper, typer := f.Object()
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(cmd, false)); err != nil {
return err
}
object = hpa.Object
}
if cmdutil.GetDryRunFlag(cmd) {
return f.PrintObject(cmd, false, 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, false, mapper, object, out)
}
f.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,199 @@
/*
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"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"github.com/spf13/cobra"
)
func NewCmdCertificate(f cmdutil.Factory, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "certificate SUBCOMMAND",
Short: i18n.T("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.IsFilenameSliceEmpty(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: i18n.T("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: i18n.T("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, _ := f.Object()
c, err := f.ClientSet()
if err != nil {
return err
}
r := f.NewBuilder().
Internal().
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++
f.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
}

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

@ -0,0 +1,143 @@
/*
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"
"strconv"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilnet "k8s.io/apimachinery/pkg/util/net"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
ct "github.com/daviddengcn/go-colortext"
"github.com/spf13/cobra"
)
var (
longDescr = templates.LongDesc(i18n.T(`
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'.`))
clusterinfoExample = templates.Examples(i18n.T(`
# 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",
Short: i18n.T("Display cluster info"),
Long: longDescr,
Example: clusterinfoExample,
Run: func(cmd *cobra.Command, args []string) {
err := RunClusterInfo(f, out, cmd)
cmdutil.CheckErr(err)
},
}
cmdutil.AddInclude3rdPartyFlags(cmd)
cmd.AddCommand(NewCmdClusterInfoDump(f, out))
return cmd
}
func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error {
client, err := f.ClientConfig()
if err != nil {
return err
}
printService(out, "Kubernetes master", client.Host)
cmdNamespace := cmdutil.GetFlagString(cmd, "namespace")
if cmdNamespace == "" {
cmdNamespace = metav1.NamespaceSystem
}
// TODO use generalized labels once they are implemented (#341)
b := f.NewBuilder().
Internal().
NamespaceParam(cmdNamespace).DefaultNamespace().
LabelSelectorParam("kubernetes.io/cluster-service=true").
ResourceTypeOrNameArgs(false, []string{"services"}...).
Latest()
err = 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 {
name := service.ObjectMeta.Name
if len(service.Spec.Ports) > 0 {
port := service.Spec.Ports[0]
// guess if the scheme is https
scheme := ""
if port.Name == "https" || port.Port == 443 {
scheme = "https"
}
// format is <scheme>:<service-name>:<service-port-name>
name = utilnet.JoinSchemeNamePort(scheme, service.ObjectMeta.Name, port.Name)
}
if len(client.GroupVersion.Group) == 0 {
link = client.Host + "/api/" + client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"
} else {
link = client.Host + "/api/" + client.GroupVersion.Group + "/" + client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"
}
}
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 err
// 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,233 @@
/*
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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
)
// 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: i18n.T("Dump lots of relevant info for debugging and diagnosis"),
Long: dumpLong,
Example: dumpExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(dumpClusterInfo(f, cmd, cmdOut))
},
}
cmd.Flags().String("output-directory", "", i18n.T("Where to output the files. If empty or '-' uses stdout, otherwise creates a directory hierarchy in that directory"))
cmd.Flags().StringSlice("namespaces", []string{}, "A comma separated list of namespaces to dump.")
cmd.Flags().Bool("all-namespaces", false, "If true, dump all namespaces. If true, --namespaces is ignored.")
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodLogsTimeout)
return cmd
}
var (
dumpLong = templates.LongDesc(i18n.T(`
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(i18n.T(`
# 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, out io.Writer) error {
timeout, err := cmdutil.GetPodRunningTimeoutFlag(cmd)
if err != nil {
return cmdutil.UsageErrorf(cmd, err.Error())
}
clientset, err := f.ClientSet()
if err != nil {
return err
}
printer := &printers.JSONPrinter{}
nodes, err := clientset.Core().Nodes().List(metav1.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(metav1.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{
metav1.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(metav1.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(metav1.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(metav1.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(metav1.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(metav1.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(metav1.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(metav1.ListOptions{})
if err != nil {
return err
}
if err := printer.PrintObj(pods, setupOutputWriter(cmd, out, path.Join(namespace, "pods.json"))); err != nil {
return err
}
printContainer := func(writer io.Writer, container api.Container, pod *api.Pod) {
writer.Write([]byte(fmt.Sprintf("==== START logs for container %s of pod %s/%s ====\n", container.Name, pod.Namespace, pod.Name)))
defer writer.Write([]byte(fmt.Sprintf("==== END logs for container %s of pod %s/%s ====\n", container.Name, pod.Namespace, pod.Name)))
request, err := f.LogsForObject(pod, &api.PodLogOptions{Container: container.Name}, timeout)
if err != nil {
// Print error and return.
writer.Write([]byte(fmt.Sprintf("Create log request error: %s\n", err.Error())))
return
}
data, err := request.DoRaw()
if err != nil {
// Print error and return.
writer.Write([]byte(fmt.Sprintf("Request log error: %s\n", err.Error())))
return
}
writer.Write(data)
}
for ix := range pods.Items {
pod := &pods.Items[ix]
containers := pod.Spec.Containers
writer := setupOutputWriter(cmd, out, path.Join(namespace, pod.Name, "logs.txt"))
for i := range containers {
printContainer(writer, containers[i], pod)
}
}
}
dir := cmdutil.GetFlagString(cmd, "output-directory")
if len(dir) == 0 {
dir = "standard output"
}
if dir != "-" {
fmt.Fprintf(out, "Cluster info dumped to %s\n", dir)
}
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)
}
}

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

@ -0,0 +1,379 @@
/*
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/apiserver/pkg/util/flag"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/auth"
cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config"
"k8s.io/kubernetes/pkg/kubectl/cmd/resource"
"k8s.io/kubernetes/pkg/kubectl/cmd/rollout"
"k8s.io/kubernetes/pkg/kubectl/cmd/set"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"github.com/golang/glog"
"github.com/spf13/cobra"
)
const (
bashCompletionFunc = `# call kubectl get $1,
__kubectl_override_flag_list=(--kubeconfig --cluster --user --context --namespace --server -n -s)
__kubectl_override_flags()
{
local ${__kubectl_override_flag_list[*]##*-} two_word_of of var
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 var in "${__kubectl_override_flag_list[@]##*-}"; do
if eval "test -n \"\$${var}\""; then
eval "echo \${${var}}"
fi
done
}
__kubectl_config_get_contexts()
{
__kubectl_parse_config "contexts"
}
__kubectl_config_get_clusters()
{
__kubectl_parse_config "clusters"
}
__kubectl_config_get_users()
{
__kubectl_parse_config "users"
}
# $1 has to be "contexts", "clusters" or "users"
__kubectl_parse_config()
{
local template kubectl_out
template="{{ range .$1 }}{{ .name }} {{ end }}"
if kubectl_out=$(kubectl config $(__kubectl_override_flags) -o template --template="${template}" view 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_namespace()
{
__kubectl_parse_get "namespace"
}
__kubectl_get_resource_pod()
{
__kubectl_parse_get "pod"
}
__kubectl_get_resource_rc()
{
__kubectl_parse_get "rc"
}
__kubectl_get_resource_node()
{
__kubectl_parse_get "node"
}
__kubectl_get_resource_clusterrole()
{
__kubectl_parse_get "clusterrole"
}
# $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_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
;;
kubectl_config_use-context | kubectl_config_rename-context)
__kubectl_config_get_contexts
return
;;
kubectl_config_delete-cluster)
__kubectl_config_get_clusters
return
;;
*)
;;
esac
}
`
)
var (
bash_completion_flags = map[string]string{
"namespace": "__kubectl_get_resource_namespace",
"context": "__kubectl_config_get_contexts",
"cluster": "__kubectl_config_get_clusters",
"user": "__kubectl_config_get_users",
}
)
// 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: i18n.T("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: bashCompletionFunc,
}
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, in, out, err),
deprecatedAlias("run-container", NewCmdRun(f, in, out, err)),
},
},
{
Message: "Basic Commands (Intermediate):",
Commands: []*cobra.Command{
resource.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, out, err),
auth.NewCmdAuth(f, out, err),
},
},
{
Message: "Advanced Commands:",
Commands: []*cobra.Command{
NewCmdApply("kubectl", 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(out, ""),
},
},
}
groups.Add(cmds)
filters := []string{"options"}
// Hide the "alpha" subcommand if there are no alpha commands in this build.
alpha := NewCmdAlpha(f, in, out, err)
if !alpha.HasSubCommands() {
filters = append(filters, alpha.Name())
}
templates.ActsAsRootCommand(cmds, filters, groups...)
for name, completion := range bash_completion_flags {
if cmds.Flag(name) != nil {
if cmds.Flag(name).Annotations == nil {
cmds.Flag(name).Annotations = map[string][]string{}
}
cmds.Flag(name).Annotations[cobra.BashCompCustom] = append(
cmds.Flag(name).Annotations[cobra.BashCompCustom],
completion,
)
}
}
cmds.AddCommand(alpha)
cmds.AddCommand(cmdconfig.NewCmdConfig(clientcmd.NewDefaultPathOptions(), out, err))
cmds.AddCommand(NewCmdPlugin(f, in, out, err))
cmds.AddCommand(NewCmdVersion(f, out))
cmds.AddCommand(NewCmdApiVersions(f, out))
cmds.AddCommand(NewCmdOptions(out))
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)
}
// deprecatedAlias is intended to be used to create a "wrapper" command around
// an existing command. The wrapper works the same but prints a deprecation
// message before running. This command is identical functionality.
func deprecatedAlias(deprecatedVersion string, cmd *cobra.Command) *cobra.Command {
// Have to be careful here because Cobra automatically extracts the name
// of the command from the .Use field.
originalName := cmd.Name()
cmd.Use = deprecatedVersion
cmd.Deprecated = fmt.Sprintf("use %q instead", originalName)
cmd.Short = fmt.Sprintf("%s. This command is deprecated, use %q instead", cmd.Short, originalName)
cmd.Hidden = true
return cmd
}
// deprecated is similar to deprecatedAlias, but it is used for deprecations
// that are not simple aliases; this command is actually a different
// (deprecated) codepath.
func deprecated(baseName, to string, parent, cmd *cobra.Command) string {
cmd.Long = fmt.Sprintf("Deprecated: all functionality can be found in \"%s %s\"", baseName, to)
cmd.Short = fmt.Sprintf("Deprecated: use %s", to)
parent.AddCommand(cmd)
return cmd.Name()
}

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

@ -0,0 +1,754 @@
/*
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"
stdstrings "strings"
"testing"
"time"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/testapi"
apitesting "k8s.io/kubernetes/pkg/api/testing"
api "k8s.io/kubernetes/pkg/apis/core"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
"k8s.io/kubernetes/pkg/util/strings"
)
// This init should be removed after switching this command and its tests to user external types.
func init() {
api.AddToScheme(scheme.Scheme)
}
func initTestErrorHandler(t *testing.T) {
cmdutil.BehaviorOnFatal(func(str string, code int) {
t.Errorf("Error running command (exit code %d): %s", code, str)
})
}
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: scheme.Codecs,
ContentType: runtime.ContentTypeJSON,
GroupVersion: &schema.GroupVersion{Version: "v1"},
},
}
}
func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList) {
pods := &api.PodList{
ListMeta: metav1.ListMeta{
ResourceVersion: "15",
},
Items: []api.Pod{
{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: apitesting.DeepEqualSafePodSpec(),
},
{
ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"},
Spec: apitesting.DeepEqualSafePodSpec(),
},
},
}
svc := &api.ServiceList{
ListMeta: metav1.ListMeta{
ResourceVersion: "16",
},
Items: []api.Service{
{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
SessionAffinity: "None",
Type: api.ServiceTypeClusterIP,
},
},
},
}
rc := &api.ReplicationControllerList{
ListMeta: metav1.ListMeta{
ResourceVersion: "17",
},
Items: []api.ReplicationController{
{
ObjectMeta: metav1.ObjectMeta{Name: "rc1", Namespace: "test", ResourceVersion: "18"},
Spec: api.ReplicationControllerSpec{
Replicas: 1,
},
},
},
}
return pods, svc, rc
}
type testPrinter struct {
Objects []runtime.Object
Err error
GenericPrinter bool
}
func (t *testPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
t.Objects = append(t.Objects, obj)
fmt.Fprintf(out, "%#v", obj)
return t.Err
}
// TODO: implement HandledResources()
func (t *testPrinter) HandledResources() []string {
return []string{}
}
func (t *testPrinter) AfterPrint(output io.Writer, res string) error {
return nil
}
func (t *testPrinter) IsGeneric() bool {
return t.GenericPrinter
}
type testDescriber struct {
Name, Namespace string
Settings printers.DescriberSettings
Output string
Err error
}
func (t *testDescriber) Describe(namespace, name string, describerSettings printers.DescriberSettings) (output string, err error) {
t.Namespace, t.Name = namespace, name
t.Settings = describerSettings
return t.Output, t.Err
}
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
}
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()
p := printers.NewHumanReadablePrinter(nil, nil, printers.PrintOptions{
WithNamespace: true,
ColumnLabels: []string{},
})
printersinternal.AddHandlers(p)
tf.Printer = p
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
ctrl := &api.ReplicationController{
ObjectMeta: metav1.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: metav1.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, false, 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()
p := printers.NewHumanReadablePrinter(nil, nil, printers.PrintOptions{
Wide: true,
ColumnLabels: []string{},
})
printersinternal.AddHandlers(p)
tf.Printer = p
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
ctrl := &api.ReplicationController{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"foo": "bar"},
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.ReplicationControllerSpec{
Replicas: 1,
Selector: map[string]string{"foo": "bar"},
Template: &api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "foo",
Image: "someimage",
},
{
Name: "foo2",
Image: "someimage2",
},
},
},
},
},
Status: api.ReplicationControllerStatus{
Replicas: 1,
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, false, mapper, ctrl, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
// foo 1 1 0 10y foo,foo2 someimage,someimage2 foo=bar
}
func Example_printReplicationController() {
f, tf, _, ns := cmdtesting.NewAPIFactory()
p := printers.NewHumanReadablePrinter(nil, nil, printers.PrintOptions{
ColumnLabels: []string{},
})
printersinternal.AddHandlers(p)
tf.Printer = p
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
ctrl := &api.ReplicationController{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"foo": "bar"},
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.ReplicationControllerSpec{
Replicas: 1,
Selector: map[string]string{"foo": "bar"},
Template: &api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "foo",
Image: "someimage",
},
{
Name: "foo2",
Image: "someimage",
},
},
},
},
},
Status: api.ReplicationControllerStatus{
Replicas: 1,
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, false, 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()
p := printers.NewHumanReadablePrinter(nil, nil, printers.PrintOptions{
Wide: true,
ColumnLabels: []string{},
})
printersinternal.AddHandlers(p)
tf.Printer = p
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: metav1.ObjectMeta{
Name: "test1",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: "podPhase",
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
PodIP: "10.1.1.3",
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, false, 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()
p := printers.NewHumanReadablePrinter(nil, nil, printers.PrintOptions{
ShowLabels: true,
ColumnLabels: []string{},
})
printersinternal.AddHandlers(p)
tf.Printer = p
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: metav1.ObjectMeta{
Name: "test1",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
Labels: map[string]string{
"l1": "key",
"l2": "value",
},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: "podPhase",
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, false, 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: metav1.ObjectMeta{
Name: "test1",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodPending,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "test2",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodRunning,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "test3",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodSucceeded,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "test4",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodFailed,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "test5",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: api.PodUnknown,
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
}},
}
}
func Example_printPodHideTerminated() {
f, tf, _, ns := cmdtesting.NewAPIFactory()
p := printers.NewHumanReadablePrinter(nil, nil, printers.PrintOptions{
ColumnLabels: []string{},
})
printersinternal.AddHandlers(p)
tf.Printer = p
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 := cmdutil.ExtractCmdPrintOptions(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, false, 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()
p := printers.NewHumanReadablePrinter(nil, nil, printers.PrintOptions{
ShowAll: true,
ColumnLabels: []string{},
})
printersinternal.AddHandlers(p)
tf.Printer = p
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, false, 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()
p := printers.NewHumanReadablePrinter(nil, nil, printers.PrintOptions{
WithNamespace: true,
ColumnLabels: []string{"l1"},
})
printersinternal.AddHandlers(p)
tf.Printer = p
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
svc := &api.ServiceList{
Items: []api.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc1",
Namespace: "ns1",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
Labels: map[string]string{
"l1": "value",
},
},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Protocol: "UDP", Port: 53},
{Protocol: "TCP", Port: 53},
},
Selector: map[string]string{
"s": "magic",
},
ClusterIP: "10.1.1.1",
Type: api.ServiceTypeClusterIP,
},
Status: api.ServiceStatus{},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc2",
Namespace: "ns2",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
Labels: map[string]string{
"l1": "dolla-bill-yall",
},
},
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Protocol: "TCP", Port: 80},
{Protocol: "TCP", Port: 8080},
},
Selector: map[string]string{
"s": "kazam",
},
ClusterIP: "10.1.1.2",
Type: api.ServiceTypeClusterIP,
},
Status: api.ServiceStatus{},
}},
}
ld := strings.NewLineDelimiter(os.Stdout, "|")
defer ld.Flush()
mapper, _ := f.Object()
err := f.PrintObject(cmd, false, mapper, svc, ld)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// |NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE L1|
// |ns1 svc1 ClusterIP 10.1.1.1 <none> 53/UDP,53/TCP 10y value|
// |ns2 svc2 ClusterIP 10.1.1.2 <none> 80/TCP,8080/TCP 10y dolla-bill-yall|
// ||
}
func TestNormalizationFuncGlobalExistence(t *testing.T) {
// This test can be safely deleted when we will not support multiple flag formats
root := NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr)
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
}
func Test_deprecatedAlias(t *testing.T) {
var correctCommandCalled bool
makeCobraCommand := func() *cobra.Command {
cobraCmd := new(cobra.Command)
cobraCmd.Use = "print five lines"
cobraCmd.Run = func(*cobra.Command, []string) {
correctCommandCalled = true
}
return cobraCmd
}
original := makeCobraCommand()
alias := deprecatedAlias("echo", makeCobraCommand())
if len(alias.Deprecated) == 0 {
t.Error("deprecatedAlias should always have a non-empty .Deprecated")
}
if !stdstrings.Contains(alias.Deprecated, "print") {
t.Error("deprecatedAlias should give the name of the new function in its .Deprecated field")
}
if !alias.Hidden {
t.Error("deprecatedAlias should never have .Hidden == false (deprecated aliases should be hidden)")
}
if alias.Name() != "echo" {
t.Errorf("deprecatedAlias has name %q, expected %q",
alias.Name(), "echo")
}
if original.Name() != "print" {
t.Errorf("original command has name %q, expected %q",
original.Name(), "print")
}
buffer := new(bytes.Buffer)
alias.SetOutput(buffer)
alias.Execute()
str := buffer.String()
if !stdstrings.Contains(str, "deprecated") || !stdstrings.Contains(str, "print") {
t.Errorf("deprecation warning %q does not include enough information", str)
}
// It would be nice to test to see that original.Run == alias.Run
// Unfortunately Golang does not allow comparing functions. I could do
// this with reflect, but that's technically invoking undefined
// behavior. Best we can do is make sure that the function is called.
if !correctCommandCalled {
t.Errorf("original function doesn't appear to have been called by alias")
}
}

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

@ -0,0 +1,309 @@
/*
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"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
const defaultBoilerPlate = `
# 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(i18n.T(`
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.
Detailed instructions on how to do this are available here:
https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion
Note for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2`))
completion_example = templates.Examples(i18n.T(`
# Installing bash completion on macOS using homebrew
## If running Bash 3.2 included with macOS
brew install bash-completion
## or, if running Bash 4.1+
brew install bash-completion@2
## If kubectl is installed via homebrew, this should start working immediately.
## If you've installed via other means, you may need add the completion to your completion directory
kubectl completion bash > $(brew --prefix)/etc/bash_completion.d/kubectl
# Installing bash completion on Linux
## 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 "
# Kubectl shell completion
source '$HOME/.kube/completion.bash.inc'
" >> $HOME/.bash_profile
source $HOME/.bash_profile
# Load the kubectl completion code for zsh[1] into the current shell
source <(kubectl completion zsh)
# Set the kubectl completion code for zsh[1] to autoload on startup
kubectl completion zsh > "${fpath[1]}/_kubectl"`))
)
var (
completion_shells = map[string]func(out io.Writer, boilerPlate string, cmd *cobra.Command) error{
"bash": runCompletionBash,
"zsh": runCompletionZsh,
}
)
func NewCmdCompletion(out io.Writer, boilerPlate string) *cobra.Command {
shells := []string{}
for s := range completion_shells {
shells = append(shells, s)
}
cmd := &cobra.Command{
Use: "completion SHELL",
Short: i18n.T("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, boilerPlate, cmd, args)
cmdutil.CheckErr(err)
},
ValidArgs: shells,
}
return cmd
}
func RunCompletion(out io.Writer, boilerPlate string, cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return cmdutil.UsageErrorf(cmd, "Shell not specified.")
}
if len(args) > 1 {
return cmdutil.UsageErrorf(cmd, "Too many arguments. Expected only the shell type.")
}
run, found := completion_shells[args[0]]
if !found {
return cmdutil.UsageErrorf(cmd, "Unsupported shell type %q.", args[0])
}
return run(out, boilerPlate, cmd.Parent())
}
func runCompletionBash(out io.Writer, boilerPlate string, kubectl *cobra.Command) error {
if len(boilerPlate) == 0 {
boilerPlate = defaultBoilerPlate
}
if _, err := out.Write([]byte(boilerPlate)); err != nil {
return err
}
return kubectl.GenBashCompletion(out)
}
func runCompletionZsh(out io.Writer, boilerPlate string, kubectl *cobra.Command) error {
zsh_head := "#compdef kubectl\n"
out.Write([]byte(zsh_head))
if len(boilerPlate) == 0 {
boilerPlate = defaultBoilerPlate
}
if _, err := out.Write([]byte(boilerPlate)); 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_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/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \
-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}/builtin 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)
_complete kubectl 2>/dev/null
`
out.Write([]byte(zsh_tail))
return nil
}

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

@ -0,0 +1,91 @@
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",
"rename_context.go",
"set.go",
"unset.go",
"use_context.go",
"view.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/config",
visibility = [
"//build/visible_to:pkg_kubectl_cmd_config_CONSUMERS",
],
deps = [
"//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/printers:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api/latest:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"config_test.go",
"create_authinfo_test.go",
"create_cluster_test.go",
"create_context_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",
"rename_context_test.go",
"set_test.go",
"unset_test.go",
"use_context_test.go",
"view_test.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/config",
library = ":go_default_library",
deps = [
"//pkg/kubectl/cmd/util:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = [
"//build/visible_to:pkg_kubectl_cmd_config_CONSUMERS",
],
)

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 (
"fmt"
"io"
"path"
"strconv"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
// 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: i18n.T("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, errOut, pathOptions))
cmd.AddCommand(NewCmdConfigSetCluster(out, pathOptions))
cmd.AddCommand(NewCmdConfigSetAuthInfo(out, pathOptions))
cmd.AddCommand(NewCmdConfigSetContext(out, pathOptions))
cmd.AddCommand(NewCmdConfigSet(out, pathOptions))
cmd.AddCommand(NewCmdConfigUnset(out, pathOptions))
cmd.AddCommand(NewCmdConfigCurrentContext(out, pathOptions))
cmd.AddCommand(NewCmdConfigUseContext(out, pathOptions))
cmd.AddCommand(NewCmdConfigGetContexts(out, pathOptions))
cmd.AddCommand(NewCmdConfigGetClusters(out, pathOptions))
cmd.AddCommand(NewCmdConfigDeleteCluster(out, pathOptions))
cmd.AddCommand(NewCmdConfigDeleteContext(out, errOut, pathOptions))
cmd.AddCommand(NewCmdConfigRenameContext(out, pathOptions))
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
}
func helpErrorf(cmd *cobra.Command, format string, args ...interface{}) error {
cmd.Help()
msg := fmt.Sprintf(format, args...)
return fmt.Errorf("%s\n", msg)
}

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"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/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 !apiequality.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,298 @@
/*
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/apiserver/pkg/util/flag"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
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: i18n.T("Sets a user entry in kubeconfig"),
Long: create_authinfo_long,
Example: create_authinfo_example,
Run: func(cmd *cobra.Command, args []string) {
err := options.complete(cmd, out)
if err != nil {
cmd.Help()
cmdutil.CheckErr(err)
}
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' arguments 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) error {
args := cmd.Flags().Args()
if len(args) != 1 {
return fmt.Errorf("Unexpected args: %v", args)
}
authProviderArgs, err := cmd.Flags().GetStringSlice(flagAuthProviderArg)
if err != nil {
return fmt.Errorf("Error: %s\n", err)
}
if len(authProviderArgs) > 0 {
newPairs, removePairs, err := cmdutil.ParsePairs(authProviderArgs, flagAuthProviderArg, true)
if err != nil {
return fmt.Errorf("Error: %s\n", err)
}
o.authProviderArgs = newPairs
o.authProviderArgsToRemove = removePairs
}
o.name = args[0]
return nil
}
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,260 @@
/*
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"
"io/ioutil"
"os"
"reflect"
"testing"
"k8s.io/apiserver/pkg/util/flag"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
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 err := opts.complete(cmd, buff); err != nil {
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)
}
}
}
type createAuthInfoTest struct {
description string
config clientcmdapi.Config
args []string
flags []string
expected string
expectedConfig clientcmdapi.Config
}
func TestCreateAuthInfo(t *testing.T) {
conf := clientcmdapi.Config{}
test := createAuthInfoTest{
description: "Testing for create aythinfo",
config: conf,
args: []string{"cluster-admin"},
flags: []string{
"--username=admin",
"--password=uXFGweU9l35qcif",
},
expected: `User "cluster-admin" set.` + "\n",
expectedConfig: clientcmdapi.Config{
AuthInfos: map[string]*clientcmdapi.AuthInfo{
"cluster-admin": {Username: "admin", Password: "uXFGweU9l35qcif"}},
},
}
test.run(t)
}
func (test createAuthInfoTest) run(t *testing.T) {
fakeKubeFile, err := ioutil.TempFile(os.TempDir(), "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
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 := NewCmdConfigSetAuthInfo(buf, pathOptions)
cmd.SetArgs(test.args)
cmd.Flags().Parse(test.flags)
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpected error executing command: %v,kubectl config set-credentials args: %v,flags: %v", err, test.args, test.flags)
}
config, err := clientcmd.LoadFromFile(fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error loading kubeconfig file: %v", err)
}
if len(test.expected) != 0 {
if buf.String() != test.expected {
t.Errorf("Fail in %q:\n expected %v\n but got %v\n", test.description, test.expected, buf.String())
}
}
if test.expectedConfig.AuthInfos != nil {
expectAuthInfo := test.expectedConfig.AuthInfos[test.args[0]]
actualAuthInfo := config.AuthInfos[test.args[0]]
if expectAuthInfo.Username != actualAuthInfo.Username || expectAuthInfo.Password != actualAuthInfo.Password {
t.Errorf("Fail in %q:\n expected AuthInfo%v\n but found %v in kubeconfig\n", test.description, expectAuthInfo, actualAuthInfo)
}
}
}

View File

@ -0,0 +1,178 @@
/*
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/apiserver/pkg/util/flag"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
type createClusterOptions struct {
configAccess clientcmd.ConfigAccess
name string
server 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: i18n.T("Sets a cluster entry in kubeconfig"),
Long: create_cluster_long,
Example: create_cluster_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.complete(cmd))
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")
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) error {
args := cmd.Flags().Args()
if len(args) != 1 {
return helpErrorf(cmd, "Unexpected args: %v", args)
}
o.name = args[0]
return nil
}
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,119 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"bytes"
"io/ioutil"
"os"
"testing"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
type createClusterTest struct {
description string
config clientcmdapi.Config
args []string
flags []string
expected string
expectedConfig clientcmdapi.Config
}
func TestCreateCluster(t *testing.T) {
conf := clientcmdapi.Config{}
test := createClusterTest{
description: "Testing 'kubectl config set-cluster' with a new cluster",
config: conf,
args: []string{"my-cluster"},
flags: []string{
"--server=http://192.168.0.1",
},
expected: `Cluster "my-cluster" set.` + "\n",
expectedConfig: clientcmdapi.Config{
Clusters: map[string]*clientcmdapi.Cluster{
"my-cluster": {Server: "http://192.168.0.1"},
},
},
}
test.run(t)
}
func TestModifyCluster(t *testing.T) {
conf := clientcmdapi.Config{
Clusters: map[string]*clientcmdapi.Cluster{
"my-cluster": {Server: "https://192.168.0.1"},
},
}
test := createClusterTest{
description: "Testing 'kubectl config set-cluster' with an existing cluster",
config: conf,
args: []string{"my-cluster"},
flags: []string{
"--server=https://192.168.0.99",
},
expected: `Cluster "my-cluster" set.` + "\n",
expectedConfig: clientcmdapi.Config{
Clusters: map[string]*clientcmdapi.Cluster{
"my-cluster": {Server: "https://192.168.0.99"},
},
},
}
test.run(t)
}
func (test createClusterTest) run(t *testing.T) {
fakeKubeFile, err := ioutil.TempFile(os.TempDir(), "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
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 := NewCmdConfigSetCluster(buf, pathOptions)
cmd.SetArgs(test.args)
cmd.Flags().Parse(test.flags)
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpected error executing command: %v, args: %v, flags: %v", err, test.args, test.flags)
}
config, err := clientcmd.LoadFromFile(fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error loading kubeconfig file: %v", err)
}
if len(test.expected) != 0 {
if buf.String() != test.expected {
t.Errorf("Failed in %q\n expected %v\n but got %v", test.description, test.expected, buf.String())
}
}
if len(test.args) > 0 {
cluster, ok := config.Clusters[test.args[0]]
if !ok {
t.Errorf("expected cluster %v, but got nil", test.args[0])
return
}
if cluster.Server != test.expectedConfig.Clusters[test.args[0]].Server {
t.Errorf("Fail in %q\n expected cluster server %v\n but got %v\n ", test.description, test.expectedConfig.Clusters[test.args[0]].Server, cluster.Server)
}
}
}

View File

@ -0,0 +1,137 @@
/*
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/apiserver/pkg/util/flag"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
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: i18n.T("Sets a context entry in kubeconfig"),
Long: create_context_long,
Example: create_context_example,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.complete(cmd))
exists, err := options.run()
cmdutil.CheckErr(err)
if exists {
fmt.Fprintf(out, "Context %q modified.\n", options.name)
} else {
fmt.Fprintf(out, "Context %q created.\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() (bool, error) {
err := o.validate()
if err != nil {
return false, err
}
config, err := o.configAccess.GetStartingConfig()
if err != nil {
return false, 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 exists, err
}
return exists, 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) error {
args := cmd.Flags().Args()
if len(args) != 1 {
return helpErrorf(cmd, "Unexpected args: %v", args)
}
o.name = args[0]
return nil
}
func (o createContextOptions) validate() error {
if len(o.name) == 0 {
return errors.New("you must specify a non-empty context name")
}
return nil
}

View File

@ -0,0 +1,118 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"bytes"
"io/ioutil"
"os"
"testing"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
type createContextTest struct {
description string
config clientcmdapi.Config //initiate kubectl config
args []string //kubectl set-context args
flags []string //kubectl set-context flags
expected string //expectd out
expectedConfig clientcmdapi.Config //expect kubectl config
}
func TestCreateContext(t *testing.T) {
conf := clientcmdapi.Config{}
test := createContextTest{
description: "Testing for create a new context",
config: conf,
args: []string{"shaker-context"},
flags: []string{
"--cluster=cluster_nickname",
"--user=user_nickname",
"--namespace=namespace",
},
expected: `Context "shaker-context" created.` + "\n",
expectedConfig: clientcmdapi.Config{
Contexts: map[string]*clientcmdapi.Context{
"shaker-context": {AuthInfo: "user_nickname", Cluster: "cluster_nickname", Namespace: "namespace"}},
},
}
test.run(t)
}
func TestModifyContext(t *testing.T) {
conf := clientcmdapi.Config{
Contexts: map[string]*clientcmdapi.Context{
"shaker-context": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"},
"not-this": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}}
test := createContextTest{
description: "Testing for modify a already exist context",
config: conf,
args: []string{"shaker-context"},
flags: []string{
"--cluster=cluster_nickname",
"--user=user_nickname",
"--namespace=namespace",
},
expected: `Context "shaker-context" modified.` + "\n",
expectedConfig: clientcmdapi.Config{
Contexts: map[string]*clientcmdapi.Context{
"shaker-context": {AuthInfo: "user_nickname", Cluster: "cluster_nickname", Namespace: "namespace"},
"not-this": {AuthInfo: "blue-user", Cluster: "big-cluster", Namespace: "saw-ns"}}},
}
test.run(t)
}
func (test createContextTest) run(t *testing.T) {
fakeKubeFile, err := ioutil.TempFile(os.TempDir(), "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
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 := NewCmdConfigSetContext(buf, pathOptions)
cmd.SetArgs(test.args)
cmd.Flags().Parse(test.flags)
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpected error executing command: %v,kubectl set-context args: %v,flags: %v", err, test.args, test.flags)
}
config, err := clientcmd.LoadFromFile(fakeKubeFile.Name())
if err != nil {
t.Fatalf("unexpected error loading kubeconfig file: %v", err)
}
if len(test.expected) != 0 {
if buf.String() != test.expected {
t.Errorf("Fail in %q:\n expected %v\n but got %v\n", test.description, test.expected, buf.String())
}
}
if test.expectedConfig.Contexts != nil {
expectContext := test.expectedConfig.Contexts[test.args[0]]
actualContext := config.Contexts[test.args[0]]
if expectContext.AuthInfo != actualContext.AuthInfo || expectContext.Cluster != actualContext.Cluster ||
expectContext.Namespace != actualContext.Namespace {
t.Errorf("Fail in %q:\n expected Context %v\n but found %v in kubeconfig\n", test.description, expectContext, actualContext)
}
}
}

View File

@ -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 config
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
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: i18n.T("Displays the current-context"),
Long: current_context_long,
Example: current_context_example,
Run: func(cmd *cobra.Command, args []string) {
err := RunCurrentContext(out, options)
cmdutil.CheckErr(err)
},
}
return cmd
}
func RunCurrentContext(out io.Writer, 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,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 config
import (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/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, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
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, &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,83 @@
/*
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/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
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: i18n.T("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,97 @@
/*
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/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/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, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
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,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 config
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
var (
delete_context_example = templates.Examples(`
# Delete the context for the minikube cluster
kubectl config delete-context minikube`)
)
func NewCmdConfigDeleteContext(out, errOut io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
cmd := &cobra.Command{
Use: "delete-context NAME",
Short: i18n.T("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, errOut, configAccess, cmd)
cmdutil.CheckErr(err)
},
}
return cmd
}
func runDeleteContext(out, errOut 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)
}
if config.CurrentContext == name {
fmt.Fprint(errOut, "warning: this removed your active context, use \"kubectl config use-context\" to select a different one\n")
}
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,98 @@
/*
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/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/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, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
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{})
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfigDeleteContext(buf, errBuf, 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,65 @@
/*
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/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
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: i18n.T("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,84 @@
/*
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/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/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, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
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,173 @@
/*
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"
"sort"
"strings"
"text/tabwriter"
"github.com/spf13/cobra"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
)
// 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: i18n.T("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 = printers.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)
}
}
sort.Strings(toPrint)
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,178 @@
/*
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/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/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 TestGetContextsAllSorted(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"},
"abc": {AuthInfo: "blue-user", Cluster: "abc-cluster", Namespace: "kube-system"},
"xyz": {AuthInfo: "blue-user", Cluster: "xyz-cluster", Namespace: "default"}}}
test := getContextsTest{
startingConfig: tconf,
names: []string{},
noHeader: false,
nameOnly: false,
expectedOut: `CURRENT NAME CLUSTER AUTHINFO NAMESPACE
abc abc-cluster blue-user kube-system
* shaker-context big-cluster blue-user saw-ns
xyz xyz-cluster blue-user default
`,
}
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, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
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
}
}

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/client-go/tools/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/client-go/tools/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)
}
}

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